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::{AtomicBool, Ordering};
45use defmt::global_logger;
46use embedded_io::Write;
47
48static mut ENCODER: defmt::Encoder = defmt::Encoder::new();
49static TAKEN: AtomicBool = AtomicBool::new(false);
50static mut CS_RESTORE: critical_section::RestoreState = critical_section::RestoreState::invalid();
51
52/// All of this nonsense is to try and erase the Error type of the `embedded_hal::serial::nb::Write` implementor.
53pub trait EraseWrite {
54    fn write(&mut self, buf: &[u8]);
55    fn flush(&mut self);
56}
57
58impl<T: Write> EraseWrite for T {
59    fn write(&mut self, buf: &[u8]) {
60        self.write_all(buf).ok();
61    }
62
63    fn flush(&mut self) {
64        self.flush().ok();
65    }
66}
67
68static mut ERASEDWRITE: Option<&mut dyn EraseWrite> = None;
69
70/// Assign a serial peripheral to receive defmt-messages.
71///
72///
73/// ```no_run
74///     static SERIAL: StaticCell<hal::uart::Uart0> = StaticCell::new();
75///     let serial = hal::uart::Uart0::new(dp.UART0, pins.tx0, pins.rx0);
76///     defmt_serial::defmt_serial(SERIAL.init(serial));
77///
78///     defmt::info!("Hello from defmt!");
79/// ```
80///
81/// The peripheral should implement the [`embedded_hal::blocking::serial::Write`] trait. If your HAL
82/// already has the non-blocking [`Write`](embedded_hal::serial::Write) implemented, it can opt-in
83/// to the [default implementation](embedded_hal::blocking::serial::write::Default).
84///
85/// Will panic if assigned more than once.
86pub fn defmt_serial<T: EraseWrite>(serial: &'static mut T) {
87    unsafe {
88        critical_section::with(|_| {
89            assert!(
90                ERASEDWRITE.is_none(),
91                "Tried to assign serial port when one was already assigned."
92            );
93            ERASEDWRITE = Some(serial);
94        });
95    }
96}
97
98/// Release the serial port from defmt.
99pub fn release() {
100    unsafe {
101        critical_section::with(|_| {
102            if TAKEN.load(Ordering::Relaxed) {
103                panic!("defmt logger taken reentrantly"); // I don't think this actually is
104                                                          // possible.
105            }
106
107            ERASEDWRITE = None;
108        });
109    }
110}
111
112#[global_logger]
113struct GlobalSerialLogger;
114
115unsafe impl defmt::Logger for GlobalSerialLogger {
116    fn acquire() {
117        let restore = unsafe { critical_section::acquire() };
118
119        if TAKEN.load(Ordering::Relaxed) {
120            panic!("defmt logger taken reentrantly");
121        }
122
123        TAKEN.store(true, Ordering::Relaxed);
124
125        unsafe {
126            CS_RESTORE = restore;
127        }
128
129        unsafe { ENCODER.start_frame(write_serial) }
130    }
131
132    unsafe fn release() {
133        ENCODER.end_frame(write_serial);
134        TAKEN.store(false, Ordering::Relaxed);
135
136        let restore = CS_RESTORE;
137        critical_section::release(restore);
138    }
139
140    unsafe fn write(bytes: &[u8]) {
141        ENCODER.write(bytes, write_serial);
142    }
143
144    unsafe fn flush() {
145        if let Some(writer) = &mut *addr_of_mut!(ERASEDWRITE) {
146            (*writer).flush();
147        }
148    }
149}
150
151/// Write to serial using proxy function. We must ensure this function is not called
152/// several times in parallel.
153fn write_serial(remaining: &[u8]) {
154    unsafe {
155        if let Some(writer) = &mut *addr_of_mut!(ERASEDWRITE) {
156            (*writer).write(remaining);
157        }
158    }
159}