aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--embassy-usb-logger/src/lib.rs84
-rw-r--r--examples/rp/src/bin/usb_serial_with_logger.rs117
2 files changed, 184 insertions, 17 deletions
diff --git a/embassy-usb-logger/src/lib.rs b/embassy-usb-logger/src/lib.rs
index 45d780bf8..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 _;
6 6
7use embassy_futures::join::join; 7use embassy_futures::join::join;
8use embassy_sync::pipe::Pipe; 8use embassy_sync::pipe::Pipe;
9use embassy_usb::class::cdc_acm::{CdcAcmClass, State}; 9use embassy_usb::class::cdc_acm::{CdcAcmClass, Receiver, Sender, State};
10use embassy_usb::driver::Driver; 10use embassy_usb::driver::Driver;
11use embassy_usb::{Builder, Config}; 11use embassy_usb::{Builder, Config};
12use log::{Metadata, Record}; 12use log::{Metadata, Record};
@@ -37,6 +37,9 @@ impl<'d> LoggerState<'d> {
37 } 37 }
38} 38}
39 39
40/// The packet size used in the usb logger, to be used with `create_future_from_class`
41pub const MAX_PACKET_SIZE: u8 = 64;
42
40/// The logger handle, which contains a pipe with configurable size for buffering log messages. 43/// The logger handle, which contains a pipe with configurable size for buffering log messages.
41pub struct UsbLogger<const N: usize> { 44pub struct UsbLogger<const N: usize> {
42 buffer: Pipe<CS, N>, 45 buffer: Pipe<CS, N>,
@@ -54,7 +57,6 @@ impl<const N: usize> UsbLogger<N> {
54 D: Driver<'d>, 57 D: Driver<'d>,
55 Self: 'd, 58 Self: 'd,
56 { 59 {
57 const MAX_PACKET_SIZE: u8 = 64;
58 let mut config = Config::new(0xc0de, 0xcafe); 60 let mut config = Config::new(0xc0de, 0xcafe);
59 config.manufacturer = Some("Embassy"); 61 config.manufacturer = Some("Embassy");
60 config.product = Some("USB-serial logger"); 62 config.product = Some("USB-serial logger");
@@ -87,22 +89,46 @@ impl<const N: usize> UsbLogger<N> {
87 let mut device = builder.build(); 89 let mut device = builder.build();
88 loop { 90 loop {
89 let run_fut = device.run(); 91 let run_fut = device.run();
90 let log_fut = async { 92 let class_fut = self.run_logger_class(&mut sender, &mut receiver);
91 let mut rx: [u8; MAX_PACKET_SIZE as usize] = [0; MAX_PACKET_SIZE as usize]; 93 join(run_fut, class_fut).await;
92 sender.wait_connection().await; 94 }
93 loop { 95 }
94 let len = self.buffer.read(&mut rx[..]).await; 96
95 let _ = sender.write_packet(&rx[..len]).await; 97 async fn run_logger_class<'d, D>(&self, sender: &mut Sender<'d, D>, receiver: &mut Receiver<'d, D>)
96 } 98 where
97 }; 99 D: Driver<'d>,
98 let discard_fut = async { 100 {
99 let mut discard_buf: [u8; MAX_PACKET_SIZE as usize] = [0; MAX_PACKET_SIZE as usize]; 101 let log_fut = async {
100 receiver.wait_connection().await; 102 let mut rx: [u8; MAX_PACKET_SIZE as usize] = [0; MAX_PACKET_SIZE as usize];
101 loop { 103 sender.wait_connection().await;
102 let _ = receiver.read_packet(&mut discard_buf).await; 104 loop {
105 let len = self.buffer.read(&mut rx[..]).await;
106 let _ = sender.write_packet(&rx[..len]).await;
107 if len as u8 == MAX_PACKET_SIZE {
108 let _ = sender.write_packet(&[]).await;
103 } 109 }
104 }; 110 }
105 join(run_fut, join(log_fut, discard_fut)).await; 111 };
112 let discard_fut = async {
113 let mut discard_buf: [u8; MAX_PACKET_SIZE as usize] = [0; MAX_PACKET_SIZE as usize];
114 receiver.wait_connection().await;
115 loop {
116 let _ = receiver.read_packet(&mut discard_buf).await;
117 }
118 };
119
120 join(log_fut, discard_fut).await;
121 }
122
123 /// Creates the futures needed for the logger from a given class
124 /// This can be used in cases where the usb device is already in use for another connection
125 pub async fn create_future_from_class<'d, D>(&'d self, class: CdcAcmClass<'d, D>)
126 where
127 D: Driver<'d>,
128 {
129 let (mut sender, mut receiver) = class.split();
130 loop {
131 self.run_logger_class(&mut sender, &mut receiver).await;
106 } 132 }
107 } 133 }
108} 134}
@@ -153,3 +179,27 @@ macro_rules! run {
153 let _ = LOGGER.run(&mut ::embassy_usb_logger::LoggerState::new(), $p).await; 179 let _ = LOGGER.run(&mut ::embassy_usb_logger::LoggerState::new(), $p).await;
154 }; 180 };
155} 181}
182
183/// Initialize the USB serial logger from a serial class and return the future to run it.
184///
185/// Arguments specify the buffer size, log level and the serial class, respectively.
186///
187/// # Usage
188///
189/// ```
190/// embassy_usb_logger::with_class!(1024, log::LevelFilter::Info, class);
191/// ```
192///
193/// # Safety
194///
195/// This macro should only be invoked only once since it is setting the global logging state of the application.
196#[macro_export]
197macro_rules! with_class {
198 ( $x:expr, $l:expr, $p:ident ) => {{
199 static LOGGER: ::embassy_usb_logger::UsbLogger<$x> = ::embassy_usb_logger::UsbLogger::new();
200 unsafe {
201 let _ = ::log::set_logger_racy(&LOGGER).map(|()| log::set_max_level_racy($l));
202 }
203 LOGGER.create_future_from_class($p)
204 }};
205}
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 @@
1//! 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
2//!
3//! This creates a USB serial port that echos. It will also print out logging information on a separate serial device
4
5#![no_std]
6#![no_main]
7
8use defmt::{info, panic};
9use embassy_executor::Spawner;
10use embassy_futures::join::join;
11use embassy_rp::bind_interrupts;
12use embassy_rp::peripherals::USB;
13use embassy_rp::usb::{Driver, Instance, InterruptHandler};
14use embassy_usb::class::cdc_acm::{CdcAcmClass, State};
15use embassy_usb::driver::EndpointError;
16use embassy_usb::{Builder, Config};
17use {defmt_rtt as _, panic_probe as _};
18
19bind_interrupts!(struct Irqs {
20 USBCTRL_IRQ => InterruptHandler<USB>;
21});
22
23#[embassy_executor::main]
24async fn main(_spawner: Spawner) {
25 info!("Hello there!");
26
27 let p = embassy_rp::init(Default::default());
28
29 // Create the driver, from the HAL.
30 let driver = Driver::new(p.USB, Irqs);
31
32 // Create embassy-usb Config
33 let mut config = Config::new(0xc0de, 0xcafe);
34 config.manufacturer = Some("Embassy");
35 config.product = Some("USB-serial example");
36 config.serial_number = Some("12345678");
37 config.max_power = 100;
38 config.max_packet_size_0 = 64;
39
40 // Required for windows compatibility.
41 // https://developer.nordicsemi.com/nRF_Connect_SDK/doc/1.9.1/kconfig/CONFIG_CDC_ACM_IAD.html#help
42 config.device_class = 0xEF;
43 config.device_sub_class = 0x02;
44 config.device_protocol = 0x01;
45 config.composite_with_iads = true;
46
47 // Create embassy-usb DeviceBuilder using the driver and config.
48 // It needs some buffers for building the descriptors.
49 let mut device_descriptor = [0; 256];
50 let mut config_descriptor = [0; 256];
51 let mut bos_descriptor = [0; 256];
52 let mut control_buf = [0; 64];
53
54 let mut state = State::new();
55 let mut logger_state = State::new();
56
57 let mut builder = Builder::new(
58 driver,
59 config,
60 &mut device_descriptor,
61 &mut config_descriptor,
62 &mut bos_descriptor,
63 &mut [], // no msos descriptors
64 &mut control_buf,
65 );
66
67 // Create classes on the builder.
68 let mut class = CdcAcmClass::new(&mut builder, &mut state, 64);
69
70 // Create a class for the logger
71 let logger_class = CdcAcmClass::new(&mut builder, &mut logger_state, 64);
72
73 // Creates the logger and returns the logger future
74 // 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)
75 let log_fut = embassy_usb_logger::with_class!(1024, log::LevelFilter::Info, logger_class);
76
77 // Build the builder.
78 let mut usb = builder.build();
79
80 // Run the USB device.
81 let usb_fut = usb.run();
82
83 // Do stuff with the class!
84 let echo_fut = async {
85 loop {
86 class.wait_connection().await;
87 log::info!("Connected");
88 let _ = echo(&mut class).await;
89 log::info!("Disconnected");
90 }
91 };
92
93 // Run everything concurrently.
94 // If we had made everything `'static` above instead, we could do this using separate tasks instead.
95 join(usb_fut, join(echo_fut, log_fut)).await;
96}
97
98struct Disconnected {}
99
100impl From<EndpointError> for Disconnected {
101 fn from(val: EndpointError) -> Self {
102 match val {
103 EndpointError::BufferOverflow => panic!("Buffer overflow"),
104 EndpointError::Disabled => Disconnected {},
105 }
106 }
107}
108
109async fn echo<'d, T: Instance + 'd>(class: &mut CdcAcmClass<'d, Driver<'d, T>>) -> Result<(), Disconnected> {
110 let mut buf = [0; 64];
111 loop {
112 let n = class.read_packet(&mut buf).await?;
113 let data = &buf[..n];
114 info!("data: {:x}", data);
115 class.write_packet(data).await?;
116 }
117}