diff --git a/firmware/Cargo.lock b/firmware/Cargo.lock index 877ee56..a5c0826 100644 --- a/firmware/Cargo.lock +++ b/firmware/Cargo.lock @@ -306,6 +306,7 @@ dependencies = [ "rp2040-boot2", "rp2040-hal", "usb-device", + "usbd-serial", ] [[package]] @@ -645,6 +646,18 @@ dependencies = [ "portable-atomic", ] +[[package]] +name = "usbd-serial" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "065e4eaf93db81d5adac82d9cef8f8da314cb640fa7f89534b972383f1cf80fc" +dependencies = [ + "embedded-hal 0.2.7", + "embedded-io", + "nb 1.1.0", + "usb-device", +] + [[package]] name = "vcell" version = "0.1.3" diff --git a/firmware/Cargo.toml b/firmware/Cargo.toml index f54d78f..9be6b56 100644 --- a/firmware/Cargo.toml +++ b/firmware/Cargo.toml @@ -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 diff --git a/firmware/src/hid_class.rs b/firmware/src/hid_class.rs index c19cf0e..dbd2964 100644 --- a/firmware/src/hid_class.rs +++ b/firmware/src/hid_class.rs @@ -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 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?) @@ -52,6 +73,9 @@ pub struct HidClass<'a, B: UsbBus> { // if declared. // out_endpoint: EndpointOut<'a, B>, _bus: PhantomData, + + last_report: [u8; 8], + led_state: LedState, } impl<'a, B: UsbBus> HidClass<'a, B> { @@ -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 { + self.last_report = data; + self.in_endpoint.write(&data) } - pub fn write_raw_report(&self, data: &[u8]) -> Result { - self.in_endpoint.write(data) + pub fn led_state(&self) -> LedState { + self.led_state } } @@ -144,7 +181,25 @@ impl UsbClass for HidClass<'_, B> { fn poll(&mut self) {} fn control_out(&mut self, xfer: ControlOut) { - 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: @@ -152,6 +207,13 @@ impl UsbClass for HidClass<'_, B> { // * 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) { + 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; @@ -188,7 +250,17 @@ impl UsbClass 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(); + } + }, _ => {}, } } diff --git a/firmware/src/key_codes.rs b/firmware/src/key_codes.rs index 4bca6b0..da8eb21 100644 --- a/firmware/src/key_codes.rs +++ b/firmware/src/key_codes.rs @@ -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, diff --git a/firmware/src/key_scan.rs b/firmware/src/key_scan.rs index 1dc68e2..b6aa000 100644 --- a/firmware/src/key_scan.rs +++ b/firmware/src/key_scan.rs @@ -83,6 +83,10 @@ impl From> = None; static USB_HID_CLASS: Mutex>>> = Mutex::new(RefCell::new(None)); +static USB_SERIAL_CLASS: Mutex>>> = + Mutex::new(RefCell::new(None)); + #[defmt::panic_handler] fn panic() -> ! { cortex_m::asm::udf() @@ -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)) @@ -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); @@ -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 { @@ -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"), @@ -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:?}"); + }); + } } } @@ -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]); }); }