defmt_serial/lib.rs
1#![no_std]
2//! A defmt target for logging messages over a serial interface. The serial interface must
3//! implement [`embedded_hal::blocking::serial::Write`].
4//!
5//! The received defmt-frames can be read using e.g. `socat` and `defmt-print`, so that you can set
6//! it up as `cargo run`ner. See the [example-artemis](https://github.com/gauteh/defmt-serial/tree/main/example-artemis) for how to do that.
7//!
8//! You can also use it to have defmt work on std/hosted OSes, see [example-std](https://github.com/gauteh/defmt-serial/tree/main/example-std).
9//!
10//! ```no_run
11//! #![no_std]
12//! #![no_main]
13//!
14//!
15//! use panic_probe as _;
16//! use cortex_m::asm;
17//! use cortex_m_rt::entry;
18//! use ambiq_hal::{self as hal, prelude::*};
19//!
20//! use static_cell::StaticCell;
21//! use defmt;
22//! use defmt_serial as _;
23//!
24//! static SERIAL: StaticCell<hal::uart::Uart0> = StaticCell::new();
25//!
26//! #[entry]
27//! fn main() -> ! {
28//! let mut dp = hal::pac::Peripherals::take().unwrap();
29//! let pins = hal::gpio::Pins::new(dp.GPIO);
30//!
31//! // set up serial
32//! let serial = hal::uart::Uart0::new(dp.UART0, pins.tx0, pins.rx0);
33//! defmt_serial::defmt_serial(SERIAL.init(serial));
34//!
35//! defmt::info!("Hello from defmt!");
36//!
37//! loop {
38//! asm::wfi();
39//! }
40//! }
41//! ```
42
43use core::ptr::addr_of_mut;
44use core::sync::atomic::Ordering;
45use portable_atomic::AtomicBool;
46use defmt::global_logger;
47use embedded_io::Write;
48
49static mut ENCODER: defmt::Encoder = defmt::Encoder::new();
50static TAKEN: AtomicBool = AtomicBool::new(false);
51static mut CS_RESTORE: critical_section::RestoreState = critical_section::RestoreState::invalid();
52
53/// All of this nonsense is to try and erase the Error type of the `embedded_hal::serial::nb::Write` implementor.
54pub trait EraseWrite {
55 fn write(&mut self, buf: &[u8]);
56 fn flush(&mut self);
57}
58
59impl<T: Write> EraseWrite for T {
60 fn write(&mut self, buf: &[u8]) {
61 self.write_all(buf).ok();
62 }
63
64 fn flush(&mut self) {
65 self.flush().ok();
66 }
67}
68
69static mut ERASEDWRITE: Option<&mut dyn EraseWrite> = None;
70
71/// Assign a serial peripheral to receive defmt-messages.
72///
73///
74/// ```no_run
75/// static SERIAL: StaticCell<hal::uart::Uart0> = StaticCell::new();
76/// let serial = hal::uart::Uart0::new(dp.UART0, pins.tx0, pins.rx0);
77/// defmt_serial::defmt_serial(SERIAL.init(serial));
78///
79/// defmt::info!("Hello from defmt!");
80/// ```
81///
82/// The peripheral should implement the [`embedded_hal::blocking::serial::Write`] trait. If your HAL
83/// already has the non-blocking [`Write`](embedded_hal::serial::Write) implemented, it can opt-in
84/// to the [default implementation](embedded_hal::blocking::serial::write::Default).
85///
86/// Will panic if assigned more than once.
87pub fn defmt_serial<T: EraseWrite>(serial: &'static mut T) {
88 unsafe {
89 critical_section::with(|_| {
90 assert!(
91 (&raw mut ERASEDWRITE).as_ref().unwrap().is_none(),
92 "Tried to assign serial port when one was already assigned."
93 );
94 ERASEDWRITE = Some(serial);
95 });
96 }
97}
98
99/// Release the serial port from defmt.
100pub fn release() {
101 unsafe {
102 critical_section::with(|_| {
103 if TAKEN.load(Ordering::Relaxed) {
104 panic!("defmt logger taken reentrantly"); // I don't think this actually is
105 // possible.
106 }
107
108 ERASEDWRITE = None;
109 });
110 }
111}
112
113#[global_logger]
114struct GlobalSerialLogger;
115
116unsafe impl defmt::Logger for GlobalSerialLogger {
117 fn acquire() {
118 let restore = unsafe { critical_section::acquire() };
119
120 if TAKEN.load(Ordering::Relaxed) {
121 panic!("defmt logger taken reentrantly");
122 }
123
124 TAKEN.store(true, Ordering::Relaxed);
125
126 unsafe {
127 CS_RESTORE = restore;
128 }
129
130 #[cfg(feature = "espflash")]
131 write_serial(&[0xFF, 0x00]);
132
133 unsafe { (&raw mut ENCODER).as_mut().unwrap().start_frame(write_serial) }
134 }
135
136 unsafe fn release() {
137 unsafe { (&raw mut ENCODER).as_mut().unwrap().end_frame(write_serial); }
138 TAKEN.store(false, Ordering::Relaxed);
139
140 let restore = (&raw mut CS_RESTORE);
141 unsafe { critical_section::release(*restore); }
142 }
143
144 unsafe fn write(bytes: &[u8]) {
145 unsafe { (&raw mut ENCODER).as_mut().unwrap().write(bytes, write_serial); }
146 }
147
148 unsafe fn flush() {
149 unsafe {
150 if let Some(writer) = &mut *addr_of_mut!(ERASEDWRITE) {
151 (*writer).flush();
152 }
153 }
154 }
155}
156
157/// Write to serial using proxy function. We must ensure this function is not called
158/// several times in parallel.
159fn write_serial(remaining: &[u8]) {
160 unsafe {
161 if let Some(writer) = &mut *addr_of_mut!(ERASEDWRITE) {
162 (*writer).write(remaining);
163 }
164 }
165}