diff options
| author | Ulf Lilleengen <[email protected]> | 2022-11-17 09:26:15 +0100 |
|---|---|---|
| committer | Ulf Lilleengen <[email protected]> | 2022-11-18 11:22:58 +0100 |
| commit | a444a65ebfcea674e74dcedc7f26a7aa37f59c69 (patch) | |
| tree | 49ed7bb77196dcba8ca5a4ebab9fb555d1877a7a | |
| parent | 2528f451387e6c7b27c3140cd87d47521d1971a2 (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.toml | 13 | ||||
| -rw-r--r-- | embassy-usb-logger/src/lib.rs | 146 | ||||
| -rw-r--r-- | examples/rp/Cargo.toml | 2 | ||||
| -rw-r--r-- | examples/rp/src/bin/usb_logger.rs | 30 |
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] | ||
| 2 | name = "embassy-usb-logger" | ||
| 3 | version = "0.1.0" | ||
| 4 | edition = "2021" | ||
| 5 | |||
| 6 | [dependencies] | ||
| 7 | embassy-usb = { version = "0.1.0", path = "../embassy-usb" } | ||
| 8 | embassy-sync = { version = "0.1.0", path = "../embassy-sync" } | ||
| 9 | embassy-futures = { version = "0.1.0", path = "../embassy-futures" } | ||
| 10 | futures = { version = "0.3", default-features = false } | ||
| 11 | static_cell = "1" | ||
| 12 | usbd-hid = "0.6.0" | ||
| 13 | log = "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 | |||
| 5 | use core::fmt::Write as _; | ||
| 6 | |||
| 7 | use embassy_futures::join::join; | ||
| 8 | use embassy_sync::pipe::Pipe; | ||
| 9 | use embassy_usb::class::cdc_acm::{CdcAcmClass, State}; | ||
| 10 | use embassy_usb::driver::Driver; | ||
| 11 | use embassy_usb::{Builder, Config}; | ||
| 12 | use log::{Metadata, Record}; | ||
| 13 | |||
| 14 | type CS = embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; | ||
| 15 | |||
| 16 | /// The logger state containing buffers that must live as long as the USB peripheral. | ||
| 17 | pub 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 | |||
| 25 | impl<'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. | ||
| 39 | pub struct UsbLogger<const N: usize> { | ||
| 40 | buffer: Pipe<CS, N>, | ||
| 41 | } | ||
| 42 | |||
| 43 | impl<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 | |||
| 101 | impl<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 | |||
| 115 | struct Writer<'d, const N: usize>(&'d Pipe<CS, N>); | ||
| 116 | |||
| 117 | impl<'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] | ||
| 138 | macro_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" | |||
| 13 | embassy-usb = { version = "0.1.0", path = "../../embassy-usb", features = ["defmt"] } | 13 | embassy-usb = { version = "0.1.0", path = "../../embassy-usb", features = ["defmt"] } |
| 14 | embassy-net = { version = "0.1.0", path = "../../embassy-net", features = ["defmt", "nightly", "tcp", "dhcpv4", "medium-ethernet", "pool-16"] } | 14 | embassy-net = { version = "0.1.0", path = "../../embassy-net", features = ["defmt", "nightly", "tcp", "dhcpv4", "medium-ethernet", "pool-16"] } |
| 15 | embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } | 15 | embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } |
| 16 | embassy-usb-logger = { version = "0.1.0", path = "../../embassy-usb-logger" } | ||
| 16 | 17 | ||
| 17 | defmt = "0.3" | 18 | defmt = "0.3" |
| 18 | defmt-rtt = "0.3" | 19 | defmt-rtt = "0.3" |
| @@ -32,6 +33,7 @@ embedded-hal-async = { version = "0.1.0-alpha.3" } | |||
| 32 | embedded-io = { version = "0.3.1", features = ["async", "defmt"] } | 33 | embedded-io = { version = "0.3.1", features = ["async", "defmt"] } |
| 33 | embedded-storage = { version = "0.3" } | 34 | embedded-storage = { version = "0.3" } |
| 34 | static_cell = "1.0.0" | 35 | static_cell = "1.0.0" |
| 36 | log = "0.4" | ||
| 35 | 37 | ||
| 36 | [profile.release] | 38 | [profile.release] |
| 37 | debug = true | 39 | debug = 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 | |||
| 5 | use embassy_executor::Spawner; | ||
| 6 | use embassy_rp::interrupt; | ||
| 7 | use embassy_rp::peripherals::USB; | ||
| 8 | use embassy_rp::usb::Driver; | ||
| 9 | use embassy_time::{Duration, Timer}; | ||
| 10 | use {defmt_rtt as _, panic_probe as _}; | ||
| 11 | |||
| 12 | #[embassy_executor::task] | ||
| 13 | async fn logger_task(driver: Driver<'static, USB>) { | ||
| 14 | embassy_usb_logger::run!(1024, log::LevelFilter::Info, driver); | ||
| 15 | } | ||
| 16 | |||
| 17 | #[embassy_executor::main] | ||
| 18 | async 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 | } | ||
