aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.cargo/config33
-rw-r--r--.gitignore5
-rw-r--r--Cargo.toml36
-rw-r--r--memory.x5
-rw-r--r--openocd.cfg2
-rw-r--r--openocd.gdb40
-rw-r--r--src/app.rs362
-rw-r--r--src/build.rs18
8 files changed, 501 insertions, 0 deletions
diff --git a/.cargo/config b/.cargo/config
new file mode 100644
index 0000000..fa909f5
--- /dev/null
+++ b/.cargo/config
@@ -0,0 +1,33 @@
+[target.thumbv7m-none-eabi]
+# uncomment this to make `cargo run` execute programs on QEMU
+# runner = "qemu-system-arm -cpu cortex-m3 -machine lm3s6965evb -nographic -semihosting-config enable=on,target=native -kernel"
+
+[target.'cfg(all(target_arch = "arm", target_os = "none"))']
+# uncomment ONE of these three option to make `cargo run` start a GDB session
+# which option to pick depends on your system
+# runner = "arm-none-eabi-gdb -q -x openocd.gdb"
+# runner = "gdb-multiarch -q -x openocd.gdb"
+# runner = "gdb -q -x openocd.gdb"
+
+rustflags = [
+ # LLD (shipped with the Rust toolchain) is used as the default linker
+ "-C", "link-arg=-Tlink.x",
+
+ # if you run into problems with LLD switch to the GNU linker by commenting out
+ # this line
+ # "-C", "linker=arm-none-eabi-ld",
+
+ # if you need to link to pre-compiled C libraries provided by a C toolchain
+ # use GCC as the linker by commenting out both lines above and then
+ # uncommenting the three lines below
+ # "-C", "linker=arm-none-eabi-gcc",
+ # "-C", "link-arg=-Wl,-Tlink.x",
+ # "-C", "link-arg=-nostartfiles",
+]
+
+[build]
+# Pick ONE of these compilation targets
+# target = "thumbv6m-none-eabi" # Cortex-M0 and Cortex-M0+
+target = "thumbv7m-none-eabi" # Cortex-M3
+# target = "thumbv7em-none-eabi" # Cortex-M4 and Cortex-M7 (no FPU)
+# target = "thumbv7em-none-eabihf" # Cortex-M4F and Cortex-M7F (with FPU)
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..59a4524
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,5 @@
+**/*.rs.bk
+.#*
+.gdb_history
+Cargo.lock
+target/
diff --git a/Cargo.toml b/Cargo.toml
new file mode 100644
index 0000000..17b3be0
--- /dev/null
+++ b/Cargo.toml
@@ -0,0 +1,36 @@
+[package]
+authors = ["Determinant <[email protected]>"]
+edition = "2018"
+readme = "README.md"
+name = "cessna-knobox"
+version = "0.1.0"
+
+[dependencies]
+cortex-m = "0.6"
+cortex-m-rt = "0.6"
+cortex-m-semihosting = "0.3"
+cortex-m-rtic = "0.5"
+panic-halt = "0.2.0"
+panic-semihosting = "0.5.3"
+nb = "0.1.2"
+embedded-hal = "0.2.3"
+heapless = "0.5.1"
+rand_core = "0.4"
+usb-device = "0.2.4"
+
+[dependencies.stm32f1]
+version = "0.8.0"
+features = ["stm32f103", "rt"]
+
+[dependencies.stm32f1xx-hal]
+version = "0.6.1"
+features = ["stm32f103", "rt", "stm32-usbd"]
+
+[[bin]]
+name ="knobox"
+path = "src/app.rs"
+
+[profile.release]
+codegen-units = 1 # better optimizations
+debug = true # symbols are nice and they don't increase the size on Flash
+lto = true # better optimizations
diff --git a/memory.x b/memory.x
new file mode 100644
index 0000000..e20ff2b
--- /dev/null
+++ b/memory.x
@@ -0,0 +1,5 @@
+MEMORY
+{
+ FLASH : ORIGIN = 0x08000000, LENGTH = 128K
+ RAM : ORIGIN = 0x20000000, LENGTH = 20K
+}
diff --git a/openocd.cfg b/openocd.cfg
new file mode 100644
index 0000000..b00e2bf
--- /dev/null
+++ b/openocd.cfg
@@ -0,0 +1,2 @@
+source [find interface/stlink-v2.cfg]
+source [find target/stm32f1x.cfg]
diff --git a/openocd.gdb b/openocd.gdb
new file mode 100644
index 0000000..7795319
--- /dev/null
+++ b/openocd.gdb
@@ -0,0 +1,40 @@
+target extended-remote :3333
+
+# print demangled symbols
+set print asm-demangle on
+
+# set backtrace limit to not have infinite backtrace loops
+set backtrace limit 32
+
+# detect unhandled exceptions, hard faults and panics
+break DefaultHandler
+break HardFault
+break rust_begin_unwind
+# # run the next few lines so the panic message is printed immediately
+# # the number needs to be adjusted for your panic handler
+# commands $bpnum
+# next 4
+# end
+
+# *try* to stop at the user entry point (it might be gone due to inlining)
+break main
+
+monitor arm semihosting enable
+
+# # send captured ITM to the file itm.fifo
+# # (the microcontroller SWO pin must be connected to the programmer SWO pin)
+# # 8000000 must match the core clock frequency
+# monitor tpiu config internal itm.txt uart off 8000000
+
+# # OR: make the microcontroller SWO pin output compatible with UART (8N1)
+# # 8000000 must match the core clock frequency
+# # 2000000 is the frequency of the SWO pin
+# monitor tpiu config external uart off 8000000 2000000
+
+# # enable ITM port 0
+# monitor itm port 0 on
+
+load
+
+# start the process but immediately halt the processor
+stepi
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");
+}