From e94f2eb8d78c78502d25f2b9e0748d1e39528fcf Mon Sep 17 00:00:00 2001 From: hodasemi Date: Wed, 22 Mar 2023 11:24:32 +0100 Subject: [PATCH] Add custom HID struct for serial transmission --- Cargo.toml | 2 + src/custom_hid.rs | 90 ++++++++++++++++++++++++++++++++++ src/main.rs | 93 ++++++----------------------------- src/mouse_hid.rs | 116 ++++++++++++++++++++++++++++++++++++++++++++ src/mouse_sensor.rs | 11 +++-- src/serial.rs | 104 +++++++++++++++++++++++++++++++++++++++ 6 files changed, 335 insertions(+), 81 deletions(-) create mode 100644 src/custom_hid.rs create mode 100644 src/mouse_hid.rs create mode 100644 src/serial.rs diff --git a/Cargo.toml b/Cargo.toml index 41301ce..be28cea 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,3 +16,5 @@ usbd-hid = "0.6.1" critical-section = "1.1.1" embedded-hal = "0.2.7" fugit = "0.3.6" +numtoa = "0.2.4" +usbd-serial = "0.1.1" diff --git a/src/custom_hid.rs b/src/custom_hid.rs new file mode 100644 index 0000000..8c9755a --- /dev/null +++ b/src/custom_hid.rs @@ -0,0 +1,90 @@ +use crate::serial::Serial; + +use rp_pico as bsp; + +use bsp::{ + hal::{ + clocks::ClocksManager, + pac, + pac::{interrupt, Interrupt}, + usb::UsbBus, + Clock, + }, + pac::{RESETS, USBCTRL_DPRAM, USBCTRL_REGS}, +}; + +use cortex_m::{delay::Delay, Peripherals as CortexPeripherals}; + +use usb_device::{class_prelude::*, prelude::*}; +use usbd_hid::{ + descriptor::{MouseReport, SerializedDescriptor}, + hid_class::HIDClass, +}; + +static mut USB_BUS: Option> = None; +static mut USB_SERIAL: Option = None; +static mut USB_DEVICE: Option> = None; + +pub struct CustomHID<'a> { + out_ep: EndpointOut<'a, UsbBus>, + in_ep: EndpointIn<'a, UsbBus>, +} + +impl<'a> CustomHID<'a> { + pub fn new( + usbctrl_reg: USBCTRL_REGS, + usbctrl_dpram: USBCTRL_DPRAM, + resets: &mut RESETS, + core: CortexPeripherals, + clocks: ClocksManager, + poll_ms: u8, + ) -> Self { + // Set up the USB driver + unsafe { + USB_BUS = Some(UsbBusAllocator::new(UsbBus::new( + usbctrl_reg, + usbctrl_dpram, + clocks.usb_clock, + true, + resets, + ))) + }; + + unsafe { + USB_SERIAL = Some(Serial::new(USB_BUS.as_ref().unwrap())); + } + + unsafe { + USB_DEVICE = Some( + UsbDeviceBuilder::new(USB_BUS.as_ref().unwrap(), UsbVidPid(0x16c0, 0x27dd)) + .manufacturer("Fake company") + .product("Serial port") + .serial_number("TEST") + .device_class(2) // from: https://www.usb.org/defined-class-codes + .build(), + ); + } + + let (in_ep, out_ep) = unsafe { + pac::NVIC::unmask(Interrupt::USBCTRL_IRQ); + + ( + USB_BUS.as_ref().unwrap().interrupt(64, poll_ms), + USB_BUS.as_ref().unwrap().interrupt(64, poll_ms), + ) + }; + + Self { in_ep, out_ep } + } + + pub fn run(&mut self) {} +} + +#[allow(non_snake_case)] +#[interrupt] +unsafe fn USBCTRL_IRQ() { + // Handle USB request + let usb_dev = USB_DEVICE.as_mut().unwrap(); + let usb_hid = USB_SERIAL.as_mut().unwrap(); + usb_dev.poll(&mut [usb_hid]); +} diff --git a/src/main.rs b/src/main.rs index a794317..08d1b23 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,8 +1,12 @@ #![no_std] #![no_main] +mod custom_hid; +mod mouse_hid; mod mouse_sensor; +mod serial; +use mouse_hid::MouseHID; // Ensure we halt the program on panic (if we don't mention this crate it won't // be linked) use panic_halt as _; @@ -30,15 +34,6 @@ use usbd_hid::{ use crate::mouse_sensor::MouseSensor; -/// The USB Device Driver (shared with the interrupt). -static mut USB_DEVICE: Option> = None; - -/// The USB Bus Driver (shared with the interrupt). -static mut USB_BUS: Option> = None; - -/// The USB Human Interface Device Driver (shared with the interrupt). -static mut USB_HID: Option> = None; - #[entry] fn main() -> ! { // Grab our singleton objects @@ -61,6 +56,16 @@ fn main() -> ! { .ok() .unwrap(); + let peripheral_clock = clocks.peripheral_clock.freq(); + + let mut mouse_hid = MouseHID::new( + pac.USBCTRL_REGS, + pac.USBCTRL_DPRAM, + &mut pac.RESETS, + core, + clocks, + ); + // The single-cycle I/O block controls our GPIO pins let sio = hal::Sio::new(pac.SIO); @@ -72,75 +77,9 @@ fn main() -> ! { &mut pac.RESETS, ); - let mouse_sensor = MouseSensor::new(pins, &mut pac.RESETS, pac.SPI0, &clocks); - - unsafe { - USB_BUS = Some(UsbBusAllocator::new(UsbBus::new( - pac.USBCTRL_REGS, - pac.USBCTRL_DPRAM, - clocks.usb_clock, - true, - &mut pac.RESETS, - ))) - }; - - unsafe { - USB_HID = Some(HIDClass::new( - USB_BUS.as_ref().unwrap(), - MouseReport::desc(), - 60, - )); - } - - unsafe { - USB_DEVICE = Some( - UsbDeviceBuilder::new(USB_BUS.as_ref().unwrap(), UsbVidPid(0x046d, 0x101b)) - .manufacturer("Logitech") - .product("Marathon Mouse/Performance Plus M705") - .serial_number("B14D65DA") - .device_class(0) - .build(), - ); - } - - unsafe { - pac::NVIC::unmask(Interrupt::USBCTRL_IRQ); - } - - let mut delay = Delay::new(core.SYST, clocks.system_clock.freq().to_Hz()); - const PIXEL: i8 = 2; - const WAIT_TIME: u32 = 10000; + let mouse_sensor = MouseSensor::new(pins, &mut pac.RESETS, pac.SPI0, peripheral_clock); loop { - mouse_move(&mut delay, WAIT_TIME, PIXEL); - mouse_move(&mut delay, 50, -PIXEL); + mouse_hid.run(); } } - -fn mouse_move(delay: &mut Delay, wait_time: u32, v: i8) { - delay.delay_ms(wait_time); - - let rep = MouseReport { - x: 0, - y: v, - buttons: 0, - wheel: 0, - pan: 0, - }; - push_mouse_movement(rep).unwrap_or(0); -} - -/// with interrupts disabled, to avoid a race hazard with the USB IRQ. -fn push_mouse_movement(report: MouseReport) -> Result { - critical_section::with(|_| unsafe { USB_HID.as_mut().map(|hid| hid.push_input(&report)) }) - .unwrap() -} - -#[allow(non_snake_case)] -#[interrupt] -unsafe fn USBCTRL_IRQ() { - // Handle USB request - let usb_dev = USB_DEVICE.as_mut().unwrap(); - let usb_hid = USB_HID.as_mut().unwrap(); - usb_dev.poll(&mut [usb_hid]); -} diff --git a/src/mouse_hid.rs b/src/mouse_hid.rs new file mode 100644 index 0000000..e4be11a --- /dev/null +++ b/src/mouse_hid.rs @@ -0,0 +1,116 @@ +use rp_pico as bsp; + +use bsp::{ + hal::{ + clocks::ClocksManager, + pac, + pac::{interrupt, Interrupt}, + usb::UsbBus, + Clock, + }, + pac::{RESETS, USBCTRL_DPRAM, USBCTRL_REGS}, +}; + +use cortex_m::{delay::Delay, Peripherals as CortexPeripherals}; + +use usb_device::{class_prelude::*, prelude::*}; +use usbd_hid::{ + descriptor::{MouseReport, SerializedDescriptor}, + hid_class::HIDClass, +}; + +/// The USB Device Driver (shared with the interrupt). +static mut USB_DEVICE: Option> = None; + +/// The USB Bus Driver (shared with the interrupt). +static mut USB_BUS: Option> = None; + +/// The USB Human Interface Device Driver (shared with the interrupt). +static mut USB_HID: Option> = None; + +pub struct MouseHID { + delay: Delay, +} + +impl MouseHID { + const PIXEL: i8 = 2; + const WAIT_TIME: u32 = 10000; + + pub fn new( + usbctrl_reg: USBCTRL_REGS, + usbctrl_dpram: USBCTRL_DPRAM, + resets: &mut RESETS, + core: CortexPeripherals, + clocks: ClocksManager, + ) -> Self { + unsafe { + USB_BUS = Some(UsbBusAllocator::new(UsbBus::new( + usbctrl_reg, + usbctrl_dpram, + clocks.usb_clock, + true, + resets, + ))) + }; + + unsafe { + USB_HID = Some(HIDClass::new( + USB_BUS.as_ref().unwrap(), + MouseReport::desc(), + 60, + )); + } + + unsafe { + USB_DEVICE = Some( + UsbDeviceBuilder::new(USB_BUS.as_ref().unwrap(), UsbVidPid(0x046d, 0x101b)) + .manufacturer("Logitech") + .product("Marathon Mouse/Performance Plus M705") + .serial_number("B14D65DA") + .device_class(0) + .build(), + ); + } + + unsafe { + pac::NVIC::unmask(Interrupt::USBCTRL_IRQ); + } + + let delay = Delay::new(core.SYST, clocks.system_clock.freq().to_Hz()); + + Self { delay } + } + + pub fn run(&mut self) { + self.mouse_move(Self::WAIT_TIME, Self::PIXEL); + self.mouse_move(50, -Self::PIXEL); + } + + fn mouse_move(&mut self, wait_time: u32, v: i8) { + self.delay.delay_ms(wait_time); + + let rep = MouseReport { + x: 0, + y: v, + buttons: 0, + wheel: 0, + pan: 0, + }; + push_mouse_movement(rep).unwrap_or(0); + } +} + +/// with interrupts disabled, to avoid a race hazard with the USB IRQ. +fn push_mouse_movement(report: MouseReport) -> Result { + critical_section::with(|_| unsafe { USB_HID.as_mut().map(|hid| hid.push_input(&report)) }) + .unwrap() +} + +#[allow(non_snake_case)] +#[interrupt] +unsafe fn USBCTRL_IRQ() { + // Handle USB request + let usb_dev = USB_DEVICE.as_mut().unwrap(); + let usb_hid = USB_HID.as_mut().unwrap(); + usb_dev.poll(&mut [usb_hid]); +} diff --git a/src/mouse_sensor.rs b/src/mouse_sensor.rs index 547900d..990f6ad 100644 --- a/src/mouse_sensor.rs +++ b/src/mouse_sensor.rs @@ -3,14 +3,12 @@ use embedded_hal::digital::v2::OutputPin; use fugit::HertzU32; use rp_pico::{ hal::{ - clocks::ClocksManager, gpio::{ self, bank0::{Gpio2, Gpio3, Gpio4, Gpio5}, Function, Output, Pin, PushPull, Spi, }, spi::{self, Enabled, Spi as SpiDevice}, - Clock, }, pac::{RESETS, SPI0}, Pins, @@ -29,7 +27,12 @@ impl MouseSensor { const READ: u8 = 0x7F; const WRITE: u8 = 0x80; - pub fn new(pins: Pins, resets: &mut RESETS, spio: SPI0, clocks: &ClocksManager) -> Self { + pub fn new( + pins: Pins, + resets: &mut RESETS, + spio: SPI0, + peripheral_clock: impl Into, + ) -> Self { // These are implicitly used by the spi driver if they are in the correct mode let _sclk = pins.gpio2.into_mode::(); let _mosi = pins.gpio3.into_mode::(); @@ -42,7 +45,7 @@ impl MouseSensor { // Exchange the uninitialised SPI driver for an initialised one let spi = spi.init( resets, - clocks.peripheral_clock.freq(), + peripheral_clock, HertzU32::from_raw(16_000_000u32), &embedded_hal::spi::MODE_0, ); diff --git a/src/serial.rs b/src/serial.rs new file mode 100644 index 0000000..018042e --- /dev/null +++ b/src/serial.rs @@ -0,0 +1,104 @@ +use rp_pico as bsp; + +use bsp::hal::usb::UsbBus; + +use usb_device::class_prelude::*; +use usbd_serial::SerialPort; + +use numtoa::NumToA; + +pub enum Commands { + Reset, +} + +pub struct Serial<'a> { + serial: SerialPort<'a, UsbBus>, + buf: [u8; 256], +} + +impl<'a> Serial<'a> { + pub fn new(usb_bus: &'a UsbBusAllocator) -> Self { + Self { + serial: SerialPort::new(&usb_bus), + buf: [0u8; 256], + } + } + + pub fn send(&mut self, i: impl NumToA) { + let s = i.numtoa_str(10, &mut self.buf); + + // Send back to the host + let mut wr_ptr = s.as_bytes(); + + while !wr_ptr.is_empty() { + match self.serial.write(wr_ptr) { + Ok(len) => wr_ptr = &wr_ptr[len..], + // On error, just drop unwritten data. + // One possible error is Err(WouldBlock), meaning the USB + // write buffer is full. + Err(_) => break, + }; + } + } + + pub fn read(&mut self) -> Option { + match self.serial.read(&mut self.buf) { + Err(_e) => None, + Ok(0) => None, + Ok(count) => { + let c = count.min(self.buf.len()); + let msg = &self.buf[..c]; + + match msg { + b"reset" => Some(Commands::Reset), + _ => None, + } + } + } + } +} + +impl<'a> usb_device::class::UsbClass for Serial<'a> { + fn get_configuration_descriptors( + &self, + writer: &mut DescriptorWriter, + ) -> usb_device::Result<()> { + self.serial.get_configuration_descriptors(writer) + } + + fn get_bos_descriptors(&self, writer: &mut BosWriter) -> usb_device::Result<()> { + self.serial.get_bos_descriptors(writer) + } + + fn get_string(&self, index: StringIndex, lang_id: u16) -> Option<&str> { + self.serial.get_string(index, lang_id) + } + + fn reset(&mut self) { + self.serial.reset() + } + + fn poll(&mut self) { + self.serial.poll() + } + + fn control_out(&mut self, xfer: ControlOut) { + self.serial.control_out(xfer) + } + + fn control_in(&mut self, xfer: ControlIn) { + self.serial.control_in(xfer) + } + + fn endpoint_setup(&mut self, addr: EndpointAddress) { + self.serial.endpoint_setup(addr) + } + + fn endpoint_out(&mut self, addr: EndpointAddress) { + self.serial.endpoint_out(addr) + } + + fn endpoint_in_complete(&mut self, addr: EndpointAddress) { + self.serial.endpoint_in_complete(addr) + } +}