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