Merge branch 'main' of https://git.cmtec.se/cm/cmdr-joystick
This commit is contained in:
commit
abc5b70e00
1
.gitignore
vendored
1
.gitignore
vendored
@ -2,3 +2,4 @@ firmware/.cache/clangd/index
|
|||||||
firmware/compile_commands.json
|
firmware/compile_commands.json
|
||||||
rp2040/target
|
rp2040/target
|
||||||
rp2040/Cargo.lock
|
rp2040/Cargo.lock
|
||||||
|
firmware/.pio/build
|
||||||
|
|||||||
182
README.md
182
README.md
@ -1,79 +1,73 @@
|
|||||||
# CMDR Joystick 24
|
# CMDR Joystick 25
|
||||||
|
|
||||||
RC Joystick with 2 hall effect gimbals, 2 hat switches and 24 buttons for use both with simulators and ELRS Rx equipped quads.
|
RC(ELRS)/USB Joystick with 2 hall effect gimbals, 2 hat switches and 25 buttons for use both with PC simulators and ELRS Rx equipped quads.
|
||||||
|
|
||||||
## Layout
|
## Layout
|
||||||
|
|
||||||
```cpp
|
```cpp
|
||||||
USB Joystick Layer 0
|
Button index map:
|
||||||
--------------------------------------------------------------
|
---------------------------------------------------------------
|
||||||
| FnL | B1 | | B5 | FnR |
|
| 0 L| 1 U| | 2 | | 3 L| 4 U|
|
||||||
--------------------------------------------------------------
|
---------------------------------------------------------------
|
||||||
| | B2 | B3 | MoL | | MoR | B7 | B6 | |
|
| | 5 | 6 | 7 | | 12 | 11 | 10 | |
|
||||||
| |
|
| |
|
||||||
| | B4 | | B8 | |
|
| | 8 | | 13 | |
|
||||||
| | B17 | | B18 | |
|
| | 9 | | 14 | |
|
||||||
| Z/RZ X/Y |
|
| X1/Y1 X2/Y2 |
|
||||||
|
| | 16 | | 21 | |
|
||||||
|
| | 19 | 15 | 17 | | 24 | 20 | 22 | |
|
||||||
|
| | 18 | | 23 | |
|
||||||
|
---------------------------------------------------------------
|
||||||
|
|
||||||
|
USB HID joystick map:
|
||||||
|
---------------------------------------------------------------
|
||||||
|
| B1 L| B2 U| | B3 | | B4 L| B5 U|
|
||||||
|
---------------------------------------------------------------
|
||||||
|
| | B6 | B7 | B8/16| |B13/17| B12 | B11 | |
|
||||||
|
| |
|
||||||
|
| | B9 | | B14 | |
|
||||||
|
| | B10 | | B15 | |
|
||||||
|
| X1/Y1 X2/Y2 |
|
||||||
| | H1U | | H2U | |
|
| | H1U | | H2U | |
|
||||||
| | H1L | H1B | H1R || H2L | H2B | H2R | |
|
| | H1L | B18 | H1R | | H2L | B19 | H2R | |
|
||||||
| | H1D | | H2D | |
|
| | H1D | | H2D | |
|
||||||
--------------------------------------------------------------
|
---------------------------------------------------------------
|
||||||
|
Button (Switch) 7 changes following:
|
||||||
|
* hat1 => hat3 (button press B20).
|
||||||
|
Button (switch) 12 changes following:
|
||||||
|
* B4 => B21
|
||||||
|
* B5 => B22
|
||||||
|
* B14 => B23
|
||||||
|
* B15 => B24
|
||||||
|
* hat2 => hat4 (button bpress B25)
|
||||||
|
|
||||||
USB Joystick Layer 1 (FnL)
|
ELRS channel map (+ = ON, - = OFF, CHxP/M/Z = trim)
|
||||||
--------------------------------------------------------------
|
---------------------------------------------------------------
|
||||||
| FnL | B9 | | B5 | FnR |
|
|CH7-L|CH7+U| | - | |CH8-L|CH8+U|
|
||||||
--------------------------------------------------------------
|
---------------------------------------------------------------
|
||||||
| | B10 | B11 | MoL | | MoR | B7 | B6 | |
|
| | THL | CH9 | CH5 | | CH6 |CH11-|CH11+| |
|
||||||
| |
|
| |
|
||||||
| | B12 | | B8 | |
|
| |CH10+| |CH12+| |
|
||||||
| | B19 | | B18 | |
|
| |CH10-| |CH12-| |
|
||||||
| Z/RZ X/Y |
|
| CH1/CH2 CH3/CH4 |
|
||||||
| | H3U | | H2U | |
|
| | - | |CH4P | |
|
||||||
| | H3L | H3B | H3R || H2L | H2B | H2R | |
|
| |CH1M |CH12Z|CH1P | |CH3M |CH34Z|CH3P | |
|
||||||
| | H3D | | H2D | |
|
| | - | |CH4M | |
|
||||||
--------------------------------------------------------------
|
---------------------------------------------------------------
|
||||||
|
|
||||||
USB Joystick Layer 2 (FnR)
|
Config Layer (holding CONFIG button)
|
||||||
--------------------------------------------------------------
|
---------------------------------------------------------------
|
||||||
| FnL | B1 | | B13 | FnR |
|
|BOOT L| CAL U| | CONFIG | | USB L|ELRS U|
|
||||||
--------------------------------------------------------------
|
---------------------------------------------------------------
|
||||||
| | B2 | B3 | MoL | | MoR | B15 | B14 | |
|
| | THL-| THL+| - | | - | - | - | |
|
||||||
| |
|
|
||||||
| | B4 | | B16 | |
|
|
||||||
| | B17 | | B20 | |
|
|
||||||
| Z/RZ X(RX)/Y(RY) |
|
|
||||||
| | H1U | | H4U | |
|
|
||||||
| | H1L | H1B | H1R || H4L | H4B | H4R | |
|
|
||||||
| | H1D | | H4D | |
|
|
||||||
--------------------------------------------------------------
|
|
||||||
|
|
||||||
USB Joystick Layer 3 (FnL + FnR)
|
|
||||||
--------------------------------------------------------------
|
|
||||||
| FnL | B9 | | B13 | FnR |
|
|
||||||
--------------------------------------------------------------
|
|
||||||
| | B10 | B11 | MoL | | MoR | B15 | B14 | |
|
|
||||||
| |
|
|
||||||
| | B12 | | B16 | |
|
|
||||||
| | B19 | | B20 | |
|
|
||||||
| Z/RZ X(RX)/Y(RY) |
|
|
||||||
| | H3U | | H4U | |
|
|
||||||
| | H3L | H3B | H3R || H4L | H4B | H4R | |
|
|
||||||
| | H3D | | H4D | |
|
|
||||||
--------------------------------------------------------------
|
|
||||||
|
|
||||||
ELRS Layer
|
|
||||||
--------------------------------------------------------------
|
|
||||||
| CH7 | CH8 | | CH9 | CH10|
|
|
||||||
--------------------------------------------------------------
|
|
||||||
| | CH11| - | CH5 | | CH6 | - | CH12| |
|
|
||||||
| |
|
| |
|
||||||
| | - | | - | |
|
| | - | | - | |
|
||||||
| | - | | - | |
|
| | - | | - | |
|
||||||
| X(CH1)/Y(CH2) X(CH3)/Y(CH4) |
|
| -/- -/- |
|
||||||
| | - | | - | |
|
| | - | | - | |
|
||||||
| | - | - | - || - | - | - | |
|
| | - | - | - | | - | - | - | |
|
||||||
| | - | | - | |
|
| | - | | - | |
|
||||||
--------------------------------------------------------------
|
---------------------------------------------------------------
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -81,31 +75,61 @@ ELRS Layer
|
|||||||
|
|
||||||
- Ergonomic design (low profile)
|
- Ergonomic design (low profile)
|
||||||
- Hall effect gimbals
|
- Hall effect gimbals
|
||||||
- Supports both USB HID joystick and ELRS Tx module
|
- Supports both USB HID joystick and ELRS Tx
|
||||||
- Total 6x axis, 4x hat switches and 24x buttons (using Fn mode) implemented in USB HID mode
|
- Total 6x axis, 4x hat switches and 25x buttons (using Fn mode) implemented in USB HID mode
|
||||||
- 12 Channels implemented in ELRS mode (4x axis, 8x buttons)
|
- 12 Channels implemented in ELRS mode (4x axis, 8x buttons)
|
||||||
- Low latency (1.6ms ELRS, 10ms USB)
|
|
||||||
|
|
||||||
## Build environment
|
|
||||||
|
|
||||||
- Cargo (rust embedded)
|
|
||||||
- Flashing via Cargo
|
|
||||||
- Pressing boot button on teensy
|
|
||||||
- Press and hold "top lower right button" when powering the unit
|
|
||||||
|
|
||||||
## Hardware
|
## Hardware
|
||||||
|
|
||||||
- 1x rp2040zero MCU board
|
- 2x FrSky M7 or M10 gimbals [M7 datasheet](https://www.frsky-rc.com/product/m7/)
|
||||||
- 2x FrSky M7 or M10 gimbals
|
- 6x Kailh choc low profile switches [Brown](http://www.kailh.com/en/Products/Ks/CS/)
|
||||||
- 6x Kailh choc low profile switches
|
- 6x Cherry MX switches [Brown](https://www.cherrymx.de/en/cherry-mx/mx-original/mx-brown.html)
|
||||||
- 6x Cherry MX switches
|
- 2x Miniature Toggle Switch (M6 shaft, 7-8mm wide body. Ex Apem 5636) [Apem 5000 series](https://www.farnell.com/datasheets/2626614.pdf?_ga=2.22934718.461231604.1570510103-1672862477.1542183430)
|
||||||
- 2x Miniature Toggle Switch (M6 shaft, 7mm wide body)
|
- 2x Alpine RKJXM1015004 hat switches [pdf](https://www.mouser.se/datasheet/2/15/RKJXM-1662398.pdf)
|
||||||
- 2x Alpine RKJXM1015004 hat switches
|
|
||||||
- 1x PCB (prototype board)
|
|
||||||
- 1x Bottom case (3D printed)
|
- 1x Bottom case (3D printed)
|
||||||
- 1x Top plate (3D printed)
|
- 1x Top plate (3D printed)
|
||||||
- 2x Hat swith top (3D printed)
|
- 2x Hat swith top (3D printed) [stl](/mCAD/Hat_Castle_Short_scale_99_99_130.stl)
|
||||||
|
- 1x Custom PCB (CMDR Joystick 25 rev A)
|
||||||
|
- 
|
||||||
|
- 
|
||||||
|
- Gerber files: [zip](/eCAD/cmdr-joystick/cmdr-joystick_rev_a_gerber.zip)
|
||||||
|
- Schematics: [pdf](/eCAD/cmdr-joystick/cmdr-joystick_rev_a_schematics.pdf)
|
||||||
|
- rp2040zero pinout: [jpg](https://www.waveshare.com/w/upload/2/2b/RP2040-Zero-details-7.jpg)
|
||||||
|
- rp2040zero schematic: [pdf](https://www.waveshare.com/w/upload/4/4c/RP2040_Zero.pdf)
|
||||||
|
- eeprom 24C32LV: [pdf](https://www.mouser.se/datasheet/2/308/1/NV24C32LV_D-2319484.pdf)
|
||||||
|
- P-Fet si2372eds: [pdf](https://www.vishay.com/docs/63924/si2371eds.pdf)
|
||||||
|
- N-Fet 2N7002: [pdf](https://www.mouser.se/datasheet/2/408/T2N7002AK_datasheet_en_20150401-1916411.pdf)
|
||||||
|
- Small signal diod 1N4148W: [pdf](https://www.diodes.com/assets/Datasheets/BAV16W_1N4148W.pdf)
|
||||||
|
|
||||||
|
- 1x ELRS TX (using a EP1 TCXO Dual receiver reprogrammed as a tramsmitter)
|
||||||
|
- [Link to EP1](https://www.happymodel.cn/index.php/2022/11/07/2-4g-elrs-ep1-ep2-ep1dual-tcxo-receiver/)
|
||||||
|
- Reprogramming instructions (using ExpressLRS Configurator):
|
||||||
|
1. Select latest release
|
||||||
|
2. Device category = Generic target used as base 2.4Ghz
|
||||||
|
3. Device = Generic ESP32 2.4GHz Gemini TX
|
||||||
|
4. Flash to device
|
||||||
|
5. Use following custom settings in 10.0.0.1/hardware.html:
|
||||||
|
```
|
||||||
|
{"customised":"true","serial_rx":3,"serial_tx":1,"radio_busy":36,"radio_dio1":37,"radio_miso":33,"radio_mosi":32,"radio_nss":27,"radio_rst":26,"radio_sck":25,"radio_busy_2":39,"radio_dio1_2":34,"radio_nss_2":13,"power_rxen":10,"power_txen":14,"power_rxen_2":9,"power_txen_2":15,"power_min":0,"power_high":2,"power_max":2,"power_default":0,"power_control":0,"power_values":[-10,-6,-3],"button":0,"led_rgb":22,"led_rgb_isgrb":true,"screen_type":0}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Software Build environment
|
||||||
|
##### Rust
|
||||||
|
|
||||||
|
- Cargo (rust embedded)
|
||||||
|
- Flashing via Cargo
|
||||||
|
- Press and hold boot button on rp2040zero board while perform a reset
|
||||||
|
- Press and hold CONF and press BOOT button.
|
||||||
|
|
||||||
|
## References
|
||||||
|
|
||||||
|
- CRSF protocol description (for communicating with ELRS TX): [Link](https://github.com/ExpressLRS/ExpressLRS/wiki/CRSF-Protocol)
|
||||||
|
- rp2040 datasheet: [pdf](https://datasheets.raspberrypi.com/rp2040/rp2040-datasheet.pdf)
|
||||||
|
|
||||||
## Calibration
|
## Calibration
|
||||||
|
|
||||||
No calibration needed
|
1. Center both gimbals.
|
||||||
|
2. Press and hold CONF button and press CAL botton. Status led will start blinking green.
|
||||||
|
3. Move both gimbals to all corners.
|
||||||
|
4. Press right hat switch to save calibration data to eeprom.
|
||||||
|
|
||||||
|
|||||||
@ -23,6 +23,7 @@ pio = "0.2.0"
|
|||||||
defmt = { version = "0.3", optional = true }
|
defmt = { version = "0.3", optional = true }
|
||||||
libm = "0.2.7"
|
libm = "0.2.7"
|
||||||
dyn-smooth = "0.2.0"
|
dyn-smooth = "0.2.0"
|
||||||
|
eeprom24x = "0.6.0"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
# This is the set of features we enable by default
|
# This is the set of features we enable by default
|
||||||
|
|||||||
@ -62,10 +62,10 @@ impl<'a, const R: usize, const C: usize, const N: usize> ButtonMatrix<'a, R, C,
|
|||||||
pub fn scan_matrix(&mut self, delay: &mut Delay) {
|
pub fn scan_matrix(&mut self, delay: &mut Delay) {
|
||||||
for col_index in 0..self.cols.len() {
|
for col_index in 0..self.cols.len() {
|
||||||
self.cols[col_index].set_low().unwrap();
|
self.cols[col_index].set_low().unwrap();
|
||||||
delay.delay_us(10);
|
delay.delay_us(1);
|
||||||
self.process_column(col_index);
|
self.process_column(col_index);
|
||||||
self.cols[col_index].set_high().unwrap();
|
self.cols[col_index].set_high().unwrap();
|
||||||
delay.delay_us(10);
|
delay.delay_us(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
194
rp2040/src/elrs.rs
Normal file
194
rp2040/src/elrs.rs
Normal file
@ -0,0 +1,194 @@
|
|||||||
|
//! Project: CMtec CMDR joystick 24
|
||||||
|
//! Date: 2023-08-01
|
||||||
|
//! Author: Christoffer Martinsson
|
||||||
|
//! Email: cm@cmtec.se
|
||||||
|
//! License: Please refer to LICENSE in root directory
|
||||||
|
|
||||||
|
use embedded_hal::serial::Read;
|
||||||
|
use rp2040_hal::uart::{Enabled, State, UartDevice, UartPeripheral, ValidUartPinout};
|
||||||
|
|
||||||
|
const CRSF_CRC8TAB: [u8; 256] = [
|
||||||
|
0x00, 0xD5, 0x7F, 0xAA, 0xFE, 0x2B, 0x81, 0x54, 0x29, 0xFC, 0x56, 0x83, 0xD7, 0x02, 0xA8, 0x7D,
|
||||||
|
0x52, 0x87, 0x2D, 0xF8, 0xAC, 0x79, 0xD3, 0x06, 0x7B, 0xAE, 0x04, 0xD1, 0x85, 0x50, 0xFA, 0x2F,
|
||||||
|
0xA4, 0x71, 0xDB, 0x0E, 0x5A, 0x8F, 0x25, 0xF0, 0x8D, 0x58, 0xF2, 0x27, 0x73, 0xA6, 0x0C, 0xD9,
|
||||||
|
0xF6, 0x23, 0x89, 0x5C, 0x08, 0xDD, 0x77, 0xA2, 0xDF, 0x0A, 0xA0, 0x75, 0x21, 0xF4, 0x5E, 0x8B,
|
||||||
|
0x9D, 0x48, 0xE2, 0x37, 0x63, 0xB6, 0x1C, 0xC9, 0xB4, 0x61, 0xCB, 0x1E, 0x4A, 0x9F, 0x35, 0xE0,
|
||||||
|
0xCF, 0x1A, 0xB0, 0x65, 0x31, 0xE4, 0x4E, 0x9B, 0xE6, 0x33, 0x99, 0x4C, 0x18, 0xCD, 0x67, 0xB2,
|
||||||
|
0x39, 0xEC, 0x46, 0x93, 0xC7, 0x12, 0xB8, 0x6D, 0x10, 0xC5, 0x6F, 0xBA, 0xEE, 0x3B, 0x91, 0x44,
|
||||||
|
0x6B, 0xBE, 0x14, 0xC1, 0x95, 0x40, 0xEA, 0x3F, 0x42, 0x97, 0x3D, 0xE8, 0xBC, 0x69, 0xC3, 0x16,
|
||||||
|
0xEF, 0x3A, 0x90, 0x45, 0x11, 0xC4, 0x6E, 0xBB, 0xC6, 0x13, 0xB9, 0x6C, 0x38, 0xED, 0x47, 0x92,
|
||||||
|
0xBD, 0x68, 0xC2, 0x17, 0x43, 0x96, 0x3C, 0xE9, 0x94, 0x41, 0xEB, 0x3E, 0x6A, 0xBF, 0x15, 0xC0,
|
||||||
|
0x4B, 0x9E, 0x34, 0xE1, 0xB5, 0x60, 0xCA, 0x1F, 0x62, 0xB7, 0x1D, 0xC8, 0x9C, 0x49, 0xE3, 0x36,
|
||||||
|
0x19, 0xCC, 0x66, 0xB3, 0xE7, 0x32, 0x98, 0x4D, 0x30, 0xE5, 0x4F, 0x9A, 0xCE, 0x1B, 0xB1, 0x64,
|
||||||
|
0x72, 0xA7, 0x0D, 0xD8, 0x8C, 0x59, 0xF3, 0x26, 0x5B, 0x8E, 0x24, 0xF1, 0xA5, 0x70, 0xDA, 0x0F,
|
||||||
|
0x20, 0xF5, 0x5F, 0x8A, 0xDE, 0x0B, 0xA1, 0x74, 0x09, 0xDC, 0x76, 0xA3, 0xF7, 0x22, 0x88, 0x5D,
|
||||||
|
0xD6, 0x03, 0xA9, 0x7C, 0x28, 0xFD, 0x57, 0x82, 0xFF, 0x2A, 0x80, 0x55, 0x01, 0xD4, 0x7E, 0xAB,
|
||||||
|
0x84, 0x51, 0xFB, 0x2E, 0x7A, 0xAF, 0x05, 0xD0, 0xAD, 0x78, 0xD2, 0x07, 0x53, 0x86, 0x2C, 0xF9,
|
||||||
|
];
|
||||||
|
|
||||||
|
const CONNECTION_TIMEOUT: u16 = 500;
|
||||||
|
const INIT_TIMEOUT: u16 = 1000;
|
||||||
|
|
||||||
|
pub struct Elrs<S: State, D: UartDevice, P>
|
||||||
|
where
|
||||||
|
S: State,
|
||||||
|
D: UartDevice,
|
||||||
|
P: ValidUartPinout<D>,
|
||||||
|
{
|
||||||
|
uart: UartPeripheral<S, D, P>,
|
||||||
|
elsr_init_done: bool,
|
||||||
|
elrs_init_counter: u16,
|
||||||
|
elrs_connected: bool,
|
||||||
|
elrs_connected_timeout: u16,
|
||||||
|
rx_buffer_index: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S, D, P> Elrs<S, D, P>
|
||||||
|
where
|
||||||
|
S: State,
|
||||||
|
D: UartDevice,
|
||||||
|
P: ValidUartPinout<D>,
|
||||||
|
{
|
||||||
|
pub fn new(uart: UartPeripheral<S, D, P>) -> Self {
|
||||||
|
let elsr_init_done = false;
|
||||||
|
let elrs_init_counter = 0;
|
||||||
|
let elrs_connected = false;
|
||||||
|
let elrs_connected_timeout = CONNECTION_TIMEOUT;
|
||||||
|
let rx_buffer_index = 0;
|
||||||
|
Self {
|
||||||
|
uart,
|
||||||
|
elsr_init_done,
|
||||||
|
elrs_init_counter,
|
||||||
|
elrs_connected,
|
||||||
|
elrs_connected_timeout,
|
||||||
|
rx_buffer_index,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<D, P> Elrs<Enabled, D, P>
|
||||||
|
where
|
||||||
|
D: UartDevice,
|
||||||
|
P: ValidUartPinout<D>,
|
||||||
|
{
|
||||||
|
pub fn send(&mut self, data: [u16; 12]) {
|
||||||
|
if self.elsr_init_done {
|
||||||
|
self.uart
|
||||||
|
.write_full_blocking(&self.prepare_crsf_data_packet(data));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if self.elrs_init_counter < INIT_TIMEOUT {
|
||||||
|
self.uart
|
||||||
|
.write_full_blocking(&self.prepare_crsf_data_packet(data));
|
||||||
|
self.elrs_init_counter += 1;
|
||||||
|
} else if self.elrs_init_counter < INIT_TIMEOUT + 5 {
|
||||||
|
self.uart
|
||||||
|
// Setting Packet Rate to 150Hz
|
||||||
|
// 0 = 50Hz LoRa, 1 = 100Hz Full, 2 = 150Hz LoRa, 3 = 250Hz LoRa,
|
||||||
|
// 4 = 333H Full, 5 = 500Hz LoRa, 6 = 250Hz DejaVu, 7 = 500Hz DejaVu,
|
||||||
|
// 8 = 500Hz FLRC, 9 = 1000Hz FLRC
|
||||||
|
.write_full_blocking(&self.prepare_crsf_cmd_packet(0x01, 0x00));
|
||||||
|
self.elrs_init_counter += 1;
|
||||||
|
} else if self.elrs_init_counter < INIT_TIMEOUT + 10 {
|
||||||
|
self.uart
|
||||||
|
// Setting Power to 10mW
|
||||||
|
// 0 = 10mW, 1 = 25mW, 2 = 50mW, 3 = 100mW,
|
||||||
|
// 4 = 200mW, 5 = 500mW, 6 = 1000mW, 7 = 2000mW
|
||||||
|
.write_full_blocking(&self.prepare_crsf_cmd_packet(0x06, 0x00));
|
||||||
|
self.elrs_init_counter += 1;
|
||||||
|
} else {
|
||||||
|
self.elsr_init_done = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.check_link_frame();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn connected(&self) -> bool {
|
||||||
|
self.elrs_connected
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn reset(&mut self) {
|
||||||
|
self.elsr_init_done = false;
|
||||||
|
self.elrs_init_counter = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn check_link_frame(&mut self) {
|
||||||
|
if self.elrs_connected_timeout == 0 {
|
||||||
|
self.elrs_connected = false;
|
||||||
|
} else {
|
||||||
|
self.elrs_connected_timeout -= 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut rx_byte: u8;
|
||||||
|
|
||||||
|
while self.uart.uart_is_readable() {
|
||||||
|
match self.uart.read() {
|
||||||
|
Ok(byte) => {
|
||||||
|
rx_byte = byte;
|
||||||
|
}
|
||||||
|
Err(_) => {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Simple RX telemetry link frame detection: 0xEA, 0xXX, 0x14
|
||||||
|
if (rx_byte == 0xEA && self.rx_buffer_index == 0) || self.rx_buffer_index == 1 {
|
||||||
|
self.rx_buffer_index += 1;
|
||||||
|
} else if rx_byte == 0x14 && self.rx_buffer_index == 2 {
|
||||||
|
self.elrs_connected = true;
|
||||||
|
self.elrs_connected_timeout = CONNECTION_TIMEOUT;
|
||||||
|
self.rx_buffer_index = 0;
|
||||||
|
} else {
|
||||||
|
self.rx_buffer_index = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn prepare_crsf_data_packet(&self, data: [u16; 12]) -> [u8; 26] {
|
||||||
|
let mut packet: [u8; 26] = [0; 26];
|
||||||
|
let mut crc: u8 = 0;
|
||||||
|
packet[0] = 0xEE;
|
||||||
|
packet[1] = 0x18;
|
||||||
|
packet[2] = 0x16;
|
||||||
|
packet[3] = data[0] as u8;
|
||||||
|
packet[4] = ((data[0] >> 8) | (data[1] << 3)) as u8;
|
||||||
|
packet[5] = ((data[1] >> 5) | (data[2] << 6)) as u8;
|
||||||
|
packet[6] = (data[2] >> 2) as u8;
|
||||||
|
packet[7] = ((data[2] >> 10) | (data[3] << 1)) as u8;
|
||||||
|
packet[8] = ((data[3] >> 7) | (data[4] << 4)) as u8;
|
||||||
|
packet[9] = ((data[4] >> 4) | (data[5] << 7)) as u8;
|
||||||
|
packet[10] = (data[5] >> 1) as u8;
|
||||||
|
packet[11] = ((data[5] >> 9) | (data[6] << 2)) as u8;
|
||||||
|
packet[12] = ((data[6] >> 6) | (data[7] << 5)) as u8;
|
||||||
|
packet[13] = (data[7] >> 3) as u8;
|
||||||
|
packet[14] = data[8] as u8;
|
||||||
|
packet[15] = ((data[8] >> 8) | (data[9] << 3)) as u8;
|
||||||
|
packet[16] = ((data[9] >> 5) | (data[10] << 6)) as u8;
|
||||||
|
packet[17] = (data[10] >> 2) as u8;
|
||||||
|
packet[18] = ((data[10] >> 10) | (data[11] << 1)) as u8;
|
||||||
|
packet[19] = (data[11] >> 7) as u8;
|
||||||
|
// Channel 13-16 are not used in ELRS
|
||||||
|
for i in 2..25 {
|
||||||
|
crc = CRSF_CRC8TAB[(crc ^ packet[i]) as usize];
|
||||||
|
}
|
||||||
|
packet[25] = crc;
|
||||||
|
packet
|
||||||
|
}
|
||||||
|
|
||||||
|
fn prepare_crsf_cmd_packet(&self, command: u8, value: u8) -> [u8; 8] {
|
||||||
|
let mut packet: [u8; 8] = [0; 8];
|
||||||
|
let mut crc: u8 = 0;
|
||||||
|
packet[0] = 0xEE;
|
||||||
|
packet[1] = 0x06;
|
||||||
|
packet[2] = 0x2D;
|
||||||
|
packet[3] = 0xEE;
|
||||||
|
packet[4] = 0xEA;
|
||||||
|
packet[5] = command;
|
||||||
|
packet[6] = value;
|
||||||
|
for i in 2..7 {
|
||||||
|
crc = CRSF_CRC8TAB[(crc ^ packet[i]) as usize];
|
||||||
|
}
|
||||||
|
packet[7] = crc;
|
||||||
|
packet
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,261 +0,0 @@
|
|||||||
//! Project: CMtec CMDR joystick 24
|
|
||||||
//! Date: 2023-08-01
|
|
||||||
//! Author: Christoffer Martinsson
|
|
||||||
//! Email: cm@cmtec.se
|
|
||||||
//! License: Please refer to LICENSE in root directory
|
|
||||||
|
|
||||||
use crate::NUMBER_OF_BUTTONS;
|
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
#[derive(Debug, PartialEq, PartialOrd, Copy, Clone)]
|
|
||||||
pub enum ElrsButton {
|
|
||||||
CH1 = 0,
|
|
||||||
CH2 = 1,
|
|
||||||
CH3 = 2,
|
|
||||||
CH4 = 3,
|
|
||||||
CH5 = 4,
|
|
||||||
CH6 = 5,
|
|
||||||
CH7 = 6,
|
|
||||||
CH8 = 7,
|
|
||||||
CH9 = 8,
|
|
||||||
CH10 = 9,
|
|
||||||
CH11 = 10,
|
|
||||||
CH12 = 11,
|
|
||||||
CH5ON = 12,
|
|
||||||
CH5OFF = 13,
|
|
||||||
CH6ON = 14,
|
|
||||||
CH6OFF = 15,
|
|
||||||
CH7ON = 16,
|
|
||||||
CH7OFF = 17,
|
|
||||||
CH8ON = 18,
|
|
||||||
CH8OFF = 19,
|
|
||||||
CH9ON = 20,
|
|
||||||
CH9OFF = 21,
|
|
||||||
CH10ON = 22,
|
|
||||||
CH10OFF = 23,
|
|
||||||
CH11ON = 24,
|
|
||||||
CH11OFF = 25,
|
|
||||||
CH12ON = 26,
|
|
||||||
CH12OFF = 27,
|
|
||||||
NoEventIndicated = 28,
|
|
||||||
}
|
|
||||||
#[derive(Debug, PartialEq, PartialOrd, Copy, Clone)]
|
|
||||||
pub enum HidButton {
|
|
||||||
B1 = 0,
|
|
||||||
B2 = 1,
|
|
||||||
B3 = 2,
|
|
||||||
B4 = 3,
|
|
||||||
B5 = 4,
|
|
||||||
B6 = 5,
|
|
||||||
B7 = 6,
|
|
||||||
B8 = 7,
|
|
||||||
B9 = 8,
|
|
||||||
B10 = 9,
|
|
||||||
B11 = 10,
|
|
||||||
B12 = 11,
|
|
||||||
B13 = 12,
|
|
||||||
B14 = 13,
|
|
||||||
B15 = 14,
|
|
||||||
B16 = 15,
|
|
||||||
B17 = 16,
|
|
||||||
B18 = 17,
|
|
||||||
B19 = 18,
|
|
||||||
B20 = 19,
|
|
||||||
FnL = 20,
|
|
||||||
FnR = 21,
|
|
||||||
ModeL = 22,
|
|
||||||
ModeR = 23,
|
|
||||||
Hat1U = 24,
|
|
||||||
Hat1R = 25,
|
|
||||||
Hat1D = 26,
|
|
||||||
Hat1L = 27,
|
|
||||||
Hat1B = 28,
|
|
||||||
Hat2U = 29,
|
|
||||||
Hat2R = 30,
|
|
||||||
Hat2D = 31,
|
|
||||||
Hat2L = 32,
|
|
||||||
Hat2B = 33,
|
|
||||||
Hat3U = 34,
|
|
||||||
Hat3R = 35,
|
|
||||||
Hat3D = 36,
|
|
||||||
Hat3L = 37,
|
|
||||||
Hat3B = 38,
|
|
||||||
Hat4U = 39,
|
|
||||||
Hat4R = 40,
|
|
||||||
Hat4D = 41,
|
|
||||||
Hat4L = 42,
|
|
||||||
Hat4B = 43,
|
|
||||||
NoEventIndicated = 44,
|
|
||||||
}
|
|
||||||
#[warn(dead_code)]
|
|
||||||
// Button index map:
|
|
||||||
// --------------------------------------------------------------
|
|
||||||
// | 0 | 1 | | 3 | 4 | (2)
|
|
||||||
// --------------------------------------------------------------
|
|
||||||
// | | 5 | 6 | 7 | | 12 | 11 | 10 | |
|
|
||||||
// | |
|
|
||||||
// | | 8 | | 13 | |
|
|
||||||
// | | 9 | | 14 | |
|
|
||||||
// | X1/Y1 X2/Y2 |
|
|
||||||
// | | 16 | | 21 | |
|
|
||||||
// | | 17 | 15 | 18 || 22 | 20 | 23 | |
|
|
||||||
// | | 19 | | 24 | |
|
|
||||||
// --------------------------------------------------------------
|
|
||||||
//
|
|
||||||
/// Button map to HID key (four function layers)
|
|
||||||
/// Please make sure to set FnL, FnR, ModeL and ModeR at the same position for all layers
|
|
||||||
/// alt. only set these at function layer 0 and set NoEventIndicated in layer 1-3.
|
|
||||||
/// Hat button 1-4 = HID B21-B24.
|
|
||||||
pub const HID_MAP: [[HidButton; NUMBER_OF_BUTTONS]; 4] = [
|
|
||||||
[
|
|
||||||
// Function layer 0
|
|
||||||
// HID Key // Button Index
|
|
||||||
// -----------------------------------------
|
|
||||||
HidButton::FnL, // 0
|
|
||||||
HidButton::B1, // 1
|
|
||||||
HidButton::NoEventIndicated, // 2 Not connected to any button
|
|
||||||
HidButton::FnR, // 3
|
|
||||||
HidButton::B6, // 4
|
|
||||||
HidButton::B2, // 5
|
|
||||||
HidButton::B3, // 6
|
|
||||||
HidButton::ModeL, // 7
|
|
||||||
HidButton::B4, // 8
|
|
||||||
HidButton::B5, // 9
|
|
||||||
HidButton::B7, // 10
|
|
||||||
HidButton::B8, // 11
|
|
||||||
HidButton::ModeR, // 12
|
|
||||||
HidButton::B9, // 13
|
|
||||||
HidButton::B10, // 14
|
|
||||||
HidButton::Hat1B, // 15 button 21
|
|
||||||
HidButton::Hat1U, // 16
|
|
||||||
HidButton::Hat1R, // 17
|
|
||||||
HidButton::Hat1D, // 18
|
|
||||||
HidButton::Hat1L, // 19
|
|
||||||
HidButton::Hat2B, // 20 button 22
|
|
||||||
HidButton::Hat2U, // 21
|
|
||||||
HidButton::Hat2R, // 22
|
|
||||||
HidButton::Hat2D, // 23
|
|
||||||
HidButton::Hat2L, // 24
|
|
||||||
],
|
|
||||||
[
|
|
||||||
// Function layer 1 (left Fn button pressed)
|
|
||||||
// HID Key // Button Index
|
|
||||||
// -----------------------------------------
|
|
||||||
HidButton::FnL, // 0
|
|
||||||
HidButton::B11, // 1
|
|
||||||
HidButton::NoEventIndicated, // 2 Not connected to any button
|
|
||||||
HidButton::FnR, // 3
|
|
||||||
HidButton::B6, // 4
|
|
||||||
HidButton::B12, // 5
|
|
||||||
HidButton::B13, // 6
|
|
||||||
HidButton::ModeL, // 7
|
|
||||||
HidButton::B14, // 8
|
|
||||||
HidButton::B15, // 9
|
|
||||||
HidButton::B7, // 10
|
|
||||||
HidButton::B8, // 11
|
|
||||||
HidButton::ModeR, // 12
|
|
||||||
HidButton::B9, // 13
|
|
||||||
HidButton::B10, // 14
|
|
||||||
HidButton::Hat3B, // 15 button 23
|
|
||||||
HidButton::Hat3U, // 16
|
|
||||||
HidButton::Hat3R, // 17
|
|
||||||
HidButton::Hat3D, // 18
|
|
||||||
HidButton::Hat3L, // 19
|
|
||||||
HidButton::Hat2B, // 20 button 22
|
|
||||||
HidButton::Hat2U, // 21
|
|
||||||
HidButton::Hat2R, // 22
|
|
||||||
HidButton::Hat2D, // 23
|
|
||||||
HidButton::Hat2L, // 24
|
|
||||||
],
|
|
||||||
[
|
|
||||||
// Function layer 2 (right Fn button pressed)
|
|
||||||
// HID Key // Button Index
|
|
||||||
// -----------------------------------------
|
|
||||||
HidButton::FnL, // 0
|
|
||||||
HidButton::B1, // 1
|
|
||||||
HidButton::NoEventIndicated, // 2 Not connected to any button
|
|
||||||
HidButton::FnR, // 3
|
|
||||||
HidButton::B16, // 4
|
|
||||||
HidButton::B2, // 5
|
|
||||||
HidButton::B3, // 6
|
|
||||||
HidButton::ModeL, // 7
|
|
||||||
HidButton::B4, // 8
|
|
||||||
HidButton::B5, // 9
|
|
||||||
HidButton::B17, // 10
|
|
||||||
HidButton::B18, // 11
|
|
||||||
HidButton::ModeR, // 12
|
|
||||||
HidButton::B19, // 13
|
|
||||||
HidButton::B20, // 14
|
|
||||||
HidButton::Hat1B, // 15 button 21
|
|
||||||
HidButton::Hat1U, // 16
|
|
||||||
HidButton::Hat1R, // 17
|
|
||||||
HidButton::Hat1D, // 18
|
|
||||||
HidButton::Hat1L, // 19
|
|
||||||
HidButton::Hat4B, // 20 button 24
|
|
||||||
HidButton::Hat4U, // 21
|
|
||||||
HidButton::Hat4R, // 22
|
|
||||||
HidButton::Hat4D, // 23
|
|
||||||
HidButton::Hat4L, // 24
|
|
||||||
],
|
|
||||||
[
|
|
||||||
// Function layer 3 (left + right Fn button pressed)
|
|
||||||
// HID Key // Button Index
|
|
||||||
// -----------------------------------------
|
|
||||||
HidButton::FnL, // 0
|
|
||||||
HidButton::B11, // 1
|
|
||||||
HidButton::NoEventIndicated, // 2 Not connected to any button
|
|
||||||
HidButton::FnR, // 3
|
|
||||||
HidButton::B16, // 4
|
|
||||||
HidButton::B12, // 5
|
|
||||||
HidButton::B13, // 6
|
|
||||||
HidButton::ModeL, // 7
|
|
||||||
HidButton::B14, // 8
|
|
||||||
HidButton::B15, // 9
|
|
||||||
HidButton::B17, // 10
|
|
||||||
HidButton::B18, // 11
|
|
||||||
HidButton::ModeR, // 12
|
|
||||||
HidButton::B19, // 13
|
|
||||||
HidButton::B20, // 14
|
|
||||||
HidButton::Hat3B, // 15 button 23
|
|
||||||
HidButton::Hat3U, // 16
|
|
||||||
HidButton::Hat3R, // 17
|
|
||||||
HidButton::Hat3D, // 18
|
|
||||||
HidButton::Hat3L, // 19
|
|
||||||
HidButton::Hat4B, // 20 button 24
|
|
||||||
HidButton::Hat4U, // 21
|
|
||||||
HidButton::Hat4R, // 22
|
|
||||||
HidButton::Hat4D, // 23
|
|
||||||
HidButton::Hat4L, // 24
|
|
||||||
],
|
|
||||||
];
|
|
||||||
|
|
||||||
pub const ELRS_MAP: [ElrsButton; NUMBER_OF_BUTTONS] = [
|
|
||||||
// Function layer 0
|
|
||||||
// HID Key // Button Index
|
|
||||||
// -----------------------------------------
|
|
||||||
ElrsButton::CH7OFF, // 0
|
|
||||||
ElrsButton::CH7ON, // 1
|
|
||||||
ElrsButton::NoEventIndicated, // 2 Not connected to any button
|
|
||||||
ElrsButton::CH8OFF, // 3
|
|
||||||
ElrsButton::CH8ON, // 4
|
|
||||||
ElrsButton::CH9ON, // 5
|
|
||||||
ElrsButton::CH9OFF, // 6
|
|
||||||
ElrsButton::CH5, // 7
|
|
||||||
ElrsButton::CH10ON, // 8
|
|
||||||
ElrsButton::CH10OFF, // 9
|
|
||||||
ElrsButton::CH11ON, // 10
|
|
||||||
ElrsButton::CH11OFF, // 11
|
|
||||||
ElrsButton::CH6, // 12
|
|
||||||
ElrsButton::CH12ON, // 13
|
|
||||||
ElrsButton::CH12OFF, // 14
|
|
||||||
ElrsButton::NoEventIndicated, // 15
|
|
||||||
ElrsButton::NoEventIndicated, // 16
|
|
||||||
ElrsButton::NoEventIndicated, // 17
|
|
||||||
ElrsButton::NoEventIndicated, // 18
|
|
||||||
ElrsButton::NoEventIndicated, // 19
|
|
||||||
ElrsButton::NoEventIndicated, // 20
|
|
||||||
ElrsButton::NoEventIndicated, // 21
|
|
||||||
ElrsButton::NoEventIndicated, // 22
|
|
||||||
ElrsButton::NoEventIndicated, // 23
|
|
||||||
ElrsButton::NoEventIndicated, // 24
|
|
||||||
];
|
|
||||||
1035
rp2040/src/main.rs
1035
rp2040/src/main.rs
@ -1,14 +1,78 @@
|
|||||||
//! Project: CMtec CMDR joystick 24
|
//! Project: CMtec CMDR joystick 25
|
||||||
//! Date: 2023-08-01
|
//! Date: 2023-08-01
|
||||||
//! Author: Christoffer Martinsson
|
//! Author: Christoffer Martinsson
|
||||||
//! Email: cm@cmtec.se
|
//! Email: cm@cmtec.se
|
||||||
//! License: Please refer to LICENSE in root directory
|
//! License: Please refer to LICENSE in root directory
|
||||||
|
//
|
||||||
|
// Button index map:
|
||||||
|
// ---------------------------------------------------------------
|
||||||
|
// | 0 L| 1 U| | 2 | | 3 L| 4 U|
|
||||||
|
// ---------------------------------------------------------------
|
||||||
|
// | | 5 | 6 | 7 | | 12 | 11 | 10 | |
|
||||||
|
// | |
|
||||||
|
// | | 8 | | 13 | |
|
||||||
|
// | | 9 | | 14 | |
|
||||||
|
// | X1/Y1 X2/Y2 |
|
||||||
|
// | | 16 | | 21 | |
|
||||||
|
// | | 19 | 15 | 17 | | 24 | 20 | 22 | |
|
||||||
|
// | | 18 | | 23 | |
|
||||||
|
// ---------------------------------------------------------------
|
||||||
|
//
|
||||||
|
// USB HID joystick map:
|
||||||
|
// ---------------------------------------------------------------
|
||||||
|
// | B1 L| B2 U| | B3 | | B4 L| B5 U|
|
||||||
|
// ---------------------------------------------------------------
|
||||||
|
// | | B6 | B7 | B8/16| |B13/17| B12 | B11 | |
|
||||||
|
// | |
|
||||||
|
// | | B9 | | B14 | |
|
||||||
|
// | | B10 | | B15 | |
|
||||||
|
// | X1/Y1 X2/Y2 |
|
||||||
|
// | | H1U | | H2U | |
|
||||||
|
// | | H1L | B18 | H1R | | H2L | B19 | H2R | |
|
||||||
|
// | | H1D | | H2D | |
|
||||||
|
// ---------------------------------------------------------------
|
||||||
|
// Button (Switch) 7 changes following:
|
||||||
|
// * hat1 => hat3 (button press B20).
|
||||||
|
// Button (switch) 12 changes following:
|
||||||
|
// * B4 => B21
|
||||||
|
// * B5 => B22
|
||||||
|
// * B14 => B23
|
||||||
|
// * B15 => B24
|
||||||
|
// * hat2 => hat4 (button bpress B25)
|
||||||
|
//
|
||||||
|
// ELRS channel map (+ = ON, - = OFF, CHxP/M/Z = trim)
|
||||||
|
// ---------------------------------------------------------------
|
||||||
|
// |CH7-L|CH7+U| | - | |CH8-L|CH8+U|
|
||||||
|
// ---------------------------------------------------------------
|
||||||
|
// | | THL | CH9 | CH5 | | CH6 |CH11-|CH11+| |
|
||||||
|
// | |
|
||||||
|
// | |CH10+| |CH12+| |
|
||||||
|
// | |CH10-| |CH12-| |
|
||||||
|
// | CH1/CH2 CH3/CH4 |
|
||||||
|
// | | - | |CH4P | |
|
||||||
|
// | |CH1M |CH12Z|CH1P | |CH3M |CH34Z|CH3P | |
|
||||||
|
// | | - | |CH4M | |
|
||||||
|
// ---------------------------------------------------------------
|
||||||
|
//
|
||||||
|
// Config Layer (holding CONFIG button)
|
||||||
|
// ---------------------------------------------------------------
|
||||||
|
// |BOOT L| CAL U| | CONFIG | | USB L|ELRS U|
|
||||||
|
// ---------------------------------------------------------------
|
||||||
|
// | | THL-| THL+| - | | - | - | - | |
|
||||||
|
// | |
|
||||||
|
// | | - | | - | |
|
||||||
|
// | | - | | - | |
|
||||||
|
// | -/- -/- |
|
||||||
|
// | | - | | - | |
|
||||||
|
// | | - | - | - | | - | - | - | |
|
||||||
|
// | | - | | - | |
|
||||||
|
// ---------------------------------------------------------------
|
||||||
|
|
||||||
#![no_std]
|
#![no_std]
|
||||||
#![no_main]
|
#![no_main]
|
||||||
|
|
||||||
mod button_matrix;
|
mod button_matrix;
|
||||||
mod layout;
|
mod elrs;
|
||||||
mod status_led;
|
mod status_led;
|
||||||
mod usb_joystick_device;
|
mod usb_joystick_device;
|
||||||
|
|
||||||
@ -16,16 +80,20 @@ use button_matrix::ButtonMatrix;
|
|||||||
use core::convert::Infallible;
|
use core::convert::Infallible;
|
||||||
use cortex_m::delay::Delay;
|
use cortex_m::delay::Delay;
|
||||||
use dyn_smooth::{DynamicSmootherEcoI32, I32_FRAC_BITS};
|
use dyn_smooth::{DynamicSmootherEcoI32, I32_FRAC_BITS};
|
||||||
|
use eeprom24x::{Eeprom24x, SlaveAddr};
|
||||||
|
use elrs::Elrs;
|
||||||
use embedded_hal::adc::OneShot;
|
use embedded_hal::adc::OneShot;
|
||||||
use embedded_hal::digital::v2::*;
|
use embedded_hal::digital::v2::*;
|
||||||
use embedded_hal::timer::CountDown;
|
use embedded_hal::timer::CountDown;
|
||||||
use fugit::ExtU32;
|
use fugit::{ExtU32, RateExtU32};
|
||||||
use libm::powf;
|
use libm::powf;
|
||||||
use panic_halt as _;
|
use panic_halt as _;
|
||||||
use rp2040_hal::{
|
use rp2040_hal::{
|
||||||
adc::Adc,
|
adc::Adc,
|
||||||
gpio::{Function, FunctionConfig, PinId, ValidPinMode},
|
gpio::{Function, FunctionConfig, FunctionUart, PinId, ValidPinMode},
|
||||||
|
i2c::I2C,
|
||||||
pio::StateMachineIndex,
|
pio::StateMachineIndex,
|
||||||
|
uart::{DataBits, StopBits, UartConfig, UartPeripheral},
|
||||||
};
|
};
|
||||||
use status_led::{StatusMode, Ws2812StatusLed};
|
use status_led::{StatusMode, Ws2812StatusLed};
|
||||||
use usb_device::class_prelude::*;
|
use usb_device::class_prelude::*;
|
||||||
@ -52,25 +120,49 @@ pub const NUMBER_OF_BUTTONS: usize = BUTTON_ROWS * BUTTON_COLS;
|
|||||||
|
|
||||||
pub const AXIS_MIN: u16 = 0;
|
pub const AXIS_MIN: u16 = 0;
|
||||||
pub const AXIS_MAX: u16 = 4095;
|
pub const AXIS_MAX: u16 = 4095;
|
||||||
pub const AXIS_CENTER: u16 = AXIS_MAX / 2;
|
pub const AXIS_CENTER: u16 = (AXIS_MIN + AXIS_MAX) / 2;
|
||||||
|
|
||||||
|
pub const ELRS_MIN: u16 = 172;
|
||||||
|
pub const ELRS_MAX: u16 = 1811;
|
||||||
|
pub const ELRS_CENTER: u16 = (ELRS_MIN + ELRS_MAX) / 2;
|
||||||
|
|
||||||
pub const NBR_OF_GIMBAL_AXIS: usize = 4;
|
pub const NBR_OF_GIMBAL_AXIS: usize = 4;
|
||||||
pub const GIMBAL_AXIS_LEFT_X: usize = 0;
|
pub const GIMBAL_AXIS_LEFT_X: usize = 0;
|
||||||
pub const GIMBAL_AXIS_LEFT_Y: usize = 1;
|
pub const GIMBAL_AXIS_LEFT_Y: usize = 1;
|
||||||
pub const GIMBAL_AXIS_RIGHT_X: usize = 2;
|
pub const GIMBAL_AXIS_RIGHT_X: usize = 2;
|
||||||
pub const GIMBAL_AXIS_RIGHT_Y: usize = 3;
|
pub const GIMBAL_AXIS_RIGHT_Y: usize = 3;
|
||||||
|
pub const GIMBAL_MODE_M10: u8 = 0;
|
||||||
|
pub const GIMBAL_MODE_M7: u8 = 1;
|
||||||
|
|
||||||
// Analog smoothing settings.
|
// Analog smoothing settings.
|
||||||
pub const BASE_FREQ: i32 = 2 << I32_FRAC_BITS;
|
pub const BASE_FREQ: i32 = 2 << I32_FRAC_BITS;
|
||||||
pub const SAMPLE_FREQ: i32 = 1000 << I32_FRAC_BITS;
|
pub const SAMPLE_FREQ: i32 = 1000 << I32_FRAC_BITS;
|
||||||
pub const SENSITIVITY: i32 = (0.01 * ((1 << I32_FRAC_BITS) as f32)) as i32;
|
pub const SENSITIVITY: i32 = (0.01 * ((1 << I32_FRAC_BITS) as f32)) as i32;
|
||||||
|
|
||||||
|
pub const DEBOUNCE: u8 = 10;
|
||||||
|
|
||||||
|
pub const RELEASE_RIMEOUT: u16 = 30; // => 300ms
|
||||||
|
|
||||||
// Public types
|
// Public types
|
||||||
#[derive(Copy, Clone, Default)]
|
#[derive(Copy, Clone, Default)]
|
||||||
pub struct Button {
|
pub struct Button {
|
||||||
pub pressed: bool,
|
pub pressed: bool,
|
||||||
pub previous_pressed: bool,
|
pub previous_pressed: bool,
|
||||||
pub fn_mode: u8,
|
pub usb_changed: bool,
|
||||||
|
pub usb_changed_to_pressed: bool,
|
||||||
|
pub usb_button: usize,
|
||||||
|
pub usb_button_sec_enable: bool,
|
||||||
|
pub usb_button_sec: usize,
|
||||||
|
pub usb_button_sec_trigger_index: usize,
|
||||||
|
pub usb_button_sec_pressed: bool,
|
||||||
|
pub usb_button_toggle_enable: bool,
|
||||||
|
pub usb_release_timeout: u16,
|
||||||
|
pub elrs_changed: bool,
|
||||||
|
pub elrs_changed_to_pressed: bool,
|
||||||
|
pub elrs_channel: usize,
|
||||||
|
pub elrs_lock_enable: bool,
|
||||||
|
pub elrs_lock_state_on: bool,
|
||||||
|
pub elrs_lock_state: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone)]
|
#[derive(Copy, Clone)]
|
||||||
@ -81,9 +173,11 @@ pub struct GimbalAxis {
|
|||||||
pub max: u16,
|
pub max: u16,
|
||||||
pub min: u16,
|
pub min: u16,
|
||||||
pub center: u16,
|
pub center: u16,
|
||||||
pub fn_mode: u8,
|
|
||||||
pub deadzone: (u16, u16, u16),
|
pub deadzone: (u16, u16, u16),
|
||||||
pub expo: f32,
|
pub expo: bool,
|
||||||
|
pub trim: i16,
|
||||||
|
pub hold: u16,
|
||||||
|
pub hold_pending: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for GimbalAxis {
|
impl Default for GimbalAxis {
|
||||||
@ -95,9 +189,11 @@ impl Default for GimbalAxis {
|
|||||||
max: AXIS_MAX,
|
max: AXIS_MAX,
|
||||||
min: AXIS_MIN,
|
min: AXIS_MIN,
|
||||||
center: AXIS_CENTER,
|
center: AXIS_CENTER,
|
||||||
fn_mode: 0,
|
deadzone: (100, 50, 100),
|
||||||
deadzone: (50, 50, 50),
|
expo: true,
|
||||||
expo: 0.2,
|
trim: 0,
|
||||||
|
hold: 0,
|
||||||
|
hold_pending: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -136,6 +232,31 @@ fn main() -> ! {
|
|||||||
&mut pac.RESETS,
|
&mut pac.RESETS,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Set up UART on GP0 and GP1 (Pico pins 1 and 2)
|
||||||
|
let uart_pins = (
|
||||||
|
pins.gp0.into_mode::<FunctionUart>(),
|
||||||
|
pins.gp1.into_mode::<FunctionUart>(),
|
||||||
|
);
|
||||||
|
|
||||||
|
let elrs_uart = UartPeripheral::new(pac.UART0, uart_pins, &mut pac.RESETS)
|
||||||
|
.enable(
|
||||||
|
UartConfig::new(400000.Hz(), DataBits::Eight, None, StopBits::One),
|
||||||
|
clocks.peripheral_clock.freq(),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let i2c = I2C::i2c1(
|
||||||
|
pac.I2C1,
|
||||||
|
pins.gp14.into_mode(), // sda
|
||||||
|
pins.gp15.into_mode(), // scl
|
||||||
|
400.kHz(),
|
||||||
|
&mut pac.RESETS,
|
||||||
|
125_000_000.Hz(),
|
||||||
|
);
|
||||||
|
|
||||||
|
let i2c_address = SlaveAddr::Alternative(false, false, false);
|
||||||
|
let mut eeprom = Eeprom24x::new_24x32(i2c, i2c_address);
|
||||||
|
|
||||||
// Enable adc
|
// Enable adc
|
||||||
let mut adc = Adc::new(pac.ADC, &mut pac.RESETS);
|
let mut adc = Adc::new(pac.ADC, &mut pac.RESETS);
|
||||||
|
|
||||||
@ -149,25 +270,28 @@ fn main() -> ! {
|
|||||||
|
|
||||||
// Setting up array with pins connected to button rows
|
// Setting up array with pins connected to button rows
|
||||||
let button_matrix_row_pins: &[&dyn InputPin<Error = Infallible>; BUTTON_ROWS] = &[
|
let button_matrix_row_pins: &[&dyn InputPin<Error = Infallible>; BUTTON_ROWS] = &[
|
||||||
&pins.gp11.into_pull_up_input(),
|
&pins.gp6.into_pull_up_input(),
|
||||||
&pins.gp13.into_pull_up_input(),
|
&pins.gp8.into_pull_up_input(),
|
||||||
&pins.gp9.into_pull_up_input(),
|
&pins.gp4.into_pull_up_input(),
|
||||||
&pins.gp12.into_pull_up_input(),
|
&pins.gp7.into_pull_up_input(),
|
||||||
&pins.gp10.into_pull_up_input(),
|
&pins.gp5.into_pull_up_input(),
|
||||||
];
|
];
|
||||||
|
|
||||||
// Setting up array with pins connected to button columns
|
// Setting up array with pins connected to button columns
|
||||||
let button_matrix_col_pins: &mut [&mut dyn OutputPin<Error = Infallible>; BUTTON_COLS] = &mut [
|
let button_matrix_col_pins: &mut [&mut dyn OutputPin<Error = Infallible>; BUTTON_COLS] = &mut [
|
||||||
&mut pins.gp4.into_push_pull_output(),
|
&mut pins.gp9.into_push_pull_output(),
|
||||||
&mut pins.gp5.into_push_pull_output(),
|
&mut pins.gp10.into_push_pull_output(),
|
||||||
&mut pins.gp6.into_push_pull_output(),
|
&mut pins.gp11.into_push_pull_output(),
|
||||||
&mut pins.gp7.into_push_pull_output(),
|
&mut pins.gp12.into_push_pull_output(),
|
||||||
&mut pins.gp8.into_push_pull_output(),
|
&mut pins.gp13.into_push_pull_output(),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
let mut elrs_en_pin = pins.gp2.into_push_pull_output();
|
||||||
|
let mut elrs = Elrs::new(elrs_uart);
|
||||||
|
|
||||||
// Create button matrix object that scans all buttons
|
// Create button matrix object that scans all buttons
|
||||||
let mut button_matrix: ButtonMatrix<BUTTON_ROWS, BUTTON_COLS, NUMBER_OF_BUTTONS> =
|
let mut button_matrix: ButtonMatrix<BUTTON_ROWS, BUTTON_COLS, NUMBER_OF_BUTTONS> =
|
||||||
ButtonMatrix::new(button_matrix_row_pins, button_matrix_col_pins, 5);
|
ButtonMatrix::new(button_matrix_row_pins, button_matrix_col_pins, DEBOUNCE);
|
||||||
|
|
||||||
// Initialize button matrix
|
// Initialize button matrix
|
||||||
button_matrix.init_pins();
|
button_matrix.init_pins();
|
||||||
@ -181,9 +305,10 @@ fn main() -> ! {
|
|||||||
clocks.peripheral_clock.freq(),
|
clocks.peripheral_clock.freq(),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let mut delay = Delay::new(core.SYST, clocks.system_clock.freq().to_Hz());
|
||||||
|
|
||||||
// Scan matrix to get initial state and check if bootloader should be entered
|
// Scan matrix to get initial state and check if bootloader should be entered
|
||||||
// This is done by holding button 0 pressed while power on the unit
|
// This is done by holding button 0 pressed while power on the unit
|
||||||
let mut delay = Delay::new(core.SYST, clocks.system_clock.freq().to_Hz());
|
|
||||||
for _ in 0..10 {
|
for _ in 0..10 {
|
||||||
button_matrix.scan_matrix(&mut delay);
|
button_matrix.scan_matrix(&mut delay);
|
||||||
}
|
}
|
||||||
@ -197,48 +322,104 @@ fn main() -> ! {
|
|||||||
// Create timers
|
// Create timers
|
||||||
let timer = Timer::new(pac.TIMER, &mut pac.RESETS);
|
let timer = Timer::new(pac.TIMER, &mut pac.RESETS);
|
||||||
|
|
||||||
let mut usb_hid_report_count_down = timer.count_down();
|
let mut status_led_count_down = timer.count_down();
|
||||||
usb_hid_report_count_down.start(10.millis());
|
status_led_count_down.start(250.millis());
|
||||||
|
|
||||||
let mut scan_count_down = timer.count_down();
|
let mut scan_count_down = timer.count_down();
|
||||||
scan_count_down.start(1.millis());
|
scan_count_down.start(200u32.micros());
|
||||||
|
|
||||||
let mut status_led_count_down = timer.count_down();
|
let mut data_process_count_down = timer.count_down();
|
||||||
status_led_count_down.start(50.millis());
|
data_process_count_down.start(1200u32.micros());
|
||||||
|
|
||||||
let mut elrs_count_down = timer.count_down();
|
let mut elrs_start_count_down = timer.count_down();
|
||||||
elrs_count_down.start(1660u32.micros());
|
elrs_start_count_down.start(2000.millis());
|
||||||
|
|
||||||
|
let mut elrs_update_count_down = timer.count_down();
|
||||||
|
elrs_update_count_down.start(1666u32.micros());
|
||||||
|
|
||||||
|
let mut usb_update_count_down = timer.count_down();
|
||||||
|
usb_update_count_down.start(10.millis());
|
||||||
|
|
||||||
let mut mode: u8 = 0;
|
|
||||||
let mut safety_check: bool = false;
|
let mut safety_check: bool = false;
|
||||||
let mut activity: bool = false;
|
let mut usb_activity: bool = false;
|
||||||
let mut idle: bool = false;
|
let mut idle: bool = false;
|
||||||
let mut usb_active: bool = false;
|
let mut usb_active: bool = false;
|
||||||
|
let mut elrs_active: bool = false;
|
||||||
|
let _elrs_connected: bool = false;
|
||||||
|
let mut calibration_active: bool = false;
|
||||||
|
let mut throttle_hold: bool = false;
|
||||||
|
|
||||||
let mut axis: [GimbalAxis; NBR_OF_GIMBAL_AXIS] = [Default::default(); NBR_OF_GIMBAL_AXIS];
|
let mut axis: [GimbalAxis; NBR_OF_GIMBAL_AXIS] = [Default::default(); NBR_OF_GIMBAL_AXIS];
|
||||||
let mut buttons: [Button; NUMBER_OF_BUTTONS] = [Button::default(); NUMBER_OF_BUTTONS];
|
let mut buttons: [Button; NUMBER_OF_BUTTONS] = [Button::default(); NUMBER_OF_BUTTONS];
|
||||||
|
let mut gimbal_mode: u8;
|
||||||
|
|
||||||
// Set up left gimbal Y axis as full range without return to center spring
|
// Set up usb button layout
|
||||||
axis[GIMBAL_AXIS_LEFT_Y].idle_value = AXIS_MIN;
|
buttons[0].usb_button = 1;
|
||||||
axis[GIMBAL_AXIS_LEFT_Y].deadzone = (50, 0, 50);
|
buttons[1].usb_button = 2;
|
||||||
axis[GIMBAL_AXIS_LEFT_Y].expo = 0.0;
|
buttons[2].usb_button = 3;
|
||||||
|
buttons[3].usb_button = 4;
|
||||||
|
buttons[3].usb_button_sec_enable = true;
|
||||||
|
buttons[3].usb_button_sec = 21;
|
||||||
|
buttons[3].usb_button_sec_trigger_index = 12;
|
||||||
|
buttons[4].usb_button = 5;
|
||||||
|
buttons[4].usb_button_sec_enable = true;
|
||||||
|
buttons[4].usb_button_sec = 22;
|
||||||
|
buttons[4].usb_button_sec_trigger_index = 12;
|
||||||
|
buttons[5].usb_button = 6;
|
||||||
|
buttons[6].usb_button = 7;
|
||||||
|
buttons[7].usb_button = 8;
|
||||||
|
buttons[7].usb_button_sec = 16;
|
||||||
|
buttons[7].usb_button_toggle_enable = true;
|
||||||
|
buttons[8].usb_button = 9;
|
||||||
|
buttons[9].usb_button = 10;
|
||||||
|
buttons[10].usb_button = 11;
|
||||||
|
buttons[11].usb_button = 12;
|
||||||
|
buttons[12].usb_button = 13;
|
||||||
|
buttons[12].usb_button_sec = 17;
|
||||||
|
buttons[12].usb_button_toggle_enable = true;
|
||||||
|
buttons[13].usb_button = 14;
|
||||||
|
buttons[13].usb_button_sec_enable = true;
|
||||||
|
buttons[13].usb_button_sec = 23;
|
||||||
|
buttons[13].usb_button_sec_trigger_index = 12;
|
||||||
|
buttons[14].usb_button = 15;
|
||||||
|
buttons[14].usb_button_sec_enable = true;
|
||||||
|
buttons[14].usb_button_sec = 24;
|
||||||
|
buttons[14].usb_button_sec_trigger_index = 12;
|
||||||
|
|
||||||
// Manual calibation values
|
// Set up elrs button layout
|
||||||
// TODO: add external EEPROM and make calibration routine
|
buttons[0].elrs_channel = 7;
|
||||||
axis[GIMBAL_AXIS_LEFT_X].center = AXIS_CENTER;
|
buttons[0].elrs_lock_enable = true;
|
||||||
axis[GIMBAL_AXIS_LEFT_X].max = AXIS_MAX - 450;
|
buttons[1].elrs_channel = 7;
|
||||||
axis[GIMBAL_AXIS_LEFT_X].min = AXIS_MIN + 500;
|
buttons[1].elrs_lock_enable = true;
|
||||||
axis[GIMBAL_AXIS_LEFT_Y].center = AXIS_CENTER + 105;
|
buttons[1].elrs_lock_state_on = true;
|
||||||
axis[GIMBAL_AXIS_LEFT_Y].max = AXIS_MAX - 250;
|
buttons[3].elrs_channel = 8;
|
||||||
axis[GIMBAL_AXIS_LEFT_Y].min = AXIS_MIN + 500;
|
buttons[3].elrs_lock_enable = true;
|
||||||
axis[GIMBAL_AXIS_RIGHT_X].center = AXIS_CENTER - 230;
|
buttons[4].elrs_channel = 8;
|
||||||
axis[GIMBAL_AXIS_RIGHT_X].max = AXIS_MAX - 700;
|
buttons[4].elrs_lock_enable = true;
|
||||||
axis[GIMBAL_AXIS_RIGHT_X].min = AXIS_MIN + 350;
|
buttons[4].elrs_lock_state_on = true;
|
||||||
axis[GIMBAL_AXIS_RIGHT_Y].center = AXIS_CENTER - 68;
|
buttons[6].elrs_channel = 9;
|
||||||
axis[GIMBAL_AXIS_RIGHT_Y].max = AXIS_MAX - 700;
|
buttons[7].elrs_channel = 5;
|
||||||
axis[GIMBAL_AXIS_RIGHT_Y].min = AXIS_MIN + 450;
|
buttons[8].elrs_channel = 10;
|
||||||
|
buttons[8].elrs_lock_enable = true;
|
||||||
|
buttons[9].elrs_channel = 10;
|
||||||
|
buttons[9].elrs_lock_enable = true;
|
||||||
|
buttons[9].elrs_lock_state_on = true;
|
||||||
|
buttons[10].elrs_channel = 11;
|
||||||
|
buttons[10].elrs_lock_enable = true;
|
||||||
|
buttons[11].elrs_channel = 11;
|
||||||
|
buttons[11].elrs_lock_enable = true;
|
||||||
|
buttons[11].elrs_lock_state_on = true;
|
||||||
|
buttons[12].elrs_channel = 6;
|
||||||
|
buttons[13].elrs_channel = 12;
|
||||||
|
buttons[13].elrs_lock_enable = true;
|
||||||
|
buttons[14].elrs_channel = 12;
|
||||||
|
buttons[14].elrs_lock_enable = true;
|
||||||
|
buttons[14].elrs_lock_state_on = true;
|
||||||
|
|
||||||
|
// Table for gimbal expo curve lookup insded of doing floating point math for every analog read
|
||||||
|
let expo_lut: [u16; AXIS_MAX as usize + 1] = generate_expo_lut(0.3);
|
||||||
|
|
||||||
// Create dynamic smoother array for gimbal axis
|
// Create dynamic smoother array for gimbal axis
|
||||||
// TODO: Find a way to store dynamic smoother in the axis struct
|
|
||||||
let mut smoother: [DynamicSmootherEcoI32; NBR_OF_GIMBAL_AXIS] = [
|
let mut smoother: [DynamicSmootherEcoI32; NBR_OF_GIMBAL_AXIS] = [
|
||||||
DynamicSmootherEcoI32::new(BASE_FREQ, SAMPLE_FREQ, SENSITIVITY),
|
DynamicSmootherEcoI32::new(BASE_FREQ, SAMPLE_FREQ, SENSITIVITY),
|
||||||
DynamicSmootherEcoI32::new(BASE_FREQ, SAMPLE_FREQ, SENSITIVITY),
|
DynamicSmootherEcoI32::new(BASE_FREQ, SAMPLE_FREQ, SENSITIVITY),
|
||||||
@ -261,38 +442,158 @@ fn main() -> ! {
|
|||||||
|
|
||||||
let mut usb_dev = UsbDeviceBuilder::new(&usb_bus, UsbVidPid(0x1209, 0x0002))
|
let mut usb_dev = UsbDeviceBuilder::new(&usb_bus, UsbVidPid(0x1209, 0x0002))
|
||||||
.manufacturer("CMtec")
|
.manufacturer("CMtec")
|
||||||
.product("CMDR Joystick")
|
.product("CMDR Joystick 25")
|
||||||
.serial_number("0001")
|
.serial_number("0001")
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
loop {
|
// Read calibration data from eeprom
|
||||||
// Temporary way to enter bootloader -------------------------
|
for (index, item) in axis.iter_mut().enumerate() {
|
||||||
// TODO: Remove this after testing
|
item.min = eeprom.read_byte((index as u32 * 6) + 2).unwrap() as u16;
|
||||||
if button_matrix.buttons_pressed()[0]
|
item.min <<= 8;
|
||||||
&& button_matrix.buttons_pressed()[1]
|
item.min |= eeprom.read_byte((index as u32 * 6) + 1).unwrap() as u16;
|
||||||
&& button_matrix.buttons_pressed()[5]
|
item.max = eeprom.read_byte((index as u32 * 6) + 4).unwrap() as u16;
|
||||||
&& button_matrix.buttons_pressed()[6]
|
item.max <<= 8;
|
||||||
&& button_matrix.buttons_pressed()[8]
|
item.max |= eeprom.read_byte((index as u32 * 6) + 3).unwrap() as u16;
|
||||||
&& button_matrix.buttons_pressed()[9]
|
item.center = eeprom.read_byte((index as u32 * 6) + 6).unwrap() as u16;
|
||||||
{
|
item.center <<= 8;
|
||||||
status_led.update(StatusMode::Bootloader);
|
item.center |= eeprom.read_byte((index as u32 * 6) + 5).unwrap() as u16;
|
||||||
let gpio_activity_pin_mask: u32 = 0;
|
}
|
||||||
let disable_interface_mask: u32 = 0;
|
gimbal_mode = eeprom.read_byte(25).unwrap();
|
||||||
rp2040_hal::rom_data::reset_to_usb_boot(gpio_activity_pin_mask, disable_interface_mask);
|
|
||||||
|
loop {
|
||||||
|
// Take care of USB HID poll requests
|
||||||
|
if usb_dev.poll(&mut [&mut usb_hid_joystick]) {
|
||||||
|
usb_active = true;
|
||||||
}
|
}
|
||||||
// -----------------------------------------------------------
|
|
||||||
|
|
||||||
if scan_count_down.wait().is_ok() {
|
if scan_count_down.wait().is_ok() {
|
||||||
button_matrix.scan_matrix(&mut delay);
|
button_matrix.scan_matrix(&mut delay);
|
||||||
|
|
||||||
// Have not figured out hov to store the adc pins in an array yet
|
let mut left_x: u16 = adc.read(&mut adc_pin_left_x).unwrap();
|
||||||
// so we have to read them one by one
|
let mut left_y: u16 = adc.read(&mut adc_pin_left_y).unwrap();
|
||||||
// TODO: Find a way to store adc pins in an array
|
let mut right_x: u16 = adc.read(&mut adc_pin_right_x).unwrap();
|
||||||
smoother[GIMBAL_AXIS_LEFT_X].tick(adc.read(&mut adc_pin_left_x).unwrap());
|
let mut right_y: u16 = adc.read(&mut adc_pin_right_y).unwrap();
|
||||||
smoother[GIMBAL_AXIS_LEFT_Y].tick(adc.read(&mut adc_pin_left_y).unwrap());
|
|
||||||
smoother[GIMBAL_AXIS_RIGHT_X].tick(adc.read(&mut adc_pin_right_x).unwrap());
|
|
||||||
smoother[GIMBAL_AXIS_RIGHT_Y].tick(adc.read(&mut adc_pin_right_y).unwrap());
|
|
||||||
|
|
||||||
|
if gimbal_mode == GIMBAL_MODE_M10 {
|
||||||
|
// Invert X1 and Y2 axis (M10 gimbals)
|
||||||
|
left_x = AXIS_MAX - left_x;
|
||||||
|
right_y = AXIS_MAX - right_y;
|
||||||
|
} else if gimbal_mode == GIMBAL_MODE_M7 {
|
||||||
|
// Invert Y1 and X2 axis (M7 gimbals)
|
||||||
|
left_y = AXIS_MAX - left_y;
|
||||||
|
right_x = AXIS_MAX - right_x;
|
||||||
|
}
|
||||||
|
|
||||||
|
smoother[GIMBAL_AXIS_LEFT_X].tick(left_x as i32);
|
||||||
|
smoother[GIMBAL_AXIS_LEFT_Y].tick(left_y as i32);
|
||||||
|
smoother[GIMBAL_AXIS_RIGHT_X].tick(right_x as i32);
|
||||||
|
smoother[GIMBAL_AXIS_RIGHT_Y].tick(right_y as i32);
|
||||||
|
}
|
||||||
|
|
||||||
|
if status_led_count_down.wait().is_ok() {
|
||||||
|
update_status_led(
|
||||||
|
&mut status_led,
|
||||||
|
&usb_active,
|
||||||
|
&elrs_active,
|
||||||
|
&elrs.connected(),
|
||||||
|
&safety_check,
|
||||||
|
&calibration_active,
|
||||||
|
&throttle_hold,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if all axis are in idle position and no buttons are pressed
|
||||||
|
if idle && !safety_check && elrs_active {
|
||||||
|
safety_check = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if data_process_count_down.wait().is_ok() {
|
||||||
|
// Update pressed keys status
|
||||||
|
for (index, key) in button_matrix.buttons_pressed().iter().enumerate() {
|
||||||
|
buttons[index].pressed = *key;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Secondary way to enter bootloader (pressing all left hands buttons except the hat
|
||||||
|
if buttons[0].pressed && buttons[2].pressed {
|
||||||
|
status_led.update(StatusMode::Bootloader);
|
||||||
|
let gpio_activity_pin_mask: u32 = 0;
|
||||||
|
let disable_interface_mask: u32 = 0;
|
||||||
|
rp2040_hal::rom_data::reset_to_usb_boot(
|
||||||
|
gpio_activity_pin_mask,
|
||||||
|
disable_interface_mask,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ON/OFF switch for ELRS radio
|
||||||
|
if buttons[4].pressed && buttons[2].pressed && !elrs_active {
|
||||||
|
safety_check = false;
|
||||||
|
elrs_active = true;
|
||||||
|
} else if buttons[3].pressed && buttons[2].pressed && elrs_active {
|
||||||
|
elrs_active = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ON/OFF switch for Throttle hold mode
|
||||||
|
if buttons[6].pressed && buttons[2].pressed && !throttle_hold {
|
||||||
|
throttle_hold = true;
|
||||||
|
axis[GIMBAL_AXIS_LEFT_Y].hold = 0;
|
||||||
|
} else if buttons[5].pressed && buttons[2].pressed && throttle_hold {
|
||||||
|
throttle_hold = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calibration of center position (pressing all right hands buttons except
|
||||||
|
// the hat switch)
|
||||||
|
if buttons[1].pressed && buttons[2].pressed {
|
||||||
|
for (index, item) in axis.iter_mut().enumerate() {
|
||||||
|
item.center = smoother[index].value() as u16;
|
||||||
|
item.min = item.center;
|
||||||
|
item.max = item.center;
|
||||||
|
}
|
||||||
|
calibration_active = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calibration of min and max position
|
||||||
|
if calibration_active {
|
||||||
|
for (index, item) in axis.iter_mut().enumerate() {
|
||||||
|
if (smoother[index].value() as u16) < item.min {
|
||||||
|
item.min = smoother[index].value() as u16;
|
||||||
|
} else if (smoother[index].value() as u16) > item.max {
|
||||||
|
item.max = smoother[index].value() as u16;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if calibration_active && buttons[8].pressed {
|
||||||
|
gimbal_mode = GIMBAL_MODE_M10;
|
||||||
|
for (index, item) in axis.iter_mut().enumerate() {
|
||||||
|
item.center = smoother[index].value() as u16;
|
||||||
|
item.min = item.center;
|
||||||
|
item.max = item.center;
|
||||||
|
}
|
||||||
|
} else if calibration_active && buttons[9].pressed {
|
||||||
|
gimbal_mode = GIMBAL_MODE_M7;
|
||||||
|
for (index, item) in axis.iter_mut().enumerate() {
|
||||||
|
item.center = smoother[index].value() as u16;
|
||||||
|
item.min = item.center;
|
||||||
|
item.max = item.center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Save calibration data to eeprom (pressing right hat switch)
|
||||||
|
else if calibration_active && buttons[20].pressed {
|
||||||
|
let mut eeprom_data: [u8; 25] = [0; 25];
|
||||||
|
for (index, item) in axis.iter_mut().enumerate() {
|
||||||
|
eeprom_data[index * 6] = item.min as u8;
|
||||||
|
eeprom_data[(index * 6) + 1] = (item.min >> 8) as u8;
|
||||||
|
eeprom_data[(index * 6) + 2] = item.max as u8;
|
||||||
|
eeprom_data[(index * 6) + 3] = (item.max >> 8) as u8;
|
||||||
|
eeprom_data[(index * 6) + 4] = item.center as u8;
|
||||||
|
eeprom_data[(index * 6) + 5] = (item.center >> 8) as u8;
|
||||||
|
}
|
||||||
|
eeprom_data[24] = gimbal_mode;
|
||||||
|
let _ = eeprom.write_page(0x01, &eeprom_data);
|
||||||
|
calibration_active = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process axis values
|
||||||
for (index, item) in axis.iter_mut().enumerate() {
|
for (index, item) in axis.iter_mut().enumerate() {
|
||||||
item.value = calculate_axis_value(
|
item.value = calculate_axis_value(
|
||||||
smoother[index].value() as u16,
|
smoother[index].value() as u16,
|
||||||
@ -301,41 +602,43 @@ fn main() -> ! {
|
|||||||
item.center,
|
item.center,
|
||||||
item.deadzone,
|
item.deadzone,
|
||||||
item.expo,
|
item.expo,
|
||||||
|
&expo_lut,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let pressed_keys = button_matrix.buttons_pressed();
|
// Process throttle hold value
|
||||||
mode = get_mode(pressed_keys);
|
if throttle_hold
|
||||||
|
&& axis[GIMBAL_AXIS_LEFT_Y].value < AXIS_CENTER
|
||||||
// Update pressed keys status
|
&& !axis[GIMBAL_AXIS_LEFT_Y].hold_pending
|
||||||
for (index, key) in pressed_keys.iter().enumerate() {
|
|
||||||
buttons[index].pressed = *key;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update Fn mode for all axis that are in idle position
|
|
||||||
// This is to avoid the Fn mode switching when moving the gimbal
|
|
||||||
idle = true;
|
|
||||||
for item in axis.iter_mut() {
|
|
||||||
if item.value == item.idle_value {
|
|
||||||
item.fn_mode = mode & 0x0F;
|
|
||||||
} else {
|
|
||||||
idle = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set fn mode for all keys taht are in idle position
|
|
||||||
// This is to avoid the Fn mode switching when using a button
|
|
||||||
for (index, key) in buttons.iter_mut().enumerate() {
|
|
||||||
if !key.pressed {
|
|
||||||
key.fn_mode = mode & 0x0F;
|
|
||||||
} else if (usb_active
|
|
||||||
&& layout::HID_MAP[key.fn_mode as usize][index] != layout::HidButton::FnL
|
|
||||||
&& layout::HID_MAP[key.fn_mode as usize][index] != layout::HidButton::FnR
|
|
||||||
&& layout::HID_MAP[key.fn_mode as usize][index] != layout::HidButton::ModeL
|
|
||||||
&& layout::HID_MAP[key.fn_mode as usize][index] != layout::HidButton::ModeR)
|
|
||||||
|| (!usb_active
|
|
||||||
&& layout::ELRS_MAP[index] != layout::ElrsButton::NoEventIndicated)
|
|
||||||
{
|
{
|
||||||
|
axis[GIMBAL_AXIS_LEFT_Y].value = remap(
|
||||||
|
axis[GIMBAL_AXIS_LEFT_Y].value,
|
||||||
|
AXIS_MIN,
|
||||||
|
AXIS_CENTER,
|
||||||
|
AXIS_MIN,
|
||||||
|
axis[GIMBAL_AXIS_LEFT_Y].hold,
|
||||||
|
);
|
||||||
|
} else if throttle_hold
|
||||||
|
&& axis[GIMBAL_AXIS_LEFT_Y].value > AXIS_CENTER
|
||||||
|
&& !axis[GIMBAL_AXIS_LEFT_Y].hold_pending
|
||||||
|
{
|
||||||
|
axis[GIMBAL_AXIS_LEFT_Y].value = remap(
|
||||||
|
axis[GIMBAL_AXIS_LEFT_Y].value,
|
||||||
|
AXIS_CENTER,
|
||||||
|
AXIS_MAX,
|
||||||
|
axis[GIMBAL_AXIS_LEFT_Y].hold,
|
||||||
|
AXIS_MAX,
|
||||||
|
);
|
||||||
|
} else if throttle_hold && axis[GIMBAL_AXIS_LEFT_Y].value == AXIS_CENTER {
|
||||||
|
axis[GIMBAL_AXIS_LEFT_Y].value = axis[GIMBAL_AXIS_LEFT_Y].hold;
|
||||||
|
axis[GIMBAL_AXIS_LEFT_Y].hold_pending = false;
|
||||||
|
} else if throttle_hold {
|
||||||
|
axis[GIMBAL_AXIS_LEFT_Y].value = axis[GIMBAL_AXIS_LEFT_Y].hold;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set idle state
|
||||||
|
for key in buttons.iter_mut() {
|
||||||
|
if key.pressed {
|
||||||
idle = false;
|
idle = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -343,51 +646,65 @@ fn main() -> ! {
|
|||||||
// Generate led activity when gimbal is moved from idle position
|
// Generate led activity when gimbal is moved from idle position
|
||||||
for item in axis.iter_mut() {
|
for item in axis.iter_mut() {
|
||||||
if item.value != item.previous_value {
|
if item.value != item.previous_value {
|
||||||
activity = true;
|
usb_activity = true;
|
||||||
}
|
}
|
||||||
item.previous_value = item.value;
|
item.previous_value = item.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Make sure usb will be updated during timeout countdown
|
||||||
|
for key in buttons.iter() {
|
||||||
|
if key.usb_release_timeout != 0 {
|
||||||
|
usb_activity = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Generate led activity when a button is pressed
|
// Generate led activity when a button is pressed
|
||||||
// FnL, FnR, and ModeR are excluded
|
|
||||||
for (index, key) in buttons.iter_mut().enumerate() {
|
for (index, key) in buttons.iter_mut().enumerate() {
|
||||||
if (usb_active
|
if key.pressed != key.previous_pressed {
|
||||||
&& key.pressed != key.previous_pressed
|
key.usb_changed = true;
|
||||||
&& layout::HID_MAP[key.fn_mode as usize][index] != layout::HidButton::FnL
|
key.usb_changed_to_pressed = key.pressed;
|
||||||
&& layout::HID_MAP[key.fn_mode as usize][index] != layout::HidButton::FnR
|
key.elrs_changed = true;
|
||||||
&& layout::HID_MAP[key.fn_mode as usize][index] != layout::HidButton::ModeR)
|
key.elrs_changed_to_pressed = key.pressed;
|
||||||
|| (!usb_active
|
usb_activity = true;
|
||||||
&& key.pressed != key.previous_pressed
|
}
|
||||||
&& layout::ELRS_MAP[index] != layout::ElrsButton::NoEventIndicated)
|
// Set throttle_hold_value
|
||||||
|
if key.pressed != key.previous_pressed && key.pressed && throttle_hold && index == 5
|
||||||
{
|
{
|
||||||
activity = true;
|
axis[GIMBAL_AXIS_LEFT_Y].hold = axis[GIMBAL_AXIS_LEFT_Y].value;
|
||||||
|
axis[GIMBAL_AXIS_LEFT_Y].hold_pending = true;
|
||||||
}
|
}
|
||||||
key.previous_pressed = key.pressed;
|
key.previous_pressed = key.pressed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Reset channel locks when calibration is active
|
||||||
|
if calibration_active {
|
||||||
|
for axis in axis.iter_mut() {
|
||||||
|
axis.hold = 0;
|
||||||
|
}
|
||||||
|
for button in buttons.iter_mut() {
|
||||||
|
button.elrs_lock_state = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if usb_dev.poll(&mut [&mut usb_hid_joystick]) {
|
if elrs_update_count_down.wait().is_ok() {
|
||||||
usb_active = true;
|
// Send ELRS data
|
||||||
|
if elrs_active {
|
||||||
|
elrs_en_pin.set_high().unwrap();
|
||||||
|
elrs.send(get_elrs_channels(&mut buttons, &mut axis));
|
||||||
|
} else {
|
||||||
|
elrs_en_pin.set_low().unwrap();
|
||||||
|
elrs.reset();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if status_led_count_down.wait().is_ok() {
|
|
||||||
update_status_led(
|
|
||||||
&mut status_led,
|
|
||||||
&mut activity,
|
|
||||||
&usb_active,
|
|
||||||
&idle,
|
|
||||||
&safety_check,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if usb_hid_report_count_down.wait().is_ok() && activity {
|
|
||||||
// Dont send USB HID joystick report if there is no activity
|
// Dont send USB HID joystick report if there is no activity
|
||||||
// This is to avoid preventing the computer from going to sleep
|
// This is to avoid preventing the computer from going to sleep
|
||||||
match usb_hid_joystick.device().write_report(&get_joystick_report(
|
if usb_update_count_down.wait().is_ok() && usb_activity {
|
||||||
&mut buttons,
|
match usb_hid_joystick
|
||||||
&mut axis,
|
.device()
|
||||||
&mode,
|
.write_report(&get_joystick_report(&mut buttons, &mut axis))
|
||||||
)) {
|
{
|
||||||
Err(UsbHidError::WouldBlock) => {}
|
Err(UsbHidError::WouldBlock) => {}
|
||||||
Ok(_) => {}
|
Ok(_) => {}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
@ -395,15 +712,8 @@ fn main() -> ! {
|
|||||||
core::panic!("Failed to write joystick report: {:?}", e)
|
core::panic!("Failed to write joystick report: {:?}", e)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
usb_activity = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if all axis are in idle position and no buttons are pressed
|
|
||||||
if idle && !safety_check && !usb_active {
|
|
||||||
safety_check = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Implement ELRS
|
|
||||||
if elrs_count_down.wait().is_ok() && !usb_active && safety_check {}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -422,191 +732,148 @@ fn main() -> ! {
|
|||||||
/// * `safety_check` - Reference to bool that indicates if safety check has passed
|
/// * `safety_check` - Reference to bool that indicates if safety check has passed
|
||||||
fn update_status_led<P, SM, I>(
|
fn update_status_led<P, SM, I>(
|
||||||
status_led: &mut Ws2812StatusLed<P, SM, I>,
|
status_led: &mut Ws2812StatusLed<P, SM, I>,
|
||||||
activity: &mut bool,
|
|
||||||
usb_active: &bool,
|
usb_active: &bool,
|
||||||
axis_idle: &bool,
|
elrs_active: &bool,
|
||||||
|
elrs_connected: &bool,
|
||||||
safety_check: &bool,
|
safety_check: &bool,
|
||||||
|
calibration_active: &bool,
|
||||||
|
throttle_hold: &bool,
|
||||||
) where
|
) where
|
||||||
P: PIOExt + FunctionConfig,
|
P: PIOExt + FunctionConfig,
|
||||||
I: PinId,
|
I: PinId,
|
||||||
Function<P>: ValidPinMode<I>,
|
Function<P>: ValidPinMode<I>,
|
||||||
SM: StateMachineIndex,
|
SM: StateMachineIndex,
|
||||||
{
|
{
|
||||||
if !usb_active && !*safety_check {
|
if *calibration_active {
|
||||||
|
status_led.update(StatusMode::ActivityFlash);
|
||||||
|
} else if *elrs_active && !*safety_check {
|
||||||
status_led.update(StatusMode::Warning);
|
status_led.update(StatusMode::Warning);
|
||||||
} else if *activity && status_led.get_mode() != StatusMode::Activity {
|
} else if !*usb_active && !*elrs_active {
|
||||||
|
status_led.update(StatusMode::NormalFlash);
|
||||||
|
} else if *usb_active && !*elrs_active && *throttle_hold {
|
||||||
status_led.update(StatusMode::Activity);
|
status_led.update(StatusMode::Activity);
|
||||||
} else if *activity && status_led.get_mode() == StatusMode::Activity {
|
} else if *usb_active && !*elrs_active && !*throttle_hold {
|
||||||
status_led.update(StatusMode::Off);
|
|
||||||
*activity = false;
|
|
||||||
} else if !*axis_idle && status_led.get_mode() != StatusMode::Activity {
|
|
||||||
status_led.update(StatusMode::Activity);
|
|
||||||
} else if *usb_active && status_led.get_mode() != StatusMode::Normal {
|
|
||||||
status_led.update(StatusMode::Normal);
|
status_led.update(StatusMode::Normal);
|
||||||
} else if status_led.get_mode() != StatusMode::Other {
|
} else if *elrs_active && *elrs_connected {
|
||||||
status_led.update(StatusMode::Other);
|
status_led.update(StatusMode::Other);
|
||||||
|
} else if *elrs_active && !*elrs_connected {
|
||||||
|
status_led.update(StatusMode::OtherFlash);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get current Fn mode (0, 1, 2 or 3 and alt l/r mode)
|
/// Generate keyboard report based on pressed keys and Fn mode (0, 1, 2 or 3)
|
||||||
/// layout::MAP contains the button types
|
|
||||||
///
|
|
||||||
/// # Arguments
|
|
||||||
///
|
|
||||||
/// * `pressed_keys` - Array of pressed keys
|
|
||||||
fn get_mode(pressed_keys: [bool; NUMBER_OF_BUTTONS]) -> u8 {
|
|
||||||
// Check how many Fn keys are pressed
|
|
||||||
let mut mode: u8 = 0;
|
|
||||||
let mut fn_l_active: bool = false;
|
|
||||||
let mut fn_r_active: bool = false;
|
|
||||||
let mut alt_l_active: bool = false;
|
|
||||||
let mut alt_r_active: bool = false;
|
|
||||||
|
|
||||||
for (index, key) in pressed_keys.iter().enumerate() {
|
|
||||||
if *key && layout::HID_MAP[0][index] == layout::HidButton::FnL {
|
|
||||||
fn_l_active = true;
|
|
||||||
}
|
|
||||||
if *key && layout::HID_MAP[0][index] == layout::HidButton::FnR {
|
|
||||||
fn_r_active = true;
|
|
||||||
}
|
|
||||||
if *key && layout::HID_MAP[0][index] == layout::HidButton::ModeL {
|
|
||||||
alt_l_active = true;
|
|
||||||
}
|
|
||||||
if *key && layout::HID_MAP[0][index] == layout::HidButton::ModeR {
|
|
||||||
alt_r_active = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if fn_l_active && fn_r_active {
|
|
||||||
mode = 3;
|
|
||||||
} else if fn_r_active {
|
|
||||||
mode = 2;
|
|
||||||
} else if fn_l_active {
|
|
||||||
mode = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set bit 4 and 5 if alt l/r is active
|
|
||||||
if alt_l_active {
|
|
||||||
mode |= 0x10;
|
|
||||||
}
|
|
||||||
if alt_r_active {
|
|
||||||
mode |= 0x20;
|
|
||||||
}
|
|
||||||
|
|
||||||
mode
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Generate keyboard report based on pressed keys and Fn mode (0, 1 or 2)
|
|
||||||
/// layout::MAP contains the keycodes for each key in each Fn mode
|
/// layout::MAP contains the keycodes for each key in each Fn mode
|
||||||
///
|
///
|
||||||
/// # Arguments
|
/// # Arguments
|
||||||
///
|
///
|
||||||
/// * `matrix_keys` - Array of pressed keys
|
/// * `matrix_keys` - Array of pressed keys
|
||||||
/// * `axis` - Array of joystick axis values
|
/// * `axis` - Array of joystick axis values
|
||||||
/// * `fn_mode` - Fn mode (0, 1, 2 or 3)
|
|
||||||
/// * `alt_l_mode` - Is left alt mode active
|
|
||||||
/// * `alt_r_mode` - Is right alt mode active
|
|
||||||
fn get_joystick_report(
|
fn get_joystick_report(
|
||||||
matrix_keys: &mut [Button; NUMBER_OF_BUTTONS],
|
matrix_keys: &mut [Button; NUMBER_OF_BUTTONS],
|
||||||
axis: &mut [GimbalAxis; 4],
|
axis: &mut [GimbalAxis; 4],
|
||||||
mode: &u8,
|
|
||||||
) -> JoystickReport {
|
) -> JoystickReport {
|
||||||
let mut x: u16 = axis[GIMBAL_AXIS_RIGHT_X].value;
|
let mut x: u16 = axis[GIMBAL_AXIS_RIGHT_X].value;
|
||||||
let mut y: u16 = axis[GIMBAL_AXIS_RIGHT_Y].value;
|
let mut y: u16 = AXIS_MAX - axis[GIMBAL_AXIS_RIGHT_Y].value;
|
||||||
let z: u16 = axis[GIMBAL_AXIS_LEFT_X].value;
|
let z: u16 = axis[GIMBAL_AXIS_LEFT_X].value;
|
||||||
let mut rx: u16 = AXIS_CENTER;
|
let mut rx: u16 = AXIS_CENTER;
|
||||||
let mut ry: u16 = AXIS_CENTER;
|
let mut ry: u16 = AXIS_CENTER;
|
||||||
let mut rz: u16 = axis[GIMBAL_AXIS_LEFT_Y].value;
|
let rz: u16 = axis[GIMBAL_AXIS_LEFT_Y].value;
|
||||||
|
let (mut hat1, mut hat_button1) = format_hat_value(0);
|
||||||
// Left Alt mode active (bit 4)
|
let (mut hat2, mut hat_button2) = format_hat_value(0);
|
||||||
// Full range of left gimbal gives half range of joystick axis (center to max)
|
let (mut hat3, mut hat_button3) = format_hat_value(0);
|
||||||
// Left Fn mode = reversed range (center to min)
|
let (mut hat4, mut hat_button4) = format_hat_value(0);
|
||||||
if mode & 0x10 == 0x10
|
|
||||||
&& (axis[GIMBAL_AXIS_LEFT_Y].fn_mode == 0 || axis[GIMBAL_AXIS_LEFT_Y].fn_mode == 2)
|
|
||||||
{
|
|
||||||
rz = remap(
|
|
||||||
axis[GIMBAL_AXIS_LEFT_Y].value,
|
|
||||||
AXIS_MIN,
|
|
||||||
AXIS_MAX,
|
|
||||||
AXIS_CENTER,
|
|
||||||
AXIS_MAX,
|
|
||||||
);
|
|
||||||
} else if mode & 0x10 == 0x10
|
|
||||||
&& (axis[GIMBAL_AXIS_LEFT_Y].fn_mode == 1 || axis[GIMBAL_AXIS_LEFT_Y].fn_mode == 3)
|
|
||||||
{
|
|
||||||
rz = AXIS_MAX
|
|
||||||
- remap(
|
|
||||||
axis[GIMBAL_AXIS_LEFT_Y].value,
|
|
||||||
AXIS_MIN,
|
|
||||||
AXIS_MAX,
|
|
||||||
AXIS_CENTER,
|
|
||||||
AXIS_MAX,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Right Alt mode active (bit 5)
|
// Right Alt mode active (bit 5)
|
||||||
// Right gimbal control third joystick axis when right Fn mode is active
|
// Right gimbal control third joystick axis when right Fn mode is active
|
||||||
if mode & 0x20 == 0x20
|
if matrix_keys[8].pressed {
|
||||||
&& (axis[GIMBAL_AXIS_RIGHT_X].fn_mode == 2 || axis[GIMBAL_AXIS_RIGHT_X].fn_mode == 3)
|
|
||||||
{
|
|
||||||
x = AXIS_CENTER;
|
x = AXIS_CENTER;
|
||||||
rx = axis[GIMBAL_AXIS_RIGHT_X].value;
|
rx = axis[GIMBAL_AXIS_RIGHT_X].value;
|
||||||
}
|
|
||||||
if mode & 0x20 == 0x20
|
|
||||||
&& (axis[GIMBAL_AXIS_RIGHT_Y].fn_mode == 2 || axis[GIMBAL_AXIS_RIGHT_Y].fn_mode == 3)
|
|
||||||
{
|
|
||||||
y = AXIS_CENTER;
|
y = AXIS_CENTER;
|
||||||
ry = axis[GIMBAL_AXIS_RIGHT_Y].value;
|
ry = AXIS_MAX - axis[GIMBAL_AXIS_RIGHT_Y].value;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate array for all four hat switches with following structure:
|
let mut hat_left: u8 = 0;
|
||||||
// * bit 1: Up
|
let mut hat_right: u8 = 0;
|
||||||
// * bit 2: Right
|
|
||||||
// * bit 3: Down
|
|
||||||
// * bit 4: Left
|
|
||||||
// * bit 5: Button
|
|
||||||
// * value 0 = not pressed
|
|
||||||
// * value 1 = pressed
|
|
||||||
let mut hats: [u8; 4] = [0; 4];
|
|
||||||
for (index, key) in matrix_keys.iter_mut().enumerate() {
|
for (index, key) in matrix_keys.iter_mut().enumerate() {
|
||||||
if key.pressed
|
if (15..=19).contains(&index) && key.pressed {
|
||||||
&& layout::HID_MAP[key.fn_mode as usize][index] >= layout::HidButton::Hat1U
|
hat_left |= 1 << (index - 15);
|
||||||
&& layout::HID_MAP[key.fn_mode as usize][index] <= layout::HidButton::Hat4B
|
}
|
||||||
{
|
if (20..=24).contains(&index) && key.pressed {
|
||||||
hats[(layout::HID_MAP[key.fn_mode as usize][index] as usize
|
hat_right |= 1 << (index - 20);
|
||||||
- layout::HidButton::Hat1U as usize)
|
|
||||||
/ 5] |= 1
|
|
||||||
<< ((layout::HID_MAP[key.fn_mode as usize][index] as usize
|
|
||||||
- layout::HidButton::Hat1U as usize)
|
|
||||||
- (5 * ((layout::HID_MAP[key.fn_mode as usize][index] as usize
|
|
||||||
- layout::HidButton::Hat1U as usize)
|
|
||||||
/ 5)));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert hat switch data to HID code
|
// Convert hat switch data to HID code
|
||||||
let (hat1, hat_button1) = format_hat_value(hats[0]);
|
if matrix_keys[7].pressed {
|
||||||
let (hat2, hat_button2) = format_hat_value(hats[1]);
|
(hat3, hat_button3) = format_hat_value(hat_left);
|
||||||
let (hat3, hat_button3) = format_hat_value(hats[2]);
|
} else {
|
||||||
let (hat4, hat_button4) = format_hat_value(hats[3]);
|
(hat1, hat_button1) = format_hat_value(hat_left);
|
||||||
|
}
|
||||||
|
if matrix_keys[12].pressed {
|
||||||
|
(hat4, hat_button4) = format_hat_value(hat_right);
|
||||||
|
} else {
|
||||||
|
(hat2, hat_button2) = format_hat_value(hat_right);
|
||||||
|
}
|
||||||
|
|
||||||
// Update button state for joystick button 21-24 according to hat button 1-4
|
// Update button state for joystick button 21-24 according to hat button 1-4
|
||||||
let mut buttons: u32 = (hat_button1 as u32) << 20
|
let mut buttons: u32 = (hat_button1 as u32) << 17
|
||||||
| ((hat_button2 as u32) << 21)
|
| ((hat_button2 as u32) << 18)
|
||||||
| ((hat_button3 as u32) << 22)
|
| ((hat_button3 as u32) << 19)
|
||||||
| ((hat_button4 as u32) << 23);
|
| ((hat_button4 as u32) << 24);
|
||||||
|
|
||||||
// Update button state for joystick button 1-20
|
// Update button array with Sec button trigger status
|
||||||
for (index, key) in matrix_keys.iter_mut().enumerate() {
|
// Using indexing instead of iterating to be able to iterate inside loop
|
||||||
if key.pressed
|
for index in 0..NUMBER_OF_BUTTONS {
|
||||||
&& layout::HID_MAP[key.fn_mode as usize][index] as usize
|
let mut sec_button_pressed: bool = false;
|
||||||
>= layout::HidButton::B1 as usize
|
for (sec_index, sec_key) in matrix_keys.iter().enumerate() {
|
||||||
&& layout::HID_MAP[key.fn_mode as usize][index] as usize
|
if matrix_keys[index].usb_button_sec_enable
|
||||||
<= layout::HidButton::B20 as usize
|
&& matrix_keys[index].usb_button_sec_trigger_index == sec_index
|
||||||
|
&& sec_key.pressed
|
||||||
{
|
{
|
||||||
buttons |= 1 << layout::HID_MAP[key.fn_mode as usize][index] as usize;
|
sec_button_pressed = true;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
matrix_keys[index].usb_button_sec_pressed = sec_button_pressed;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update button state for joystick buttons
|
||||||
|
for key in matrix_keys.iter_mut() {
|
||||||
|
// Toggle mode button
|
||||||
|
if key.usb_changed && key.usb_button_toggle_enable {
|
||||||
|
key.usb_release_timeout = RELEASE_RIMEOUT;
|
||||||
|
}
|
||||||
|
if key.pressed
|
||||||
|
&& key.usb_button != 0
|
||||||
|
&& key.usb_button_toggle_enable
|
||||||
|
&& key.usb_release_timeout > 1
|
||||||
|
{
|
||||||
|
buttons |= 1 << (key.usb_button - 1);
|
||||||
|
} else if !key.pressed
|
||||||
|
&& key.usb_button_sec != 0
|
||||||
|
&& key.usb_button_toggle_enable
|
||||||
|
&& key.usb_release_timeout > 1
|
||||||
|
{
|
||||||
|
buttons |= 1 << (key.usb_button_sec - 1);
|
||||||
|
// Sec button mode
|
||||||
|
} else if key.pressed && key.usb_button_sec != 0 && key.usb_button_sec_pressed {
|
||||||
|
buttons |= 1 << (key.usb_button_sec - 1);
|
||||||
|
} else if key.pressed && key.usb_button != 0 && !key.usb_button_toggle_enable {
|
||||||
|
buttons |= 1 << (key.usb_button - 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Auto release button when in toggle mode
|
||||||
|
for key in matrix_keys.iter_mut() {
|
||||||
|
if key.usb_release_timeout > 0 {
|
||||||
|
key.usb_release_timeout -= 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset changed flags
|
||||||
|
for key in matrix_keys.iter_mut() {
|
||||||
|
key.usb_changed = false;
|
||||||
|
}
|
||||||
|
|
||||||
JoystickReport {
|
JoystickReport {
|
||||||
x,
|
x,
|
||||||
@ -627,8 +894,8 @@ fn get_joystick_report(
|
|||||||
///
|
///
|
||||||
/// # Arguments
|
/// # Arguments
|
||||||
/// * `input` - Hat value coded as
|
/// * `input` - Hat value coded as
|
||||||
/// bit 1-4: direction (U R D L)
|
/// bit 2-5: direction (U L R D)
|
||||||
/// bit 5: button state
|
/// bit 1: button state
|
||||||
/// 0 = not pressed
|
/// 0 = not pressed
|
||||||
/// 1 = pressed
|
/// 1 = pressed
|
||||||
fn format_hat_value(input: u8) -> (u8, u8) {
|
fn format_hat_value(input: u8) -> (u8, u8) {
|
||||||
@ -642,21 +909,21 @@ fn format_hat_value(input: u8) -> (u8, u8) {
|
|||||||
const HAT_LEFT: u8 = 6;
|
const HAT_LEFT: u8 = 6;
|
||||||
const HAT_UP_LEFT: u8 = 7;
|
const HAT_UP_LEFT: u8 = 7;
|
||||||
|
|
||||||
let direction: u8 = match input & 0x0F {
|
let direction: u8 = match input & 0xFE {
|
||||||
1 => HAT_UP,
|
2 => HAT_UP,
|
||||||
2 => HAT_RIGHT,
|
4 => HAT_RIGHT,
|
||||||
3 => HAT_UP_RIGHT,
|
6 => HAT_UP_RIGHT,
|
||||||
4 => HAT_DOWN,
|
8 => HAT_DOWN,
|
||||||
6 => HAT_DOWN_RIGHT,
|
12 => HAT_DOWN_RIGHT,
|
||||||
8 => HAT_LEFT,
|
16 => HAT_LEFT,
|
||||||
12 => HAT_DOWN_LEFT,
|
24 => HAT_DOWN_LEFT,
|
||||||
9 => HAT_UP_LEFT,
|
18 => HAT_UP_LEFT,
|
||||||
_ => HAT_CENTER,
|
_ => HAT_CENTER,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Alpine hat switch button filter
|
// Alpine hat switch button filter
|
||||||
let mut button_state: u8 = 0;
|
let mut button_state: u8 = 0;
|
||||||
if input & 0x10 == 0x10 && direction == HAT_CENTER {
|
if input & 0x01 == 0x01 && direction == HAT_CENTER {
|
||||||
button_state = 1;
|
button_state = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -671,15 +938,24 @@ fn format_hat_value(input: u8) -> (u8, u8) {
|
|||||||
/// * `max` - Upper bound of the value's current range
|
/// * `max` - Upper bound of the value's current range
|
||||||
/// * `center` - Center of the value's current range
|
/// * `center` - Center of the value's current range
|
||||||
/// * `deadzone` - Deadzone of the value's current range (min, center, max)
|
/// * `deadzone` - Deadzone of the value's current range (min, center, max)
|
||||||
/// * `expo` - Exponential curve factor
|
/// * `expo` - Exponential curve factor enabled
|
||||||
|
/// * `expo_lut` - Exponential curve lookup table
|
||||||
fn calculate_axis_value(
|
fn calculate_axis_value(
|
||||||
value: u16,
|
value: u16,
|
||||||
min: u16,
|
min: u16,
|
||||||
max: u16,
|
max: u16,
|
||||||
center: u16,
|
center: u16,
|
||||||
deadzone: (u16, u16, u16),
|
deadzone: (u16, u16, u16),
|
||||||
expo: f32,
|
expo: bool,
|
||||||
|
expo_lut: &[u16; AXIS_MAX as usize + 1],
|
||||||
) -> u16 {
|
) -> u16 {
|
||||||
|
if value <= min {
|
||||||
|
return AXIS_MIN;
|
||||||
|
}
|
||||||
|
if value >= max {
|
||||||
|
return AXIS_MAX;
|
||||||
|
}
|
||||||
|
|
||||||
let mut calibrated_value = AXIS_CENTER;
|
let mut calibrated_value = AXIS_CENTER;
|
||||||
|
|
||||||
if value > (center + deadzone.1) {
|
if value > (center + deadzone.1) {
|
||||||
@ -700,17 +976,8 @@ fn calculate_axis_value(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if expo != 0.0 {
|
if expo && calibrated_value != AXIS_CENTER {
|
||||||
let joystick_x_float = calibrated_value as f32 / AXIS_MAX as f32;
|
calibrated_value = expo_lut[calibrated_value as usize];
|
||||||
// Calculate expo using 9th order polynomial function with 0.5 as center point
|
|
||||||
let joystick_x_exp: f32 = expo * (0.5 + 256.0 * powf(joystick_x_float - 0.5, 9.0))
|
|
||||||
+ (1.0 - expo) * joystick_x_float;
|
|
||||||
|
|
||||||
calibrated_value = constrain(
|
|
||||||
(joystick_x_exp * AXIS_MAX as f32) as u16,
|
|
||||||
AXIS_MIN,
|
|
||||||
AXIS_MAX,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
calibrated_value
|
calibrated_value
|
||||||
@ -749,3 +1016,163 @@ fn constrain<T: PartialOrd>(value: T, out_min: T, out_max: T) -> T {
|
|||||||
value
|
value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Generate exponential lookup table for 12bit values
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
/// * `expo` - Exponential curve factor (range 0.0 - 1.0)
|
||||||
|
fn generate_expo_lut(expo: f32) -> [u16; AXIS_MAX as usize + 1] {
|
||||||
|
let mut lut: [u16; AXIS_MAX as usize + 1] = [0; AXIS_MAX as usize + 1];
|
||||||
|
for i in 0..AXIS_MAX + 1 {
|
||||||
|
let value_float = i as f32 / AXIS_MAX as f32;
|
||||||
|
// Calculate expo using 9th order polynomial function with 0.5 as center point
|
||||||
|
let value_exp: f32 =
|
||||||
|
expo * (0.5 + 256.0 * powf(value_float - 0.5, 9.0)) + (1.0 - expo) * value_float;
|
||||||
|
lut[i as usize] = constrain((value_exp * AXIS_MAX as f32) as u16, AXIS_MIN, AXIS_MAX);
|
||||||
|
}
|
||||||
|
lut
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get ELRS channel values
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
/// * `matrix_keys` - Array of buttons
|
||||||
|
/// * `axis` - Array of axis
|
||||||
|
fn get_elrs_channels(
|
||||||
|
matrix_keys: &mut [Button; NUMBER_OF_BUTTONS],
|
||||||
|
axis: &mut [GimbalAxis; 4],
|
||||||
|
) -> [u16; 12] {
|
||||||
|
let mut channels: [u16; 12] = [ELRS_MIN; 12];
|
||||||
|
|
||||||
|
// Check and store trim values
|
||||||
|
for (index, key) in matrix_keys.iter_mut().enumerate() {
|
||||||
|
// Left gimbal X
|
||||||
|
if key.pressed && index == 17 && axis[GIMBAL_AXIS_LEFT_X].trim < ELRS_CENTER as i16 {
|
||||||
|
axis[GIMBAL_AXIS_LEFT_X].trim += 1;
|
||||||
|
} else if key.pressed
|
||||||
|
&& index == 19
|
||||||
|
&& axis[GIMBAL_AXIS_LEFT_X].trim > (0 - ELRS_CENTER as i16)
|
||||||
|
{
|
||||||
|
axis[GIMBAL_AXIS_LEFT_X].trim -= 1;
|
||||||
|
// Left gimbal Y
|
||||||
|
} else if key.pressed && index == 16 && axis[GIMBAL_AXIS_LEFT_Y].trim < ELRS_CENTER as i16 {
|
||||||
|
axis[GIMBAL_AXIS_LEFT_Y].trim += 1;
|
||||||
|
} else if key.pressed
|
||||||
|
&& index == 18
|
||||||
|
&& axis[GIMBAL_AXIS_LEFT_Y].trim > (0 - ELRS_CENTER as i16)
|
||||||
|
{
|
||||||
|
axis[GIMBAL_AXIS_LEFT_Y].trim -= 1;
|
||||||
|
// Right gimbal X
|
||||||
|
} else if key.pressed && index == 22 && axis[GIMBAL_AXIS_RIGHT_X].trim < ELRS_CENTER as i16
|
||||||
|
{
|
||||||
|
axis[GIMBAL_AXIS_RIGHT_X].trim += 1;
|
||||||
|
} else if key.pressed
|
||||||
|
&& index == 24
|
||||||
|
&& axis[GIMBAL_AXIS_RIGHT_X].trim > (0 - ELRS_CENTER as i16)
|
||||||
|
{
|
||||||
|
axis[GIMBAL_AXIS_RIGHT_X].trim -= 1;
|
||||||
|
// Right gimbal Y
|
||||||
|
} else if key.pressed && index == 21 && axis[GIMBAL_AXIS_RIGHT_Y].trim < ELRS_CENTER as i16
|
||||||
|
{
|
||||||
|
axis[GIMBAL_AXIS_RIGHT_Y].trim += 1;
|
||||||
|
} else if key.pressed
|
||||||
|
&& index == 23
|
||||||
|
&& axis[GIMBAL_AXIS_RIGHT_Y].trim > (0 - ELRS_CENTER as i16)
|
||||||
|
{
|
||||||
|
axis[GIMBAL_AXIS_RIGHT_Y].trim -= 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Alpine hat switch filter
|
||||||
|
let mut hat_left_button_only: bool = true;
|
||||||
|
let mut hat_right_button_only: bool = true;
|
||||||
|
for (index, key) in matrix_keys.iter_mut().enumerate() {
|
||||||
|
if key.pressed && (16..=19).contains(&index) {
|
||||||
|
hat_left_button_only = false;
|
||||||
|
}
|
||||||
|
if key.pressed && (21..=24).contains(&index) {
|
||||||
|
hat_right_button_only = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check and reset trim values
|
||||||
|
for (index, key) in matrix_keys.iter_mut().enumerate() {
|
||||||
|
if key.elrs_changed && key.elrs_changed_to_pressed && index == 15 && hat_left_button_only {
|
||||||
|
axis[GIMBAL_AXIS_LEFT_X].trim = 0;
|
||||||
|
axis[GIMBAL_AXIS_LEFT_Y].trim = 0;
|
||||||
|
} else if key.elrs_changed
|
||||||
|
&& key.elrs_changed_to_pressed
|
||||||
|
&& index == 20
|
||||||
|
&& hat_right_button_only
|
||||||
|
{
|
||||||
|
axis[GIMBAL_AXIS_RIGHT_X].trim = 0;
|
||||||
|
axis[GIMBAL_AXIS_RIGHT_Y].trim = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Match ELRS channel 1-4 to new min/max values
|
||||||
|
for (index, item) in axis.iter_mut().enumerate() {
|
||||||
|
channels[index] = remap(item.value, AXIS_MIN, AXIS_MAX, ELRS_MIN, ELRS_MAX);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply trim to ELRS channel 1,3,4
|
||||||
|
for (index, item) in axis.iter().enumerate() {
|
||||||
|
if index != GIMBAL_AXIS_LEFT_Y && channels[index] > ELRS_CENTER {
|
||||||
|
channels[index] = remap(
|
||||||
|
channels[index],
|
||||||
|
ELRS_CENTER,
|
||||||
|
ELRS_MAX,
|
||||||
|
(ELRS_CENTER as i16 + item.trim) as u16,
|
||||||
|
ELRS_MAX,
|
||||||
|
);
|
||||||
|
} else if index != GIMBAL_AXIS_LEFT_Y && channels[index] < ELRS_CENTER {
|
||||||
|
channels[index] = remap(
|
||||||
|
channels[index],
|
||||||
|
ELRS_MIN,
|
||||||
|
ELRS_CENTER,
|
||||||
|
ELRS_MIN,
|
||||||
|
(ELRS_CENTER as i16 + item.trim) as u16,
|
||||||
|
);
|
||||||
|
} else if index != GIMBAL_AXIS_LEFT_Y {
|
||||||
|
channels[index] = (ELRS_CENTER as i16 + item.trim) as u16;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update button state for ELRS channels
|
||||||
|
for key in matrix_keys.iter_mut() {
|
||||||
|
if key.elrs_changed
|
||||||
|
&& key.elrs_changed_to_pressed
|
||||||
|
&& key.elrs_lock_enable
|
||||||
|
&& key.elrs_lock_state_on
|
||||||
|
&& key.elrs_channel != 0
|
||||||
|
{
|
||||||
|
key.elrs_lock_state = true;
|
||||||
|
} else if key.elrs_changed
|
||||||
|
&& !key.elrs_changed_to_pressed
|
||||||
|
&& key.elrs_lock_enable
|
||||||
|
&& !key.elrs_lock_state_on
|
||||||
|
&& key.elrs_channel != 0
|
||||||
|
{
|
||||||
|
key.elrs_lock_state = false;
|
||||||
|
} else if key.pressed && !key.elrs_lock_enable && key.elrs_channel != 0 {
|
||||||
|
channels[key.elrs_channel] = ELRS_MAX;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply locking to ELRS channels
|
||||||
|
for key in matrix_keys.iter_mut() {
|
||||||
|
if key.elrs_lock_enable
|
||||||
|
&& key.elrs_lock_state
|
||||||
|
&& key.elrs_lock_state_on
|
||||||
|
&& key.elrs_channel != 0
|
||||||
|
{
|
||||||
|
channels[key.elrs_channel] = ELRS_MAX;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset changed flags
|
||||||
|
for key in matrix_keys.iter_mut() {
|
||||||
|
key.elrs_changed = false;
|
||||||
|
}
|
||||||
|
channels
|
||||||
|
}
|
||||||
|
|||||||
@ -25,11 +25,14 @@ use ws2812_pio::Ws2812Direct;
|
|||||||
pub enum StatusMode {
|
pub enum StatusMode {
|
||||||
Off = 0,
|
Off = 0,
|
||||||
Normal = 1,
|
Normal = 1,
|
||||||
Activity = 2,
|
NormalFlash = 2,
|
||||||
Other = 3,
|
Activity = 3,
|
||||||
Warning = 4,
|
ActivityFlash = 4,
|
||||||
Error = 5,
|
Other = 5,
|
||||||
Bootloader = 6,
|
OtherFlash = 6,
|
||||||
|
Warning = 7,
|
||||||
|
Error = 8,
|
||||||
|
Bootloader = 9,
|
||||||
}
|
}
|
||||||
#[warn(dead_code)]
|
#[warn(dead_code)]
|
||||||
|
|
||||||
@ -91,41 +94,68 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Get current status mode
|
/// Get current status mode
|
||||||
|
#[allow(dead_code)]
|
||||||
pub fn get_mode(&self) -> StatusMode {
|
pub fn get_mode(&self) -> StatusMode {
|
||||||
self.mode
|
self.mode
|
||||||
}
|
}
|
||||||
|
#[warn(dead_code)]
|
||||||
|
|
||||||
/// Update status LED
|
/// Update status LED
|
||||||
/// Depending on the mode, the LED will be set to a different colour
|
/// Depending on the mode, the LED will be set to a different colour
|
||||||
///
|
///
|
||||||
/// * OFF = off
|
/// * OFF = off
|
||||||
/// * NORMAL = green
|
/// * NORMAL = green
|
||||||
|
/// * NORMALFLASH = green (flashing)
|
||||||
/// * ACTIVITY = blue
|
/// * ACTIVITY = blue
|
||||||
|
/// * ACTIVITYFLASH = blue (flashing)
|
||||||
/// * OTHER = orange
|
/// * OTHER = orange
|
||||||
|
/// * OTHERFLASH = orange (flashing)
|
||||||
/// * WARNING = red (flashing)
|
/// * WARNING = red (flashing)
|
||||||
/// * ERROR = red
|
/// * ERROR = red
|
||||||
/// * BOOTLOADER = purple
|
/// * BOOTLOADER = purple
|
||||||
///
|
///
|
||||||
/// Make sure to call this function regularly to keep the LED flashing
|
/// Make sure to call this function regularly to keep the LED flashing
|
||||||
pub fn update(&mut self, mode: StatusMode) {
|
pub fn update(&mut self, mode: StatusMode) {
|
||||||
let colors: [RGB8; 7] = [
|
let colors: [RGB8; 10] = [
|
||||||
(0, 0, 0).into(), // Off
|
(0, 0, 0).into(), // Off
|
||||||
(10, 7, 0).into(), // Green
|
(10, 7, 0).into(), // Green
|
||||||
|
(10, 7, 0).into(), // Green
|
||||||
(10, 4, 10).into(), // Blue
|
(10, 4, 10).into(), // Blue
|
||||||
|
(10, 4, 10).into(), // Blue
|
||||||
|
(5, 10, 0).into(), // Orange
|
||||||
(5, 10, 0).into(), // Orange
|
(5, 10, 0).into(), // Orange
|
||||||
(2, 20, 0).into(), // Red
|
(2, 20, 0).into(), // Red
|
||||||
(2, 20, 0).into(), // Red
|
(2, 20, 0).into(), // Red
|
||||||
(0, 10, 10).into(), // Purple
|
(0, 10, 10).into(), // Purple
|
||||||
];
|
];
|
||||||
|
|
||||||
|
if mode == StatusMode::Warning
|
||||||
|
|| mode == StatusMode::NormalFlash
|
||||||
|
|| mode == StatusMode::ActivityFlash
|
||||||
|
|| mode == StatusMode::OtherFlash
|
||||||
|
|| mode != self.mode
|
||||||
|
{
|
||||||
self.mode = mode;
|
self.mode = mode;
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if mode == StatusMode::Warning && !self.state {
|
if (mode == StatusMode::Warning
|
||||||
|
|| mode == StatusMode::NormalFlash
|
||||||
|
|| mode == StatusMode::ActivityFlash
|
||||||
|
|| mode == StatusMode::OtherFlash)
|
||||||
|
&& !self.state
|
||||||
|
{
|
||||||
self.ws2812_direct
|
self.ws2812_direct
|
||||||
.write([colors[mode as usize]].iter().copied())
|
.write([colors[mode as usize]].iter().copied())
|
||||||
.unwrap();
|
.unwrap();
|
||||||
self.state = true;
|
self.state = true;
|
||||||
} else if mode == StatusMode::Warning || mode == StatusMode::Off {
|
} else if mode == StatusMode::Warning
|
||||||
|
|| mode == StatusMode::NormalFlash
|
||||||
|
|| mode == StatusMode::ActivityFlash
|
||||||
|
|| mode == StatusMode::OtherFlash
|
||||||
|
|| mode == StatusMode::Off
|
||||||
|
{
|
||||||
self.ws2812_direct
|
self.ws2812_direct
|
||||||
.write([colors[0]].iter().copied())
|
.write([colors[0]].iter().copied())
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|||||||
@ -68,7 +68,7 @@ impl<T, E> Try for Result<T, E> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Based on example device from https://github.com/dlkj/usbd-human-interface-device/blob/main/src/device/joystick.rs
|
// Based on example device from https://github.com/dlkj/usbd-human-interface-device/blob/main/src/device/joystick.rs
|
||||||
// Updated to 6pc 12bit axis, 24pc buttons and 4pc hat switches
|
// Updated to 6x 12bit axis, 25x buttons and 4x hat switches
|
||||||
#[rustfmt::skip]
|
#[rustfmt::skip]
|
||||||
pub const JOYSTICK_DESCRIPTOR: &[u8] = &[
|
pub const JOYSTICK_DESCRIPTOR: &[u8] = &[
|
||||||
0x05, 0x01, // Usage Page (Generic Desktop)
|
0x05, 0x01, // Usage Page (Generic Desktop)
|
||||||
@ -90,12 +90,15 @@ pub const JOYSTICK_DESCRIPTOR: &[u8] = &[
|
|||||||
0xc0, // End Collection
|
0xc0, // End Collection
|
||||||
0x05, 0x09, // Usage Page (Button)
|
0x05, 0x09, // Usage Page (Button)
|
||||||
0x19, 0x01, // Usage Minimum (1)
|
0x19, 0x01, // Usage Minimum (1)
|
||||||
0x29, 0x18, // Usage Maximum (24)
|
0x29, 0x19, // Usage Maximum (25)
|
||||||
0x15, 0x00, // Logical Minimum (0)
|
0x15, 0x00, // Logical Minimum (0)
|
||||||
0x25, 0x01, // Logical Maximum (1)
|
0x25, 0x01, // Logical Maximum (1)
|
||||||
0x75, 0x01, // Report Size (1)
|
0x75, 0x01, // Report Size (1)
|
||||||
0x95, 0x18, // Report Count (24)
|
0x95, 0x19, // Report Count (25)
|
||||||
0x81, 0x02, // Input (Data, Variable, Absolute)
|
0x81, 0x02, // Input (Data, Variable, Absolute)
|
||||||
|
0x75, 0x01, // Report Size (1) PADDING
|
||||||
|
0x95, 0x07, // Report Count (7) PADDING
|
||||||
|
0x81, 0x03, // Input (Const, Variable, Absolute) PADDING
|
||||||
0x15, 0x00, // Logical Minimum (0)
|
0x15, 0x00, // Logical Minimum (0)
|
||||||
0x25, 0x07, // Logical Maximum (7)
|
0x25, 0x07, // Logical Maximum (7)
|
||||||
0x35, 0x00, // Physical Minimum (0)
|
0x35, 0x00, // Physical Minimum (0)
|
||||||
@ -120,7 +123,7 @@ pub struct JoystickReport {
|
|||||||
pub rx: u16, // 12bit
|
pub rx: u16, // 12bit
|
||||||
pub ry: u16, // 12bit
|
pub ry: u16, // 12bit
|
||||||
pub rz: u16, // 12bit
|
pub rz: u16, // 12bit
|
||||||
pub buttons: u32, // 24bit
|
pub buttons: u32, // 32bit
|
||||||
pub hat1: u8, // 4bit
|
pub hat1: u8, // 4bit
|
||||||
pub hat2: u8, // 4bit
|
pub hat2: u8, // 4bit
|
||||||
pub hat3: u8, // 4bit
|
pub hat3: u8, // 4bit
|
||||||
@ -133,7 +136,7 @@ pub struct Joystick<'a, B: UsbBus> {
|
|||||||
|
|
||||||
impl<'a, B: UsbBus> Joystick<'a, B> {
|
impl<'a, B: UsbBus> Joystick<'a, B> {
|
||||||
pub fn write_report(&mut self, report: &JoystickReport) -> Result<(), UsbHidError> {
|
pub fn write_report(&mut self, report: &JoystickReport) -> Result<(), UsbHidError> {
|
||||||
let mut data: [u8; 14] = [0; 14];
|
let mut data: [u8; 15] = [0; 15];
|
||||||
|
|
||||||
// Did not make the packed struct work, so doing it manually
|
// Did not make the packed struct work, so doing it manually
|
||||||
// TODO: make this work with packed struct
|
// TODO: make this work with packed struct
|
||||||
@ -149,8 +152,9 @@ impl<'a, B: UsbBus> Joystick<'a, B> {
|
|||||||
data[9] = report.buttons as u8;
|
data[9] = report.buttons as u8;
|
||||||
data[10] = (report.buttons >> 8) as u8;
|
data[10] = (report.buttons >> 8) as u8;
|
||||||
data[11] = (report.buttons >> 16) as u8;
|
data[11] = (report.buttons >> 16) as u8;
|
||||||
data[12] = (report.hat1) | (report.hat2 << 4);
|
data[12] = (report.buttons >> 24) as u8;
|
||||||
data[13] = (report.hat3) | (report.hat4 << 4);
|
data[13] = (report.hat1) | (report.hat2 << 4);
|
||||||
|
data[14] = (report.hat3) | (report.hat4 << 4);
|
||||||
|
|
||||||
self.interface
|
self.interface
|
||||||
.write_report(&data)
|
.write_report(&data)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user