aboutsummaryrefslogtreecommitdiff
path: root/embassy-usb-logger/src
diff options
context:
space:
mode:
authorUlf Lilleengen <[email protected]>2022-11-17 09:26:15 +0100
committerUlf Lilleengen <[email protected]>2022-11-18 11:22:58 +0100
commita444a65ebfcea674e74dcedc7f26a7aa37f59c69 (patch)
tree49ed7bb77196dcba8ca5a4ebab9fb555d1877a7a /embassy-usb-logger/src
parent2528f451387e6c7b27c3140cd87d47521d1971a2 (diff)
feat: embassy-usb-logger and example for rpi pico
* Add embassy-usb-logger which allows logging over USB for any device implementing embassy-usb * Add example using logger for rpi pico.
Diffstat (limited to 'embassy-usb-logger/src')
-rw-r--r--embassy-usb-logger/src/lib.rs146
1 files changed, 146 insertions, 0 deletions
diff --git a/embassy-usb-logger/src/lib.rs b/embassy-usb-logger/src/lib.rs
new file mode 100644
index 000000000..6386e2096
--- /dev/null
+++ b/embassy-usb-logger/src/lib.rs
@@ -0,0 +1,146 @@
1#![no_std]
2#![doc = include_str!("../README.md")]
3#![warn(missing_docs)]
4
5use core::fmt::Write as _;
6
7use embassy_futures::join::join;
8use embassy_sync::pipe::Pipe;
9use embassy_usb::class::cdc_acm::{CdcAcmClass, State};
10use embassy_usb::driver::Driver;
11use embassy_usb::{Builder, Config};
12use log::{Metadata, Record};
13
14type CS = embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
15
16/// The logger state containing buffers that must live as long as the USB peripheral.
17pub struct LoggerState<'d> {
18 state: State<'d>,
19 device_descriptor: [u8; 32],
20 config_descriptor: [u8; 128],
21 bos_descriptor: [u8; 16],
22 control_buf: [u8; 64],
23}
24
25impl<'d> LoggerState<'d> {
26 /// Create a new instance of the logger state.
27 pub fn new() -> Self {
28 Self {
29 state: State::new(),
30 device_descriptor: [0; 32],
31 config_descriptor: [0; 128],
32 bos_descriptor: [0; 16],
33 control_buf: [0; 64],
34 }
35 }
36}
37
38/// The logger handle, which contains a pipe with configurable size for buffering log messages.
39pub struct UsbLogger<const N: usize> {
40 buffer: Pipe<CS, N>,
41}
42
43impl<const N: usize> UsbLogger<N> {
44 /// Create a new logger instance.
45 pub const fn new() -> Self {
46 Self { buffer: Pipe::new() }
47 }
48
49 /// Run the USB logger using the state and USB driver. Never returns.
50 pub async fn run<'d, D>(&'d self, state: &'d mut LoggerState<'d>, driver: D) -> !
51 where
52 D: Driver<'d>,
53 Self: 'd,
54 {
55 const MAX_PACKET_SIZE: u8 = 64;
56 let mut config = Config::new(0xc0de, 0xcafe);
57 config.manufacturer = Some("Embassy");
58 config.product = Some("USB-serial logger");
59 config.serial_number = None;
60 config.max_power = 100;
61 config.max_packet_size_0 = MAX_PACKET_SIZE;
62
63 // Required for windows compatiblity.
64 // https://developer.nordicsemi.com/nRF_Connect_SDK/doc/1.9.1/kconfig/CONFIG_CDC_ACM_IAD.html#help
65 config.device_class = 0xEF;
66 config.device_sub_class = 0x02;
67 config.device_protocol = 0x01;
68 config.composite_with_iads = true;
69
70 let mut builder = Builder::new(
71 driver,
72 config,
73 &mut state.device_descriptor,
74 &mut state.config_descriptor,
75 &mut state.bos_descriptor,
76 &mut state.control_buf,
77 None,
78 );
79
80 // Create classes on the builder.
81 let mut class = CdcAcmClass::new(&mut builder, &mut state.state, MAX_PACKET_SIZE as u16);
82
83 // Build the builder.
84 let mut device = builder.build();
85
86 loop {
87 let run_fut = device.run();
88 let log_fut = async {
89 let mut rx: [u8; MAX_PACKET_SIZE as usize] = [0; MAX_PACKET_SIZE as usize];
90 class.wait_connection().await;
91 loop {
92 let len = self.buffer.read(&mut rx[..]).await;
93 let _ = class.write_packet(&rx[..len]).await;
94 }
95 };
96 join(run_fut, log_fut).await;
97 }
98 }
99}
100
101impl<const N: usize> log::Log for UsbLogger<N> {
102 fn enabled(&self, _metadata: &Metadata) -> bool {
103 true
104 }
105
106 fn log(&self, record: &Record) {
107 if self.enabled(record.metadata()) {
108 let _ = write!(Writer(&self.buffer), "{}\r\n", record.args());
109 }
110 }
111
112 fn flush(&self) {}
113}
114
115struct Writer<'d, const N: usize>(&'d Pipe<CS, N>);
116
117impl<'d, const N: usize> core::fmt::Write for Writer<'d, N> {
118 fn write_str(&mut self, s: &str) -> Result<(), core::fmt::Error> {
119 let _ = self.0.try_write(s.as_bytes());
120 Ok(())
121 }
122}
123
124/// Initialize and run the USB serial logger, never returns.
125///
126/// Arguments specify the buffer size, log level and the USB driver, respectively.
127///
128/// # Usage
129///
130/// ```
131/// embassy_usb_logger::run!(1024, log::LevelFilter::Info, driver);
132/// ```
133///
134/// # Safety
135///
136/// This macro should only be invoked only once since it is setting the global logging state of the application.
137#[macro_export]
138macro_rules! run {
139 ( $x:expr, $l:expr, $p:ident ) => {
140 static LOGGER: ::embassy_usb_logger::UsbLogger<$x> = ::embassy_usb_logger::UsbLogger::new();
141 unsafe {
142 let _ = ::log::set_logger_racy(&LOGGER).map(|()| log::set_max_level($l));
143 }
144 let _ = LOGGER.run(&mut ::embassy_usb_logger::LoggerState::new(), $p).await;
145 };
146}