Skip to main content

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().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        unsafe { (&raw mut ENCODER).as_mut().unwrap().start_frame(write_serial) }
131    }
132
133    unsafe fn release() {
134        unsafe { (&raw mut ENCODER).as_mut().unwrap().end_frame(write_serial); }
135        TAKEN.store(false, Ordering::Relaxed);
136
137        let restore = (&raw mut CS_RESTORE);
138        unsafe { critical_section::release(*restore); }
139    }
140
141    unsafe fn write(bytes: &[u8]) {
142        unsafe { (&raw mut ENCODER).as_mut().unwrap().write(bytes, write_serial); }
143    }
144
145    unsafe fn flush() {
146        unsafe {
147            if let Some(writer) = &mut *addr_of_mut!(ERASEDWRITE) {
148                (*writer).flush();
149            }
150        }
151    }
152}
153
154/// Write to serial using proxy function. We must ensure this function is not called
155/// several times in parallel.
156fn write_serial(remaining: &[u8]) {
157    unsafe {
158        if let Some(writer) = &mut *addr_of_mut!(ERASEDWRITE) {
159            (*writer).write(remaining);
160        }
161    }
162}