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