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 = "esp32p4",    // as mentioned in 'build.rs'
139        feature = "esp32s3"
140    )
141))]
142mod auto_printer {
143    use crate::{
144        LockToken,
145        serial_jtag_printer::Printer as PrinterSerialJtag,
146        uart_printer::Printer as PrinterUart,
147    };
148
149    pub struct Printer;
150    impl Printer {
151        fn use_jtag() -> bool {
152            // Decide if serial-jtag is used by checking SOF interrupt flag.
153            // SOF packet is sent by the HOST every 1ms on a full speed bus.
154            // Between two consecutive ticks, there will be at least 1ms (selectable tick
155            // rate range is 1 - 1000Hz).
156            // We don't reset the flag - if it was ever connected we assume serial-jtag is
157            // used
158            #[cfg(feature = "esp32c3")]
159            const USB_DEVICE_INT_RAW: *const u32 = 0x60043008 as *const u32;
160            #[cfg(feature = "esp32c6")]
161            const USB_DEVICE_INT_RAW: *const u32 = 0x6000f008 as *const u32;
162            #[cfg(feature = "esp32h2")]
163            const USB_DEVICE_INT_RAW: *const u32 = 0x6000f008 as *const u32;
164            #[cfg(feature = "esp32p4")]
165            const USB_DEVICE_INT_RAW: *const u32 = unimplemented!();
166            #[cfg(feature = "esp32s3")]
167            const USB_DEVICE_INT_RAW: *const u32 = 0x60038000 as *const u32;
168
169            const SOF_INT_MASK: u32 = 0b10;
170
171            unsafe { (USB_DEVICE_INT_RAW.read_volatile() & SOF_INT_MASK) != 0 }
172        }
173
174        pub fn write_bytes_in_cs(bytes: &[u8], token: LockToken<'_>) {
175            if Self::use_jtag() {
176                PrinterSerialJtag::write_bytes_in_cs(bytes, token);
177            } else {
178                PrinterUart::write_bytes_in_cs(bytes, token);
179            }
180        }
181
182        pub fn flush(token: LockToken<'_>) {
183            if Self::use_jtag() {
184                PrinterSerialJtag::flush(token);
185            } else {
186                PrinterUart::flush(token);
187            }
188        }
189    }
190}
191
192#[cfg(all(
193    feature = "auto",
194    not(any(
195        feature = "esp32c3",
196        feature = "esp32c6",
197        feature = "esp32h2",
198        feature = "esp32p4",
199        feature = "esp32s3"
200    ))
201))]
202mod auto_printer {
203    // models that only have UART
204    pub type Printer = crate::uart_printer::Printer;
205}
206
207#[cfg(all(
208    any(feature = "jtag-serial", feature = "auto"),
209    any(
210        feature = "esp32c3",
211        feature = "esp32c6",
212        feature = "esp32h2",
213        feature = "esp32p4",
214        feature = "esp32s3"
215    )
216))]
217mod serial_jtag_printer {
218    use portable_atomic::{AtomicBool, Ordering};
219
220    use super::LockToken;
221    pub struct Printer;
222
223    #[cfg(feature = "esp32c3")]
224    const SERIAL_JTAG_FIFO_REG: usize = 0x6004_3000;
225    #[cfg(feature = "esp32c3")]
226    const SERIAL_JTAG_CONF_REG: usize = 0x6004_3004;
227
228    #[cfg(any(feature = "esp32c6", feature = "esp32h2"))]
229    const SERIAL_JTAG_FIFO_REG: usize = 0x6000_F000;
230    #[cfg(any(feature = "esp32c6", feature = "esp32h2"))]
231    const SERIAL_JTAG_CONF_REG: usize = 0x6000_F004;
232
233    #[cfg(feature = "esp32p4")]
234    const SERIAL_JTAG_FIFO_REG: usize = 0x500D_2000;
235    #[cfg(feature = "esp32p4")]
236    const SERIAL_JTAG_CONF_REG: usize = 0x500D_2004;
237
238    #[cfg(feature = "esp32s3")]
239    const SERIAL_JTAG_FIFO_REG: usize = 0x6003_8000;
240    #[cfg(feature = "esp32s3")]
241    const SERIAL_JTAG_CONF_REG: usize = 0x6003_8004;
242
243    /// A previous wait has timed out. We use this flag to avoid blocking
244    /// forever if there is no host attached.
245    static TIMED_OUT: AtomicBool = AtomicBool::new(false);
246
247    fn fifo_flush() {
248        let conf = SERIAL_JTAG_CONF_REG as *mut u32;
249        unsafe { conf.write_volatile(0b001) };
250    }
251
252    fn fifo_full() -> bool {
253        let conf = SERIAL_JTAG_CONF_REG as *mut u32;
254        unsafe { conf.read_volatile() & 0b010 == 0b000 }
255    }
256
257    fn fifo_write(byte: u8) {
258        let fifo = SERIAL_JTAG_FIFO_REG as *mut u32;
259        unsafe { fifo.write_volatile(byte as u32) }
260    }
261
262    fn wait_for_flush() -> bool {
263        const TIMEOUT_ITERATIONS: usize = 50_000;
264
265        // Wait for some time for the FIFO to clear.
266        let mut timeout = TIMEOUT_ITERATIONS;
267        while fifo_full() {
268            if timeout == 0 {
269                TIMED_OUT.store(true, Ordering::Relaxed);
270                return false;
271            }
272            timeout -= 1;
273        }
274
275        true
276    }
277
278    impl Printer {
279        pub fn write_bytes_in_cs(bytes: &[u8], _token: LockToken<'_>) {
280            if fifo_full() {
281                // The FIFO is full. Let's see if we can progress.
282
283                if TIMED_OUT.load(Ordering::Relaxed) {
284                    // Still wasn't able to drain the FIFO. Let's assume we won't be able to, and
285                    // don't queue up more data.
286                    // This is important so we don't block forever if there is no host attached.
287                    return;
288                }
289
290                // Give the fifo some time to drain.
291                if !wait_for_flush() {
292                    return;
293                }
294            } else {
295                // Reset the flag - we managed to clear our FIFO.
296                TIMED_OUT.store(false, Ordering::Relaxed);
297            }
298
299            for &b in bytes {
300                if fifo_full() {
301                    fifo_flush();
302
303                    // Wait for the FIFO to clear, we have more data to shift out.
304                    if !wait_for_flush() {
305                        return;
306                    }
307                }
308                fifo_write(b);
309            }
310        }
311
312        pub fn flush(_token: LockToken<'_>) {
313            fifo_flush();
314        }
315    }
316}
317
318#[cfg(all(any(feature = "uart", feature = "auto"), feature = "esp32"))]
319mod uart_printer {
320    use super::LockToken;
321    const UART_TX_ONE_CHAR: usize = 0x4000_9200;
322
323    pub struct Printer;
324    impl Printer {
325        pub fn write_bytes_in_cs(bytes: &[u8], _token: LockToken<'_>) {
326            for &b in bytes {
327                unsafe {
328                    let uart_tx_one_char: unsafe extern "C" fn(u8) -> i32 =
329                        core::mem::transmute(UART_TX_ONE_CHAR);
330                    uart_tx_one_char(b)
331                };
332            }
333        }
334
335        pub fn flush(_token: LockToken<'_>) {}
336    }
337}
338
339#[cfg(all(any(feature = "uart", feature = "auto"), feature = "esp32s2"))]
340mod uart_printer {
341    use super::LockToken;
342    pub struct Printer;
343    impl Printer {
344        pub fn write_bytes_in_cs(bytes: &[u8], _token: LockToken<'_>) {
345            // On ESP32-S2 the UART_TX_ONE_CHAR ROM-function seems to have some issues.
346            for chunk in bytes.chunks(64) {
347                for &b in chunk {
348                    unsafe {
349                        // write FIFO
350                        (0x3f400000 as *mut u32).write_volatile(b as u32);
351                    };
352                }
353
354                // wait for TX_DONE
355                while unsafe { (0x3f400004 as *const u32).read_volatile() } & (1 << 14) == 0 {}
356                unsafe {
357                    // reset TX_DONE
358                    (0x3f400010 as *mut u32).write_volatile(1 << 14);
359                }
360            }
361        }
362
363        pub fn flush(_token: LockToken<'_>) {}
364    }
365}
366
367#[cfg(all(
368    any(feature = "uart", feature = "auto"),
369    not(any(feature = "esp32", feature = "esp32s2"))
370))]
371mod uart_printer {
372    use super::LockToken;
373    trait Functions {
374        const TX_ONE_CHAR: usize;
375        const CHUNK_SIZE: usize = 32;
376
377        fn tx_byte(b: u8) {
378            unsafe {
379                let tx_one_char: unsafe extern "C" fn(u8) -> i32 =
380                    core::mem::transmute(Self::TX_ONE_CHAR);
381                tx_one_char(b);
382            }
383        }
384
385        fn flush();
386    }
387
388    struct Device;
389
390    #[cfg(feature = "esp32c2")]
391    impl Functions for Device {
392        const TX_ONE_CHAR: usize = 0x4000_005C;
393
394        fn flush() {
395            // tx_one_char waits for empty
396        }
397    }
398
399    #[cfg(feature = "esp32c3")]
400    impl Functions for Device {
401        const TX_ONE_CHAR: usize = 0x4000_0068;
402
403        fn flush() {
404            unsafe {
405                const TX_FLUSH: usize = 0x4000_0080;
406                const GET_CHANNEL: usize = 0x4000_058C;
407                let tx_flush: unsafe extern "C" fn(u8) = core::mem::transmute(TX_FLUSH);
408                let get_channel: unsafe extern "C" fn() -> u8 = core::mem::transmute(GET_CHANNEL);
409
410                const G_USB_PRINT_ADDR: usize = 0x3FCD_FFD0;
411                let g_usb_print = G_USB_PRINT_ADDR as *mut bool;
412
413                let channel = if *g_usb_print {
414                    // Flush USB-JTAG
415                    3
416                } else {
417                    get_channel()
418                };
419                tx_flush(channel);
420            }
421        }
422    }
423
424    #[cfg(feature = "esp32s3")]
425    impl Functions for Device {
426        const TX_ONE_CHAR: usize = 0x4000_0648;
427
428        fn flush() {
429            unsafe {
430                const TX_FLUSH: usize = 0x4000_0690;
431                const GET_CHANNEL: usize = 0x4000_1A58;
432                let tx_flush: unsafe extern "C" fn(u8) = core::mem::transmute(TX_FLUSH);
433                let get_channel: unsafe extern "C" fn() -> u8 = core::mem::transmute(GET_CHANNEL);
434
435                const G_USB_PRINT_ADDR: usize = 0x3FCE_FFB8;
436                let g_usb_print = G_USB_PRINT_ADDR as *mut bool;
437
438                let channel = if *g_usb_print {
439                    // Flush USB-JTAG
440                    4
441                } else {
442                    get_channel()
443                };
444                tx_flush(channel);
445            }
446        }
447    }
448
449    #[cfg(any(feature = "esp32c6", feature = "esp32h2"))]
450    impl Functions for Device {
451        const TX_ONE_CHAR: usize = 0x4000_0058;
452
453        fn flush() {
454            unsafe {
455                const TX_FLUSH: usize = 0x4000_0074;
456                const GET_CHANNEL: usize = 0x4000_003C;
457
458                let tx_flush: unsafe extern "C" fn(u8) = core::mem::transmute(TX_FLUSH);
459                let get_channel: unsafe extern "C" fn() -> u8 = core::mem::transmute(GET_CHANNEL);
460
461                tx_flush(get_channel());
462            }
463        }
464    }
465
466    #[cfg(feature = "esp32p4")]
467    impl Functions for Device {
468        const TX_ONE_CHAR: usize = 0x4FC0_0054;
469
470        fn flush() {
471            unsafe {
472                const TX_FLUSH: usize = 0x4FC0_0074;
473                const GET_CHANNEL: usize = 0x4FC0_0038;
474
475                let tx_flush: unsafe extern "C" fn(u8) = core::mem::transmute(TX_FLUSH);
476                let get_channel: unsafe extern "C" fn() -> u8 = core::mem::transmute(GET_CHANNEL);
477
478                tx_flush(get_channel());
479            }
480        }
481    }
482
483    pub struct Printer;
484    impl Printer {
485        pub fn write_bytes_in_cs(bytes: &[u8], _token: LockToken<'_>) {
486            for chunk in bytes.chunks(Device::CHUNK_SIZE) {
487                for &b in chunk {
488                    Device::tx_byte(b);
489                }
490
491                Device::flush();
492            }
493        }
494
495        pub fn flush(_token: LockToken<'_>) {}
496    }
497}
498
499#[cfg(not(feature = "critical-section"))]
500use core::marker::PhantomData;
501
502#[cfg(not(feature = "critical-section"))]
503type LockInner<'a> = PhantomData<&'a ()>;
504#[cfg(feature = "critical-section")]
505type LockInner<'a> = critical_section::CriticalSection<'a>;
506
507#[derive(Clone, Copy)]
508struct LockToken<'a>(LockInner<'a>);
509
510impl LockToken<'_> {
511    #[allow(unused)]
512    unsafe fn conjure() -> Self {
513        unsafe {
514            #[cfg(feature = "critical-section")]
515            let inner = critical_section::CriticalSection::new();
516            #[cfg(not(feature = "critical-section"))]
517            let inner = PhantomData;
518
519            LockToken(inner)
520        }
521    }
522}
523
524/// Runs the callback in a critical section, if enabled.
525#[inline]
526fn with<R>(f: impl FnOnce(LockToken) -> R) -> R {
527    #[cfg(feature = "critical-section")]
528    return critical_section::with(|cs| f(LockToken(cs)));
529
530    #[cfg(not(feature = "critical-section"))]
531    f(unsafe { LockToken::conjure() })
532}