Meow
Signed-off-by: fly <merspieler@alwaysdata.com>
This commit is contained in:
parent
2c7db2f6de
commit
e0449c9ac8
6 changed files with 277 additions and 93 deletions
12
firmware/Cargo.lock
generated
12
firmware/Cargo.lock
generated
|
@ -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",
|
||||
|
|
|
@ -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"
|
||||
|
|
17
firmware/descriptor.wara
Normal file
17
firmware/descriptor.wara
Normal file
|
@ -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]
|
2
firmware/hidtools.sh
Executable file
2
firmware/hidtools.sh
Executable file
|
@ -0,0 +1,2 @@
|
|||
#! /usr/bin/env bash
|
||||
nix run github:feathecutie/hidtools -- -s descriptor.wara
|
138
firmware/src/device.rs
Normal file
138
firmware/src/device.rs
Normal file
|
@ -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<i16, packed_bits::Bits<12>>,
|
||||
#[packed_field]
|
||||
pub x: Integer<i16, packed_bits::Bits<12>>,
|
||||
}
|
||||
|
||||
#[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<CustomOutputReport, UsbHidError> {
|
||||
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<B>) -> Self::Allocated {
|
||||
Self::Allocated {
|
||||
interface: Interface::new(usb_alloc, self.interface),
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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::<Tim2NoRemap, _, _>(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::<Tim2NoRemap, _, _>(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<pac::ADC1>,
|
||||
) -> JoystickReport {
|
||||
fn get_report(pins: &mut MyPins, adc1: &mut adc::Adc<pac::ADC1>) -> 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,
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue