diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/app.rs | 362 | ||||
-rw-r--r-- | src/build.rs | 18 |
2 files changed, 380 insertions, 0 deletions
diff --git a/src/app.rs b/src/app.rs new file mode 100644 index 0000000..1a4a424 --- /dev/null +++ b/src/app.rs @@ -0,0 +1,362 @@ +#![no_std] +#![no_main] + +extern crate panic_semihosting; + +use cortex_m::{asm::delay, peripheral::DWT}; +use embedded_hal::digital::v2::{InputPin, OutputPin}; +use rtic::cyccnt::{Instant, U32Ext as _}; +use stm32f1xx_hal::usb::{Peripheral, UsbBus, UsbBusType}; +use stm32f1xx_hal::{adc, gpio, prelude::*, stm32::ADC1}; +use usb_device::bus; +use usb_device::prelude::*; + +pub struct PanelState { + throttle0: u16, + throttle1: u16, + sw: u8, +} + +#[allow(unused)] +pub mod hid { + + use usb_device::class_prelude::*; + use usb_device::Result; + + const USB_CLASS_HID: u8 = 0x03; + + const USB_SUBCLASS_NONE: u8 = 0x00; + const USB_SUBCLASS_BOOT: u8 = 0x01; + + const USB_INTERFACE_NONE: u8 = 0x00; + const USB_INTERFACE_KEYBOARD: u8 = 0x01; + const USB_INTERFACE_MOUSE: u8 = 0x02; + + const REQ_GET_REPORT: u8 = 0x01; + const REQ_GET_IDLE: u8 = 0x02; + const REQ_GET_PROTOCOL: u8 = 0x03; + const REQ_SET_REPORT: u8 = 0x09; + const REQ_SET_IDLE: u8 = 0x0a; + const REQ_SET_PROTOCOL: u8 = 0x0b; + + const REPORT_DESCR: &[u8] = &[ + 0x05, 0x01, // USAGE_PAGE (Generic Desktop) + 0x09, 0x04, // USAGE (Joystick) + 0xa1, 0x01, // COLLECTION (Application) + 0x05, 0x02, // USAGE_PAGE (Simulation) + 0x09, 0xbb, // USAGE (Throttle) + 0x16, 0x00, 0x80, /* Logical Minimum (-32768) */ + 0x26, 0xff, 0x7f, /* Logical Maximum (32767) */ + 0x95, 0x01, // REPORT_COUNT (1) + 0x75, 0x10, // REPORT_SIZE (16) + 0x81, 0x02, // INPUT (Data,Var,Abs) + 0x05, 0x01, // USAGE_PAGE (Generic Desktop) + 0x09, 0x32, // USAGE (Z) + 0x16, 0x00, 0x80, /* Logical Minimum (-32768) */ + 0x26, 0xff, 0x7f, /* Logical Maximum (32767) */ + 0x95, 0x01, // REPORT_COUNT (1) + 0x75, 0x10, // REPORT_SIZE (16) + 0x81, 0x02, // INPUT (Data,Var,Abs) + 0x05, 0x09, // USAGE_PAGE (Button) + 0x19, 0x01, // USAGE_MINIMUM(Button 1) + 0x29, 0x05, // USAGE_MAXIMUM(Button 5) + 0x95, 0x05, // REPORT_COUNT (5) + 0x75, 0x01, // REPORT_SIZE (1) + 0x15, 0x00, /* Logical Minimum (0) */ + 0x25, 0x01, /* Logical Maximum (1) */ + 0x81, 0x02, // INPUT (Data,Var,Abs) + 0x95, 0x03, // REPORT_COUNT (3) + 0x75, 0x01, // REPORT_SIZE (1) + 0x81, 0x01, // INPUT (Cnst,Arr,Abs) + 0xc0, // END_COLLECTION + ]; + + pub fn report(s: super::PanelState) -> [u8; 5] { + [ + s.throttle1 as u8, // throttle 0 + (s.throttle1 >> 8) as u8, + s.throttle0 as u8, // throttle 1 + (s.throttle0 >> 8) as u8, + s.sw, + ] + } + + pub struct HIDClass<'a, B: UsbBus> { + report_if: InterfaceNumber, + report_ep: EndpointIn<'a, B>, + } + + impl<B: UsbBus> HIDClass<'_, B> { + /// Creates a new HIDClass with the provided UsbBus and max_packet_size in bytes. For + /// full-speed devices, max_packet_size has to be one of 8, 16, 32 or 64. + pub fn new(alloc: &UsbBusAllocator<B>) -> HIDClass<'_, B> { + HIDClass { + report_if: alloc.interface(), + report_ep: alloc.interrupt(8, 10), + } + } + + pub fn write(&mut self, data: &[u8]) { + self.report_ep.write(data).ok(); + } + } + + impl<B: UsbBus> UsbClass<B> for HIDClass<'_, B> { + fn get_configuration_descriptors(&self, writer: &mut DescriptorWriter) -> Result<()> { + writer.interface( + self.report_if, + USB_CLASS_HID, + USB_SUBCLASS_NONE, + USB_INTERFACE_MOUSE, + )?; + + let descr_len: u16 = REPORT_DESCR.len() as u16; + writer.write( + 0x21, + &[ + 0x01, // bcdHID + 0x01, // bcdHID + 0x00, // bContryCode + 0x01, // bNumDescriptors + 0x22, // bDescriptorType + descr_len as u8, // wDescriptorLength + (descr_len >> 8) as u8, // wDescriptorLength + ], + )?; + + writer.endpoint(&self.report_ep)?; + + Ok(()) + } + + fn control_in(&mut self, xfer: ControlIn<B>) { + let req = xfer.request(); + + if req.request_type == control::RequestType::Standard { + match (req.recipient, req.request) { + (control::Recipient::Interface, control::Request::GET_DESCRIPTOR) => { + let (dtype, _index) = req.descriptor_type_index(); + if dtype == 0x21 { + // HID descriptor + cortex_m::asm::bkpt(); + let descr_len: u16 = REPORT_DESCR.len() as u16; + + // HID descriptor + let descr = &[ + 0x09, // length + 0x21, // descriptor type + 0x01, // bcdHID + 0x01, // bcdHID + 0x00, // bCountryCode + 0x01, // bNumDescriptors + 0x22, // bDescriptorType + descr_len as u8, // wDescriptorLength + (descr_len >> 8) as u8, // wDescriptorLength + ]; + + xfer.accept_with(descr).ok(); + return; + } else if dtype == 0x22 { + // Report descriptor + xfer.accept_with(REPORT_DESCR).ok(); + return; + } + } + _ => { + return; + } + }; + } + + if !(req.request_type == control::RequestType::Class + && req.recipient == control::Recipient::Interface + && req.index == u8::from(self.report_if) as u16) + { + return; + } + + match req.request { + REQ_GET_REPORT => { + // USB host requests for report + // I'm not sure what should we do here, so just send empty report + xfer.accept_with(&report(super::PanelState { + throttle0: 0, + throttle1: 0, + sw: 0, + })) + .ok(); + } + _ => { + xfer.reject().ok(); + } + } + } + + fn control_out(&mut self, xfer: ControlOut<B>) { + let req = xfer.request(); + + if !(req.request_type == control::RequestType::Class + && req.recipient == control::Recipient::Interface + && req.index == u8::from(self.report_if) as u16) + { + return; + } + + xfer.reject().ok(); + } + } +} + +use hid::HIDClass; + +const PERIOD: u32 = 80_000; + +#[rtic::app(device = stm32f1xx_hal::stm32, peripherals = true, monotonic = rtic::cyccnt::CYCCNT)] +const APP: () = { + struct Resources { + usb_dev: UsbDevice<'static, UsbBusType>, + hid: HIDClass<'static, UsbBusType>, + adc1: adc::Adc<ADC1>, + th0: gpio::gpioa::PA1<gpio::Analog>, + th1: gpio::gpioa::PA2<gpio::Analog>, + sw0: gpio::gpioa::PA8<gpio::Input<gpio::PullUp>>, + sw1: gpio::gpioa::PA9<gpio::Input<gpio::PullUp>>, + sw2: gpio::gpioa::PA10<gpio::Input<gpio::PullUp>>, + sw3: gpio::gpiob::PB14<gpio::Input<gpio::PullUp>>, + sw4: gpio::gpiob::PB15<gpio::Input<gpio::PullUp>>, + } + + #[init(schedule = [on_tick])] + fn init(mut cx: init::Context) -> init::LateResources { + static mut USB_BUS: Option<bus::UsbBusAllocator<UsbBusType>> = None; + + cx.core.DCB.enable_trace(); + DWT::unlock(); + cx.core.DWT.enable_cycle_counter(); + + let mut flash = cx.device.FLASH.constrain(); + let mut rcc = cx.device.RCC.constrain(); + + let clocks = rcc + .cfgr + .use_hse(8.mhz()) + .sysclk(48.mhz()) + .pclk1(24.mhz()) + .adcclk(2.mhz()) + .freeze(&mut flash.acr); + + assert!(clocks.usbclk_valid()); + + let mut gpioa = cx.device.GPIOA.split(&mut rcc.apb2); + let mut gpiob = cx.device.GPIOB.split(&mut rcc.apb2); + + // BluePill board has a pull-up resistor on the D+ line. + // Pull the D+ pin down to send a RESET condition to the USB bus. + let mut usb_dp = gpioa.pa12.into_push_pull_output(&mut gpioa.crh); + usb_dp.set_low().ok(); + delay(clocks.sysclk().0 / 100); + + let usb_dm = gpioa.pa11; + let usb_dp = usb_dp.into_floating_input(&mut gpioa.crh); + + let usb = Peripheral { + usb: cx.device.USB, + pin_dm: usb_dm, + pin_dp: usb_dp, + }; + + *USB_BUS = Some(UsbBus::new(usb)); + + let hid = HIDClass::new(USB_BUS.as_ref().unwrap()); + let adc1 = adc::Adc::adc1(cx.device.ADC1, &mut rcc.apb2, clocks); + let th0 = gpioa.pa1.into_analog(&mut gpioa.crl); + let th1 = gpioa.pa2.into_analog(&mut gpioa.crl); + let sw0 = gpioa.pa8.into_pull_up_input(&mut gpioa.crh); + let sw1 = gpioa.pa9.into_pull_up_input(&mut gpioa.crh); + let sw2 = gpioa.pa10.into_pull_up_input(&mut gpioa.crh); + let sw3 = gpiob.pb14.into_pull_up_input(&mut gpiob.crh); + let sw4 = gpiob.pb15.into_pull_up_input(&mut gpiob.crh); + + let usb_dev = UsbDeviceBuilder::new(USB_BUS.as_ref().unwrap(), UsbVidPid(0x0483, 0x002a)) + .manufacturer("Ted's") + .product("Cessna Knobox") + .serial_number("001") + .device_class(0) + .build(); + + cx.schedule.on_tick(cx.start + PERIOD.cycles()).ok(); + + init::LateResources { + usb_dev, + hid, + adc1, + th0, + th1, + sw0, + sw1, + sw2, + sw3, + sw4, + } + } + + #[task(schedule = [on_tick], resources = [hid, adc1, th0, th1, sw0, sw1, sw2, sw3, sw4])] + fn on_tick(mut cx: on_tick::Context) { + cx.schedule.on_tick(Instant::now() + PERIOD.cycles()).ok(); + + let hid = &mut cx.resources.hid; + let adc1 = &mut cx.resources.adc1; + let raw0: u32 = adc1.read(cx.resources.th0).unwrap(); + let raw1: u32 = adc1.read(cx.resources.th1).unwrap(); + let throttle0 = (raw0 * 32767 / 4095) as u16; + let throttle1 = (raw1 * 32767 / 4095) as u16; + hid.write(&hid::report(PanelState { + throttle0, + throttle1, + sw: if cx.resources.sw0.is_low().unwrap() { + 1 + } else { + 0 + } | (if cx.resources.sw1.is_low().unwrap() { + 1 + } else { + 0 + }) << 1 + | (if cx.resources.sw2.is_low().unwrap() { + 1 + } else { + 0 + }) << 2 + | (if cx.resources.sw3.is_low().unwrap() { + 1 + } else { + 0 + }) << 3 + | (if cx.resources.sw4.is_low().unwrap() { + 1 + } else { + 0 + }) << 4, + })); + } + + #[task(binds=USB_HP_CAN_TX, resources = [usb_dev, hid])] + fn usb_tx(mut cx: usb_tx::Context) { + usb_poll(&mut cx.resources.usb_dev, &mut cx.resources.hid); + } + + #[task(binds=USB_LP_CAN_RX0, resources = [usb_dev, hid])] + fn usb_rx(mut cx: usb_rx::Context) { + usb_poll(&mut cx.resources.usb_dev, &mut cx.resources.hid); + } + + extern "C" { + fn EXTI0(); + } +}; + +fn usb_poll<B: bus::UsbBus>(usb_dev: &mut UsbDevice<'static, B>, hid: &mut HIDClass<'static, B>) { + if !usb_dev.poll(&mut [hid]) { + return; + } +} diff --git a/src/build.rs b/src/build.rs new file mode 100644 index 0000000..98f603e --- /dev/null +++ b/src/build.rs @@ -0,0 +1,18 @@ +use std::env; +use std::fs::File; +use std::io::Write; +use std::path::PathBuf; + +fn main() { + // Put the linker script somewhere the linker can find it + let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap()); + File::create(out.join("memory.x")) + .unwrap() + .write_all(include_bytes!("memory.x")) + .unwrap(); + println!("cargo:rustc-link-search={}", out.display()); + + // Only re-run the build script when memory.x is changed, + // instead of when any part of the source code changes. + println!("cargo:rerun-if-changed=memory.x"); +} |