Merge pull request #2414 from JomerDev/usb-logger-without-device
Adds function and macro to create usb logger without device (also fixes a logger issue)
This commit is contained in:
commit
a2b7b0c8e0
2 changed files with 184 additions and 17 deletions
|
@ -6,7 +6,7 @@ use core::fmt::Write as _;
|
||||||
|
|
||||||
use embassy_futures::join::join;
|
use embassy_futures::join::join;
|
||||||
use embassy_sync::pipe::Pipe;
|
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::driver::Driver;
|
||||||
use embassy_usb::{Builder, Config};
|
use embassy_usb::{Builder, Config};
|
||||||
use log::{Metadata, Record};
|
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.
|
/// The logger handle, which contains a pipe with configurable size for buffering log messages.
|
||||||
pub struct UsbLogger<const N: usize> {
|
pub struct UsbLogger<const N: usize> {
|
||||||
buffer: Pipe<CS, N>,
|
buffer: Pipe<CS, N>,
|
||||||
|
@ -54,7 +57,6 @@ impl<const N: usize> UsbLogger<N> {
|
||||||
D: Driver<'d>,
|
D: Driver<'d>,
|
||||||
Self: 'd,
|
Self: 'd,
|
||||||
{
|
{
|
||||||
const MAX_PACKET_SIZE: u8 = 64;
|
|
||||||
let mut config = Config::new(0xc0de, 0xcafe);
|
let mut config = Config::new(0xc0de, 0xcafe);
|
||||||
config.manufacturer = Some("Embassy");
|
config.manufacturer = Some("Embassy");
|
||||||
config.product = Some("USB-serial logger");
|
config.product = Some("USB-serial logger");
|
||||||
|
@ -87,12 +89,24 @@ impl<const N: usize> UsbLogger<N> {
|
||||||
let mut device = builder.build();
|
let mut device = builder.build();
|
||||||
loop {
|
loop {
|
||||||
let run_fut = device.run();
|
let run_fut = device.run();
|
||||||
|
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 log_fut = async {
|
||||||
let mut rx: [u8; MAX_PACKET_SIZE as usize] = [0; MAX_PACKET_SIZE as usize];
|
let mut rx: [u8; MAX_PACKET_SIZE as usize] = [0; MAX_PACKET_SIZE as usize];
|
||||||
sender.wait_connection().await;
|
sender.wait_connection().await;
|
||||||
loop {
|
loop {
|
||||||
let len = self.buffer.read(&mut rx[..]).await;
|
let len = self.buffer.read(&mut rx[..]).await;
|
||||||
let _ = sender.write_packet(&rx[..len]).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 discard_fut = async {
|
||||||
|
@ -102,7 +116,19 @@ impl<const N: usize> UsbLogger<N> {
|
||||||
let _ = receiver.read_packet(&mut discard_buf).await;
|
let _ = receiver.read_packet(&mut discard_buf).await;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
join(run_fut, join(log_fut, discard_fut)).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>,
|
||||||
|
{
|
||||||
|
let (mut sender, mut receiver) = class.split();
|
||||||
|
loop {
|
||||||
|
self.run_logger_class(&mut sender, &mut receiver).await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -153,3 +179,27 @@ macro_rules! run {
|
||||||
let _ = LOGGER.run(&mut ::embassy_usb_logger::LoggerState::new(), $p).await;
|
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)
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
|
117
examples/rp/src/bin/usb_serial_with_logger.rs
Normal file
117
examples/rp/src/bin/usb_serial_with_logger.rs
Normal file
|
@ -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<USB>;
|
||||||
|
});
|
||||||
|
|
||||||
|
#[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<EndpointError> 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?;
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue