1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157
#![no_std]
//! A defmt target for logging messages over a serial interface. The serial interface must
//! implement [`embedded_hal::blocking::serial::Write`].
//!
//! The received defmt-frames can be read using e.g. `socat` and `defmt-print`, so that you can set
//! 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.
//!
//! 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).
//!
//! ```no_run
//! #![no_std]
//! #![no_main]
//!
//!
//! use panic_probe as _;
//! use cortex_m::asm;
//! use cortex_m_rt::entry;
//! use ambiq_hal::{self as hal, prelude::*};
//! use defmt;
//! use defmt_serial as _;
//!
//! #[entry]
//! fn main() -> ! {
//! let mut dp = hal::pac::Peripherals::take().unwrap();
//! let pins = hal::gpio::Pins::new(dp.GPIO);
//!
//! // set up serial
//! let mut serial = hal::uart::Uart0::new(dp.UART0, pins.tx0, pins.rx0);
//! defmt_serial::defmt_serial(serial);
//!
//! defmt::info!("Hello from defmt!");
//!
//! loop {
//! asm::wfi();
//! }
//! }
//! ```
use core::sync::atomic::{AtomicBool, Ordering};
use defmt::global_logger;
static mut ENCODER: defmt::Encoder = defmt::Encoder::new();
static TAKEN: AtomicBool = AtomicBool::new(false);
static mut CS_RESTORE: critical_section::RestoreState = critical_section::RestoreState::invalid();
/// All of this nonsense is to try and erase the Error type of the `embedded_hal::serial::nb::Write` implementor.
type WriteCB = unsafe fn(SFn);
static mut WRITEFN: Option<WriteCB> = None;
enum SFn<'a> {
Buf(&'a [u8]),
Flush,
}
unsafe fn trampoline<F>(buf: SFn)
where
F: FnMut(SFn),
{
if let Some(wfn) = WRITEFN {
let wfn = &mut *(wfn as *mut F);
wfn(buf);
}
}
fn get_trampoline<F>(_closure: &F) -> WriteCB
where
F: FnMut(SFn),
{
trampoline::<F>
}
/// Assign a serial peripheral to receive defmt-messages.
///
///
/// ```no_run
/// let mut serial = hal::uart::Uart0::new(dp.UART0, pins.tx0, pins.rx0);
/// defmt_serial::defmt_serial(serial);
///
/// defmt::info!("Hello from defmt!");
/// ```
///
/// The peripheral should implement the [`embedded_hal::blocking::serial::Write`] trait. If your HAL
/// already has the non-blocking [`Write`](embedded_hal::serial::Write) implemented, it can opt-in
/// to the [default implementation](embedded_hal::blocking::serial::write::Default).
pub fn defmt_serial(serial: impl embedded_hal::blocking::serial::Write<u8> + 'static) {
let mut serial = core::mem::ManuallyDrop::new(serial);
let wfn = move |a: SFn| {
match a {
SFn::Buf(buf) => {
for b in buf {
serial.bwrite_all(&b.to_ne_bytes()).ok();
}
}
SFn::Flush => {
serial.bflush().ok();
}
};
};
let trampoline = get_trampoline(&wfn);
unsafe {
let token = critical_section::acquire();
WRITEFN = Some(trampoline);
critical_section::release(token);
}
}
#[global_logger]
struct GlobalSerialLogger;
unsafe impl defmt::Logger for GlobalSerialLogger {
fn acquire() {
let restore = unsafe { critical_section::acquire() };
if TAKEN.load(Ordering::Relaxed) {
panic!("defmt logger taken reentrantly");
}
TAKEN.store(true, Ordering::Relaxed);
unsafe {
CS_RESTORE = restore;
}
unsafe { ENCODER.start_frame(write_serial) }
}
unsafe fn release() {
ENCODER.end_frame(write_serial);
TAKEN.store(false, Ordering::Relaxed);
let restore = CS_RESTORE;
critical_section::release(restore);
}
unsafe fn write(bytes: &[u8]) {
ENCODER.write(bytes, write_serial);
}
unsafe fn flush() {
if let Some(wfn) = WRITEFN {
wfn(SFn::Flush);
}
}
}
/// Write to serial using proxy function. We must ensure this function is not called
/// several times in parallel.
fn write_serial(remaining: &[u8]) {
unsafe {
if let Some(wfn) = WRITEFN {
wfn(SFn::Buf(remaining));
}
}
}