diff --git a/Cargo.toml b/Cargo.toml index 3f3b111d57a7fbd0464c49505a01cdab059178eb..e9e9cc8aa7afc31ed89b859c624aa7bc9ab20101 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,6 +11,7 @@ cortex-m-rt = "0.6.13" cortex-m-semihosting = "0.3.7" cortex-m-rtic = "0.5.5" embedded-hal = "0.2.4" +usb-device = "0.2.7" # Panic handlers, comment all but one to generate doc! panic-halt = "0.2.0" @@ -37,7 +38,7 @@ features = ["stm32f411", "rt"] [dependencies.stm32f4xx-hal] version = "0.8.3" -features = ["rt", "stm32f411"] +features = ["rt", "stm32f411", "usb_fs"] # this lets you use `cargo fix`! [[bin]] diff --git a/README.md b/README.md index 98e13368783a5f46f38773e6cdfccfc921a1cbce..884c8e68852cff04998f742b762fd6e1b27681d4 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,8 @@ - [RM0383 - F411 Reference Manual](https://www.st.com/resource/zh/reference_manual/dm00119316-stm32f411xce-advanced-armbased-32bit-mcus-stmicroelectronics.pdf) and [RM0368 - F401 Reference Manual](https://www.st.com/resource/en/reference_manual/dm00096844-stm32f401xbc-and-stm32f401xde-advanced-armbased-32bit-mcus-stmicroelectronics.pdf) +- [Nucleo 64 Schematics](https://www.st.com/resource/en/schematic_pack/nucleo_64pins_sch.zip) (The file MB1136.pdf is the schematics in pdf.) + - [PM0214 - M4 Programming manual](https://www.google.com/url?sa=t&rct=j&q=&esrc=s&source=web&cd=&ved=2ahUKEwjOtd645OTtAhXEHXcKHdwYCoQQFjAAegQIBhAC&url=https%3A%2F%2Fwww.st.com%2Fresource%2Fen%2Fprogramming_manual%2Fdm00046982-stm32-cortex-m4-mcus-and-mpus-programming-manual-stmicroelectronics.pdf&usg=AOvVaw0n3XXybtMMDbifhDZse1Pl) - [PMW3389DM-T3QU](https://www.google.com/url?sa=t&rct=j&q=&esrc=s&source=web&cd=&ved=2ahUKEwicx5OA9eTtAhWC-yoKHVfKAJ0QFjAAegQIBhAC&url=https%3A%2F%2Fwww.pixart.com%2F_getfs.php%3Ftb%3Dproduct%26id%3D4%26fs%3Dck2_fs_cn&usg=AOvVaw1A1rR533Pt-7EgnVSS-_ch), optical navigation chip @@ -14,6 +16,17 @@ - [Introduction to SPI](https://www.analog.com/en/analog-dialogue/articles/introduction-to-spi-interface.html#), a short introduction to the SPI interface. +- [USB Cable] + +| Signl | Color | Pin | +| ----- | ----- | ---- | +| V+ | Red | ---- | +| D- | White | PA11 | +| D+ | Green | PA12 | +| Gnd | Black | ---- | + +D+ used for re-enumeration + ## Debug interface - Serial Wire debugging uses pins PA13 and PA14. diff --git a/examples/usb-mouse.rs b/examples/usb-mouse.rs new file mode 100644 index 0000000000000000000000000000000000000000..8a241931db4baadb6526d3e6fa792b170bf05109 --- /dev/null +++ b/examples/usb-mouse.rs @@ -0,0 +1,343 @@ +#![no_std] +#![no_main] + +// extern crate panic_semihosting; +// use panic_rtt_target as _; +use panic_halt as _; + +use cortex_m::{asm::delay, peripheral::DWT}; +use embedded_hal::digital::v2::OutputPin; +use rtic::cyccnt::{Instant, U32Ext as _}; +// use stm32f4xx_hal::usb::{Peripheral, UsbBus, UsbBusType}; +use stm32f4xx_hal::{ + gpio, + otg_fs::{UsbBus, UsbBusType, USB}, + prelude::*, +}; +use usb_device::bus; +use usb_device::prelude::*; + +#[allow(unused)] +pub mod hid { + use usb_device::class_prelude::*; + use usb_device::Result; + + pub 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; + + // https://docs.microsoft.com/en-us/windows-hardware/design/component-guidelines/mouse-collection-report-descriptor + const REPORT_DESCR: &[u8] = &[ + 0x05, 0x01, // USAGE_PAGE (Generic Desktop) + 0x09, 0x02, // USAGE (Mouse) + 0xa1, 0x01, // COLLECTION (Application) + 0x09, 0x01, // USAGE (Pointer) + 0xa1, 0x00, // COLLECTION (Physical) + 0x05, 0x09, // USAGE_PAGE (Button) + 0x19, 0x01, // USAGE_MINIMUM (Button 1) + 0x29, 0x03, // USAGE_MAXIMUM (Button 3) + 0x15, 0x00, // LOGICAL_MINIMUM (0) + 0x25, 0x01, // LOGICAL_MAXIMUM (1) + 0x95, 0x03, // REPORT_COUNT (3) + 0x75, 0x01, // REPORT_SIZE (1) + 0x81, 0x02, // INPUT (Data,Var,Abs) + 0x95, 0x01, // REPORT_COUNT (1) + 0x75, 0x05, // REPORT_SIZE (5) + 0x81, 0x03, // INPUT (Cnst,Var,Abs) + 0x05, 0x01, // USAGE_PAGE (Generic Desktop) + 0x09, 0x30, // USAGE (X) + 0x09, 0x31, // USAGE (Y) + 0x15, 0x81, // LOGICAL_MINIMUM (-127) + 0x25, 0x7f, // LOGICAL_MAXIMUM (127) + 0x75, 0x08, // REPORT_SIZE (8) + 0x95, 0x02, // REPORT_COUNT (2) + 0x81, 0x06, // INPUT (Data,Var,Rel) + 0xc0, // END_COLLECTION + 0xc0, // END_COLLECTION + ]; + + pub fn report(x: i8, y: i8) -> [u8; 3] { + [ + 0x00, // button: none + x as u8, // x-axis + y as u8, // y-axis + ] + } + + 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(0, 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; + +type LED = gpio::gpioa::PA5<gpio::Output<gpio::PushPull>>; + +const PERIOD: u32 = 8_000_000; + +#[rtic::app(device = stm32f4xx_hal::stm32, peripherals = true, monotonic = rtic::cyccnt::CYCCNT)] +const APP: () = { + struct Resources { + counter: u8, + led: LED, + + usb_dev: UsbDevice<'static, UsbBusType>, + hid: HIDClass<'static, UsbBusType>, + } + + #[init(schedule = [on_tick])] + fn init(mut cx: init::Context) -> init::LateResources { + static mut USB_BUS: Option<bus::UsbBusAllocator<UsbBusType>> = None; + static mut EP_MEMORY: [u32; 1024] = [0; 1024]; + cx.core.DCB.enable_trace(); + DWT::unlock(); + cx.core.DWT.enable_cycle_counter(); + + let rcc = cx.device.RCC.constrain(); + + let clocks = rcc + .cfgr + // .use_hse(8.mhz()) + .sysclk(48.mhz()) + .pclk1(24.mhz()) + .freeze(); + + // assert!(clocks.usbclk_valid()); + + let gpioa = cx.device.GPIOA.split(); + let led = gpioa.pa5.into_push_pull_output(); + + // Pull the D+ pin down to send a RESET condition to the USB bus. + let mut usb_dp = gpioa.pa12.into_push_pull_output(); + usb_dp.set_low().ok(); + delay(clocks.sysclk().0 / 100); + let usb_dp = usb_dp.into_floating_input(); + + let usb_dm = gpioa.pa11; + + let usb = USB { + usb_global: cx.device.OTG_FS_GLOBAL, + usb_device: cx.device.OTG_FS_DEVICE, + usb_pwrclk: cx.device.OTG_FS_PWRCLK, + pin_dm: usb_dm.into_alternate_af10(), + pin_dp: usb_dp.into_alternate_af10(), + }; + + *USB_BUS = Some(UsbBus::new(usb, EP_MEMORY)); + + // let usb_bus = UsbBus::new(usb, unsafe { &mut EP_MEMORY }); + + let hid = HIDClass::new(USB_BUS.as_ref().unwrap()); + + let usb_dev = UsbDeviceBuilder::new(USB_BUS.as_ref().unwrap(), UsbVidPid(0xc410, 0x0000)) + .manufacturer("Fake company") + .product("mouse") + .serial_number("TEST") + .device_class(0) + .build(); + + cx.schedule.on_tick(cx.start + PERIOD.cycles()).ok(); + + init::LateResources { + counter: 0, + led, + + usb_dev, + hid, + } + } + + #[task(schedule = [on_tick], resources = [counter, led, hid])] + fn on_tick(mut cx: on_tick::Context) { + cx.schedule.on_tick(Instant::now() + PERIOD.cycles()).ok(); + + let counter: &mut u8 = &mut cx.resources.counter; + let led = &mut cx.resources.led; + let hid = &mut cx.resources.hid; + + const P: u8 = 2; + *counter = (*counter + 1) % P; + + // move mouse cursor horizontally (x-axis) while blinking LED + if *counter < P / 2 { + led.set_high().ok(); + hid.write(&hid::report(10, 0)); + } else { + led.set_low().ok(); + hid.write(&hid::report(-10, 0)); + } + } + + // #[task(binds=USB_HP_CAN_TX, resources = [counter, led, usb_dev, hid])] + // fn usb_tx(mut cx: usb_tx::Context) { + // usb_poll( + // &mut cx.resources.counter, + // &mut cx.resources.led, + // &mut cx.resources.usb_dev, + // &mut cx.resources.hid, + // ); + // } + + // #[task(binds=USB_LP_CAN_RX0, resources = [counter, led, usb_dev, hid])] + // fn usb_rx(mut cx: usb_rx::Context) { + // usb_poll( + // &mut cx.resources.counter, + // &mut cx.resources.led, + // &mut cx.resources.usb_dev, + // &mut cx.resources.hid, + // ); + // } + + #[task(binds=OTG_FS, resources = [counter, led, usb_dev, hid])] + fn usb_rx(mut cx: usb_rx::Context) { + usb_poll( + &mut cx.resources.counter, + &mut cx.resources.led, + &mut cx.resources.usb_dev, + &mut cx.resources.hid, + ); + } + + extern "C" { + fn EXTI0(); + } +}; + +fn usb_poll<B: bus::UsbBus>( + _counter: &mut u8, + _led: &mut LED, + usb_dev: &mut UsbDevice<'static, B>, + hid: &mut HIDClass<'static, B>, +) { + if !usb_dev.poll(&mut [hid]) { + return; + } +}