Merge pull request #1 from blmaier/move-usb-to-module

Move USB to module & use Channel for events
This commit is contained in:
2026-07-18 08:25:36 -05:00
committed by GitHub
5 changed files with 337 additions and 209 deletions

1
.rustfmt.toml Normal file
View File

@@ -0,0 +1 @@
style_edition = "2024"

19
prek.toml Normal file
View File

@@ -0,0 +1,19 @@
[[repos]]
repo = "local"
hooks = [
{
id = "cargo-fmt",
name = "cargo-fmt",
language = "system",
entry = "cargo fmt --",
types = ["rust"],
},
{
id = "cargo-clippy",
name = "cargo-clippy",
language = "system",
entry = "cargo clippy -- -Dwarnings",
types = ["rust"],
pass_filenames = false,
},
]

View File

@@ -1,18 +1,8 @@
use core::sync::atomic::Ordering;
use crate::usb::{KeyboardAction, KeyboardEvent};
use defmt::*;
use embassy_rp::gpio::Input;
use embassy_sync::{blocking_mutex::raw::CriticalSectionRawMutex, mutex::Mutex};
use embassy_time::{Duration, Timer};
use embassy_usb::{
class::hid::{HidProtocolMode, HidWriter},
driver::Driver,
};
use usbd_hid::descriptor::KeyboardReport;
use crate::HID_PROTOCOL_MODE;
const WRITE_N: usize = 8;
use embassy_sync::blocking_mutex::raw::RawMutex;
use embassy_sync::channel::Sender;
pub struct Key<'a> {
button: Input<'a>,
@@ -20,60 +10,32 @@ pub struct Key<'a> {
}
impl Key<'_> {
pub async fn process<'d, D: Driver<'d>>(
pub async fn process<'ch, M, const N: usize>(
&mut self,
mut writer: Mutex<CriticalSectionRawMutex, HidWriter<'d, D, WRITE_N>>,
) {
sender: Sender<'ch, M, KeyboardEvent, N>,
) where
M: RawMutex,
{
loop {
self.button.wait_for_high().await;
info!("Button {} pressed", self.value);
if HID_PROTOCOL_MODE.load(Ordering::Relaxed) == HidProtocolMode::Boot as u8 {
match writer.get_mut().write(&[0, 0, 4, 0, 0, 0, 0, 0]).await {
Ok(()) => {}
Err(e) => warn!("Failed to send boot report: {:?}", e),
};
} else {
// Create a report with the A key pressed. (no shift modifier)
let report = KeyboardReport {
keycodes: [4, 0, 0, 0, 0, 0],
leds: 0,
modifier: 0,
reserved: 0,
};
// Send the report.
match writer.get_mut().write_serialize(&report).await {
Ok(()) => {}
Err(e) => warn!("Failed to send report: {:?}", e),
};
}
// Debounce
Timer::after(Duration::from_millis(50)).await;
sender
.send(KeyboardEvent {
key: self.value,
action: KeyboardAction::Press,
})
.await;
self.button.wait_for_low().await;
info!("Button {} unpressed", self.value);
if HID_PROTOCOL_MODE.load(Ordering::Relaxed) == HidProtocolMode::Boot as u8 {
match writer.get_mut().write(&[0, 0, 0, 0, 0, 0, 0, 0]).await {
Ok(()) => {}
Err(e) => warn!("Failed to send boot report: {:?}", e),
};
} else {
let report = KeyboardReport {
keycodes: [0, 0, 0, 0, 0, 0],
leds: 0,
modifier: 0,
reserved: 0,
};
match writer.get_mut().write_serialize(&report).await {
Ok(()) => {}
Err(e) => warn!("Failed to send report: {:?}", e),
};
}
// Debounce
Timer::after(Duration::from_millis(50)).await;
sender
.send(KeyboardEvent {
key: self.value,
action: KeyboardAction::Depress,
})
.await;
}
}
}
@@ -85,8 +47,7 @@ impl Key<'_> {
}
}
pub struct Keyboard<'d, D: Driver<'d>, const KEY_N: usize> {
writer: Mutex<CriticalSectionRawMutex, HidWriter<'d, D, WRITE_N>>,
pub struct Keyboard<'d, const KEY_N: usize> {
keys: [Option<Key<'d>>; KEY_N],
num_keys: usize,
}
@@ -95,10 +56,9 @@ pub enum KeyboardError {
MaxKeys,
}
impl<'d, D: Driver<'d>, const KEY_N: usize> Keyboard<'d, D, KEY_N> {
pub fn new(writer: HidWriter<'d, D, WRITE_N>) -> Self {
impl<'d, const KEY_N: usize> Keyboard<'d, KEY_N> {
pub fn new() -> Self {
Self {
writer: Mutex::new(writer),
keys: [const { None }; KEY_N],
num_keys: 0,
}
@@ -116,8 +76,11 @@ impl<'d, D: Driver<'d>, const KEY_N: usize> Keyboard<'d, D, KEY_N> {
Ok(())
}
pub async fn process(mut self) {
pub async fn process<'ch, M, const N: usize>(mut self, sender: Sender<'ch, M, KeyboardEvent, N>)
where
M: RawMutex,
{
// TODO: Make this a loop that is concatinated together
self.keys[0].as_mut().unwrap().process(self.writer).await;
self.keys[0].as_mut().unwrap().process(sender).await;
}
}

View File

@@ -3,10 +3,10 @@
mod heartbeat;
mod keyboard;
mod usb;
use crate::heartbeat::heartbeat;
use crate::keyboard::Keyboard;
use core::sync::atomic::{AtomicBool, AtomicU8, Ordering};
use defmt::*;
use embassy_executor::Spawner;
use embassy_futures::join::join;
@@ -14,12 +14,8 @@ use embassy_rp::bind_interrupts;
use embassy_rp::gpio::{Input, Level, Output, Pull};
use embassy_rp::peripherals::USB;
use embassy_rp::usb::{Driver, InterruptHandler};
use embassy_usb::class::hid::{
HidBootProtocol, HidProtocolMode, HidReaderWriter, HidSubclass, ReportId, RequestHandler, State,
};
use embassy_usb::control::OutResponse;
use embassy_usb::{Builder, Config, Handler};
use usbd_hid::descriptor::{KeyboardReport, SerializedDescriptor};
use embassy_sync::blocking_mutex::raw::ThreadModeRawMutex;
use embassy_sync::channel::Channel;
use {defmt_rtt as _, panic_probe as _};
@@ -27,59 +23,16 @@ bind_interrupts!(struct Irqs {
USBCTRL_IRQ => InterruptHandler<USB>;
});
static HID_PROTOCOL_MODE: AtomicU8 = AtomicU8::new(HidProtocolMode::Boot as u8);
#[embassy_executor::main]
async fn main(_spawner: Spawner) {
let keyboard_events: Channel<ThreadModeRawMutex, usb::KeyboardEvent, 64> = Channel::new();
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("Nicholas Mello");
config.product = Some("Pi Pico Arcade Keyboard");
config.serial_number = Some("00000001");
config.max_power = 100;
config.max_packet_size_0 = 64;
config.composite_with_iads = false;
config.device_class = 0;
config.device_sub_class = 0;
config.device_protocol = 0;
// Create embassy-usb DeviceBuilder using the driver and config.
// It needs some buffers for building the descriptors.
let mut config_descriptor = [0; 256];
let mut bos_descriptor = [0; 256];
// You can also add a Microsoft OS descriptor.
let mut msos_descriptor = [0; 256];
let mut control_buf = [0; 64];
let mut request_handler = MyRequestHandler {};
let mut device_handler = MyDeviceHandler::new();
let mut state = State::new();
let mut builder = Builder::new(
driver,
config,
&mut config_descriptor,
&mut bos_descriptor,
&mut msos_descriptor,
&mut control_buf,
);
builder.handler(&mut device_handler);
// Create classes on the builder.
let config = embassy_usb::class::hid::Config {
report_descriptor: KeyboardReport::desc(),
request_handler: None,
poll_ms: 60,
max_packet_size: 64,
hid_subclass: HidSubclass::Boot,
hid_boot_protocol: HidBootProtocol::Keyboard,
};
let hid = HidReaderWriter::<_, 1, 8>::new(&mut builder, &mut state, config);
let mut usb_buf = usb::UsbKeyboardBuf::new();
let usbkey = usb::UsbKeyboard::create_usb(&mut usb_buf, driver);
// Set up the signal pin that will be used to trigger the keyboard.
let mut signal_pin = Input::new(p.PIN_16, Pull::None);
@@ -87,9 +40,7 @@ async fn main(_spawner: Spawner) {
// Enable the schmitt trigger to slightly debounce.
signal_pin.set_schmitt(true);
let (reader, writer) = hid.split();
let mut keyboard = Keyboard::<_, 1>::new(writer);
let mut keyboard = Keyboard::<1>::new();
match keyboard.add_key(signal_pin, 'a') {
Ok(()) => info!("Key 'a' registered"),
Err(_) => {
@@ -97,95 +48,11 @@ async fn main(_spawner: Spawner) {
}
}
let out_fut = async {
reader.run(false, &mut request_handler).await;
};
let mut usb = builder.build();
let led = Output::new(p.PIN_25, Level::Low);
join(
join(usb.run(), heartbeat(led)),
join(keyboard.process(), out_fut),
join(usbkey.run(keyboard_events.receiver()), heartbeat(led)),
keyboard.process(keyboard_events.sender()),
)
.await;
}
struct MyRequestHandler {}
impl RequestHandler for MyRequestHandler {
fn get_report(&mut self, id: ReportId, _buf: &mut [u8]) -> Option<usize> {
info!("Get report for {:?}", id);
None
}
fn set_report(&mut self, id: ReportId, data: &[u8]) -> OutResponse {
info!("Set report for {:?}: {=[u8]}", id, data);
OutResponse::Accepted
}
fn get_protocol(&self) -> HidProtocolMode {
let protocol = HidProtocolMode::from(HID_PROTOCOL_MODE.load(Ordering::Relaxed));
info!("The current HID protocol mode is: {}", protocol);
protocol
}
fn set_protocol(&mut self, protocol: HidProtocolMode) -> OutResponse {
info!("Switching to HID protocol mode: {}", protocol);
HID_PROTOCOL_MODE.store(protocol as u8, Ordering::Relaxed);
OutResponse::Accepted
}
fn set_idle_ms(&mut self, id: Option<ReportId>, dur: u32) {
info!("Set idle rate for {:?} to {:?}", id, dur);
}
fn get_idle_ms(&mut self, id: Option<ReportId>) -> Option<u32> {
info!("Get idle rate for {:?}", id);
None
}
}
struct MyDeviceHandler {
configured: AtomicBool,
}
impl MyDeviceHandler {
fn new() -> Self {
MyDeviceHandler {
configured: AtomicBool::new(false),
}
}
}
impl Handler for MyDeviceHandler {
fn enabled(&mut self, enabled: bool) {
self.configured.store(false, Ordering::Relaxed);
if enabled {
info!("Device enabled");
} else {
info!("Device disabled");
}
}
fn reset(&mut self) {
self.configured.store(false, Ordering::Relaxed);
info!("Bus reset, the Vbus current limit is 100mA");
}
fn addressed(&mut self, addr: u8) {
self.configured.store(false, Ordering::Relaxed);
info!("USB address set to: {}", addr);
}
fn configured(&mut self, configured: bool) {
self.configured.store(configured, Ordering::Relaxed);
if configured {
info!(
"Device configured, it may now draw up to the configured current limit from Vbus."
)
} else {
info!("Device is no longer configured, the Vbus current limit is 100mA.");
}
}
}

278
src/usb.rs Normal file
View File

@@ -0,0 +1,278 @@
use core::cell::OnceCell;
use core::sync::atomic::{AtomicBool, AtomicU8, Ordering};
use defmt::*;
use embassy_futures::join::join;
use embassy_rp::peripherals::USB;
use embassy_rp::usb::Driver;
use embassy_sync::blocking_mutex::raw::RawMutex;
use embassy_sync::channel::Receiver;
use embassy_usb::UsbDevice;
use embassy_usb::class::hid::{
HidBootProtocol, HidProtocolMode, HidReader, HidReaderWriter, HidSubclass, HidWriter, ReportId,
RequestHandler, State,
};
use embassy_usb::control::OutResponse;
use embassy_usb::{Builder, Config, Handler};
use usbd_hid::descriptor::{KeyboardReport, SerializedDescriptor};
pub static HID_PROTOCOL_MODE: AtomicU8 = AtomicU8::new(HidProtocolMode::Boot as u8);
#[derive(Clone, Copy)]
pub enum KeyboardAction {
Press,
Depress,
}
#[derive(Clone, Copy)]
pub struct KeyboardEvent {
pub key: char,
pub action: KeyboardAction,
}
pub struct UsbKeyboardBuf<'d> {
// Create embassy-usb DeviceBuilder using the driver and config.
// It needs some buffers for building the descriptors.
config_descriptor: [u8; 256],
bos_descriptor: [u8; 256],
// You can also add a Microsoft OS descriptor.
msos_descriptor: [u8; 256],
control_buf: [u8; 64],
state: OnceCell<State<'d>>,
device_handler: OnceCell<MyDeviceHandler>,
}
impl<'d> UsbKeyboardBuf<'d> {
pub const fn new() -> Self {
Self {
config_descriptor: [0; 256],
bos_descriptor: [0; 256],
msos_descriptor: [0; 256],
control_buf: [0; 64],
state: OnceCell::new(),
device_handler: OnceCell::new(),
}
}
}
pub struct UsbKeyboard<'d> {
usb: UsbDevice<'d, Driver<'d, USB>>,
reader: HidReader<'d, Driver<'d, USB>, 1>,
writer: HidWriter<'d, Driver<'d, USB>, 8>,
}
impl<'d> UsbKeyboard<'d> {
// HidWriter<'_, Driver<'_, USB>, 8>
pub fn create_usb(buf: &'d mut UsbKeyboardBuf<'d>, driver: Driver<'d, USB>) -> Self {
// Create embassy-usb Config
let mut config = Config::new(0xc0de, 0xcafe);
config.manufacturer = Some("Nicholas Mello");
config.product = Some("Pi Pico Arcade Keyboard");
config.serial_number = Some("00000001");
config.max_power = 100;
config.max_packet_size_0 = 64;
config.composite_with_iads = false;
config.device_class = 0;
config.device_sub_class = 0;
config.device_protocol = 0;
if buf.state.set(State::new()).is_err() {
defmt::panic!("USB State already initalized");
}
let mut builder = Builder::new(
driver,
config,
&mut buf.config_descriptor,
&mut buf.bos_descriptor,
&mut buf.msos_descriptor,
&mut buf.control_buf,
);
if buf.device_handler.set(MyDeviceHandler::new()).is_err() {
defmt::panic!("USB Device Handler already initialized");
}
builder.handler(buf.device_handler.get_mut().unwrap());
// Create classes on the builder.
let config = embassy_usb::class::hid::Config {
report_descriptor: KeyboardReport::desc(),
request_handler: None,
poll_ms: 60,
max_packet_size: 64,
hid_subclass: HidSubclass::Boot,
hid_boot_protocol: HidBootProtocol::Keyboard,
};
let hid =
HidReaderWriter::<_, 1, 8>::new(&mut builder, buf.state.get_mut().unwrap(), config);
let (reader, writer) = hid.split();
let usb = builder.build();
Self {
usb,
reader,
writer,
}
}
pub async fn run<'ch, M, const N: usize>(self, receiver: Receiver<'ch, M, KeyboardEvent, N>)
where
M: RawMutex,
{
let mut request_handler = MyRequestHandler {};
let Self {
mut usb,
reader,
mut writer,
} = self;
let reader_fut = async {
reader.run(false, &mut request_handler).await;
};
let writer_fut = async {
loop {
let event = receiver.receive().await;
match event {
KeyboardEvent {
key: 'a',
action: KeyboardAction::Press,
} => {
if HID_PROTOCOL_MODE.load(Ordering::Relaxed) == HidProtocolMode::Boot as u8
{
match writer.write(&[0, 0, 4, 0, 0, 0, 0, 0]).await {
Ok(()) => {}
Err(e) => warn!("Failed to send boot report: {:?}", e),
};
} else {
// Create a report with the A key pressed. (no shift modifier)
let report = KeyboardReport {
keycodes: [4, 0, 0, 0, 0, 0],
leds: 0,
modifier: 0,
reserved: 0,
};
// Send the report.
match writer.write_serialize(&report).await {
Ok(()) => {}
Err(e) => warn!("Failed to send report: {:?}", e),
};
}
}
KeyboardEvent {
key: 'a',
action: KeyboardAction::Depress,
} => {
if HID_PROTOCOL_MODE.load(Ordering::Relaxed) == HidProtocolMode::Boot as u8
{
match writer.write(&[0, 0, 0, 0, 0, 0, 0, 0]).await {
Ok(()) => {}
Err(e) => warn!("Failed to send boot report: {:?}", e),
};
} else {
let report = KeyboardReport {
keycodes: [0, 0, 0, 0, 0, 0],
leds: 0,
modifier: 0,
reserved: 0,
};
match writer.write_serialize(&report).await {
Ok(()) => {}
Err(e) => warn!("Failed to send report: {:?}", e),
};
}
}
_ => {
defmt::panic!("Unexpected event!");
}
}
}
};
join(usb.run(), join(reader_fut, writer_fut)).await;
}
}
struct MyRequestHandler {}
impl RequestHandler for MyRequestHandler {
fn get_report(&mut self, id: ReportId, _buf: &mut [u8]) -> Option<usize> {
info!("Get report for {:?}", id);
None
}
fn set_report(&mut self, id: ReportId, data: &[u8]) -> OutResponse {
info!("Set report for {:?}: {=[u8]}", id, data);
OutResponse::Accepted
}
fn get_protocol(&self) -> HidProtocolMode {
let protocol = HidProtocolMode::from(HID_PROTOCOL_MODE.load(Ordering::Relaxed));
info!("The current HID protocol mode is: {}", protocol);
protocol
}
fn set_protocol(&mut self, protocol: HidProtocolMode) -> OutResponse {
info!("Switching to HID protocol mode: {}", protocol);
HID_PROTOCOL_MODE.store(protocol as u8, Ordering::Relaxed);
OutResponse::Accepted
}
fn set_idle_ms(&mut self, id: Option<ReportId>, dur: u32) {
info!("Set idle rate for {:?} to {:?}", id, dur);
}
fn get_idle_ms(&mut self, id: Option<ReportId>) -> Option<u32> {
info!("Get idle rate for {:?}", id);
None
}
}
struct MyDeviceHandler {
configured: AtomicBool,
}
impl MyDeviceHandler {
fn new() -> Self {
MyDeviceHandler {
configured: AtomicBool::new(false),
}
}
}
impl Handler for MyDeviceHandler {
fn enabled(&mut self, enabled: bool) {
self.configured.store(false, Ordering::Relaxed);
if enabled {
info!("Device enabled");
} else {
info!("Device disabled");
}
}
fn reset(&mut self) {
self.configured.store(false, Ordering::Relaxed);
info!("Bus reset, the Vbus current limit is 100mA");
}
fn addressed(&mut self, addr: u8) {
self.configured.store(false, Ordering::Relaxed);
info!("USB address set to: {}", addr);
}
fn configured(&mut self, configured: bool) {
self.configured.store(configured, Ordering::Relaxed);
if configured {
info!(
"Device configured, it may now draw up to the configured current limit from Vbus."
)
} else {
info!("Device is no longer configured, the Vbus current limit is 100mA.");
}
}
}