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}