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#[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#[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#[cfg(feature = "no-op")]
37#[macro_export]
38macro_rules! println {
39 ($($arg:tt)*) => {{}};
40}
41
42#[cfg(feature = "no-op")]
44#[macro_export]
45macro_rules! print {
46 ($($arg:tt)*) => {{}};
47}
48
49#[macro_export]
53macro_rules! dbg {
54 () => {
59 $crate::println!("[{}:{}]", ::core::file!(), ::core::line!())
60 };
61 ($val:expr $(,)?) => {
62 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
77pub 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 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", 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 #[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 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 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 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 if TIMED_OUT.load(Ordering::Relaxed) {
258 return;
262 }
263
264 if !wait_for_flush() {
266 return;
267 }
268 } else {
269 TIMED_OUT.store(false, Ordering::Relaxed);
271 }
272
273 for &b in bytes {
274 if fifo_full() {
275 fifo_flush();
276
277 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 for chunk in bytes.chunks(64) {
321 for &b in chunk {
322 unsafe {
323 (0x3f400000 as *mut u32).write_volatile(b as u32);
325 };
326 }
327
328 while unsafe { (0x3f400004 as *const u32).read_volatile() } & (1 << 14) == 0 {}
330 unsafe {
331 (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 }
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 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 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#[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}