Added ELRS support. Added EEPROM calibration data (not yet activated). Code cleanup
This commit is contained in:
parent
d59cbfa7b0
commit
555645fc54
31
README.md
31
README.md
@ -7,7 +7,7 @@ RC Joystick with 2 hall effect gimbals, 2 hat switches and 25 buttons for use bo
|
|||||||
```cpp
|
```cpp
|
||||||
USB Joystick Layer 0
|
USB Joystick Layer 0
|
||||||
--------------------------------------------------------------
|
--------------------------------------------------------------
|
||||||
| FnL | B1 | | B25 | | B5 | FnR |
|
| FnL | B1 | | B21 | | B5 | FnR |
|
||||||
--------------------------------------------------------------
|
--------------------------------------------------------------
|
||||||
| | B2 | B3 | MoL | | MoR | B7 | B6 | |
|
| | B2 | B3 | MoL | | MoR | B7 | B6 | |
|
||||||
| |
|
| |
|
||||||
@ -21,7 +21,7 @@ USB Joystick Layer 0
|
|||||||
|
|
||||||
USB Joystick Layer 1 (FnL)
|
USB Joystick Layer 1 (FnL)
|
||||||
--------------------------------------------------------------
|
--------------------------------------------------------------
|
||||||
| FnL | B9 | | B25 | | B5 | FnR |
|
| FnL | B9 | | B21 | | B5 | FnR |
|
||||||
--------------------------------------------------------------
|
--------------------------------------------------------------
|
||||||
| | B10 | B11 | MoL | | MoR | B7 | B6 | |
|
| | B10 | B11 | MoL | | MoR | B7 | B6 | |
|
||||||
| |
|
| |
|
||||||
@ -35,7 +35,7 @@ USB Joystick Layer 1 (FnL)
|
|||||||
|
|
||||||
USB Joystick Layer 2 (FnR)
|
USB Joystick Layer 2 (FnR)
|
||||||
--------------------------------------------------------------
|
--------------------------------------------------------------
|
||||||
| FnL | B1 | | B25 | | B13 | FnR |
|
| FnL | B1 | | B21 | | B13 | FnR |
|
||||||
--------------------------------------------------------------
|
--------------------------------------------------------------
|
||||||
| | B2 | B3 | MoL | | MoR | B15 | B14 | |
|
| | B2 | B3 | MoL | | MoR | B15 | B14 | |
|
||||||
| |
|
| |
|
||||||
@ -49,7 +49,7 @@ USB Joystick Layer 2 (FnR)
|
|||||||
|
|
||||||
USB Joystick Layer 3 (FnL + FnR)
|
USB Joystick Layer 3 (FnL + FnR)
|
||||||
--------------------------------------------------------------
|
--------------------------------------------------------------
|
||||||
| FnL | B9 | | B25 | | B13 | FnR |
|
| FnL | B9 | | B21 | | B13 | FnR |
|
||||||
--------------------------------------------------------------
|
--------------------------------------------------------------
|
||||||
| | B10 | B11 | MoL | | MoR | B15 | B14 | |
|
| | B10 | B11 | MoL | | MoR | B15 | B14 | |
|
||||||
| |
|
| |
|
||||||
@ -100,8 +100,17 @@ ELRS Layer
|
|||||||
|
|
||||||
 
|
 
|
||||||
|
|
||||||
* Gerber files: [zip](/eCAD/cmdr-joystick/cmdr-joystick_rev_a_gerber.zip)
|
- Gerber files: [zip](/eCAD/cmdr-joystick/cmdr-joystick_rev_a_gerber.zip)
|
||||||
* Schematics: [pdf](/eCAD/cmdr-joystick/cmdr-joystick_rev_a_schematics.pdf)
|
- Schematics: [pdf](/eCAD/cmdr-joystick/cmdr-joystick_rev_a_schematics.pdf)
|
||||||
|
- rp2040zero pinout: 
|
||||||
|
- rp2040zero schematic: [pdf](https://www.waveshare.com/w/upload/4/4c/RP2040_Zero.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](https://github.com/MUSTARDTIGERFPV/rx-as-tx#flashing)
|
||||||
|
|
||||||
## Software Build environment
|
## Software Build environment
|
||||||
Rust
|
Rust
|
||||||
@ -111,6 +120,14 @@ Rust
|
|||||||
- Pressing boot button on teensy
|
- Pressing boot button on teensy
|
||||||
- Press and hold "top lower right button" when powering the unit
|
- Press and hold "top lower right button" when powering the unit
|
||||||
|
|
||||||
|
- 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 all righ hand side buttons except hat switch. Status led will start blinking green.
|
||||||
|
3. Move both gimbals to all corners.
|
||||||
|
4. Press right hat switch to save calibration data to eeprom.
|
||||||
|
|
||||||
|
Done!
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -8,6 +8,7 @@
|
|||||||
#![no_main]
|
#![no_main]
|
||||||
|
|
||||||
mod button_matrix;
|
mod button_matrix;
|
||||||
|
mod elrs;
|
||||||
mod layout;
|
mod layout;
|
||||||
mod status_led;
|
mod status_led;
|
||||||
mod usb_joystick_device;
|
mod usb_joystick_device;
|
||||||
@ -16,16 +17,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,13 +57,19 @@ 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;
|
||||||
@ -83,7 +94,8 @@ pub struct GimbalAxis {
|
|||||||
pub center: u16,
|
pub center: u16,
|
||||||
pub fn_mode: u8,
|
pub fn_mode: u8,
|
||||||
pub deadzone: (u16, u16, u16),
|
pub deadzone: (u16, u16, u16),
|
||||||
pub expo: f32,
|
pub expo: bool,
|
||||||
|
pub trim: i16,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for GimbalAxis {
|
impl Default for GimbalAxis {
|
||||||
@ -96,8 +108,9 @@ impl Default for GimbalAxis {
|
|||||||
min: AXIS_MIN,
|
min: AXIS_MIN,
|
||||||
center: AXIS_CENTER,
|
center: AXIS_CENTER,
|
||||||
fn_mode: 0,
|
fn_mode: 0,
|
||||||
deadzone: (50, 50, 50),
|
deadzone: (100, 50, 100),
|
||||||
expo: 0.2,
|
expo: true,
|
||||||
|
trim: 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -136,6 +149,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 mut 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::default();
|
||||||
|
let mut eeprom = Eeprom24x::new_24x02(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);
|
||||||
|
|
||||||
@ -165,6 +203,9 @@ fn main() -> ! {
|
|||||||
&mut pins.gp8.into_push_pull_output(),
|
&mut pins.gp8.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, 5);
|
||||||
@ -181,9 +222,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);
|
||||||
}
|
}
|
||||||
@ -201,44 +243,39 @@ fn main() -> ! {
|
|||||||
usb_hid_report_count_down.start(10.millis());
|
usb_hid_report_count_down.start(10.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 status_led_count_down = timer.count_down();
|
||||||
status_led_count_down.start(50.millis());
|
status_led_count_down.start(50.millis());
|
||||||
|
|
||||||
let mut elrs_count_down = timer.count_down();
|
let mut main_count_down = timer.count_down();
|
||||||
elrs_count_down.start(1660u32.micros());
|
main_count_down.start(1660u32.micros());
|
||||||
|
|
||||||
|
let mut elrs_start_count_down = timer.count_down();
|
||||||
|
elrs_start_count_down.start(2000.millis());
|
||||||
|
|
||||||
let mut mode: u8 = 0;
|
let mut mode: u8 = 0;
|
||||||
let mut safety_check: bool = false;
|
let mut safety_check: bool = false;
|
||||||
let mut activity: bool = false;
|
let mut 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 mut calibration_active: 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 channel_locks: [bool; 12] = [false; 12];
|
||||||
|
let mut gimbal_mode: u8 = GIMBAL_MODE_M10;
|
||||||
|
|
||||||
|
let expo_lut: [u16; AXIS_MAX as usize + 1] = generate_expo_lut(0.3);
|
||||||
|
|
||||||
// Set up left gimbal Y axis as full range without return to center spring
|
// Set up left gimbal Y axis as full range without return to center spring
|
||||||
axis[GIMBAL_AXIS_LEFT_Y].idle_value = AXIS_MIN;
|
axis[GIMBAL_AXIS_LEFT_Y].idle_value = AXIS_MIN;
|
||||||
axis[GIMBAL_AXIS_LEFT_Y].deadzone = (50, 0, 50);
|
axis[GIMBAL_AXIS_LEFT_Y].deadzone = (100, 0, 100);
|
||||||
axis[GIMBAL_AXIS_LEFT_Y].expo = 0.0;
|
axis[GIMBAL_AXIS_LEFT_Y].expo = false;
|
||||||
|
|
||||||
// Manual calibation values
|
|
||||||
// TODO: add external EEPROM and make calibration routine
|
|
||||||
axis[GIMBAL_AXIS_LEFT_X].center = AXIS_CENTER;
|
|
||||||
axis[GIMBAL_AXIS_LEFT_X].max = AXIS_MAX - 450;
|
|
||||||
axis[GIMBAL_AXIS_LEFT_X].min = AXIS_MIN + 500;
|
|
||||||
axis[GIMBAL_AXIS_LEFT_Y].center = AXIS_CENTER + 105;
|
|
||||||
axis[GIMBAL_AXIS_LEFT_Y].max = AXIS_MAX - 250;
|
|
||||||
axis[GIMBAL_AXIS_LEFT_Y].min = AXIS_MIN + 500;
|
|
||||||
axis[GIMBAL_AXIS_RIGHT_X].center = AXIS_CENTER - 230;
|
|
||||||
axis[GIMBAL_AXIS_RIGHT_X].max = AXIS_MAX - 700;
|
|
||||||
axis[GIMBAL_AXIS_RIGHT_X].min = AXIS_MIN + 350;
|
|
||||||
axis[GIMBAL_AXIS_RIGHT_Y].center = AXIS_CENTER - 68;
|
|
||||||
axis[GIMBAL_AXIS_RIGHT_Y].max = AXIS_MAX - 700;
|
|
||||||
axis[GIMBAL_AXIS_RIGHT_Y].min = AXIS_MIN + 450;
|
|
||||||
|
|
||||||
// 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,113 +298,59 @@ 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();
|
||||||
|
|
||||||
|
// Read calibration data from eeprom
|
||||||
|
// if !calibration_active {
|
||||||
|
// for (index, item) in axis.iter_mut().enumerate() {
|
||||||
|
// item.min = eeprom.read_byte((index as u32 * 6) + 1).unwrap() as u16;
|
||||||
|
// item.min <<= 8;
|
||||||
|
// item.min |= eeprom.read_byte(index as u32 * 6).unwrap() as u16;
|
||||||
|
// item.max = eeprom.read_byte((index as u32 * 6) + 3).unwrap() as u16;
|
||||||
|
// item.max <<= 8;
|
||||||
|
// item.max = eeprom.read_byte((index as u32 * 6) + 2).unwrap() as u16;
|
||||||
|
// item.center = eeprom.read_byte((index as u32 * 6) + 5).unwrap() as u16;
|
||||||
|
// item.center <<= 8;
|
||||||
|
// item.center = eeprom.read_byte((index as u32 * 6) + 4).unwrap() as u16;
|
||||||
|
// }
|
||||||
|
// gimbal_mode = eeprom.read_byte(24).unwrap();
|
||||||
|
// }
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
// Temporary way to enter bootloader -------------------------
|
// Take care of USB HID poll requests
|
||||||
// TODO: Remove this after testing
|
if usb_dev.poll(&mut [&mut usb_hid_joystick]) {
|
||||||
if button_matrix.buttons_pressed()[0]
|
usb_active = true;
|
||||||
&& button_matrix.buttons_pressed()[1]
|
}
|
||||||
&& button_matrix.buttons_pressed()[5]
|
|
||||||
&& button_matrix.buttons_pressed()[6]
|
// Power up ELRS TX
|
||||||
&& button_matrix.buttons_pressed()[8]
|
if elrs_start_count_down.wait().is_ok() {
|
||||||
&& button_matrix.buttons_pressed()[9]
|
elrs_active = true;
|
||||||
{
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
// -----------------------------------------------------------
|
|
||||||
|
|
||||||
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());
|
|
||||||
|
|
||||||
for (index, item) in axis.iter_mut().enumerate() {
|
if gimbal_mode == GIMBAL_MODE_M10 {
|
||||||
item.value = calculate_axis_value(
|
// Invert X1 and Y2 axis (M10 gimbals)
|
||||||
smoother[index].value() as u16,
|
left_x = AXIS_MAX - left_x;
|
||||||
item.min,
|
right_y = AXIS_MAX - right_y;
|
||||||
item.max,
|
} else if gimbal_mode == GIMBAL_MODE_M7 {
|
||||||
item.center,
|
// Invert Y1 and X2 axis (M7 gimbals)
|
||||||
item.deadzone,
|
left_y = AXIS_MAX - left_y;
|
||||||
item.expo,
|
right_x = AXIS_MAX - right_x;
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let pressed_keys = button_matrix.buttons_pressed();
|
smoother[GIMBAL_AXIS_LEFT_X].tick(left_x as i32);
|
||||||
mode = get_mode(pressed_keys);
|
smoother[GIMBAL_AXIS_LEFT_Y].tick(left_y as i32);
|
||||||
|
smoother[GIMBAL_AXIS_RIGHT_X].tick(right_x as i32);
|
||||||
// Update pressed keys status
|
smoother[GIMBAL_AXIS_RIGHT_Y].tick(right_y as i32);
|
||||||
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)
|
|
||||||
{
|
|
||||||
idle = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Generate led activity when gimbal is moved from idle position
|
|
||||||
for item in axis.iter_mut() {
|
|
||||||
if item.value != item.previous_value {
|
|
||||||
activity = true;
|
|
||||||
}
|
|
||||||
item.previous_value = item.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Generate led activity when a button is pressed
|
|
||||||
// FnL, FnR, and ModeR are excluded
|
|
||||||
for (index, key) in buttons.iter_mut().enumerate() {
|
|
||||||
if (usb_active
|
|
||||||
&& key.pressed != key.previous_pressed
|
|
||||||
&& 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::ModeR)
|
|
||||||
|| (!usb_active
|
|
||||||
&& key.pressed != key.previous_pressed
|
|
||||||
&& layout::ELRS_MAP[index] != layout::ElrsButton::NoEventIndicated)
|
|
||||||
{
|
|
||||||
activity = true;
|
|
||||||
}
|
|
||||||
key.previous_pressed = key.pressed;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if usb_dev.poll(&mut [&mut usb_hid_joystick]) {
|
|
||||||
usb_active = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if status_led_count_down.wait().is_ok() {
|
if status_led_count_down.wait().is_ok() {
|
||||||
@ -375,14 +358,16 @@ fn main() -> ! {
|
|||||||
&mut status_led,
|
&mut status_led,
|
||||||
&mut activity,
|
&mut activity,
|
||||||
&usb_active,
|
&usb_active,
|
||||||
|
&elrs_active,
|
||||||
&idle,
|
&idle,
|
||||||
&safety_check,
|
&safety_check,
|
||||||
|
&calibration_active,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Dont send USB HID joystick report if there is no activity
|
||||||
|
// This is to avoid preventing the computer from going to sleep
|
||||||
if usb_hid_report_count_down.wait().is_ok() && activity {
|
if usb_hid_report_count_down.wait().is_ok() && activity {
|
||||||
// Dont send USB HID joystick report if there is no activity
|
|
||||||
// This is to avoid preventing the computer from going to sleep
|
|
||||||
match usb_hid_joystick.device().write_report(&get_joystick_report(
|
match usb_hid_joystick.device().write_report(&get_joystick_report(
|
||||||
&mut buttons,
|
&mut buttons,
|
||||||
&mut axis,
|
&mut axis,
|
||||||
@ -398,12 +383,184 @@ fn main() -> ! {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check if all axis are in idle position and no buttons are pressed
|
// Check if all axis are in idle position and no buttons are pressed
|
||||||
if idle && !safety_check && !usb_active {
|
if idle && !safety_check && elrs_active {
|
||||||
safety_check = true;
|
safety_check = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Implement ELRS
|
if main_count_down.wait().is_ok() {
|
||||||
if elrs_count_down.wait().is_ok() && !usb_active && safety_check {}
|
// Secondary way to enter bootloader (pressing all left hands buttons except the hat
|
||||||
|
if button_matrix.buttons_pressed()[0]
|
||||||
|
&& button_matrix.buttons_pressed()[1]
|
||||||
|
&& button_matrix.buttons_pressed()[5]
|
||||||
|
&& button_matrix.buttons_pressed()[6]
|
||||||
|
&& button_matrix.buttons_pressed()[8]
|
||||||
|
&& button_matrix.buttons_pressed()[9]
|
||||||
|
{
|
||||||
|
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,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calibration of center position (pressing all right hands buttons except the hat
|
||||||
|
// switch)
|
||||||
|
if button_matrix.buttons_pressed()[3]
|
||||||
|
&& button_matrix.buttons_pressed()[4]
|
||||||
|
&& button_matrix.buttons_pressed()[10]
|
||||||
|
&& button_matrix.buttons_pressed()[11]
|
||||||
|
&& button_matrix.buttons_pressed()[13]
|
||||||
|
&& button_matrix.buttons_pressed()[14]
|
||||||
|
{
|
||||||
|
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 && button_matrix.buttons_pressed()[8] {
|
||||||
|
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 && button_matrix.buttons_pressed()[9] {
|
||||||
|
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 && button_matrix.buttons_pressed()[20] {
|
||||||
|
// for (index, item) in axis.iter_mut().enumerate() {
|
||||||
|
// let _ = eeprom.write_byte(index as u32 * 6, item.min as u8);
|
||||||
|
// let _ = eeprom.write_byte((index as u32 * 6) + 1, (item.min >> 8) as u8);
|
||||||
|
// let _ = eeprom.write_byte((index as u32 * 6) + 2, item.max as u8);
|
||||||
|
// let _ = eeprom.write_byte((index as u32 * 6) + 3, (item.max >> 8) as u8);
|
||||||
|
// let _ = eeprom.write_byte((index as u32 * 6) + 4, item.center as u8);
|
||||||
|
// let _ = eeprom.write_byte((index as u32 * 6) + 5, (item.center >> 8) as u8);
|
||||||
|
// }
|
||||||
|
// let _ = eeprom.write_byte(24, gimbal_mode);
|
||||||
|
calibration_active = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process axis values
|
||||||
|
for (index, item) in axis.iter_mut().enumerate() {
|
||||||
|
item.value = calculate_axis_value(
|
||||||
|
smoother[index].value() as u16,
|
||||||
|
item.min,
|
||||||
|
item.max,
|
||||||
|
item.center,
|
||||||
|
item.deadzone,
|
||||||
|
item.expo,
|
||||||
|
&expo_lut,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update pressed keys status
|
||||||
|
for (index, key) in button_matrix.buttons_pressed().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
|
||||||
|
mode = get_mode(button_matrix.buttons_pressed());
|
||||||
|
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 that 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)
|
||||||
|
|| (elrs_active
|
||||||
|
&& layout::ELRS_MAP[index] != layout::ElrsButton::NoEventIndicated)
|
||||||
|
{
|
||||||
|
idle = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate led activity when gimbal is moved from idle position
|
||||||
|
for item in axis.iter_mut() {
|
||||||
|
if item.value != item.previous_value {
|
||||||
|
activity = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate led activity when a button is pressed
|
||||||
|
// FnL, FnR, and ModeR are excluded
|
||||||
|
for (index, key) in buttons.iter_mut().enumerate() {
|
||||||
|
if (usb_active
|
||||||
|
&& key.pressed != key.previous_pressed
|
||||||
|
&& 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::ModeR)
|
||||||
|
|| (elrs_active
|
||||||
|
&& key.pressed != key.previous_pressed
|
||||||
|
&& layout::ELRS_MAP[index] != layout::ElrsButton::NoEventIndicated)
|
||||||
|
{
|
||||||
|
activity = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset channel locks when calibration is active
|
||||||
|
if calibration_active {
|
||||||
|
for lock_active in channel_locks.iter_mut() {
|
||||||
|
*lock_active = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send ELRS data
|
||||||
|
if elrs_active {
|
||||||
|
elrs_en_pin.set_high().unwrap();
|
||||||
|
elrs.send(get_elrs_channels(
|
||||||
|
&mut buttons,
|
||||||
|
&mut axis,
|
||||||
|
&mut channel_locks,
|
||||||
|
elrs_active,
|
||||||
|
));
|
||||||
|
} else {
|
||||||
|
elrs_en_pin.set_low().unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear axis status
|
||||||
|
for item in axis.iter_mut() {
|
||||||
|
item.previous_value = item.value;
|
||||||
|
}
|
||||||
|
// Clear key status
|
||||||
|
for key in buttons.iter_mut() {
|
||||||
|
key.previous_pressed = key.pressed;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -424,26 +581,42 @@ fn update_status_led<P, SM, I>(
|
|||||||
status_led: &mut Ws2812StatusLed<P, SM, I>,
|
status_led: &mut Ws2812StatusLed<P, SM, I>,
|
||||||
activity: &mut bool,
|
activity: &mut bool,
|
||||||
usb_active: &bool,
|
usb_active: &bool,
|
||||||
|
elrs_active: &bool,
|
||||||
axis_idle: &bool,
|
axis_idle: &bool,
|
||||||
safety_check: &bool,
|
safety_check: &bool,
|
||||||
|
calibration_active: &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 is active, flash the status LED green
|
||||||
|
if *calibration_active && status_led.get_mode() == StatusMode::Normal {
|
||||||
|
status_led.update(StatusMode::Off);
|
||||||
|
} else if *calibration_active && status_led.get_mode() != StatusMode::Normal {
|
||||||
|
status_led.update(StatusMode::Normal);
|
||||||
|
// If in ELRS mode and safety chack failed, flash status LED red
|
||||||
|
} else if *elrs_active && !*safety_check {
|
||||||
status_led.update(StatusMode::Warning);
|
status_led.update(StatusMode::Warning);
|
||||||
|
// If activity occurs, flash status LED blue
|
||||||
} else if *activity && status_led.get_mode() != StatusMode::Activity {
|
} else if *activity && status_led.get_mode() != StatusMode::Activity {
|
||||||
status_led.update(StatusMode::Activity);
|
status_led.update(StatusMode::Activity);
|
||||||
} else if *activity && status_led.get_mode() == StatusMode::Activity {
|
} else if *activity && status_led.get_mode() == StatusMode::Activity {
|
||||||
status_led.update(StatusMode::Off);
|
status_led.update(StatusMode::Off);
|
||||||
*activity = false;
|
*activity = false;
|
||||||
|
// If no activity but not in idle position, turn status LED steady blue
|
||||||
} else if !*axis_idle && status_led.get_mode() != StatusMode::Activity {
|
} else if !*axis_idle && status_led.get_mode() != StatusMode::Activity {
|
||||||
status_led.update(StatusMode::Activity);
|
status_led.update(StatusMode::Activity);
|
||||||
} else if *usb_active && status_led.get_mode() != StatusMode::Normal {
|
// Else device idle in USB mode, turn status LED steady green
|
||||||
|
} else if *axis_idle
|
||||||
|
&& *usb_active
|
||||||
|
&& !*elrs_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 device idle in ELRS mode, turn status LED steady orange
|
||||||
|
} else if *axis_idle && *elrs_active && status_led.get_mode() != StatusMode::Other {
|
||||||
status_led.update(StatusMode::Other);
|
status_led.update(StatusMode::Other);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -570,16 +743,16 @@ fn get_joystick_report(
|
|||||||
let mut hats: [u8; 4] = [0; 4];
|
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 key.pressed
|
||||||
&& layout::HID_MAP[key.fn_mode as usize][index] >= layout::HidButton::Hat1U
|
&& layout::HID_MAP[key.fn_mode as usize][index] >= layout::HidButton::H1U
|
||||||
&& layout::HID_MAP[key.fn_mode as usize][index] <= layout::HidButton::Hat4B
|
&& layout::HID_MAP[key.fn_mode as usize][index] <= layout::HidButton::H4B
|
||||||
{
|
{
|
||||||
hats[(layout::HID_MAP[key.fn_mode as usize][index] as usize
|
hats[(layout::HID_MAP[key.fn_mode as usize][index] as usize
|
||||||
- layout::HidButton::Hat1U as usize)
|
- layout::HidButton::H1U as usize)
|
||||||
/ 5] |= 1
|
/ 5] |= 1
|
||||||
<< ((layout::HID_MAP[key.fn_mode as usize][index] as usize
|
<< ((layout::HID_MAP[key.fn_mode as usize][index] as usize
|
||||||
- layout::HidButton::Hat1U as usize)
|
- layout::HidButton::H1U as usize)
|
||||||
- (5 * ((layout::HID_MAP[key.fn_mode as usize][index] as usize
|
- (5 * ((layout::HID_MAP[key.fn_mode as usize][index] as usize
|
||||||
- layout::HidButton::Hat1U as usize)
|
- layout::HidButton::H1U as usize)
|
||||||
/ 5)));
|
/ 5)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -671,15 +844,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 +882,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 +922,146 @@ 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],
|
||||||
|
channel_locks: &mut [bool; 12],
|
||||||
|
elrs_active: bool,
|
||||||
|
) -> [u16; 12] {
|
||||||
|
let mut channels: [u16; 12] = [ELRS_MIN; 12];
|
||||||
|
|
||||||
|
// Check and store trim values
|
||||||
|
let mut trim_active = false;
|
||||||
|
for (index, key) in matrix_keys.iter_mut().enumerate() {
|
||||||
|
if elrs_active
|
||||||
|
&& key.pressed
|
||||||
|
&& key.pressed != key.previous_pressed
|
||||||
|
&& layout::ELRS_MAP[index] >= layout::ElrsButton::CH1P
|
||||||
|
&& layout::ELRS_MAP[index] <= layout::ElrsButton::CH4P
|
||||||
|
&& axis[layout::ELRS_MAP[index] as usize - layout::ElrsButton::CH1P as usize].trim
|
||||||
|
< ELRS_CENTER as i16
|
||||||
|
{
|
||||||
|
axis[layout::ELRS_MAP[index] as usize - layout::ElrsButton::CH1P as usize].trim += 1;
|
||||||
|
trim_active = true;
|
||||||
|
} else if elrs_active
|
||||||
|
&& key.pressed
|
||||||
|
&& key.pressed != key.previous_pressed
|
||||||
|
&& layout::ELRS_MAP[index] >= layout::ElrsButton::CH1M
|
||||||
|
&& layout::ELRS_MAP[index] <= layout::ElrsButton::CH4M
|
||||||
|
&& axis[layout::ELRS_MAP[index] as usize - layout::ElrsButton::CH1M as usize].trim
|
||||||
|
> (0 - ELRS_CENTER as i16)
|
||||||
|
{
|
||||||
|
axis[layout::ELRS_MAP[index] as usize - layout::ElrsButton::CH1M as usize].trim -= 1;
|
||||||
|
trim_active = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check and reser trim values
|
||||||
|
for (index, key) in matrix_keys.iter_mut().enumerate() {
|
||||||
|
if elrs_active
|
||||||
|
&& !trim_active
|
||||||
|
&& key.pressed
|
||||||
|
&& key.pressed != key.previous_pressed
|
||||||
|
&& layout::ELRS_MAP[index] == layout::ElrsButton::CH12Z
|
||||||
|
{
|
||||||
|
axis[GIMBAL_AXIS_LEFT_X].trim = 0;
|
||||||
|
axis[GIMBAL_AXIS_LEFT_Y].trim = 0;
|
||||||
|
} else if elrs_active
|
||||||
|
&& !trim_active
|
||||||
|
&& key.pressed
|
||||||
|
&& key.pressed != key.previous_pressed
|
||||||
|
&& layout::ELRS_MAP[index] == layout::ElrsButton::CH34Z
|
||||||
|
{
|
||||||
|
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 locking button state for ELRS channel 5-12
|
||||||
|
for (index, key) in matrix_keys.iter_mut().enumerate() {
|
||||||
|
if key.pressed
|
||||||
|
&& layout::ELRS_MAP[index] as usize >= layout::ElrsButton::CH5ON as usize
|
||||||
|
&& layout::ELRS_MAP[index] as usize <= layout::ElrsButton::CH12ON as usize
|
||||||
|
{
|
||||||
|
channel_locks
|
||||||
|
[layout::ELRS_MAP[index] as usize - layout::ElrsButton::CH5ON as usize + 4] = true;
|
||||||
|
}
|
||||||
|
if key.pressed
|
||||||
|
&& layout::ELRS_MAP[index] as usize >= layout::ElrsButton::CH5OFF as usize
|
||||||
|
&& layout::ELRS_MAP[index] as usize <= layout::ElrsButton::CH12OFF as usize
|
||||||
|
{
|
||||||
|
channel_locks
|
||||||
|
[layout::ELRS_MAP[index] as usize - layout::ElrsButton::CH5OFF as usize + 4] =
|
||||||
|
false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update button state for ELRS channel 5-12
|
||||||
|
for (index, key) in matrix_keys.iter_mut().enumerate() {
|
||||||
|
if key.pressed
|
||||||
|
&& layout::ELRS_MAP[index] as usize >= layout::ElrsButton::CH5 as usize
|
||||||
|
&& layout::ELRS_MAP[index] as usize <= layout::ElrsButton::CH12 as usize
|
||||||
|
{
|
||||||
|
channels[layout::ELRS_MAP[index] as usize] = ELRS_MAX;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply locking to ELRS channel 5-12
|
||||||
|
for (index, lock_active) in channel_locks.iter().enumerate() {
|
||||||
|
if *lock_active {
|
||||||
|
channels[index] = ELRS_MAX;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
channels
|
||||||
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user