esp_println/
lib.rs

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