Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions firmware/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions firmware/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ panic-reset = "0.1"
rp2040-boot2 = "0.3"
rp2040-hal = { version = "0.10", features = ["rt", "critical-section-impl"] }
usb-device = "0.3"
usbd-serial = "0.2"
critical-section = { version = "1" }

# Dependencies for debug probe
Expand Down
82 changes: 77 additions & 5 deletions firmware/src/hid_class.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,27 @@ const HID_DESCRIPTOR: [u8; 7] = [
DESCRIPTOR_LEN_BYTES[1], // wDescriptorLength - LSB first
];

#[derive(Debug, Default, Copy, Clone)]
pub struct LedState {
num_lock: bool,
caps_lock: bool,
scroll_lock: bool,
compose: bool,
kana: bool,
}

impl From<u8> for LedState {
fn from(byte: u8) -> Self {
Self {
num_lock: byte & 1 == 1,
caps_lock: (byte >> 1) & 1 == 1,
scroll_lock: (byte >> 2) & 1 == 1,
compose: (byte >> 3) & 1 == 1,
kana: (byte >> 4) & 1 == 1,
}
}
}

// A HID device is composed of the following endpoints:
// * A pair of control IN and OUT endpoints called the default endpoint
// (these are handled by usb-device?)
Expand All @@ -52,6 +73,9 @@ pub struct HidClass<'a, B: UsbBus> {
// if declared.
// out_endpoint: EndpointOut<'a, B>,
_bus: PhantomData<B>,

last_report: [u8; 8],
led_state: LedState,
}

impl<'a, B: UsbBus> HidClass<'a, B> {
Expand All @@ -64,11 +88,24 @@ impl<'a, B: UsbBus> HidClass<'a, B> {
let poll_interval = 1;
let in_endpoint = bus_allocator.interrupt(max_packet_size, poll_interval);

Self { usb_interface, in_endpoint, _bus: PhantomData {} }
let last_report = [0; 8];

Self {
usb_interface,
in_endpoint,
_bus: PhantomData {},
last_report,
led_state: LedState::default(),
}
}

pub fn write_raw_report(&mut self, data: [u8; 8]) -> Result<usize> {
self.last_report = data;
self.in_endpoint.write(&data)
}

pub fn write_raw_report(&self, data: &[u8]) -> Result<usize> {
self.in_endpoint.write(data)
pub fn led_state(&self) -> LedState {
self.led_state
}
}

Expand Down Expand Up @@ -144,14 +181,39 @@ impl<B: UsbBus> UsbClass<B> for HidClass<'_, B> {
fn poll(&mut self) {}

fn control_out(&mut self, xfer: ControlOut<B>) {
let _ = xfer;
const SET_REPORT_REQUEST: u8 = 0x09;

let request = xfer.request();

let interface = request.index;
if interface != u8::from(self.usb_interface) as u16 {
return;
}

if request.request == SET_REPORT_REQUEST {
let data_len = xfer.data().len();

if data_len > 0 {
// The keyboard OUT report is 1 byte for the LED state.
self.led_state = LedState::from(xfer.data()[0]);
}

xfer.accept().ok();
}
}

// The Control pipe is used for:
// * Receiving and responding to requests for USB control and class data.
// * Transmitting data when polled by the HID class driver (using the Get_Report request).
// * Receiving data from the host.
fn control_in(&mut self, xfer: ControlIn<B>) {
const GET_REPORT_REQUEST: u8 = 0x01;
// const GET_IDLE_REQUEST: u8 = 0x02;
// const GET_PROTOCOL_REQUEST: u8 = 0x03;
// const SET_REPORT_REQUEST: u8 = 0x09;
// const SET_IDLE_REQUEST: u8 = 0x0A;
// const SET_PROTOCOL_REQUEST: u8 = 0x0B;

let request = xfer.request();

let interface = request.index;
Expand Down Expand Up @@ -188,7 +250,17 @@ impl<B: UsbBus> UsbClass<B> for HidClass<'_, B> {
_ => {},
}
},
(RequestType::Class, 0x0) => {},
(RequestType::Class, GET_REPORT_REQUEST) => {
const REPORT_TYPE_INPUT: u8 = 0x01;
// const REPORT_TYPE_OUTPUT: u8 = 0x02;
// const REPORT_TYPE_FEATURE: u8 = 0x03;

let [_report_id, report_type] = request.value.to_le_bytes();

if report_type == REPORT_TYPE_INPUT {
xfer.accept_with(&self.last_report).ok();
}
},
_ => {},
}
}
Expand Down
1 change: 1 addition & 0 deletions firmware/src/key_codes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use defmt::Format;
#[derive(Copy, Clone, Format, PartialEq)]
pub enum KeyCode {
Empty = 0x0,
ErrorRollOver = 0x01,
A = 0x04,
B = 0x05,
C = 0x06,
Expand Down
4 changes: 4 additions & 0 deletions firmware/src/key_scan.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,10 @@ impl<const NUM_ROWS: usize, const NUM_COLS: usize> From<KeyScan<NUM_ROWS, NUM_CO
if keycode_index < keycodes.len() {
keycodes[keycode_index] = key;
keycode_index += 1;
} else {
for keycode in &mut keycodes {
*keycode = KeyCode::ErrorRollOver as u8;
}
}
};

Expand Down
30 changes: 28 additions & 2 deletions firmware/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ use rp2040_hal::{
Clock, Watchdog,
};
use usb_device::{bus::UsbBusAllocator, device::UsbDeviceBuilder, prelude::*};
use usbd_serial::{embedded_io::Write, SerialPort};

/// The rate of polling of the keyboard itself in firmware.
const SCAN_LOOP_RATE_MS: u32 = 1;
Expand Down Expand Up @@ -60,6 +61,9 @@ static mut USB_BUS: Option<UsbBusAllocator<usb::UsbBus>> = None;
static USB_HID_CLASS: Mutex<RefCell<Option<HidClass<usb::UsbBus>>>> =
Mutex::new(RefCell::new(None));

static USB_SERIAL_CLASS: Mutex<RefCell<Option<SerialPort<usb::UsbBus>>>> =
Mutex::new(RefCell::new(None));

#[defmt::panic_handler]
fn panic() -> ! {
cortex_m::asm::udf()
Expand Down Expand Up @@ -163,6 +167,7 @@ fn main() -> ! {
};

let hid_class = HidClass::new(bus_allocator_ref);
let serial = SerialPort::new(bus_allocator_ref);

// https://github.com/obdev/v-usb/blob/7a28fdc685952412dad2b8842429127bc1cf9fa7/usbdrv/USB-IDs-for-free.txt#L128
let keyboard_usb_device = UsbDeviceBuilder::new(bus_allocator_ref, UsbVidPid(0x16c0, 0x27db))
Expand All @@ -175,6 +180,7 @@ fn main() -> ! {
// Note (safety): This is safe as interrupts haven't been started yet
critical_section::with(|cs| {
USB_HID_CLASS.replace(cs, Some(hid_class));
USB_SERIAL_CLASS.replace(cs, Some(serial));
});

USB_DEVICE = Some(keyboard_usb_device);
Expand All @@ -188,6 +194,9 @@ fn main() -> ! {
let mut tick_count_down = timer.count_down();
tick_count_down.start(1.millis());

let mut print_led_state_count_down = timer.count_down();
print_led_state_count_down.start(1.secs());

let mut last_report: KeyboardReport = scan.into();

loop {
Expand All @@ -200,7 +209,7 @@ fn main() -> ! {
let mut hid_class = USB_HID_CLASS.borrow_ref_mut(cs);
let hid_class = hid_class.as_mut().unwrap();

if let Err(err) = hid_class.write_raw_report(&report.as_raw_input()) {
if let Err(err) = hid_class.write_raw_report(report.as_raw_input()) {
match err {
UsbError::WouldBlock => warn!("UsbError::WouldBlock"),
UsbError::ParseError => error!("UsbError::ParseError"),
Expand All @@ -220,6 +229,20 @@ fn main() -> ! {
});
}
}

if print_led_state_count_down.wait().is_ok() {
critical_section::with(|cs| {
let mut hid_class = USB_HID_CLASS.borrow_ref_mut(cs);
let hid_class = hid_class.as_mut().unwrap();

let mut serial = USB_SERIAL_CLASS.borrow_ref_mut(cs);
let serial = serial.as_mut().unwrap();

let led_state = hid_class.led_state();

let _ = writeln!(serial, "{led_state:?}");
});
}
}
}

Expand All @@ -233,6 +256,9 @@ unsafe fn USBCTRL_IRQ() {
let mut hid_class = USB_HID_CLASS.borrow_ref_mut(cs);
let hid_class = hid_class.as_mut().unwrap();

usb_dev.poll(&mut [hid_class]);
let mut serial = USB_SERIAL_CLASS.borrow_ref_mut(cs);
let serial = serial.as_mut().unwrap();

usb_dev.poll(&mut [hid_class, serial]);
});
}