Skip to main content

esp_println/
lib.rs

1#![doc = include_str!("../README.md")]
2//! ## Feature Flags
3#![doc = document_features::document_features!(feature_label = r#"<span class="stab portability"><code>{feature}</code></span>"#)]
4#![doc(html_logo_url = "https://docs.espressif.com/projects/rust/esp-rs-grey-bg.svg")]
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<'_>) -> ::core::result::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<'_>) -> ::core::result::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 = "esp32c5",
151        feature = "esp32c6",
152        feature = "esp32c61",
153        feature = "esp32h2",
154        feature = "esp32p4",
155        feature = "esp32s3",
156        feature = "esp32s31"
157    )
158))]
159mod auto_printer {
160    use crate::{
161        LockToken,
162        serial_jtag_printer::Printer as PrinterSerialJtag,
163        uart_printer::Printer as PrinterUart,
164    };
165
166    pub struct Printer;
167    impl Printer {
168        fn use_jtag() -> bool {
169            // Decide if serial-jtag is used by checking SOF interrupt flag.
170            // SOF packet is sent by the HOST every 1ms on a full speed bus.
171            // Between two consecutive ticks, there will be at least 1ms (selectable tick
172            // rate range is 1 - 1000Hz).
173            // We don't reset the flag - if it was ever connected we assume serial-jtag is
174            // used
175            #[cfg(feature = "esp32c3")]
176            const USB_DEVICE_INT_RAW: *const u32 = 0x60043008 as *const u32;
177            #[cfg(any(
178                feature = "esp32c5",
179                feature = "esp32c6",
180                feature = "esp32c61",
181                feature = "esp32h2"
182            ))]
183            const USB_DEVICE_INT_RAW: *const u32 = 0x6000f008 as *const u32;
184            #[cfg(feature = "esp32s3")]
185            const USB_DEVICE_INT_RAW: *const u32 = 0x60038000 as *const u32;
186            #[cfg(feature = "esp32p4")]
187            const USB_DEVICE_INT_RAW: *const u32 = 0x500D2008 as *const u32;
188            #[cfg(feature = "esp32s31")]
189            const USB_DEVICE_INT_RAW: *const u32 = 0x20391008 as *const u32;
190
191            const SOF_INT_MASK: u32 = 0b10;
192
193            unsafe { (USB_DEVICE_INT_RAW.read_volatile() & SOF_INT_MASK) != 0 }
194        }
195
196        pub fn write_bytes_in_cs(bytes: &[u8], token: LockToken<'_>) {
197            if Self::use_jtag() {
198                PrinterSerialJtag::write_bytes_in_cs(bytes, token);
199            } else {
200                PrinterUart::write_bytes_in_cs(bytes, token);
201            }
202        }
203
204        pub fn flush(token: LockToken<'_>) {
205            if Self::use_jtag() {
206                PrinterSerialJtag::flush(token);
207            } else {
208                PrinterUart::flush(token);
209            }
210        }
211    }
212}
213
214#[cfg(all(
215    feature = "auto",
216    not(any(
217        feature = "esp32c3",
218        feature = "esp32c5",
219        feature = "esp32c6",
220        feature = "esp32c61",
221        feature = "esp32h2",
222        feature = "esp32p4",
223        feature = "esp32s3",
224        feature = "esp32s31"
225    ))
226))]
227mod auto_printer {
228    // models that only have UART
229    pub type Printer = crate::uart_printer::Printer;
230}
231
232#[cfg(all(
233    any(feature = "jtag-serial", feature = "auto"),
234    any(
235        feature = "esp32c3",
236        feature = "esp32c5",
237        feature = "esp32c6",
238        feature = "esp32c61",
239        feature = "esp32h2",
240        feature = "esp32p4",
241        feature = "esp32s3",
242        feature = "esp32s31"
243    )
244))]
245mod serial_jtag_printer {
246    use portable_atomic::{AtomicBool, Ordering};
247
248    use super::LockToken;
249    pub struct Printer;
250
251    #[cfg(feature = "esp32c3")]
252    const SERIAL_JTAG_FIFO_REG: usize = 0x6004_3000;
253    #[cfg(feature = "esp32c3")]
254    const SERIAL_JTAG_CONF_REG: usize = 0x6004_3004;
255
256    #[cfg(any(
257        feature = "esp32c5",
258        feature = "esp32c6",
259        feature = "esp32c61",
260        feature = "esp32h2"
261    ))]
262    const SERIAL_JTAG_FIFO_REG: usize = 0x6000_F000;
263    #[cfg(any(
264        feature = "esp32c5",
265        feature = "esp32c6",
266        feature = "esp32c61",
267        feature = "esp32h2"
268    ))]
269    const SERIAL_JTAG_CONF_REG: usize = 0x6000_F004;
270
271    #[cfg(feature = "esp32s3")]
272    const SERIAL_JTAG_FIFO_REG: usize = 0x6003_8000;
273    #[cfg(feature = "esp32s3")]
274    const SERIAL_JTAG_CONF_REG: usize = 0x6003_8004;
275
276    // ESP32-P4: USB_DEVICE peripheral at 0x500D_2000 per PAC.
277    #[cfg(feature = "esp32p4")]
278    const SERIAL_JTAG_FIFO_REG: usize = 0x500D_2000;
279    #[cfg(feature = "esp32p4")]
280    const SERIAL_JTAG_CONF_REG: usize = 0x500D_2004;
281
282    // ESP32-S31: USB_DEVICE peripheral at 0x2039_1000 per PAC.
283    #[cfg(feature = "esp32s31")]
284    const SERIAL_JTAG_FIFO_REG: usize = 0x2039_1000;
285    #[cfg(feature = "esp32s31")]
286    const SERIAL_JTAG_CONF_REG: usize = 0x2039_1004;
287
288    /// A previous wait has timed out. We use this flag to avoid blocking
289    /// forever if there is no host attached.
290    static TIMED_OUT: AtomicBool = AtomicBool::new(false);
291
292    fn fifo_flush() {
293        let conf = SERIAL_JTAG_CONF_REG as *mut u32;
294        unsafe { conf.write_volatile(0b001) };
295    }
296
297    fn fifo_full() -> bool {
298        let conf = SERIAL_JTAG_CONF_REG as *mut u32;
299        unsafe { conf.read_volatile() & 0b010 == 0b000 }
300    }
301
302    fn fifo_write(byte: u8) {
303        let fifo = SERIAL_JTAG_FIFO_REG as *mut u32;
304        unsafe { fifo.write_volatile(byte as u32) }
305    }
306
307    fn wait_for_flush() -> bool {
308        const TIMEOUT_ITERATIONS: usize = 50_000;
309
310        // Wait for some time for the FIFO to clear.
311        let mut timeout = TIMEOUT_ITERATIONS;
312        while fifo_full() {
313            if timeout == 0 {
314                TIMED_OUT.store(true, Ordering::Relaxed);
315                return false;
316            }
317            timeout -= 1;
318        }
319
320        true
321    }
322
323    impl Printer {
324        pub fn write_bytes_in_cs(bytes: &[u8], _token: LockToken<'_>) {
325            if fifo_full() {
326                // The FIFO is full. Let's see if we can progress.
327
328                if TIMED_OUT.load(Ordering::Relaxed) {
329                    // Still wasn't able to drain the FIFO. Let's assume we won't be able to, and
330                    // don't queue up more data.
331                    // This is important so we don't block forever if there is no host attached.
332                    return;
333                }
334
335                // Give the fifo some time to drain.
336                if !wait_for_flush() {
337                    return;
338                }
339            } else {
340                // Reset the flag - we managed to clear our FIFO.
341                TIMED_OUT.store(false, Ordering::Relaxed);
342            }
343
344            for &b in bytes {
345                if fifo_full() {
346                    fifo_flush();
347
348                    // Wait for the FIFO to clear, we have more data to shift out.
349                    if !wait_for_flush() {
350                        return;
351                    }
352                }
353                fifo_write(b);
354            }
355        }
356
357        pub fn flush(_token: LockToken<'_>) {
358            fifo_flush();
359        }
360    }
361}
362
363#[cfg(all(any(feature = "uart", feature = "auto"), feature = "esp32"))]
364mod uart_printer {
365    use super::LockToken;
366    const UART_TX_ONE_CHAR: usize = 0x4000_9200;
367
368    pub struct Printer;
369    impl Printer {
370        pub fn write_bytes_in_cs(bytes: &[u8], _token: LockToken<'_>) {
371            for &b in bytes {
372                unsafe {
373                    let uart_tx_one_char: unsafe extern "C" fn(u8) -> i32 =
374                        core::mem::transmute(UART_TX_ONE_CHAR);
375                    uart_tx_one_char(b)
376                };
377            }
378        }
379
380        pub fn flush(_token: LockToken<'_>) {}
381    }
382}
383
384#[cfg(all(any(feature = "uart", feature = "auto"), feature = "esp32s2"))]
385mod uart_printer {
386    use super::LockToken;
387    pub struct Printer;
388    impl Printer {
389        pub fn write_bytes_in_cs(bytes: &[u8], _token: LockToken<'_>) {
390            // On ESP32-S2 the UART_TX_ONE_CHAR ROM-function seems to have some issues.
391            for chunk in bytes.chunks(64) {
392                for &b in chunk {
393                    unsafe {
394                        // write FIFO
395                        (0x3f400000 as *mut u32).write_volatile(b as u32);
396                    };
397                }
398
399                // wait for TX_DONE
400                while unsafe { (0x3f400004 as *const u32).read_volatile() } & (1 << 14) == 0 {}
401                unsafe {
402                    // reset TX_DONE
403                    (0x3f400010 as *mut u32).write_volatile(1 << 14);
404                }
405            }
406        }
407
408        pub fn flush(_token: LockToken<'_>) {}
409    }
410}
411
412#[cfg(all(
413    any(feature = "uart", feature = "auto"),
414    not(any(feature = "esp32", feature = "esp32s2"))
415))]
416mod uart_printer {
417    use super::LockToken;
418    trait Functions {
419        const TX_ONE_CHAR: usize;
420        const CHUNK_SIZE: usize = 32;
421
422        fn tx_byte(b: u8) {
423            unsafe {
424                let tx_one_char: unsafe extern "C" fn(u8) -> i32 =
425                    core::mem::transmute(Self::TX_ONE_CHAR);
426                tx_one_char(b);
427            }
428        }
429
430        fn flush();
431    }
432
433    struct Device;
434
435    // ESP32-P4: resolve through the linker-provided ROM symbol
436    // (`esp_rom_uart_tx_one_char` -> `uart_tx_one_char2 = 0x4fc0_0058`,
437    // see esp-rom-sys/ld/esp32p4/rom/esp32p4.rom.api.ld) instead of
438    // hardcoding the address. Matches the c5/c6/c61/h2 channel-aware path.
439    #[cfg(any(feature = "esp32p4", feature = "esp32s31"))]
440    impl Functions for Device {
441        // Unused -- tx_byte() below resolves through the linker.
442        const TX_ONE_CHAR: usize = 0;
443
444        fn tx_byte(b: u8) {
445            unsafe extern "C" {
446                fn esp_rom_uart_tx_one_char(c: u8) -> i32;
447            }
448            unsafe {
449                esp_rom_uart_tx_one_char(b);
450            }
451        }
452
453        fn flush() {
454            // tx_one_char waits for TX FIFO space
455        }
456    }
457
458    #[cfg(feature = "esp32c2")]
459    impl Functions for Device {
460        const TX_ONE_CHAR: usize = 0x4000_005C;
461
462        fn flush() {
463            // tx_one_char waits for empty
464        }
465    }
466
467    #[cfg(feature = "esp32c3")]
468    impl Functions for Device {
469        const TX_ONE_CHAR: usize = 0x4000_0068;
470
471        fn flush() {
472            unsafe {
473                const TX_FLUSH: usize = 0x4000_0080;
474                const GET_CHANNEL: usize = 0x4000_058C;
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                const G_USB_PRINT_ADDR: usize = 0x3FCD_FFD0;
479                let g_usb_print = G_USB_PRINT_ADDR as *mut bool;
480
481                let channel = if *g_usb_print {
482                    // Flush USB-JTAG
483                    3
484                } else {
485                    get_channel()
486                };
487                tx_flush(channel);
488            }
489        }
490    }
491
492    #[cfg(feature = "esp32s3")]
493    impl Functions for Device {
494        const TX_ONE_CHAR: usize = 0x4000_0648;
495
496        fn flush() {
497            unsafe {
498                const TX_FLUSH: usize = 0x4000_0690;
499                const GET_CHANNEL: usize = 0x4000_1A58;
500                let tx_flush: unsafe extern "C" fn(u8) = core::mem::transmute(TX_FLUSH);
501                let get_channel: unsafe extern "C" fn() -> u8 = core::mem::transmute(GET_CHANNEL);
502
503                const G_USB_PRINT_ADDR: usize = 0x3FCE_FFB8;
504                let g_usb_print = G_USB_PRINT_ADDR as *mut bool;
505
506                let channel = if *g_usb_print {
507                    // Flush USB-JTAG
508                    4
509                } else {
510                    get_channel()
511                };
512                tx_flush(channel);
513            }
514        }
515    }
516
517    #[cfg(any(
518        feature = "esp32c5",
519        feature = "esp32c6",
520        feature = "esp32c61",
521        feature = "esp32h2"
522    ))]
523    impl Functions for Device {
524        const TX_ONE_CHAR: usize = 0x4000_0058;
525
526        fn flush() {
527            unsafe {
528                const TX_FLUSH: usize = 0x4000_0074;
529
530                #[cfg(not(any(feature = "esp32c5", feature = "esp32c61")))]
531                const GET_CHANNEL: usize = 0x4000_003C;
532
533                #[cfg(any(feature = "esp32c5", feature = "esp32c61"))]
534                const GET_CHANNEL: usize = 0x4000_0038;
535
536                let tx_flush: unsafe extern "C" fn(u8) = core::mem::transmute(TX_FLUSH);
537                let get_channel: unsafe extern "C" fn() -> u8 = core::mem::transmute(GET_CHANNEL);
538
539                tx_flush(get_channel());
540            }
541        }
542    }
543
544    pub struct Printer;
545    impl Printer {
546        pub fn write_bytes_in_cs(bytes: &[u8], _token: LockToken<'_>) {
547            for chunk in bytes.chunks(Device::CHUNK_SIZE) {
548                for &b in chunk {
549                    Device::tx_byte(b);
550                }
551
552                Device::flush();
553            }
554        }
555
556        pub fn flush(_token: LockToken<'_>) {}
557    }
558}
559
560#[cfg(feature = "no-op")]
561mod noop {
562    pub struct Printer;
563
564    impl Printer {
565        pub fn write_bytes_in_cs(_bytes: &[u8], _token: super::LockToken<'_>) {}
566
567        pub fn flush(_token: super::LockToken<'_>) {}
568    }
569}
570
571use core::marker::PhantomData;
572
573#[derive(Clone, Copy)]
574#[doc(hidden)]
575pub struct LockToken<'a>(PhantomData<&'a ()>);
576
577impl LockToken<'_> {
578    #[allow(unused)]
579    unsafe fn conjure() -> Self {
580        LockToken(PhantomData)
581    }
582}
583
584#[cfg(feature = "critical-section")]
585static LOCK: esp_sync::RawMutex = esp_sync::RawMutex::new();
586
587/// Runs the callback in a critical section, if enabled.
588#[doc(hidden)]
589#[inline]
590pub fn with<R>(f: impl FnOnce(LockToken) -> R) -> R {
591    #[cfg(feature = "critical-section")]
592    return LOCK.lock(|| f(unsafe { LockToken::conjure() }));
593
594    #[cfg(not(feature = "critical-section"))]
595    f(unsafe { LockToken::conjure() })
596}