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.
This commit is contained in:
		
							
								
								
									
										13
									
								
								embassy-usb-logger/Cargo.toml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								embassy-usb-logger/Cargo.toml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,13 @@
 | 
			
		||||
[package]
 | 
			
		||||
name = "embassy-usb-logger"
 | 
			
		||||
version = "0.1.0"
 | 
			
		||||
edition = "2021"
 | 
			
		||||
 | 
			
		||||
[dependencies]
 | 
			
		||||
embassy-usb = { version = "0.1.0", path = "../embassy-usb" }
 | 
			
		||||
embassy-sync = { version = "0.1.0", path = "../embassy-sync" }
 | 
			
		||||
embassy-futures = { version = "0.1.0", path = "../embassy-futures" }
 | 
			
		||||
futures = { version = "0.3", default-features = false }
 | 
			
		||||
static_cell = "1"
 | 
			
		||||
usbd-hid = "0.6.0"
 | 
			
		||||
log = "0.4"
 | 
			
		||||
							
								
								
									
										146
									
								
								embassy-usb-logger/src/lib.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										146
									
								
								embassy-usb-logger/src/lib.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,146 @@
 | 
			
		||||
#![no_std]
 | 
			
		||||
#![doc = include_str!("../README.md")]
 | 
			
		||||
#![warn(missing_docs)]
 | 
			
		||||
 | 
			
		||||
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::driver::Driver;
 | 
			
		||||
use embassy_usb::{Builder, Config};
 | 
			
		||||
use log::{Metadata, Record};
 | 
			
		||||
 | 
			
		||||
type CS = embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
 | 
			
		||||
 | 
			
		||||
/// The logger state containing buffers that must live as long as the USB peripheral.
 | 
			
		||||
pub struct LoggerState<'d> {
 | 
			
		||||
    state: State<'d>,
 | 
			
		||||
    device_descriptor: [u8; 32],
 | 
			
		||||
    config_descriptor: [u8; 128],
 | 
			
		||||
    bos_descriptor: [u8; 16],
 | 
			
		||||
    control_buf: [u8; 64],
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl<'d> LoggerState<'d> {
 | 
			
		||||
    /// Create a new instance of the logger state.
 | 
			
		||||
    pub fn new() -> Self {
 | 
			
		||||
        Self {
 | 
			
		||||
            state: State::new(),
 | 
			
		||||
            device_descriptor: [0; 32],
 | 
			
		||||
            config_descriptor: [0; 128],
 | 
			
		||||
            bos_descriptor: [0; 16],
 | 
			
		||||
            control_buf: [0; 64],
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// The logger handle, which contains a pipe with configurable size for buffering log messages.
 | 
			
		||||
pub struct UsbLogger<const N: usize> {
 | 
			
		||||
    buffer: Pipe<CS, N>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl<const N: usize> UsbLogger<N> {
 | 
			
		||||
    /// Create a new logger instance.
 | 
			
		||||
    pub const fn new() -> Self {
 | 
			
		||||
        Self { buffer: Pipe::new() }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Run the USB logger using the state and USB driver. Never returns.
 | 
			
		||||
    pub async fn run<'d, D>(&'d self, state: &'d mut LoggerState<'d>, driver: D) -> !
 | 
			
		||||
    where
 | 
			
		||||
        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");
 | 
			
		||||
        config.serial_number = None;
 | 
			
		||||
        config.max_power = 100;
 | 
			
		||||
        config.max_packet_size_0 = MAX_PACKET_SIZE;
 | 
			
		||||
 | 
			
		||||
        // Required for windows compatiblity.
 | 
			
		||||
        // 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;
 | 
			
		||||
 | 
			
		||||
        let mut builder = Builder::new(
 | 
			
		||||
            driver,
 | 
			
		||||
            config,
 | 
			
		||||
            &mut state.device_descriptor,
 | 
			
		||||
            &mut state.config_descriptor,
 | 
			
		||||
            &mut state.bos_descriptor,
 | 
			
		||||
            &mut state.control_buf,
 | 
			
		||||
            None,
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        // Create classes on the builder.
 | 
			
		||||
        let mut class = CdcAcmClass::new(&mut builder, &mut state.state, MAX_PACKET_SIZE as u16);
 | 
			
		||||
 | 
			
		||||
        // Build the builder.
 | 
			
		||||
        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];
 | 
			
		||||
                class.wait_connection().await;
 | 
			
		||||
                loop {
 | 
			
		||||
                    let len = self.buffer.read(&mut rx[..]).await;
 | 
			
		||||
                    let _ = class.write_packet(&rx[..len]).await;
 | 
			
		||||
                }
 | 
			
		||||
            };
 | 
			
		||||
            join(run_fut, log_fut).await;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl<const N: usize> log::Log for UsbLogger<N> {
 | 
			
		||||
    fn enabled(&self, _metadata: &Metadata) -> bool {
 | 
			
		||||
        true
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn log(&self, record: &Record) {
 | 
			
		||||
        if self.enabled(record.metadata()) {
 | 
			
		||||
            let _ = write!(Writer(&self.buffer), "{}\r\n", record.args());
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn flush(&self) {}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
struct Writer<'d, const N: usize>(&'d Pipe<CS, N>);
 | 
			
		||||
 | 
			
		||||
impl<'d, const N: usize> core::fmt::Write for Writer<'d, N> {
 | 
			
		||||
    fn write_str(&mut self, s: &str) -> Result<(), core::fmt::Error> {
 | 
			
		||||
        let _ = self.0.try_write(s.as_bytes());
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Initialize and run the USB serial logger, never returns.
 | 
			
		||||
///
 | 
			
		||||
/// Arguments specify the buffer size, log level and the USB driver, respectively.
 | 
			
		||||
///
 | 
			
		||||
/// # Usage
 | 
			
		||||
///
 | 
			
		||||
/// ```
 | 
			
		||||
/// embassy_usb_logger::run!(1024, log::LevelFilter::Info, driver);
 | 
			
		||||
/// ```
 | 
			
		||||
///
 | 
			
		||||
/// # Safety
 | 
			
		||||
///
 | 
			
		||||
/// This macro should only be invoked only once since it is setting the global logging state of the application.
 | 
			
		||||
#[macro_export]
 | 
			
		||||
macro_rules! run {
 | 
			
		||||
    ( $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($l));
 | 
			
		||||
        }
 | 
			
		||||
        let _ = LOGGER.run(&mut ::embassy_usb_logger::LoggerState::new(), $p).await;
 | 
			
		||||
    };
 | 
			
		||||
}
 | 
			
		||||
@@ -13,6 +13,7 @@ embassy-rp = { version = "0.1.0", path = "../../embassy-rp", features = ["defmt"
 | 
			
		||||
embassy-usb = { version = "0.1.0", path = "../../embassy-usb", features = ["defmt"] }
 | 
			
		||||
embassy-net = { version = "0.1.0", path = "../../embassy-net", features = ["defmt", "nightly", "tcp", "dhcpv4", "medium-ethernet", "pool-16"] }
 | 
			
		||||
embassy-futures = { version = "0.1.0", path = "../../embassy-futures" }
 | 
			
		||||
embassy-usb-logger = { version = "0.1.0", path = "../../embassy-usb-logger" }
 | 
			
		||||
 | 
			
		||||
defmt = "0.3"
 | 
			
		||||
defmt-rtt = "0.3"
 | 
			
		||||
@@ -32,6 +33,7 @@ embedded-hal-async = { version = "0.1.0-alpha.3" }
 | 
			
		||||
embedded-io = { version = "0.3.1", features = ["async", "defmt"] }
 | 
			
		||||
embedded-storage = { version = "0.3" }
 | 
			
		||||
static_cell = "1.0.0"
 | 
			
		||||
log = "0.4"
 | 
			
		||||
 | 
			
		||||
[profile.release]
 | 
			
		||||
debug = true
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										30
									
								
								examples/rp/src/bin/usb_logger.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								examples/rp/src/bin/usb_logger.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,30 @@
 | 
			
		||||
#![no_std]
 | 
			
		||||
#![no_main]
 | 
			
		||||
#![feature(type_alias_impl_trait)]
 | 
			
		||||
 | 
			
		||||
use embassy_executor::Spawner;
 | 
			
		||||
use embassy_rp::interrupt;
 | 
			
		||||
use embassy_rp::peripherals::USB;
 | 
			
		||||
use embassy_rp::usb::Driver;
 | 
			
		||||
use embassy_time::{Duration, Timer};
 | 
			
		||||
use {defmt_rtt as _, panic_probe as _};
 | 
			
		||||
 | 
			
		||||
#[embassy_executor::task]
 | 
			
		||||
async fn logger_task(driver: Driver<'static, USB>) {
 | 
			
		||||
    embassy_usb_logger::run!(1024, log::LevelFilter::Info, driver);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[embassy_executor::main]
 | 
			
		||||
async fn main(spawner: Spawner) {
 | 
			
		||||
    let p = embassy_rp::init(Default::default());
 | 
			
		||||
    let irq = interrupt::take!(USBCTRL_IRQ);
 | 
			
		||||
    let driver = Driver::new(p.USB, irq);
 | 
			
		||||
    spawner.spawn(logger_task(driver)).unwrap();
 | 
			
		||||
 | 
			
		||||
    let mut counter = 0;
 | 
			
		||||
    loop {
 | 
			
		||||
        counter += 1;
 | 
			
		||||
        log::info!("Tick {}", counter);
 | 
			
		||||
        Timer::after(Duration::from_secs(1)).await;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user