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 /embassy-usb-logger | |
| 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.
Diffstat (limited to 'embassy-usb-logger')
| -rw-r--r-- | embassy-usb-logger/Cargo.toml | 13 | ||||
| -rw-r--r-- | embassy-usb-logger/src/lib.rs | 146 |
2 files changed, 159 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 | } | ||
