aboutsummaryrefslogtreecommitdiff
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
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.
-rw-r--r--embassy-usb-logger/Cargo.toml13
-rw-r--r--embassy-usb-logger/src/lib.rs146
-rw-r--r--examples/rp/Cargo.toml2
-rw-r--r--examples/rp/src/bin/usb_logger.rs30
4 files changed, 191 insertions, 0 deletions
diff --git a/embassy-usb-logger/Cargo.toml b/embassy-usb-logger/Cargo.toml
new file mode 100644
index 000000000..2f4665922
--- /dev/null
+++ b/embassy-usb-logger/Cargo.toml
@@ -0,0 +1,13 @@
1[package]
2name = "embassy-usb-logger"
3version = "0.1.0"
4edition = "2021"
5
6[dependencies]
7embassy-usb = { version = "0.1.0", path = "../embassy-usb" }
8embassy-sync = { version = "0.1.0", path = "../embassy-sync" }
9embassy-futures = { version = "0.1.0", path = "../embassy-futures" }
10futures = { version = "0.3", default-features = false }
11static_cell = "1"
12usbd-hid = "0.6.0"
13log = "0.4"
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}
diff --git a/examples/rp/Cargo.toml b/examples/rp/Cargo.toml
index 31f688305..250228862 100644
--- a/examples/rp/Cargo.toml
+++ b/examples/rp/Cargo.toml
@@ -13,6 +13,7 @@ embassy-rp = { version = "0.1.0", path = "../../embassy-rp", features = ["defmt"
13embassy-usb = { version = "0.1.0", path = "../../embassy-usb", features = ["defmt"] } 13embassy-usb = { version = "0.1.0", path = "../../embassy-usb", features = ["defmt"] }
14embassy-net = { version = "0.1.0", path = "../../embassy-net", features = ["defmt", "nightly", "tcp", "dhcpv4", "medium-ethernet", "pool-16"] } 14embassy-net = { version = "0.1.0", path = "../../embassy-net", features = ["defmt", "nightly", "tcp", "dhcpv4", "medium-ethernet", "pool-16"] }
15embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } 15embassy-futures = { version = "0.1.0", path = "../../embassy-futures" }
16embassy-usb-logger = { version = "0.1.0", path = "../../embassy-usb-logger" }
16 17
17defmt = "0.3" 18defmt = "0.3"
18defmt-rtt = "0.3" 19defmt-rtt = "0.3"
@@ -32,6 +33,7 @@ embedded-hal-async = { version = "0.1.0-alpha.3" }
32embedded-io = { version = "0.3.1", features = ["async", "defmt"] } 33embedded-io = { version = "0.3.1", features = ["async", "defmt"] }
33embedded-storage = { version = "0.3" } 34embedded-storage = { version = "0.3" }
34static_cell = "1.0.0" 35static_cell = "1.0.0"
36log = "0.4"
35 37
36[profile.release] 38[profile.release]
37debug = true 39debug = true
diff --git a/examples/rp/src/bin/usb_logger.rs b/examples/rp/src/bin/usb_logger.rs
new file mode 100644
index 000000000..52417a02e
--- /dev/null
+++ b/examples/rp/src/bin/usb_logger.rs
@@ -0,0 +1,30 @@
1#![no_std]
2#![no_main]
3#![feature(type_alias_impl_trait)]
4
5use embassy_executor::Spawner;
6use embassy_rp::interrupt;
7use embassy_rp::peripherals::USB;
8use embassy_rp::usb::Driver;
9use embassy_time::{Duration, Timer};
10use {defmt_rtt as _, panic_probe as _};
11
12#[embassy_executor::task]
13async fn logger_task(driver: Driver<'static, USB>) {
14 embassy_usb_logger::run!(1024, log::LevelFilter::Info, driver);
15}
16
17#[embassy_executor::main]
18async fn main(spawner: Spawner) {
19 let p = embassy_rp::init(Default::default());
20 let irq = interrupt::take!(USBCTRL_IRQ);
21 let driver = Driver::new(p.USB, irq);
22 spawner.spawn(logger_task(driver)).unwrap();
23
24 let mut counter = 0;
25 loop {
26 counter += 1;
27 log::info!("Tick {}", counter);
28 Timer::after(Duration::from_secs(1)).await;
29 }
30}