Add custom HID struct for serial transmission

This commit is contained in:
hodasemi 2023-03-22 11:24:32 +01:00
parent c356b98801
commit e94f2eb8d7
6 changed files with 335 additions and 81 deletions

View file

@ -16,3 +16,5 @@ usbd-hid = "0.6.1"
critical-section = "1.1.1" critical-section = "1.1.1"
embedded-hal = "0.2.7" embedded-hal = "0.2.7"
fugit = "0.3.6" fugit = "0.3.6"
numtoa = "0.2.4"
usbd-serial = "0.1.1"

90
src/custom_hid.rs Normal file
View file

@ -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<UsbBusAllocator<UsbBus>> = None;
static mut USB_SERIAL: Option<Serial> = None;
static mut USB_DEVICE: Option<UsbDevice<UsbBus>> = 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]);
}

View file

@ -1,8 +1,12 @@
#![no_std] #![no_std]
#![no_main] #![no_main]
mod custom_hid;
mod mouse_hid;
mod mouse_sensor; 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 // Ensure we halt the program on panic (if we don't mention this crate it won't
// be linked) // be linked)
use panic_halt as _; use panic_halt as _;
@ -30,15 +34,6 @@ use usbd_hid::{
use crate::mouse_sensor::MouseSensor; use crate::mouse_sensor::MouseSensor;
/// The USB Device Driver (shared with the interrupt).
static mut USB_DEVICE: Option<UsbDevice<UsbBus>> = None;
/// The USB Bus Driver (shared with the interrupt).
static mut USB_BUS: Option<UsbBusAllocator<UsbBus>> = None;
/// The USB Human Interface Device Driver (shared with the interrupt).
static mut USB_HID: Option<HIDClass<UsbBus>> = None;
#[entry] #[entry]
fn main() -> ! { fn main() -> ! {
// Grab our singleton objects // Grab our singleton objects
@ -61,6 +56,16 @@ fn main() -> ! {
.ok() .ok()
.unwrap(); .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 // The single-cycle I/O block controls our GPIO pins
let sio = hal::Sio::new(pac.SIO); let sio = hal::Sio::new(pac.SIO);
@ -72,75 +77,9 @@ fn main() -> ! {
&mut pac.RESETS, &mut pac.RESETS,
); );
let mouse_sensor = MouseSensor::new(pins, &mut pac.RESETS, pac.SPI0, &clocks); let mouse_sensor = MouseSensor::new(pins, &mut pac.RESETS, pac.SPI0, peripheral_clock);
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;
loop { loop {
mouse_move(&mut delay, WAIT_TIME, PIXEL); mouse_hid.run();
mouse_move(&mut delay, 50, -PIXEL);
} }
} }
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<usize, usb_device::UsbError> {
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]);
}

116
src/mouse_hid.rs Normal file
View file

@ -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<UsbDevice<UsbBus>> = None;
/// The USB Bus Driver (shared with the interrupt).
static mut USB_BUS: Option<UsbBusAllocator<UsbBus>> = None;
/// The USB Human Interface Device Driver (shared with the interrupt).
static mut USB_HID: Option<HIDClass<UsbBus>> = 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<usize, usb_device::UsbError> {
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]);
}

View file

@ -3,14 +3,12 @@ use embedded_hal::digital::v2::OutputPin;
use fugit::HertzU32; use fugit::HertzU32;
use rp_pico::{ use rp_pico::{
hal::{ hal::{
clocks::ClocksManager,
gpio::{ gpio::{
self, self,
bank0::{Gpio2, Gpio3, Gpio4, Gpio5}, bank0::{Gpio2, Gpio3, Gpio4, Gpio5},
Function, Output, Pin, PushPull, Spi, Function, Output, Pin, PushPull, Spi,
}, },
spi::{self, Enabled, Spi as SpiDevice}, spi::{self, Enabled, Spi as SpiDevice},
Clock,
}, },
pac::{RESETS, SPI0}, pac::{RESETS, SPI0},
Pins, Pins,
@ -29,7 +27,12 @@ impl MouseSensor {
const READ: u8 = 0x7F; const READ: u8 = 0x7F;
const WRITE: u8 = 0x80; 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<HertzU32>,
) -> Self {
// These are implicitly used by the spi driver if they are in the correct mode // These are implicitly used by the spi driver if they are in the correct mode
let _sclk = pins.gpio2.into_mode::<gpio::FunctionSpi>(); let _sclk = pins.gpio2.into_mode::<gpio::FunctionSpi>();
let _mosi = pins.gpio3.into_mode::<gpio::FunctionSpi>(); let _mosi = pins.gpio3.into_mode::<gpio::FunctionSpi>();
@ -42,7 +45,7 @@ impl MouseSensor {
// Exchange the uninitialised SPI driver for an initialised one // Exchange the uninitialised SPI driver for an initialised one
let spi = spi.init( let spi = spi.init(
resets, resets,
clocks.peripheral_clock.freq(), peripheral_clock,
HertzU32::from_raw(16_000_000u32), HertzU32::from_raw(16_000_000u32),
&embedded_hal::spi::MODE_0, &embedded_hal::spi::MODE_0,
); );

104
src/serial.rs Normal file
View file

@ -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<UsbBus>) -> Self {
Self {
serial: SerialPort::new(&usb_bus),
buf: [0u8; 256],
}
}
pub fn send(&mut self, i: impl NumToA<u16>) {
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<Commands> {
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<rp_pico::hal::usb::UsbBus> 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<rp_pico::hal::usb::UsbBus>) {
self.serial.control_out(xfer)
}
fn control_in(&mut self, xfer: ControlIn<rp_pico::hal::usb::UsbBus>) {
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)
}
}