From e0449c9ac8c71c372e94a4fc6cbd21ac8732ade0 Mon Sep 17 00:00:00 2001 From: fly Date: Sat, 6 Apr 2024 00:10:13 +0200 Subject: [PATCH] Meow Signed-off-by: fly --- firmware/Cargo.lock | 12 ++- firmware/Cargo.toml | 2 + firmware/descriptor.wara | 17 ++++ firmware/hidtools.sh | 2 + firmware/src/device.rs | 138 +++++++++++++++++++++++++++ firmware/src/main.rs | 199 ++++++++++++++++++++++----------------- 6 files changed, 277 insertions(+), 93 deletions(-) create mode 100644 firmware/descriptor.wara create mode 100755 firmware/hidtools.sh create mode 100644 firmware/src/device.rs diff --git a/firmware/Cargo.lock b/firmware/Cargo.lock index 46c0f9b..84b9dff 100644 --- a/firmware/Cargo.lock +++ b/firmware/Cargo.lock @@ -126,7 +126,9 @@ dependencies = [ "cortex-m", "cortex-m-rt", "embedded-hal", + "fugit", "nb 1.1.0", + "packed_struct", "panic-halt", "panic-semihosting", "ryu", @@ -160,7 +162,7 @@ checksum = "b0fa992f1656e1707946bbba340ad244f0814009ef8c0118eb7b658395f19a2e" dependencies = [ "frunk_proc_macro_helpers", "quote", - "syn 2.0.53", + "syn 2.0.55", ] [[package]] @@ -172,7 +174,7 @@ dependencies = [ "frunk_core", "proc-macro2", "quote", - "syn 2.0.53", + "syn 2.0.55", ] [[package]] @@ -257,7 +259,7 @@ checksum = "681030a937600a36906c185595136d26abfebb4aa9c65701cefcaf8578bb982b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.53", + "syn 2.0.55", ] [[package]] @@ -419,9 +421,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.53" +version = "2.0.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7383cd0e49fff4b6b90ca5670bfd3e9d6a733b3f90c686605aa7eec8c4996032" +checksum = "002a1b3dbf967edfafc32655d0f377ab0bb7b994aa1d32c8cc7e9b8bf3ebb8f0" dependencies = [ "proc-macro2", "quote", diff --git a/firmware/Cargo.toml b/firmware/Cargo.toml index 93241d5..caa1db3 100644 --- a/firmware/Cargo.toml +++ b/firmware/Cargo.toml @@ -18,6 +18,8 @@ usbd-serial = "0.1.1" ryu = "1.0.16" panic-semihosting = "0.6.0" usbd-human-interface-device = "0.4.5" +packed_struct = { version = "0.10.1", default-features = false } +fugit = "0.3.7" [dependencies.stm32f1xx-hal] version = "0.10.0" diff --git a/firmware/descriptor.wara b/firmware/descriptor.wara new file mode 100644 index 0000000..c9386cf --- /dev/null +++ b/firmware/descriptor.wara @@ -0,0 +1,17 @@ +[[applicationCollection]] +usage = ['Generic Desktop', 'Joystick'] + +[[applicationCollection.inputReport]] + +[[applicationCollection.inputReport.physicalCollection]] +usage = ['Generic Desktop', 'Pointer'] + +[[applicationCollection.inputReport.physicalCollection.variableItem]] +usage = ['Generic Desktop', 'X'] +logicalValueRange = [0, 65535] + +[[applicationCollection.outputReport]] + +[[applicationCollection.outputReport.variableItem]] +usage = ['Haptics', 'Manual Trigger'] +logicalValueRange = [0, 255] diff --git a/firmware/hidtools.sh b/firmware/hidtools.sh new file mode 100755 index 0000000..5eca413 --- /dev/null +++ b/firmware/hidtools.sh @@ -0,0 +1,2 @@ +#! /usr/bin/env bash +nix run github:feathecutie/hidtools -- -s descriptor.wara diff --git a/firmware/src/device.rs b/firmware/src/device.rs new file mode 100644 index 0000000..a8e0444 --- /dev/null +++ b/firmware/src/device.rs @@ -0,0 +1,138 @@ +use core::default::Default; +use fugit::ExtU32; +use packed_struct::prelude::*; +use usb_device::bus::UsbBus; +use usb_device::class_prelude::UsbBusAllocator; +use usbd_human_interface_device::{ + descriptor::InterfaceProtocol, + device::DeviceClass, + interface::{ + InBytes8, Interface, InterfaceBuilder, InterfaceConfig, OutBytes8, ReportSingle, + UsbAllocatable, + }, + UsbHidError, +}; + +// Generated using Waratah +#[rustfmt::skip] +pub const CUSTOM_DESCRIPTOR: &[u8] = &[ + 0x05, 0x01, // UsagePage(Generic Desktop[0x0001]) + 0x09, 0x04, // UsageId(Joystick[0x0004]) + 0xA1, 0x01, // Collection(Application) + 0x09, 0x01, // UsageId(Pointer[0x0001]) + 0xA1, 0x00, // Collection(Physical) + 0x09, 0x30, // UsageId(X[0x0030]) + 0x09, 0x31, // UsageId(Y[0x0031]) + 0x15, 0x00, // LogicalMinimum(0) + 0x27, 0xFF, 0xFF, 0x00, 0x00, // LogicalMaximum(65,535) + 0x95, 0x02, // ReportCount(2) + 0x75, 0x10, // ReportSize(16) + 0x81, 0x02, // Input(Data, Variable, Absolute, NoWrap, Linear, PreferredState, NoNullPosition, BitField) + 0xC0, // EndCollection() + 0x05, 0x09, // UsagePage(Button[0x0009]) + 0x19, 0x01, // UsageIdMin(Button 1[0x0001]) + 0x29, 0x10, // UsageIdMax(Button 16[0x0010]) + 0x25, 0x01, // LogicalMaximum(1) + 0x95, 0x10, // ReportCount(16) + 0x75, 0x01, // ReportSize(1) + 0x81, 0x02, // Input(Data, Variable, Absolute, NoWrap, Linear, PreferredState, NoNullPosition, BitField) + 0x05, 0x0E, // UsagePage(Haptics[0x000E]) + 0x09, 0x21, // UsageId(Manual Trigger[0x0021]) + 0x26, 0xFF, 0x00, // LogicalMaximum(255) + 0x95, 0x01, // ReportCount(1) + 0x75, 0x08, // ReportSize(8) + 0x91, 0x02, // Output(Data, Variable, Absolute, NoWrap, Linear, PreferredState, NoNullPosition, NonVolatile, BitField) + 0xC0, // EndCollection() +]; + +#[derive(Clone, Copy, Debug, Eq, PartialEq, Default, PackedStruct)] +#[packed_struct(endian = "lsb", size_bytes = "5")] +pub struct CustomInputReport { + #[packed_field] + pub buttons: u16, + #[packed_field] + pub y: Integer>, + #[packed_field] + pub x: Integer>, +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq, Default, PackedStruct)] +#[packed_struct(endian = "lsb", size_bytes = "1")] +pub struct CustomOutputReport { + #[packed_field] + pub int: u8, +} + +pub struct CustomDevice<'a, B: UsbBus> { + interface: Interface<'a, B, InBytes8, OutBytes8, ReportSingle>, +} + +impl<'a, B: UsbBus> CustomDevice<'a, B> { + pub fn write_report(&mut self, report: &CustomInputReport) -> Result<(), UsbHidError> { + let data = report.pack().map_err(|_| UsbHidError::SerializationError)?; + self.interface + .write_report(&data) + .map(|_| ()) + .map_err(UsbHidError::from) + } + + pub fn read_report(&mut self) -> Result { + let mut data = [0]; + self.interface + .read_report(&mut data[..]) + .map(|_| CustomOutputReport::unpack(&data).unwrap()) + .map_err(UsbHidError::from) + } +} + +impl<'a, B: UsbBus> DeviceClass<'a> for CustomDevice<'a, B> { + type I = Interface<'a, B, InBytes8, OutBytes8, ReportSingle>; + + fn interface(&mut self) -> &mut Self::I { + &mut self.interface + } + + fn reset(&mut self) {} + + fn tick(&mut self) -> Result<(), UsbHidError> { + Ok(()) + } +} + +pub struct CustomConfig<'a> { + interface: InterfaceConfig<'a, InBytes8, OutBytes8, ReportSingle>, +} + +impl<'a> Default for CustomConfig<'a> { + #[must_use] + fn default() -> Self { + Self::new( + InterfaceBuilder::new(CUSTOM_DESCRIPTOR) + .unwrap() + .boot_device(InterfaceProtocol::None) + .description(" CustomDevice") + .in_endpoint(10.millis()) + .unwrap() + .with_out_endpoint(10.millis()) + .unwrap() + .build(), + ) + } +} + +impl<'a> CustomConfig<'a> { + #[must_use] + pub fn new(interface: InterfaceConfig<'a, InBytes8, OutBytes8, ReportSingle>) -> Self { + Self { interface } + } +} + +impl<'a, B: UsbBus + 'a> UsbAllocatable<'a, B> for CustomConfig<'a> { + type Allocated = CustomDevice<'a, B>; + + fn allocate(self, usb_alloc: &'a UsbBusAllocator) -> Self::Allocated { + Self::Allocated { + interface: Interface::new(usb_alloc, self.interface), + } + } +} diff --git a/firmware/src/main.rs b/firmware/src/main.rs index f8ea4ff..b8283e5 100644 --- a/firmware/src/main.rs +++ b/firmware/src/main.rs @@ -2,120 +2,143 @@ #![no_main] #![no_std] +mod device; + use panic_halt as _; //use nb::block; -use cortex_m_rt::entry; use cortex_m::asm::delay; -use stm32f1xx_hal::{adc, pac, prelude::*, timer::{Channel, Tim2NoRemap}, gpio::{Analog, Pin}}; +use cortex_m_rt::entry; +use stm32f1xx_hal::{ + adc, + gpio::{Analog, Pin}, + pac, + prelude::*, + timer::{Channel, Tim2NoRemap}, +}; use stm32f1xx_hal::usb::{Peripheral, UsbBus}; use usb_device::prelude::*; -use usbd_human_interface_device::device::joystick::JoystickReport; use usbd_human_interface_device::prelude::*; -struct MyPins { pa1: Pin<'A', 1, Analog>, pa2: Pin<'A', 2, Analog> } +use crate::device::{CustomConfig, CustomInputReport, CustomOutputReport}; + +struct MyPins { + pa1: Pin<'A', 1, Analog>, + pa2: Pin<'A', 2, Analog>, +} #[entry] fn main() -> ! { - // ====================== general setup ================= - // Acquire peripherals -// let cp = cortex_m::Peripherals::take().unwrap(); - let p = pac::Peripherals::take().unwrap(); - let mut flash = p.FLASH.constrain(); - let rcc = p.RCC.constrain(); + // ====================== general setup ================= + // Acquire peripherals + // let cp = cortex_m::Peripherals::take().unwrap(); + let p = pac::Peripherals::take().unwrap(); + let mut flash = p.FLASH.constrain(); + let rcc = p.RCC.constrain(); - // Setup GPIOA - let mut gpioa = p.GPIOA.split(); + // Setup GPIOA + let mut gpioa = p.GPIOA.split(); - // configure clock - let clocks = rcc - .cfgr - .use_hse(16.MHz()) - .sysclk(48.MHz()) - .pclk1(24.MHz()) - .freeze(&mut flash.acr); - - // ====================== USB setup ================= - assert!(clocks.usbclk_valid()); - let mut usb_dp = gpioa.pa12.into_push_pull_output(&mut gpioa.crh); - usb_dp.set_low(); - delay(clocks.sysclk().raw() / 100); + // configure clock + let clocks = rcc + .cfgr + .use_hse(16.MHz()) + .sysclk(48.MHz()) + .pclk1(24.MHz()) + .freeze(&mut flash.acr); - let usb = Peripheral { - usb: p.USB, - pin_dm: gpioa.pa11, - pin_dp: usb_dp.into_floating_input(&mut gpioa.crh), - }; - let usb_bus = UsbBus::new(usb); + // ====================== USB setup ================= + assert!(clocks.usbclk_valid()); + let mut usb_dp = gpioa.pa12.into_push_pull_output(&mut gpioa.crh); + usb_dp.set_low(); + delay(clocks.sysclk().raw() / 100); - let mut consumer = UsbHidClassBuilder::new() - .add_device(usbd_human_interface_device::device::joystick::JoystickConfig::default()) - .build(&usb_bus); + let usb = Peripheral { + usb: p.USB, + pin_dm: gpioa.pa11, + pin_dp: usb_dp.into_floating_input(&mut gpioa.crh), + }; + let usb_bus = UsbBus::new(usb); - let mut usb_dev = UsbDeviceBuilder::new(&usb_bus, UsbVidPid(0x16c0, 0x27dd)) - .manufacturer("FLC Meow") - .product("Pedestal box") - .serial_number("01189998819991197253") - .build(); + let mut consumer = UsbHidClassBuilder::new() + .add_device(CustomConfig::default()) + .build(&usb_bus); - // ====================== ADC setup ================= - // Setup ADC - let mut adc1 = adc::Adc::adc1(p.ADC1, clocks); + let mut usb_dev = UsbDeviceBuilder::new(&usb_bus, UsbVidPid(0x16c0, 0x27dd)) + .manufacturer("FLC Meow") + .product("Pedestal box") + .serial_number("01189998819991197253") + .build(); - // ====================== Pin setup ================= - let mut input_pins = MyPins { - pa1: gpioa.pa1.into_analog(&mut gpioa.crl), - pa2: gpioa.pa2.into_analog(&mut gpioa.crl) - }; + // ====================== ADC setup ================= + // Setup ADC + let mut adc1 = adc::Adc::adc1(p.ADC1, clocks); - let mut last = get_report(&mut input_pins, &mut adc1); + // ====================== Pin setup ================= + let mut input_pins = MyPins { + pa1: gpioa.pa1.into_analog(&mut gpioa.crl), + pa2: gpioa.pa2.into_analog(&mut gpioa.crl), + }; - // ====================== PWM setup ================= - let mut afio = p.AFIO.constrain(); - let c1 = gpioa.pa0.into_alternate_push_pull(&mut gpioa.crl); - let mut pwm = p.TIM2.pwm_hz::(c1, &mut afio.mapr, 1.kHz(), &clocks); - pwm.enable(Channel::C1); - let pwm_max = pwm.get_max_duty() as u16; //48000 in our case + let mut last = get_report(&mut input_pins, &mut adc1); + let mut last_output = CustomOutputReport { int: 0 }; - loop { - let report = get_report(&mut input_pins, &mut adc1); - if report != last { - match consumer.device().write_report(&report) { - Err(UsbHidError::WouldBlock) => {} - Ok(_) => { - last = report; - } - Err(e) => { - core::panic!("Failed to write consumer report: {:?}", e) - } - } - } + // ====================== PWM setup ================= + let mut afio = p.AFIO.constrain(); + let c1 = gpioa.pa0.into_alternate_push_pull(&mut gpioa.crl); + let mut pwm = p + .TIM2 + .pwm_hz::(c1, &mut afio.mapr, 1.kHz(), &clocks); + pwm.enable(Channel::C1); + let pwm_max = pwm.get_max_duty() as u16; //48000 in our case - if usb_dev.poll(&mut [&mut consumer]) {} + loop { + let report = get_report(&mut input_pins, &mut adc1); +// if report != last { + match consumer.device().write_report(&report) { + Err(UsbHidError::WouldBlock) => {} + Ok(_) => { + last = report; + } + Err(e) => { + core::panic!("Failed to write consumer report: {:?}", e) + } + } +// } - // TODO get value and set PWM - //pwm.set_duty(Channel::C1, pwm_duty); + if usb_dev.poll(&mut [&mut consumer]) { + match consumer.device().read_report() { + Err(UsbHidError::WouldBlock) => {} + Ok(output) => { + last_output = output; + } + Err(e) => { + core::panic!("Failed to write consumer report: {:?}", e) + } + } + } - // Get data from pots - let data: u16 = adc1.read(&mut input_pins.pa1).unwrap(); - let mut pwm_val = data as f32 / 0xfff as f32; - let pwm_duty = (pwm_max as f32 * pwm_val as f32) as u16; - pwm.set_duty(Channel::C1, pwm_duty); - } + // Get data from pots + let data: u16 = adc1.read(&mut input_pins.pa2).unwrap(); + let mut pwm_val = data as f32 / 0xfff as f32; + let pwm_duty = (pwm_max as f32 * pwm_val as f32) as u16; + pwm.set_duty(Channel::C1, pwm_duty); + + } } -fn get_report( - pins: &mut MyPins, - adc1: &mut adc::Adc, -) -> JoystickReport { +fn get_report(pins: &mut MyPins, adc1: &mut adc::Adc) -> CustomInputReport { + let integLT: u16 = adc1.read(&mut pins.pa1).unwrap(); + let floodLT: u16 = adc1.read(&mut pins.pa2).unwrap(); + let retX = (integLT / 4) as i16; + let retY = floodLT as i16; + let buttons: u16 = 0; - let integLT: u16 = adc1.read(&mut pins.pa1).unwrap(); - let floodLT: u16 = adc1.read(&mut pins.pa2).unwrap(); - let retX: i8 = ((integLT / 16) as i16 -127) as i8; - let retY: i8 = ((floodLT / 16) as i16 -127) as i8; - let buttons: u8 = 0; - - let report = JoystickReport {x: retX, y: retY, buttons}; - report + + CustomInputReport { + x: retX.into(), + y: retY.into(), + buttons, + } }