Get a dummy keyboard working

This commit is contained in:
2023-03-05 21:16:59 +01:00
parent 83af700325
commit 06a47b7a2c
13 changed files with 1782 additions and 156 deletions

View File

@ -17,18 +17,11 @@ target = "thumbv6m-none-eabi"
[target.thumbv6m-none-eabi] [target.thumbv6m-none-eabi]
# Pass some extra options to rustc, some of which get passed on to the linker. # Pass some extra options to rustc, some of which get passed on to the linker.
# #
# * linker argument --nmagic turns off page alignment of sections (which saves
# flash space)
# * linker argument -Tlink.x tells the linker to use link.x as the linker
# script. This is usually provided by the cortex-m-rt crate, and by default
# the version in that crate will include a file called `memory.x` which
# describes the particular memory layout for your specific chip.
# * inline-threshold=5 makes the compiler more aggressive and inlining functions # * inline-threshold=5 makes the compiler more aggressive and inlining functions
# * no-vectorize-loops turns off the loop vectorizer (seeing as the M0+ doesn't # * no-vectorize-loops turns off the loop vectorizer (seeing as the M0+ doesn't
# have SIMD) # have SIMD)
rustflags = [ rustflags = [
"-C", "link-arg=--nmagic", "-C", "linker=flip-link",
"-C", "link-arg=-Tlink.x",
"-C", "inline-threshold=5", "-C", "inline-threshold=5",
"-C", "no-vectorize-loops", "-C", "no-vectorize-loops",
] ]

1154
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -3,11 +3,25 @@ name = "itsybitsy_rp2040_keyboard"
version = "0.1.0" version = "0.1.0"
edition = "2021" edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
adafruit-itsy-bitsy-rp2040 = "0.6.0" #adafruit-itsy-bitsy-rp2040 = "0.6.0"
cortex-m = "0.7.2" cortex-m = "0.7.6"
cortex-m-rt = "0.7" cortex-m-rt = "0.7"
embedded-hal ="0.2.5" embedded-hal ="0.2.5"
panic-halt= "0.2.0" panic-halt= "0.2.0"
usb-device = "*"
usbd-hid = "0.6.1"
static_cell = "1.0.0"
embedded-io = { version = "*", features = ["async"] }
futures = { version = "0.3", default-features = false }
embassy-executor = { git = "https://github.com/embassy-rs/embassy.git", features = ["log", "nightly", "integrated-timers" ] }
embassy-sync = { git = "https://github.com/embassy-rs/embassy.git", features = ["log", "nightly"] }
embassy-time = { git = "https://github.com/embassy-rs/embassy.git", features = ["log"] }
embassy-futures = { git = "https://github.com/embassy-rs/embassy.git", features = ["log"] }
embassy-usb = { git = "https://github.com/embassy-rs/embassy.git", features = ["usbd-hid"] }
embassy-usb-logger = { git = "https://github.com/embassy-rs/embassy.git", features = [] }
embassy-usb-driver = { git = "https://github.com/embassy-rs/embassy.git", features = [] }
embassy-rp = { git = "https://github.com/embassy-rs/embassy.git", features = ["log", "nightly", "unstable-traits", "unstable-pac", "time-driver", "pio", "critical-section-impl"] }
log = "0.4.17"

38
build.rs Normal file
View File

@ -0,0 +1,38 @@
//! This build script copies the `memory.x` file from the crate root into
//! a directory where the linker can always find it at build time.
//! For many projects this is optional, as the linker always searches the
//! project root directory -- wherever `Cargo.toml` is. However, if you
//! are using a workspace or have a more complicated build setup, this
//! build script becomes required. Additionally, by requesting that
//! Cargo re-run the build script whenever `memory.x` is changed,
//! updating `memory.x` ensures a rebuild of the application with the
//! new memory settings.
use std::env;
use std::fs::File;
use std::io::Write;
use std::path::PathBuf;
fn main() {
// Put `memory.x` in our output directory and ensure it's
// on the linker search path.
let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap());
File::create(out.join("memory.x"))
.unwrap()
.write_all(include_bytes!("memory.x"))
.unwrap();
println!("cargo:rustc-link-search={}", out.display());
// By default, Cargo will re-run a build script whenever
// any file in the project changes. By specifying `memory.x`
// here, we ensure the build script is only re-run when
// `memory.x` is changed.
println!("cargo:rerun-if-changed=memory.x");
// --nmagic turns off page alignment of sections (which saves flash space)
println!("cargo:rustc-link-arg-bins=--nmagic");
println!("cargo:rustc-link-arg-bins=-Tlink.x");
println!("cargo:rustc-link-arg-bins=-Tlink-rp.x");
//println!("cargo:rustc-link-arg-bins=-Tdefmt.x");
}

19
run Executable file
View File

@ -0,0 +1,19 @@
#!/bin/sh
set -e
cargo run
printf "waiting for serial log"
SERIAL=/dev/ttyACM0
while [ ! -e $SERIAL ]
do
printf "."
sleep 1
done
echo
sleep 1
cat $SERIAL

4
rust-toolchain.toml Normal file
View File

@ -0,0 +1,4 @@
[toolchain]
channel = "nightly-2023-02-01"
components = ["rust-std", "rust-src", "rustfmt", "cargo", "clippy"]
targets = ["thumbv6m-none-eabi"]

31
src/board.rs Normal file
View File

@ -0,0 +1,31 @@
use embassy_rp::peripherals::*;
/// Pinouts for the ItsyBitsy
#[allow(dead_code)]
pub struct Board {
pub a0: PIN_26,
pub a1: PIN_27,
pub a2: PIN_28,
pub a3: PIN_29,
pub d24: PIN_24,
pub d25: PIN_25,
pub sck: PIN_18,
pub mosi: PIN_19,
pub miso: PIN_20,
pub d2: PIN_12,
pub d3: PIN_5,
pub d4: PIN_4,
pub rx: PIN_1,
pub tx: PIN_0,
pub sda: PIN_2,
pub scl: PIN_3,
pub d5: PIN_14,
pub d7: PIN_6,
pub d9: PIN_7,
pub d10: PIN_8,
pub d11: PIN_9,
pub d12: PIN_10,
pub d13: PIN_11,
pub neopixel: PIN_17,
pub neopixel_power: PIN_16,
}

189
src/keyboard.rs Normal file
View File

@ -0,0 +1,189 @@
use core::sync::atomic::{AtomicBool, Ordering};
use embassy_time::{Duration, Timer};
pub const ROWS: usize = 3;
pub const COLS: usize = 5;
const NEW_SWITCH: Switch = Switch::new();
const NEW_ROW: [Switch; COLS] = [NEW_SWITCH; COLS];
pub static MATRIX: [[Switch; COLS]; ROWS] = [NEW_ROW; ROWS];
pub static TEST_KEYMAP: [[Button; COLS]; ROWS] = [
[
Button::KEY_A,
Button::KEY_B,
Button::KEY_C,
Button::KEY_D,
Button::KEY_E,
],
[
Button::KEY_T,
Button::KEY_R,
Button::KEY_H,
Button::KEY_I,
Button::KEY_SPACE,
],
[
Button::KEY_O,
Button::KEY_L,
Button::KEY_LSHIFT,
Button::KEY_LCTRL,
Button::KEY_LALT,
],
];
pub struct Switch {
state: AtomicBool,
}
#[allow(dead_code)]
impl Switch {
pub const fn new() -> Self {
Switch {
state: AtomicBool::new(false),
}
}
pub fn press(&self) {
self.state.store(true, Ordering::Relaxed);
}
pub fn release(&self) {
self.state.store(false, Ordering::Relaxed);
}
pub fn is_pressed(&self) -> bool {
self.state.load(Ordering::Relaxed)
}
}
#[derive(Debug, PartialEq, Eq)]
pub enum Button {
Key { keycode: u8 },
}
#[allow(dead_code)]
impl Button {
pub const fn key(keycode: u8) -> Self {
Button::Key { keycode }
}
// https://usb.org/sites/default/files/hut1_3_0.pdf
pub const KEY_A: Button = Button::key(0x04);
pub const KEY_B: Button = Button::key(0x05);
pub const KEY_C: Button = Button::key(0x06);
pub const KEY_D: Button = Button::key(0x07);
pub const KEY_E: Button = Button::key(0x08);
pub const KEY_F: Button = Button::key(0x09);
pub const KEY_G: Button = Button::key(0x0A);
pub const KEY_H: Button = Button::key(0x0B);
pub const KEY_I: Button = Button::key(0x0C);
pub const KEY_J: Button = Button::key(0x0D);
pub const KEY_K: Button = Button::key(0x0E);
pub const KEY_L: Button = Button::key(0x0F);
pub const KEY_M: Button = Button::key(0x10);
pub const KEY_N: Button = Button::key(0x11);
pub const KEY_O: Button = Button::key(0x12);
pub const KEY_P: Button = Button::key(0x13);
pub const KEY_Q: Button = Button::key(0x14);
pub const KEY_R: Button = Button::key(0x15);
pub const KEY_S: Button = Button::key(0x16);
pub const KEY_T: Button = Button::key(0x17);
pub const KEY_U: Button = Button::key(0x18);
pub const KEY_V: Button = Button::key(0x19);
pub const KEY_W: Button = Button::key(0x1A);
pub const KEY_X: Button = Button::key(0x1B);
pub const KEY_Y: Button = Button::key(0x1C);
pub const KEY_Z: Button = Button::key(0x1D);
pub const KEY_1: Button = Button::key(0x1E);
pub const KEY_2: Button = Button::key(0x1F);
pub const KEY_3: Button = Button::key(0x20);
pub const KEY_4: Button = Button::key(0x21);
pub const KEY_5: Button = Button::key(0x22);
pub const KEY_6: Button = Button::key(0x23);
pub const KEY_7: Button = Button::key(0x24);
pub const KEY_8: Button = Button::key(0x25);
pub const KEY_9: Button = Button::key(0x26);
pub const KEY_0: Button = Button::key(0x27);
pub const KEY_SPACE: Button = Button::key(0x2C);
pub const KEY_RETURN: Button = Button::key(0x28);
pub const KEY_LCTRL: Button = Button::key(0xE0);
pub const KEY_RCTRL: Button = Button::key(0xE4);
pub const KEY_LSHIFT: Button = Button::key(0xE1);
pub const KEY_RSHIFT: Button = Button::key(0xE5);
pub const KEY_LALT: Button = Button::key(0xE2);
pub const KEY_RALT: Button = Button::key(0xE6);
}
pub fn letter_to_key(c: char) -> Option<Button> {
if !c.is_ascii() {
return None;
}
let c = c.to_ascii_uppercase();
let key = match c {
'A' => Button::KEY_A,
'B' => Button::KEY_B,
'C' => Button::KEY_C,
'D' => Button::KEY_D,
'E' => Button::KEY_E,
'F' => Button::KEY_F,
'G' => Button::KEY_G,
'H' => Button::KEY_H,
'I' => Button::KEY_I,
'J' => Button::KEY_J,
'K' => Button::KEY_K,
'L' => Button::KEY_L,
'M' => Button::KEY_M,
'N' => Button::KEY_N,
'O' => Button::KEY_O,
'P' => Button::KEY_P,
'Q' => Button::KEY_Q,
'R' => Button::KEY_R,
'S' => Button::KEY_S,
'T' => Button::KEY_T,
'U' => Button::KEY_U,
'V' => Button::KEY_V,
'W' => Button::KEY_W,
'X' => Button::KEY_X,
'Y' => Button::KEY_Y,
'Z' => Button::KEY_Z,
' ' => Button::KEY_SPACE,
'\n' => Button::KEY_RETURN,
_ => {
log::info!("char {c:?} -> None");
return None;
}
};
log::info!("char {c:?} -> {key:?}");
Some(key)
}
pub async fn test_type(s: &str) {
log::info!("typing {s:?}");
for c in s.chars() {
let Some(key) = letter_to_key(c) else {
continue;
};
for col in 0..COLS {
for row in 0..ROWS {
if TEST_KEYMAP[row][col] == key {
MATRIX[row][col].press();
Timer::after(Duration::from_millis(20)).await;
MATRIX[row][col].release();
Timer::after(Duration::from_millis(5)).await;
}
}
}
}
}

25
src/led.rs Normal file
View File

@ -0,0 +1,25 @@
use adafruit_itsy_bitsy_rp2040::hal::gpio::{Output, Pin, PinId, PushPull};
use cortex_m::delay::Delay;
use embedded_hal::digital::v2::OutputPin;
pub struct Led<I: PinId> {
pub pin: Pin<I, Output<PushPull>>,
}
pub struct Blink {
ms: u32,
}
pub const SHORT: Blink = Blink { ms: 200 };
pub const LONG: Blink = Blink { ms: 600 };
impl<I: PinId> Led<I> {
pub fn dance(&mut self, delay: &mut Delay, moves: &[Blink]) {
for m in moves {
self.pin.set_high().unwrap();
delay.delay_ms(m.ms);
self.pin.set_low().unwrap();
delay.delay_ms(100);
}
}
}

View File

@ -8,90 +8,76 @@
#![no_std] #![no_std]
#![no_main] #![no_main]
#![feature(type_alias_impl_trait)]
// The macro for our start-up function extern crate cortex_m_rt;
use adafruit_itsy_bitsy_rp2040::{ extern crate panic_halt;
entry,
hal::{clocks::ClocksManager, usb}, mod board;
mod keyboard;
mod usb;
use board::Board;
use embassy_executor::Spawner;
use embassy_rp::gpio::{Level, Output};
use embassy_time::{Duration, Timer};
#[embassy_executor::main]
async fn main(spawner: Spawner) {
let p = embassy_rp::init(Default::default());
let board = Board {
a0: p.PIN_26,
a1: p.PIN_27,
a2: p.PIN_28,
a3: p.PIN_29,
d24: p.PIN_24,
d25: p.PIN_25,
sck: p.PIN_18,
mosi: p.PIN_19,
miso: p.PIN_20,
d2: p.PIN_12,
d3: p.PIN_5,
d4: p.PIN_4,
rx: p.PIN_1,
tx: p.PIN_0,
sda: p.PIN_2,
scl: p.PIN_3,
d5: p.PIN_14,
d7: p.PIN_6,
d9: p.PIN_7,
d10: p.PIN_8,
d11: p.PIN_9,
d12: p.PIN_10,
d13: p.PIN_11,
neopixel: p.PIN_17,
neopixel_power: p.PIN_16,
}; };
// Ensure we halt the program on panic (if we don't mention this crate it won't let mut led = Output::new(board.d13, Level::High);
// be linked)
use panic_halt as _;
// Some traits we need let mut builder = usb::builder(p.USB);
use embedded_hal::digital::v2::OutputPin;
use adafruit_itsy_bitsy_rp2040::{ usb::logger::setup(&mut builder).await;
hal::{
clocks::{init_clocks_and_plls, Clock},
pac,
sio::Sio,
watchdog::Watchdog,
},
Pins, XOSC_CRYSTAL_FREQ,
};
use cortex_m::delay::Delay; log::error!("log_level: error");
log::warn!("log_level: warn");
log::info!("log_level: info");
log::debug!("log_level: debug");
log::trace!("log_level: trace");
/// Entry point to our bare-metal application. usb::keyboard::setup(&mut builder).await;
///
/// The `#[entry]` macro ensures the Cortex-M start-up code calls this function
/// as soon as all global variables are initialised.
///
/// The function configures the RP2040 peripherals, then toggles a GPIO pin in
/// an infinite loop. If there is an LED connected to that pin, it will blink.
#[entry]
fn main() -> ! {
// Grab our singleton objects
let mut pac = pac::Peripherals::take().unwrap();
let core = pac::CorePeripherals::take().unwrap();
// Set up the watchdog driver - needed by the clock setup code let usb = builder.build();
let mut watchdog = Watchdog::new(pac.WATCHDOG);
// Configure the clocks spawner.must_spawn(usb::run(usb));
let clocks = init_clocks_and_plls(
XOSC_CRYSTAL_FREQ,
pac.XOSC,
pac.CLOCKS,
pac.PLL_SYS,
pac.PLL_USB,
&mut pac.RESETS,
&mut watchdog,
)
.ok()
.unwrap();
let usb = usb::UsbBus::new( Timer::after(Duration::from_millis(1000)).await;
pac.USBCTRL_REGS,
pac.USBCTRL_DPRAM,
clocks.usb_clock,
false,
&mut pac.RESETS,
);
// TODO: how to keyboard crate::keyboard::test_type("Hello there!\n").await;
let mut delay = Delay::new(core.SYST, clocks.system_clock.freq().to_Hz());
// The single-cycle I/O block controls our GPIO pins
let sio = Sio::new(pac.SIO);
let pins = Pins::new(
pac.IO_BANK0,
pac.PADS_BANK0,
sio.gpio_bank0,
&mut pac.RESETS,
);
let mut led_pin = pins.d13.into_push_pull_output();
loop { loop {
led_pin.set_high().unwrap(); Timer::after(Duration::from_millis(500)).await;
delay.delay_ms(500); led.toggle();
led_pin.set_low().unwrap();
delay.delay_ms(500);
} }
} }
// End of file

57
src/usb.rs Normal file
View File

@ -0,0 +1,57 @@
use embassy_rp::{interrupt, peripherals::USB, usb::Driver};
use embassy_usb::{Builder, Config, UsbDevice};
use static_cell::StaticCell;
pub mod keyboard;
pub mod logger;
pub const MAX_PACKET_SIZE: u8 = 64;
struct State {
device_descriptor: [u8; 256],
config_descriptor: [u8; 256],
bos_descriptor: [u8; 256],
control_buf: [u8; 64],
}
static STATE: StaticCell<State> = StaticCell::new();
pub fn builder(usb: USB) -> Builder<'static, Driver<'static, USB>> {
let state = STATE.init(State {
device_descriptor: [0; 256],
config_descriptor: [0; 256],
bos_descriptor: [0; 256],
control_buf: [0; 64],
});
// Create embassy-usb Config
let mut config = Config::new(0xc0de, 0xcafe);
config.manufacturer = Some("Tux");
config.product = Some("Tangentbord1");
config.serial_number = Some("42069");
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 driver = Driver::new(usb, interrupt::take!(USBCTRL_IRQ));
Builder::new(
driver,
config,
&mut state.device_descriptor,
&mut state.config_descriptor,
&mut state.bos_descriptor,
&mut state.control_buf,
)
}
#[embassy_executor::task]
pub async fn run(mut device: UsbDevice<'static, Driver<'static, USB>>) {
device.run().await
}

185
src/usb/keyboard.rs Normal file
View File

@ -0,0 +1,185 @@
use embassy_executor::Spawner;
use embassy_rp::{peripherals::USB, usb::Driver};
use embassy_time::{Duration, Timer};
use embassy_usb::{
class::hid::{self, HidReaderWriter, ReadError, ReportId, RequestHandler},
control::OutResponse,
Builder,
};
use embassy_usb_driver::EndpointError;
use static_cell::StaticCell;
use usbd_hid::descriptor::{KeyboardReport, MouseReport, SerializedDescriptor};
use crate::keyboard::{Button, COLS, MATRIX, ROWS, TEST_KEYMAP};
use super::MAX_PACKET_SIZE;
struct Handler;
static CONTEXT: StaticCell<Context> = StaticCell::new();
struct Context {
handler: Handler,
state: hid::State<'static>,
}
pub async fn setup(builder: &mut Builder<'static, Driver<'static, USB>>) {
log::info!("setting up usb hid");
let context = CONTEXT.init(Context {
handler: Handler,
state: hid::State::new(),
});
let config = hid::Config {
//report_descriptor: MouseReport::desc(),
report_descriptor: KeyboardReport::desc(),
request_handler: Some(&context.handler),
poll_ms: 2,
max_packet_size: MAX_PACKET_SIZE as u16,
};
let stream = HidStream::new(builder, &mut context.state, config);
let spawner = Spawner::for_current_executor().await;
spawner.must_spawn(task(stream, &context.handler));
log::info!("done setting up usb keyboard");
}
impl RequestHandler for Handler {
fn get_report(&self, id: ReportId, buf: &mut [u8]) -> Option<usize> {
log::info!("get_report({id:?}, {buf:?})");
let _ = (id, buf);
None
}
fn set_report(&self, id: ReportId, data: &[u8]) -> embassy_usb::control::OutResponse {
log::info!("set_report({id:?}, {data:?})");
let _ = (id, data);
OutResponse::Rejected
}
fn get_idle_ms(&self, id: Option<ReportId>) -> Option<u32> {
log::info!("get_idle_ms({id:?})");
let _ = id;
None
}
fn set_idle_ms(&self, id: Option<ReportId>, duration_ms: u32) {
log::info!("set_idle_ms({id:?}, {duration_ms})");
let _ = (id, duration_ms);
}
}
type HidStream = HidReaderWriter<'static, Driver<'static, USB>, 256, 256>;
#[embassy_executor::task]
async fn task(stream: HidStream, handler: &'static Handler) {
if let Err(e) = keyboard_test(stream, handler).await {
log::error!("keyboard error: {e:?}");
}
//if let Err(e) = mouse_wiggler(stream).await {
// log::error!("mouse wiggler: {e:?}");
//}
}
async fn keyboard_test(mut stream: HidStream, handler: &'static Handler) -> Result<(), Error> {
stream.ready().await;
loop {
Timer::after(Duration::from_millis(2)).await;
let keymap = &TEST_KEYMAP;
let mut keycodes = [0u8; 6];
let mut i = 0;
'keyscan: for col in 0..COLS {
for row in 0..ROWS {
if !MATRIX[row][col].is_pressed() {
continue;
}
let Button::Key { keycode } = &keymap[row][col];
// else { continue; };
keycodes[i] = *keycode;
i += 1;
if i >= keycodes.len() {
break 'keyscan;
}
}
}
if keycodes.iter().any(|&b| b != 0) {
log::info!("keycodes: {keycodes:?}");
}
stream
.write_serialize(&KeyboardReport {
modifier: 0,
reserved: 0,
leds: 0,
keycodes,
})
.await?;
}
}
#[allow(dead_code)]
async fn mouse_wiggler(mut stream: HidStream) -> Result<(), Error> {
stream.ready().await;
let (_r, mut w) = stream.split();
let write_fut = async move {
let mut x = 1;
loop {
for _ in 0..100 {
Timer::after(Duration::from_millis(10)).await;
log::info!("sending mouse report");
//w.ready().await;
w.write_serialize(&MouseReport {
x,
y: 0,
buttons: 0,
wheel: 0,
pan: 0,
})
.await?;
}
x = -x;
}
};
//let read_fut = async move {
// let mut buf = [0u8; MAX_PACKET_SIZE as usize];
// loop {
// Timer::after(Duration::from_millis(30)).await;
// let n = r.read(&mut buf).await?;
// log::info!("got packet: {:?}", &buf[..n]);
// }
//};
//let r: Result<((), ()), Error> = try_join(write_fut, read_fut).await;
let r: Result<(), Error> = write_fut.await;
r?;
Ok(())
}
#[derive(Debug)]
enum Error {
Read(ReadError),
Endpoint(EndpointError),
}
impl From<ReadError> for Error {
fn from(value: ReadError) -> Self {
Error::Read(value)
}
}
impl From<EndpointError> for Error {
fn from(value: EndpointError) -> Self {
Error::Endpoint(value)
}
}

79
src/usb/logger.rs Normal file
View File

@ -0,0 +1,79 @@
use super::MAX_PACKET_SIZE;
use core::fmt::Write as WriteFmt;
use embassy_executor::Spawner;
use embassy_rp::{peripherals::USB, usb::Driver};
use embassy_sync::{blocking_mutex::raw::CriticalSectionRawMutex, pipe::Pipe};
use embassy_time::Instant;
use embassy_usb::{
class::cdc_acm::{self, CdcAcmClass},
Builder,
};
use log::{Metadata, Record};
use static_cell::StaticCell;
pub const BUFFER_SIZE: usize = 16 * 1024;
static BUFFER: Pipe<CriticalSectionRawMutex, BUFFER_SIZE> = Pipe::new();
static STATE: StaticCell<cdc_acm::State<'static>> = StaticCell::new();
struct UsbLogger;
pub async fn setup(usb_builder: &mut Builder<'static, Driver<'static, USB>>) {
unsafe {
static LOGGER: UsbLogger = UsbLogger;
log::set_logger_racy(&LOGGER).unwrap();
log::set_max_level(log::LevelFilter::Debug);
}
let spawner = Spawner::for_current_executor().await;
let state = STATE.init(cdc_acm::State::new());
let class = CdcAcmClass::new(usb_builder, state, MAX_PACKET_SIZE as u16);
spawner.must_spawn(log_task(class));
}
//pub async fn print(s: &str) {
// BUFFER.writer().write_all(s.as_bytes()).await.ok(/* infallible */);
//}
#[embassy_executor::task]
async fn log_task(mut class: CdcAcmClass<'static, Driver<'static, USB>>) {
let mut buf = [0u8; MAX_PACKET_SIZE as usize];
class.wait_connection().await;
loop {
let n = BUFFER.read(&mut buf).await;
// not much we can do if this fails, just ignore the error
let _ = class.write_packet(&buf[..n]).await;
}
}
impl log::Log for UsbLogger {
fn enabled(&self, _metadata: &Metadata) -> bool {
true
}
fn log(&self, record: &Record) {
if self.enabled(record.metadata()) {
let mut w = Writer;
let now = Instant::now();
let s = now.as_secs();
let ms = now.as_millis() % 1000;
let level = record.metadata().level();
let _ = write!(w, "[{s}.{ms:04}] ({level}) {}\n", record.args());
}
}
fn flush(&self) {}
}
struct Writer;
impl core::fmt::Write for Writer {
fn write_str(&mut self, s: &str) -> Result<(), core::fmt::Error> {
let _ = BUFFER.try_write(s.as_bytes());
Ok(())
}
}