From 1f57692d042de781f884d50f0cbd9eea0c71626d Mon Sep 17 00:00:00 2001 From: "Jomer.Dev" Date: Mon, 8 Jan 2024 00:20:40 +0100 Subject: [PATCH 1/6] Add function to create logger from class --- embassy-usb-logger/src/lib.rs | 54 +++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/embassy-usb-logger/src/lib.rs b/embassy-usb-logger/src/lib.rs index 45d780bf8..422cefb59 100644 --- a/embassy-usb-logger/src/lib.rs +++ b/embassy-usb-logger/src/lib.rs @@ -105,6 +105,34 @@ impl UsbLogger { join(run_fut, join(log_fut, discard_fut)).await; } } + + // Creates the futures needed for the logger from a given class + pub async fn create_future_from_class<'d, D>(&'d self, class: CdcAcmClass<'d, D> ) + where + D: Driver<'d>, + { + const MAX_PACKET_SIZE: u8 = 64; + let (mut sender, mut receiver) = class.split(); + + loop { + let log_fut = async { + let mut rx: [u8; MAX_PACKET_SIZE as usize] = [0; MAX_PACKET_SIZE as usize]; + sender.wait_connection().await; + loop { + let len = self.buffer.read(&mut rx[..]).await; + let _ = sender.write_packet(&rx[..len]).await; + } + }; + let discard_fut = async { + let mut discard_buf: [u8; MAX_PACKET_SIZE as usize] = [0; MAX_PACKET_SIZE as usize]; + receiver.wait_connection().await; + loop { + let _ = receiver.read_packet(&mut discard_buf).await; + } + }; + join(log_fut, discard_fut).await; + } + } } impl log::Log for UsbLogger { @@ -153,3 +181,29 @@ macro_rules! run { let _ = LOGGER.run(&mut ::embassy_usb_logger::LoggerState::new(), $p).await; }; } + +/// Initialize the USB serial logger from a serial class and return the future to run it. +/// +/// Arguments specify the buffer size, log level and the serial class, respectively. +/// +/// # Usage +/// +/// ``` +/// embassy_usb_logger::with_class!(1024, log::LevelFilter::Info, class); +/// ``` +/// +/// # Safety +/// +/// This macro should only be invoked only once since it is setting the global logging state of the application. +#[macro_export] +macro_rules! with_class { + ( $x:expr, $l:expr, $p:ident ) => { + { + static LOGGER: ::embassy_usb_logger::UsbLogger<$x> = ::embassy_usb_logger::UsbLogger::new(); + unsafe { + let _ = ::log::set_logger_racy(&LOGGER).map(|()| log::set_max_level_racy($l)); + } + LOGGER.create_future_from_class($p) + } + }; +} From 6f505feeb1640c3d76c47aa21160a5a802fb6b93 Mon Sep 17 00:00:00 2001 From: "Jomer.Dev" Date: Mon, 8 Jan 2024 00:21:02 +0100 Subject: [PATCH 2/6] Add example --- examples/rp/src/bin/usb_serial_with_logger.rs | 117 ++++++++++++++++++ 1 file changed, 117 insertions(+) create mode 100644 examples/rp/src/bin/usb_serial_with_logger.rs diff --git a/examples/rp/src/bin/usb_serial_with_logger.rs b/examples/rp/src/bin/usb_serial_with_logger.rs new file mode 100644 index 000000000..4ba4fc25c --- /dev/null +++ b/examples/rp/src/bin/usb_serial_with_logger.rs @@ -0,0 +1,117 @@ +//! This example shows how to use USB (Universal Serial Bus) in the RP2040 chip as well as how to create multiple usb classes for one device +//! +//! This creates a USB serial port that echos. It will also print out logging information on a separate serial device + +#![no_std] +#![no_main] + +use defmt::{info, panic}; +use embassy_executor::Spawner; +use embassy_futures::join::join; +use embassy_rp::bind_interrupts; +use embassy_rp::peripherals::USB; +use embassy_rp::usb::{Driver, Instance, InterruptHandler}; +use embassy_usb::class::cdc_acm::{CdcAcmClass, State}; +use embassy_usb::driver::EndpointError; +use embassy_usb::{Builder, Config}; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + USBCTRL_IRQ => InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + info!("Hello there!"); + + let p = embassy_rp::init(Default::default()); + + // Create the driver, from the HAL. + let driver = Driver::new(p.USB, Irqs); + + // Create embassy-usb Config + let mut config = Config::new(0xc0de, 0xcafe); + config.manufacturer = Some("Embassy"); + config.product = Some("USB-serial example"); + config.serial_number = Some("12345678"); + config.max_power = 100; + config.max_packet_size_0 = 64; + + // Required for windows compatibility. + // https://developer.nordicsemi.com/nRF_Connect_SDK/doc/1.9.1/kconfig/CONFIG_CDC_ACM_IAD.html#help + config.device_class = 0xEF; + config.device_sub_class = 0x02; + config.device_protocol = 0x01; + config.composite_with_iads = true; + + // Create embassy-usb DeviceBuilder using the driver and config. + // It needs some buffers for building the descriptors. + let mut device_descriptor = [0; 256]; + let mut config_descriptor = [0; 256]; + let mut bos_descriptor = [0; 256]; + let mut control_buf = [0; 64]; + + let mut state = State::new(); + let mut logger_state = State::new(); + + let mut builder = Builder::new( + driver, + config, + &mut device_descriptor, + &mut config_descriptor, + &mut bos_descriptor, + &mut [], // no msos descriptors + &mut control_buf, + ); + + // Create classes on the builder. + let mut class = CdcAcmClass::new(&mut builder, &mut state, 64); + + // Create a class for the logger + let logger_class = CdcAcmClass::new(&mut builder, &mut logger_state, 64); + + // Creates the logger and returns the logger future + // Note: You'll need to use log::info! afterwards instead of info! for this to work (this also applies to all the other log::* macros) + let log_fut = embassy_usb_logger::with_class!(1024, log::LevelFilter::Info, logger_class); + + // Build the builder. + let mut usb = builder.build(); + + // Run the USB device. + let usb_fut = usb.run(); + + // Do stuff with the class! + let echo_fut = async { + loop { + class.wait_connection().await; + log::info!("Connected"); + let _ = echo(&mut class).await; + log::info!("Disconnected"); + } + }; + + // Run everything concurrently. + // If we had made everything `'static` above instead, we could do this using separate tasks instead. + join(usb_fut, join(echo_fut, log_fut)).await; +} + +struct Disconnected {} + +impl From for Disconnected { + fn from(val: EndpointError) -> Self { + match val { + EndpointError::BufferOverflow => panic!("Buffer overflow"), + EndpointError::Disabled => Disconnected {}, + } + } +} + +async fn echo<'d, T: Instance + 'd>(class: &mut CdcAcmClass<'d, Driver<'d, T>>) -> Result<(), Disconnected> { + let mut buf = [0; 64]; + loop { + let n = class.read_packet(&mut buf).await?; + let data = &buf[..n]; + info!("data: {:x}", data); + class.write_packet(data).await?; + } +} From f0c750422970db71304101fca821a0c254571604 Mon Sep 17 00:00:00 2001 From: "Jomer.Dev" Date: Mon, 8 Jan 2024 00:21:22 +0100 Subject: [PATCH 3/6] Fix log messages not always showing up straight away --- embassy-usb-logger/src/lib.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/embassy-usb-logger/src/lib.rs b/embassy-usb-logger/src/lib.rs index 422cefb59..5142d9073 100644 --- a/embassy-usb-logger/src/lib.rs +++ b/embassy-usb-logger/src/lib.rs @@ -93,6 +93,9 @@ impl UsbLogger { loop { let len = self.buffer.read(&mut rx[..]).await; let _ = sender.write_packet(&rx[..len]).await; + if len as u8 == MAX_PACKET_SIZE { + let _ = sender.write_packet(&[]).await; + } } }; let discard_fut = async { @@ -121,6 +124,9 @@ impl UsbLogger { loop { let len = self.buffer.read(&mut rx[..]).await; let _ = sender.write_packet(&rx[..len]).await; + if len as u8 == MAX_PACKET_SIZE { + let _ = sender.write_packet(&[]).await; + } } }; let discard_fut = async { From 03bf72f690f341b9ca7dbcd329df7571fe47d014 Mon Sep 17 00:00:00 2001 From: "Jomer.Dev" Date: Mon, 8 Jan 2024 00:24:15 +0100 Subject: [PATCH 4/6] Better explanation --- embassy-usb-logger/src/lib.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/embassy-usb-logger/src/lib.rs b/embassy-usb-logger/src/lib.rs index 5142d9073..f197c6874 100644 --- a/embassy-usb-logger/src/lib.rs +++ b/embassy-usb-logger/src/lib.rs @@ -109,7 +109,8 @@ impl UsbLogger { } } - // Creates the futures needed for the logger from a given class + /// Creates the futures needed for the logger from a given class + /// This can be used in cases where the usb device is already in use for another connection pub async fn create_future_from_class<'d, D>(&'d self, class: CdcAcmClass<'d, D> ) where D: Driver<'d>, From 2c6b475f4e663402fd87977d77dcfc4ccb70babb Mon Sep 17 00:00:00 2001 From: "Jomer.Dev" Date: Mon, 8 Jan 2024 00:39:32 +0100 Subject: [PATCH 5/6] Fix formatting --- embassy-usb-logger/src/lib.rs | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/embassy-usb-logger/src/lib.rs b/embassy-usb-logger/src/lib.rs index f197c6874..a057fcf32 100644 --- a/embassy-usb-logger/src/lib.rs +++ b/embassy-usb-logger/src/lib.rs @@ -111,7 +111,7 @@ impl UsbLogger { /// Creates the futures needed for the logger from a given class /// This can be used in cases where the usb device is already in use for another connection - pub async fn create_future_from_class<'d, D>(&'d self, class: CdcAcmClass<'d, D> ) + pub async fn create_future_from_class<'d, D>(&'d self, class: CdcAcmClass<'d, D>) where D: Driver<'d>, { @@ -121,7 +121,7 @@ impl UsbLogger { loop { let log_fut = async { let mut rx: [u8; MAX_PACKET_SIZE as usize] = [0; MAX_PACKET_SIZE as usize]; - sender.wait_connection().await; + sender.wait_connection().await; loop { let len = self.buffer.read(&mut rx[..]).await; let _ = sender.write_packet(&rx[..len]).await; @@ -204,13 +204,11 @@ macro_rules! run { /// This macro should only be invoked only once since it is setting the global logging state of the application. #[macro_export] macro_rules! with_class { - ( $x:expr, $l:expr, $p:ident ) => { - { - static LOGGER: ::embassy_usb_logger::UsbLogger<$x> = ::embassy_usb_logger::UsbLogger::new(); - unsafe { - let _ = ::log::set_logger_racy(&LOGGER).map(|()| log::set_max_level_racy($l)); - } - LOGGER.create_future_from_class($p) + ( $x:expr, $l:expr, $p:ident ) => {{ + static LOGGER: ::embassy_usb_logger::UsbLogger<$x> = ::embassy_usb_logger::UsbLogger::new(); + unsafe { + let _ = ::log::set_logger_racy(&LOGGER).map(|()| log::set_max_level_racy($l)); } - }; + LOGGER.create_future_from_class($p) + }}; } From 16ed0b1e37a6106596efb2f3fa26344d550fc1ff Mon Sep 17 00:00:00 2001 From: "Jomer.Dev" Date: Mon, 12 Feb 2024 19:01:22 +0100 Subject: [PATCH 6/6] Move usb clas loop to private function Move const to the outside of the logger --- embassy-usb-logger/src/lib.rs | 75 +++++++++++++++-------------------- 1 file changed, 33 insertions(+), 42 deletions(-) diff --git a/embassy-usb-logger/src/lib.rs b/embassy-usb-logger/src/lib.rs index a057fcf32..da5ff0f36 100644 --- a/embassy-usb-logger/src/lib.rs +++ b/embassy-usb-logger/src/lib.rs @@ -6,7 +6,7 @@ use core::fmt::Write as _; use embassy_futures::join::join; use embassy_sync::pipe::Pipe; -use embassy_usb::class::cdc_acm::{CdcAcmClass, State}; +use embassy_usb::class::cdc_acm::{CdcAcmClass, Receiver, Sender, State}; use embassy_usb::driver::Driver; use embassy_usb::{Builder, Config}; use log::{Metadata, Record}; @@ -37,6 +37,9 @@ impl<'d> LoggerState<'d> { } } +/// The packet size used in the usb logger, to be used with `create_future_from_class` +pub const MAX_PACKET_SIZE: u8 = 64; + /// The logger handle, which contains a pipe with configurable size for buffering log messages. pub struct UsbLogger { buffer: Pipe, @@ -54,7 +57,6 @@ impl UsbLogger { D: Driver<'d>, Self: 'd, { - const MAX_PACKET_SIZE: u8 = 64; let mut config = Config::new(0xc0de, 0xcafe); config.manufacturer = Some("Embassy"); config.product = Some("USB-serial logger"); @@ -87,57 +89,46 @@ impl UsbLogger { let mut device = builder.build(); loop { let run_fut = device.run(); - let log_fut = async { - let mut rx: [u8; MAX_PACKET_SIZE as usize] = [0; MAX_PACKET_SIZE as usize]; - sender.wait_connection().await; - loop { - let len = self.buffer.read(&mut rx[..]).await; - let _ = sender.write_packet(&rx[..len]).await; - if len as u8 == MAX_PACKET_SIZE { - let _ = sender.write_packet(&[]).await; - } - } - }; - let discard_fut = async { - let mut discard_buf: [u8; MAX_PACKET_SIZE as usize] = [0; MAX_PACKET_SIZE as usize]; - receiver.wait_connection().await; - loop { - let _ = receiver.read_packet(&mut discard_buf).await; - } - }; - join(run_fut, join(log_fut, discard_fut)).await; + let class_fut = self.run_logger_class(&mut sender, &mut receiver); + join(run_fut, class_fut).await; } } + async fn run_logger_class<'d, D>(&self, sender: &mut Sender<'d, D>, receiver: &mut Receiver<'d, D>) + where + D: Driver<'d>, + { + let log_fut = async { + let mut rx: [u8; MAX_PACKET_SIZE as usize] = [0; MAX_PACKET_SIZE as usize]; + sender.wait_connection().await; + loop { + let len = self.buffer.read(&mut rx[..]).await; + let _ = sender.write_packet(&rx[..len]).await; + if len as u8 == MAX_PACKET_SIZE { + let _ = sender.write_packet(&[]).await; + } + } + }; + let discard_fut = async { + let mut discard_buf: [u8; MAX_PACKET_SIZE as usize] = [0; MAX_PACKET_SIZE as usize]; + receiver.wait_connection().await; + loop { + let _ = receiver.read_packet(&mut discard_buf).await; + } + }; + + join(log_fut, discard_fut).await; + } + /// Creates the futures needed for the logger from a given class /// This can be used in cases where the usb device is already in use for another connection pub async fn create_future_from_class<'d, D>(&'d self, class: CdcAcmClass<'d, D>) where D: Driver<'d>, { - const MAX_PACKET_SIZE: u8 = 64; let (mut sender, mut receiver) = class.split(); - loop { - let log_fut = async { - let mut rx: [u8; MAX_PACKET_SIZE as usize] = [0; MAX_PACKET_SIZE as usize]; - sender.wait_connection().await; - loop { - let len = self.buffer.read(&mut rx[..]).await; - let _ = sender.write_packet(&rx[..len]).await; - if len as u8 == MAX_PACKET_SIZE { - let _ = sender.write_packet(&[]).await; - } - } - }; - let discard_fut = async { - let mut discard_buf: [u8; MAX_PACKET_SIZE as usize] = [0; MAX_PACKET_SIZE as usize]; - receiver.wait_connection().await; - loop { - let _ = receiver.read_packet(&mut discard_buf).await; - } - }; - join(log_fut, discard_fut).await; + self.run_logger_class(&mut sender, &mut receiver).await; } } }