esp_println/
lib.rs

1#![doc = include_str!("../README.md")]
2//! ## Feature Flags
3#![doc = document_features::document_features!()]
4#![doc(html_logo_url = "https://avatars.githubusercontent.com/u/46717278")]
5#![allow(rustdoc::bare_urls)]
6#![no_std]
7
8#[cfg(feature = "defmt-espflash")]
9pub mod defmt;
10#[cfg(feature = "log-04")]
11pub mod logger;
12
13macro_rules! log_format {
14    ($value:expr) => {
15        #[unsafe(link_section = concat!(".espressif.metadata"))]
16        #[used]
17        #[unsafe(export_name = concat!("espflash.LOG_FORMAT"))]
18        static LOG_FORMAT: [u8; $value.len()] = const {
19            let val_bytes = $value.as_bytes();
20            let mut val_bytes_array = [0; $value.len()];
21            let mut i = 0;
22            while i < val_bytes.len() {
23                val_bytes_array[i] = val_bytes[i];
24                i += 1;
25            }
26            val_bytes_array
27        };
28    };
29}
30
31#[cfg(feature = "defmt-espflash")]
32log_format!("defmt-espflash");
33
34#[cfg(not(feature = "defmt-espflash"))]
35log_format!("serial");
36
37/// Prints to the selected output, with a newline.
38#[cfg(not(feature = "no-op"))]
39#[macro_export]
40macro_rules! println {
41    ($($arg:tt)*) => {{
42        {
43            use core::fmt::Write;
44            writeln!($crate::Printer, $($arg)*).ok();
45        }
46    }};
47}
48
49/// Prints to the selected output.
50#[cfg(not(feature = "no-op"))]
51#[macro_export]
52macro_rules! print {
53    ($($arg:tt)*) => {{
54        {
55            use core::fmt::Write;
56            write!($crate::Printer, $($arg)*).ok();
57        }
58    }};
59}
60
61/// Prints to the configured output, with a newline.
62#[cfg(feature = "no-op")]
63#[macro_export]
64macro_rules! println {
65    ($($arg:tt)*) => {{}};
66}
67
68/// Prints to the configured output.
69#[cfg(feature = "no-op")]
70#[macro_export]
71macro_rules! print {
72    ($($arg:tt)*) => {{}};
73}
74
75/// Prints and returns the value of a given expression for quick and dirty
76/// debugging.
77// implementation adapted from `std::dbg`
78#[macro_export]
79macro_rules! dbg {
80    // NOTE: We cannot use `concat!` to make a static string as a format argument
81    // of `eprintln!` because `file!` could contain a `{` or
82    // `$val` expression could be a block (`{ .. }`), in which case the `println!`
83    // will be malformed.
84    () => {
85        $crate::println!("[{}:{}]", ::core::file!(), ::core::line!())
86    };
87    ($val:expr $(,)?) => {
88        // Use of `match` here is intentional because it affects the lifetimes
89        // of temporaries - https://stackoverflow.com/a/48732525/1063961
90        match $val {
91            tmp => {
92                $crate::println!("[{}:{}] {} = {:#?}",
93                    ::core::file!(), ::core::line!(), ::core::stringify!($val), &tmp);
94                tmp
95            }
96        }
97    };
98    ($($val:expr),+ $(,)?) => {
99        ($($crate::dbg!($val)),+,)
100    };
101}
102
103/// The printer that is used by the `print!` and `println!` macros.
104pub struct Printer;
105
106impl core::fmt::Write for Printer {
107    fn write_str(&mut self, s: &str) -> core::fmt::Result {
108        Printer::write_bytes(s.as_bytes());
109        Ok(())
110    }
111}
112
113impl Printer {
114    /// Writes a byte slice to the configured output.
115    pub fn write_bytes(bytes: &[u8]) {
116        with(|token| {
117            PrinterImpl::write_bytes_in_cs(bytes, token);
118            PrinterImpl::flush(token);
119        })
120    }
121}
122
123#[cfg(feature = "jtag-serial")]
124type PrinterImpl = serial_jtag_printer::Printer;
125
126#[cfg(feature = "uart")]
127type PrinterImpl = uart_printer::Printer;
128
129#[cfg(feature = "auto")]
130type PrinterImpl = auto_printer::Printer;
131
132#[cfg(all(
133    feature = "auto",
134    any(
135        feature = "esp32c3",
136        feature = "esp32c6",
137        feature = "esp32h2",
138        feature = "esp32s3"
139    )
140))]
141mod auto_printer {
142    use crate::{
143        LockToken,
144        serial_jtag_printer::Printer as PrinterSerialJtag,
145        uart_printer::Printer as PrinterUart,
146    };
147
148    pub struct Printer;
149    impl Printer {
150        fn use_jtag() -> bool {
151            // Decide if serial-jtag is used by checking SOF interrupt flag.
152            // SOF packet is sent by the HOST every 1ms on a full speed bus.
153            // Between two consecutive ticks, there will be at least 1ms (selectable tick
154            // rate range is 1 - 1000Hz).
155            // We don't reset the flag - if it was ever connected we assume serial-jtag is
156            // used
157            #[cfg(feature = "esp32c3")]
158            const USB_DEVICE_INT_RAW: *const u32 = 0x60043008 as *const u32;
159            #[cfg(feature = "esp32c6")]
160            const USB_DEVICE_INT_RAW: *const u32 = 0x6000f008 as *const u32;
161            #[cfg(feature = "esp32h2")]
162            const USB_DEVICE_INT_RAW: *const u32 = 0x6000f008 as *const u32;
163            #[cfg(feature = "esp32s3")]
164            const USB_DEVICE_INT_RAW: *const u32 = 0x60038000 as *const u32;
165
166            const SOF_INT_MASK: u32 = 0b10;
167
168            unsafe { (USB_DEVICE_INT_RAW.read_volatile() & SOF_INT_MASK) != 0 }
169        }
170
171        pub fn write_bytes_in_cs(bytes: &[u8], token: LockToken<'_>) {
172            if Self::use_jtag() {
173                PrinterSerialJtag::write_bytes_in_cs(bytes, token);
174            } else {
175                PrinterUart::write_bytes_in_cs(bytes, token);
176            }
177        }
178
179        pub fn flush(token: LockToken<'_>) {
180            if Self::use_jtag() {
181                PrinterSerialJtag::flush(token);
182            } else {
183                PrinterUart::flush(token);
184            }
185        }
186    }
187}
188
189#[cfg(all(
190    feature = "auto",
191    not(any(
192        feature = "esp32c3",
193        feature = "esp32c6",
194        feature = "esp32h2",
195        feature = "esp32s3"
196    ))
197))]
198mod auto_printer {
199    // models that only have UART
200    pub type Printer = crate::uart_printer::Printer;
201}
202
203#[cfg(all(
204    any(feature = "jtag-serial", feature = "auto"),
205    any(
206        feature = "esp32c3",
207        feature = "esp32c6",
208        feature = "esp32h2",
209        feature = "esp32s3"
210    )
211))]
212mod serial_jtag_printer {
213    use portable_atomic::{AtomicBool, Ordering};
214
215    use super::LockToken;
216    pub struct Printer;
217
218    #[cfg(feature = "esp32c3")]
219    const SERIAL_JTAG_FIFO_REG: usize = 0x6004_3000;
220    #[cfg(feature = "esp32c3")]
221    const SERIAL_JTAG_CONF_REG: usize = 0x6004_3004;
222
223    #[cfg(any(feature = "esp32c6", feature = "esp32h2"))]
224    const SERIAL_JTAG_FIFO_REG: usize = 0x6000_F000;
225    #[cfg(any(feature = "esp32c6", feature = "esp32h2"))]
226    const SERIAL_JTAG_CONF_REG: usize = 0x6000_F004;
227
228    #[cfg(feature = "esp32s3")]
229    const SERIAL_JTAG_FIFO_REG: usize = 0x6003_8000;
230    #[cfg(feature = "esp32s3")]
231    const SERIAL_JTAG_CONF_REG: usize = 0x6003_8004;
232
233    /// A previous wait has timed out. We use this flag to avoid blocking
234    /// forever if there is no host attached.
235    static TIMED_OUT: AtomicBool = AtomicBool::new(false);
236
237    fn fifo_flush() {
238        let conf = SERIAL_JTAG_CONF_REG as *mut u32;
239        unsafe { conf.write_volatile(0b001) };
240    }
241
242    fn fifo_full() -> bool {
243        let conf = SERIAL_JTAG_CONF_REG as *mut u32;
244        unsafe { conf.read_volatile() & 0b010 == 0b000 }
245    }
246
247    fn fifo_write(byte: u8) {
248        let fifo = SERIAL_JTAG_FIFO_REG as *mut u32;
249        unsafe { fifo.write_volatile(byte as u32) }
250    }
251
252    fn wait_for_flush() -> bool {
253        const TIMEOUT_ITERATIONS: usize = 50_000;
254
255        // Wait for some time for the FIFO to clear.
256        let mut timeout = TIMEOUT_ITERATIONS;
257        while fifo_full() {
258            if timeout == 0 {
259                TIMED_OUT.store(true, Ordering::Relaxed);
260                return false;
261            }
262            timeout -= 1;
263        }
264
265        true
266    }
267
268    impl Printer {
269        pub fn write_bytes_in_cs(bytes: &[u8], _token: LockToken<'_>) {
270            if fifo_full() {
271                // The FIFO is full. Let's see if we can progress.
272
273                if TIMED_OUT.load(Ordering::Relaxed) {
274                    // Still wasn't able to drain the FIFO. Let's assume we won't be able to, and
275                    // don't queue up more data.
276                    // This is important so we don't block forever if there is no host attached.
277                    return;
278                }
279
280                // Give the fifo some time to drain.
281                if !wait_for_flush() {
282                    return;
283                }
284            } else {
285                // Reset the flag - we managed to clear our FIFO.
286                TIMED_OUT.store(false, Ordering::Relaxed);
287            }
288
289            for &b in bytes {
290                if fifo_full() {
291                    fifo_flush();
292
293                    // Wait for the FIFO to clear, we have more data to shift out.
294                    if !wait_for_flush() {
295                        return;
296                    }
297                }
298                fifo_write(b);
299            }
300        }
301
302        pub fn flush(_token: LockToken<'_>) {
303            fifo_flush();
304        }
305    }
306}
307
308#[cfg(all(any(feature = "uart", feature = "auto"), feature = "esp32"))]
309mod uart_printer {
310    use super::LockToken;
311    const UART_TX_ONE_CHAR: usize = 0x4000_9200;
312
313    pub struct Printer;
314    impl Printer {
315        pub fn write_bytes_in_cs(bytes: &[u8], _token: LockToken<'_>) {
316            for &b in bytes {
317                unsafe {
318                    let uart_tx_one_char: unsafe extern "C" fn(u8) -> i32 =
319                        core::mem::transmute(UART_TX_ONE_CHAR);
320                    uart_tx_one_char(b)
321                };
322            }
323        }
324
325        pub fn flush(_token: LockToken<'_>) {}
326    }
327}
328
329#[cfg(all(any(feature = "uart", feature = "auto"), feature = "esp32s2"))]
330mod uart_printer {
331    use super::LockToken;
332    pub struct Printer;
333    impl Printer {
334        pub fn write_bytes_in_cs(bytes: &[u8], _token: LockToken<'_>) {
335            // On ESP32-S2 the UART_TX_ONE_CHAR ROM-function seems to have some issues.
336            for chunk in bytes.chunks(64) {
337                for &b in chunk {
338                    unsafe {
339                        // write FIFO
340                        (0x3f400000 as *mut u32).write_volatile(b as u32);
341                    };
342                }
343
344                // wait for TX_DONE
345                while unsafe { (0x3f400004 as *const u32).read_volatile() } & (1 << 14) == 0 {}
346                unsafe {
347                    // reset TX_DONE
348                    (0x3f400010 as *mut u32).write_volatile(1 << 14);
349                }
350            }
351        }
352
353        pub fn flush(_token: LockToken<'_>) {}
354    }
355}
356
357#[cfg(all(
358    any(feature = "uart", feature = "auto"),
359    not(any(feature = "esp32", feature = "esp32s2"))
360))]
361mod uart_printer {
362    use super::LockToken;
363    trait Functions {
364        const TX_ONE_CHAR: usize;
365        const CHUNK_SIZE: usize = 32;
366
367        fn tx_byte(b: u8) {
368            unsafe {
369                let tx_one_char: unsafe extern "C" fn(u8) -> i32 =
370                    core::mem::transmute(Self::TX_ONE_CHAR);
371                tx_one_char(b);
372            }
373        }
374
375        fn flush();
376    }
377
378    struct Device;
379
380    #[cfg(feature = "esp32c2")]
381    impl Functions for Device {
382        const TX_ONE_CHAR: usize = 0x4000_005C;
383
384        fn flush() {
385            // tx_one_char waits for empty
386        }
387    }
388
389    #[cfg(feature = "esp32c3")]
390    impl Functions for Device {
391        const TX_ONE_CHAR: usize = 0x4000_0068;
392
393        fn flush() {
394            unsafe {
395                const TX_FLUSH: usize = 0x4000_0080;
396                const GET_CHANNEL: usize = 0x4000_058C;
397                let tx_flush: unsafe extern "C" fn(u8) = core::mem::transmute(TX_FLUSH);
398                let get_channel: unsafe extern "C" fn() -> u8 = core::mem::transmute(GET_CHANNEL);
399
400                const G_USB_PRINT_ADDR: usize = 0x3FCD_FFD0;
401                let g_usb_print = G_USB_PRINT_ADDR as *mut bool;
402
403                let channel = if *g_usb_print {
404                    // Flush USB-JTAG
405                    3
406                } else {
407                    get_channel()
408                };
409                tx_flush(channel);
410            }
411        }
412    }
413
414    #[cfg(feature = "esp32s3")]
415    impl Functions for Device {
416        const TX_ONE_CHAR: usize = 0x4000_0648;
417
418        fn flush() {
419            unsafe {
420                const TX_FLUSH: usize = 0x4000_0690;
421                const GET_CHANNEL: usize = 0x4000_1A58;
422                let tx_flush: unsafe extern "C" fn(u8) = core::mem::transmute(TX_FLUSH);
423                let get_channel: unsafe extern "C" fn() -> u8 = core::mem::transmute(GET_CHANNEL);
424
425                const G_USB_PRINT_ADDR: usize = 0x3FCE_FFB8;
426                let g_usb_print = G_USB_PRINT_ADDR as *mut bool;
427
428                let channel = if *g_usb_print {
429                    // Flush USB-JTAG
430                    4
431                } else {
432                    get_channel()
433                };
434                tx_flush(channel);
435            }
436        }
437    }
438
439    #[cfg(any(feature = "esp32c6", feature = "esp32h2"))]
440    impl Functions for Device {
441        const TX_ONE_CHAR: usize = 0x4000_0058;
442
443        fn flush() {
444            unsafe {
445                const TX_FLUSH: usize = 0x4000_0074;
446                const GET_CHANNEL: usize = 0x4000_003C;
447
448                let tx_flush: unsafe extern "C" fn(u8) = core::mem::transmute(TX_FLUSH);
449                let get_channel: unsafe extern "C" fn() -> u8 = core::mem::transmute(GET_CHANNEL);
450
451                tx_flush(get_channel());
452            }
453        }
454    }
455
456    pub struct Printer;
457    impl Printer {
458        pub fn write_bytes_in_cs(bytes: &[u8], _token: LockToken<'_>) {
459            for chunk in bytes.chunks(Device::CHUNK_SIZE) {
460                for &b in chunk {
461                    Device::tx_byte(b);
462                }
463
464                Device::flush();
465            }
466        }
467
468        pub fn flush(_token: LockToken<'_>) {}
469    }
470}
471
472#[cfg(not(feature = "critical-section"))]
473use core::marker::PhantomData;
474
475#[cfg(not(feature = "critical-section"))]
476type LockInner<'a> = PhantomData<&'a ()>;
477#[cfg(feature = "critical-section")]
478type LockInner<'a> = critical_section::CriticalSection<'a>;
479
480#[derive(Clone, Copy)]
481struct LockToken<'a>(LockInner<'a>);
482
483impl LockToken<'_> {
484    #[allow(unused)]
485    unsafe fn conjure() -> Self {
486        unsafe {
487            #[cfg(feature = "critical-section")]
488            let inner = critical_section::CriticalSection::new();
489            #[cfg(not(feature = "critical-section"))]
490            let inner = PhantomData;
491
492            LockToken(inner)
493        }
494    }
495}
496
497/// Runs the callback in a critical section, if enabled.
498#[inline]
499fn with<R>(f: impl FnOnce(LockToken) -> R) -> R {
500    #[cfg(feature = "critical-section")]
501    return critical_section::with(|cs| f(LockToken(cs)));
502
503    #[cfg(not(feature = "critical-section"))]
504    f(unsafe { LockToken::conjure() })
505}