Added virtual axis

This commit is contained in:
Christoffer Martinsson 2025-05-28 20:38:42 +02:00
parent 89254b903f
commit 964c2cae0c
3 changed files with 67 additions and 63 deletions

View File

@ -213,17 +213,17 @@
<mxCell id="EkkbSOEkYUomZlU8jIUN-6" value="Noice" style="whiteSpace=wrap;html=1;aspect=fixed;fillStyle=hatch;fillColor=#8C1C1C;" vertex="1" parent="EkkbSOEkYUomZlU8jIUN-1">
<mxGeometry x="293" y="670" width="80" height="80" as="geometry" />
</mxCell>
<mxCell id="EkkbSOEkYUomZlU8jIUN-7" value="&lt;div style=&quot;&quot;&gt;&lt;font style=&quot;color: rgb(255, 255, 255);&quot;&gt;Gear&lt;/font&gt;&lt;/div&gt;" style="whiteSpace=wrap;html=1;aspect=fixed;fillStyle=cross-hatch;fillColor=#6D8764;" vertex="1" parent="EkkbSOEkYUomZlU8jIUN-1">
<mxCell id="EkkbSOEkYUomZlU8jIUN-7" value="&lt;div style=&quot;&quot;&gt;&lt;font style=&quot;color: rgb(255, 255, 255);&quot;&gt;TAP: Engine power&lt;/font&gt;&lt;/div&gt;&lt;div style=&quot;&quot;&gt;&lt;font style=&quot;color: rgb(255, 255, 255);&quot;&gt;HOLD: Full WP power&lt;/font&gt;&lt;/div&gt;" style="whiteSpace=wrap;html=1;aspect=fixed;fillStyle=auto;fillColor=default;" vertex="1" parent="EkkbSOEkYUomZlU8jIUN-1">
<mxGeometry x="293" y="570" width="80" height="80" as="geometry" />
</mxCell>
<mxCell id="EkkbSOEkYUomZlU8jIUN-8" value="Decoy" style="whiteSpace=wrap;html=1;aspect=fixed;fillStyle=hatch;fillColor=#8C1C1C;" vertex="1" parent="EkkbSOEkYUomZlU8jIUN-1">
<mxGeometry x="453" y="670" width="80" height="80" as="geometry" />
</mxCell>
<mxCell id="EkkbSOEkYUomZlU8jIUN-9" value="CPLD" style="whiteSpace=wrap;html=1;aspect=fixed;fillStyle=hatch;fillColor=#FF9933;" vertex="1" parent="EkkbSOEkYUomZlU8jIUN-1">
<mxCell id="EkkbSOEkYUomZlU8jIUN-9" value="Reset power" style="whiteSpace=wrap;html=1;aspect=fixed;fillStyle=auto;fillColor=default;" vertex="1" parent="EkkbSOEkYUomZlU8jIUN-1">
<mxGeometry x="453" y="570" width="80" height="80" as="geometry" />
</mxCell>
<mxCell id="EkkbSOEkYUomZlU8jIUN-10" value="&lt;div&gt;Ping&lt;/div&gt;" style="whiteSpace=wrap;html=1;aspect=fixed;fillStyle=auto;fillColor=default;" vertex="1" parent="EkkbSOEkYUomZlU8jIUN-1">
<mxGeometry x="697" y="290" width="80" height="80" as="geometry" />
<mxGeometry x="697" y="190" width="80" height="80" as="geometry" />
</mxCell>
<mxCell id="EkkbSOEkYUomZlU8jIUN-13" value="Next&lt;div&gt;SCM/NAV&lt;/div&gt;&lt;div&gt;mode&lt;/div&gt;" style="whiteSpace=wrap;html=1;aspect=fixed;fillStyle=auto;fillColor=default;" vertex="1" parent="EkkbSOEkYUomZlU8jIUN-1">
<mxGeometry x="543" y="470" width="80" height="80" as="geometry" />
@ -235,10 +235,10 @@
<mxGeometry x="453" y="780" width="80" height="80" as="geometry" />
</mxCell>
<mxCell id="EkkbSOEkYUomZlU8jIUN-16" value="&lt;div&gt;Fire/QT&lt;/div&gt;" style="whiteSpace=wrap;html=1;aspect=fixed;" vertex="1" parent="EkkbSOEkYUomZlU8jIUN-1">
<mxGeometry x="605" y="290" width="80" height="80" as="geometry" />
<mxGeometry x="697" y="290" width="80" height="80" as="geometry" />
</mxCell>
<mxCell id="EkkbSOEkYUomZlU8jIUN-17" value="Brake" style="whiteSpace=wrap;html=1;aspect=fixed;fillStyle=hatch;fillColor=#FF9933;" vertex="1" parent="EkkbSOEkYUomZlU8jIUN-1">
<mxGeometry x="697" y="190" width="80" height="80" as="geometry" />
<mxGeometry x="200" y="470" width="80" height="80" as="geometry" />
</mxCell>
<mxCell id="EkkbSOEkYUomZlU8jIUN-19" value="Up&lt;div&gt;rZ+&lt;/div&gt;" style="whiteSpace=wrap;html=1;aspect=fixed;fillStyle=dots;fillColor=#303030;" vertex="1" parent="EkkbSOEkYUomZlU8jIUN-1">
<mxGeometry x="48" y="290" width="80" height="80" as="geometry" />
@ -291,7 +291,7 @@
<mxPoint x="28" y="390" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="EkkbSOEkYUomZlU8jIUN-28" value="Reset head tracker&lt;div&gt;&lt;/div&gt;" style="whiteSpace=wrap;html=1;aspect=fixed;" vertex="1" parent="EkkbSOEkYUomZlU8jIUN-1">
<mxCell id="EkkbSOEkYUomZlU8jIUN-28" value="Request landing&lt;div&gt;&lt;/div&gt;" style="whiteSpace=wrap;html=1;aspect=fixed;" vertex="1" parent="EkkbSOEkYUomZlU8jIUN-1">
<mxGeometry x="373" y="190" width="80" height="80" as="geometry" />
</mxCell>
<mxCell id="EkkbSOEkYUomZlU8jIUN-29" value="" style="endArrow=none;html=1;rounded=0;" edge="1" parent="EkkbSOEkYUomZlU8jIUN-1">
@ -354,7 +354,7 @@
<mxCell id="EkkbSOEkYUomZlU8jIUN-44" value="&lt;div&gt;SCM&lt;/div&gt;&lt;div&gt;(Gun)&lt;/div&gt;" style="text;strokeColor=none;align=center;fillColor=none;html=1;verticalAlign=middle;whiteSpace=wrap;rounded=0;" vertex="1" parent="EkkbSOEkYUomZlU8jIUN-1">
<mxGeometry x="413" y="470" width="60" height="80" as="geometry" />
</mxCell>
<mxCell id="EkkbSOEkYUomZlU8jIUN-45" value="&lt;div&gt;Power Engine&lt;/div&gt;&lt;div&gt;Power Reset&lt;/div&gt;&lt;div&gt;Power Weapons&lt;/div&gt;&lt;div&gt;Power Sheilds&lt;/div&gt;&lt;div&gt;Brake&lt;/div&gt;&lt;div&gt;&lt;br&gt;&lt;/div&gt;" style="text;strokeColor=none;align=left;fillColor=none;html=1;verticalAlign=middle;whiteSpace=wrap;rounded=0;" vertex="1" parent="EkkbSOEkYUomZlU8jIUN-1">
<mxCell id="EkkbSOEkYUomZlU8jIUN-45" value="&lt;div&gt;VTOL&lt;/div&gt;&lt;div&gt;Open/Close doors&lt;/div&gt;&lt;div&gt;CPLD&lt;/div&gt;&lt;div&gt;Reconfigure&lt;/div&gt;&lt;div&gt;Gears&lt;/div&gt;&lt;div&gt;&lt;br&gt;&lt;/div&gt;" style="text;strokeColor=none;align=left;fillColor=none;html=1;verticalAlign=middle;whiteSpace=wrap;rounded=0;" vertex="1" parent="EkkbSOEkYUomZlU8jIUN-1">
<mxGeometry x="328" y="910" width="110" height="90" as="geometry" />
</mxCell>
<mxCell id="EkkbSOEkYUomZlU8jIUN-46" value="&lt;div&gt;Target in sight&lt;/div&gt;&lt;div&gt;Target closest hostile&lt;/div&gt;&lt;div&gt;Target next friendly&lt;/div&gt;&lt;div&gt;Target next hostile&lt;/div&gt;&lt;div&gt;TH&lt;/div&gt;&lt;div&gt;&lt;br&gt;&lt;/div&gt;" style="text;strokeColor=none;align=left;fillColor=none;html=1;verticalAlign=middle;whiteSpace=wrap;rounded=0;" vertex="1" parent="EkkbSOEkYUomZlU8jIUN-1">
@ -370,18 +370,18 @@
<mxGeometry x="353" y="470" width="67" height="80" as="geometry" />
</mxCell>
<mxCell id="EkkbSOEkYUomZlU8jIUN-50" value="Left&lt;div&gt;rY-&lt;/div&gt;" style="whiteSpace=wrap;html=1;aspect=fixed;fillStyle=dots;fillColor=#303030;" vertex="1" parent="EkkbSOEkYUomZlU8jIUN-1">
<mxGeometry x="110" y="470" width="80" height="80" as="geometry" />
<mxGeometry x="140" y="290" width="80" height="80" as="geometry" />
</mxCell>
<mxCell id="EkkbSOEkYUomZlU8jIUN-51" value="Right&lt;div&gt;rY+&lt;/div&gt;" style="whiteSpace=wrap;html=1;aspect=fixed;fillColor=#303030;fillStyle=dots;" vertex="1" parent="EkkbSOEkYUomZlU8jIUN-1">
<mxGeometry x="200" y="470" width="80" height="80" as="geometry" />
<mxGeometry x="605" y="290" width="80" height="80" as="geometry" />
</mxCell>
<mxCell id="wdcyabzTlTJtnx-9P4dW-0" value="Down&lt;div&gt;rZ-&lt;/div&gt;" style="whiteSpace=wrap;html=1;aspect=fixed;fillStyle=dots;fillColor=#303030;" vertex="1" parent="EkkbSOEkYUomZlU8jIUN-1">
<mxGeometry x="48" y="190" width="80" height="80" as="geometry" />
</mxCell>
<mxCell id="wc2aKXT8cUdF5_0YO61A-0" value="Boost" style="whiteSpace=wrap;html=1;aspect=fixed;fillStyle=hatch;fillColor=#FF9933;" vertex="1" parent="EkkbSOEkYUomZlU8jIUN-1">
<mxGeometry x="140" y="290" width="80" height="80" as="geometry" />
<mxGeometry x="110" y="470" width="80" height="80" as="geometry" />
</mxCell>
<mxCell id="wc2aKXT8cUdF5_0YO61A-1" value="Missile" style="whiteSpace=wrap;html=1;aspect=fixed;fillColor=default;fillStyle=auto;" vertex="1" parent="EkkbSOEkYUomZlU8jIUN-1">
<mxCell id="wc2aKXT8cUdF5_0YO61A-1" value="Weapon&lt;div&gt;/&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;background-color: transparent; color: light-dark(rgb(0, 0, 0), rgb(255, 255, 255));&quot;&gt;Gear&amp;nbsp;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;background-color: transparent; color: light-dark(rgb(0, 0, 0), rgb(255, 255, 255));&quot;&gt;select&lt;/span&gt;&lt;/div&gt;" style="whiteSpace=wrap;html=1;aspect=fixed;fillColor=default;fillStyle=auto;" vertex="1" parent="EkkbSOEkYUomZlU8jIUN-1">
<mxGeometry x="633" y="470" width="80" height="80" as="geometry" />
</mxCell>
</root>

View File

@ -251,6 +251,9 @@ fn main() -> ! {
let mut axis: [GimbalAxis; NBR_OF_GIMBAL_AXIS] = [Default::default(); NBR_OF_GIMBAL_AXIS];
let mut buttons: [Button; NUMBER_OF_BUTTONS + 2] = [Button::default(); NUMBER_OF_BUTTONS + 2];
let mut virtual_ry: i16 = 0;
let mut virtual_rz: i16 = 0;
let mut virtual_step: i16 = 512;
let mut gimbal_mode: u8;
// HW Button index map:
@ -281,7 +284,7 @@ fn main() -> ! {
// ---------------------------------------------------------------
// USB HID joystick map :
// ---------------------------------------------------------------
// | B4 L| B3 U| B7 U| | B26 | | B8 U| B1 U| B2 L|
// | Ry- L| Ry+ U| Rz- U| | B26 | | Rz+ U| B1 U| B2 L|
// ---------------------------------------------------------------
// | | B5 | B14 | B9 | | B10 | B15 | B6 | |
// | |
@ -560,6 +563,31 @@ fn main() -> ! {
axis[GIMBAL_AXIS_LEFT_Y].value = axis[GIMBAL_AXIS_LEFT_Y].hold;
}
// Update Virtual RY
if buttons[1].pressed && !buttons[0].pressed {
virtual_ry = virtual_ry.saturating_add(100);
usb_activity = true;
} else if buttons[0].pressed && !buttons[1].pressed {
virtual_ry = virtual_ry.saturating_sub(100);
usb_activity = true;
} else if virtual_ry != 0 && !buttons[1].pressed && !buttons[0].pressed {
// Optional: decay to center
virtual_ry = 0;
usb_activity = true;
}
// Update Virtual RZ
if buttons[25].pressed && !buttons[26].pressed {
virtual_rz = virtual_rz.saturating_add(100);
usb_activity = true;
} else if buttons[26].pressed && !buttons[25].pressed {
virtual_rz = virtual_rz.saturating_sub(100);
usb_activity = true;
} else if virtual_rz != 0 && !buttons[25].pressed && !buttons[26].pressed {
// Optional: decay to center
virtual_rz = 0;
usb_activity = true;
}
// Generate led activity when gimbal is moved from idle position
for item in axis.iter_mut() {
if item.value != item.previous_value {
@ -605,10 +633,12 @@ fn main() -> ! {
// Dont send USB HID joystick report if there is no activity
// This is to avoid preventing the computer from going to sleep
if usb_update_count_down.wait().is_ok() && usb_activity {
match usb_hid_joystick
.device()
.write_report(&get_joystick_report(&mut buttons, &mut axis))
{
match usb_hid_joystick.device().write_report(&get_joystick_report(
&mut buttons,
&mut axis,
&virtual_ry,
&virtual_rz,
)) {
Err(UsbHidError::WouldBlock) => {}
Ok(_) => {}
Err(e) => {
@ -663,11 +693,15 @@ fn update_status_led<P, SM, I>(
fn get_joystick_report(
matrix_keys: &mut [Button; NUMBER_OF_BUTTONS + 2],
axis: &mut [GimbalAxis; 4],
virtual_ry: &i16,
virtual_rz: &i16,
) -> JoystickReport {
let x: i16 = axis_12bit_to_i16(axis[GIMBAL_AXIS_LEFT_X].value);
let y: i16 = axis_12bit_to_i16(ADC_MAX - axis[GIMBAL_AXIS_LEFT_Y].value);
let z: i16 = axis_12bit_to_i16(axis[GIMBAL_AXIS_RIGHT_X].value);
let rx: i16 = axis_12bit_to_i16(ADC_MAX - axis[GIMBAL_AXIS_RIGHT_Y].value);
let ry: i16 = *virtual_ry;
let rz: i16 = *virtual_rz;
// Update button state for joystick buttons
let mut buttons: u32 = 0;
@ -687,50 +721,12 @@ fn get_joystick_report(
y,
z,
rx,
ry,
rz,
buttons,
}
}
/// Format hat value from 5 switches to USB HID coded value and button state
///
/// # Arguments
/// * `input` - Hat value coded as
/// bit 2-5: direction (U L R D)
/// bit 1: button state
/// 0 = not pressed
/// 1 = pressed
fn format_hat_value(input: u8) -> (u8, u8) {
const HAT_CENTER: u8 = 8; //8 or 15 (OS-dependent; usually 8)
const HAT_UP: u8 = 0;
const HAT_UP_RIGHT: u8 = 1;
const HAT_RIGHT: u8 = 2;
const HAT_DOWN_RIGHT: u8 = 3;
const HAT_DOWN: u8 = 4;
const HAT_DOWN_LEFT: u8 = 5;
const HAT_LEFT: u8 = 6;
const HAT_UP_LEFT: u8 = 7;
let direction: u8 = match input & 0xFE {
2 => HAT_UP,
4 => HAT_RIGHT,
6 => HAT_UP_RIGHT,
8 => HAT_DOWN,
12 => HAT_DOWN_RIGHT,
16 => HAT_LEFT,
24 => HAT_DOWN_LEFT,
18 => HAT_UP_LEFT,
_ => HAT_CENTER,
};
// Alpine hat switch button filter
let mut button_state: u8 = 0;
if input & 0x01 == 0x01 && direction == HAT_CENTER {
button_state = 1;
}
(direction, button_state)
}
/// Calculate value for joystick axis
///
/// # Arguments

View File

@ -67,15 +67,17 @@ pub const JOYSTICK_DESCRIPTOR: &[u8] = &[
0x09, 0x04, // Usage (Joystick)
0xA1, 0x01, // Collection (Application)
// 4 signed 16-bit axes: X, Y, Z, Rx
// 6 signed 16-bit axes: X, Y, Z, Rx, Ry, Rz
0x09, 0x30, // Usage (X)
0x09, 0x31, // Usage (Y)
0x09, 0x32, // Usage (Z)
0x09, 0x33, // Usage (Rx)
0x09, 0x34, // Usage (Ry)
0x09, 0x35, // Usage (Rz)
0x16, 0x00, 0x80, // Logical Minimum (-32768)
0x26, 0xFF, 0x7F, // Logical Maximum (32767)
0x75, 0x10, // Report Size (16)
0x95, 0x04, // Report Count (4)
0x95, 0x06, // Report Count (6)
0x81, 0x02, // Input (Data,Var,Abs)
// 26 Buttons (1-bit each)
@ -102,6 +104,8 @@ pub struct JoystickReport {
pub y: i16, // 16bit
pub z: i16, // 16bit
pub rx: i16, // 16bit
pub ry: i16, // 16bit
pub rz: i16, // 16bit
pub buttons: u32, // 32bit
}
@ -111,7 +115,7 @@ pub struct Joystick<'a, B: UsbBus> {
impl<B: UsbBus> Joystick<'_, B> {
pub fn write_report(&mut self, report: &JoystickReport) -> Result<(), UsbHidError> {
let mut data: [u8; 12] = [0; 12];
let mut data: [u8; 16] = [0; 16];
// Did not make the packed struct work, so doing it manually
data[0] = report.x as u8;
@ -122,10 +126,14 @@ impl<B: UsbBus> Joystick<'_, B> {
data[5] = (report.z >> 8) as u8;
data[6] = report.rx as u8;
data[7] = (report.rx >> 8) as u8;
data[8] = report.buttons as u8;
data[9] = (report.buttons >> 8) as u8;
data[10] = (report.buttons >> 16) as u8;
data[11] = (report.buttons >> 24) as u8;
data[8] = report.ry as u8;
data[9] = (report.ry >> 8) as u8;
data[10] = report.rz as u8;
data[11] = (report.rz >> 8) as u8;
data[12] = report.buttons as u8;
data[13] = (report.buttons >> 8) as u8;
data[14] = (report.buttons >> 16) as u8;
data[15] = (report.buttons >> 24) as u8;
self.interface
.write_report(&data)