/* * Copyright (C) EdgeTX * * Based on code named * opentx - https://github.com/opentx/opentx * th9x - http://code.google.com/p/th9x * er9x - http://code.google.com/p/er9x * gruvin9x - http://code.google.com/p/gruvin9x * * License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ #include "usb_joystick.h" #include #include "hal/usb_driver.h" #include "edgetx.h" #include #define MAX_HID_REPORTDESC 160 #define MAX_HID_REPORT 80 uint8_t* _hidReportDesc = nullptr; uint8_t _hidReportDescSize = 0; uint8_t* _hidReport = nullptr; uint8_t _hidReportSize = 0; struct _usbJSData { uint16_t _usbLastChannelOutput[USBJ_MAX_JOYSTICK_CHANNELS]; uint8_t _usbChannelTimerActive[USBJ_MAX_JOYSTICK_CHANNELS]; uint8_t _usbChannelTimer[USBJ_MAX_JOYSTICK_CHANNELS]; uint8_t _usbJoystickButtonChannels[USBJ_MAX_JOYSTICK_CHANNELS]; uint8_t _usbJoystickAxisChannels[USBJ_MAX_JOYSTICK_CHANNELS]; }; static struct _usbJSData* _usbJS = nullptr; static uint8_t _buttonState[(USBJ_BUTTON_SIZE + 7) >> 3] = {}; static uint8_t _usbJoystickIfMode = 0; static uint8_t _usbJoystickCircularCut = 0; static uint8_t _usbJoystickButtonCount = 0; static uint8_t _usbJoystickAxisCount = 0; static uint8_t _usbJoystickAxisPairs[2][2] = {}; static int8_t _usbJoystickDpadChannel = -1; // clang-format off /* This USB HID endpoint report description defines a device with: * 24 digital buttons * 8 analog axes with 11bit resolution * remaining battery capacity Report packet described as C struct is: struct { uint8_t buttons1; //bit 0 - button 1, bit 1 - button 2, ..., mapped to channels 9-16, on if channel > 0 uint8_t buttons2; // mapped to channels 17-24, on if channel > 0 uint8_t buttons3; // mapped to channels 25-32, on if channel > 0 uint16_t X; //analog value, mapped to channel 1 uint16_t Y; //analog value, mapped to channel 2 uint16_t Z; //analog value, mapped to channel 3 uint16_t Rx; //analog value, mapped to channel 4 uint16_t Ry //analog value, mapped to channel 5 uint16_t Rz; //analog value, mapped to channel 6 uint16_t S1; //analog value, mapped to channel 7 uint16_t S2; //analog value, mapped to channel 8 uint8_t system; // remaining battery capacity } */ static const uint8_t HID_JOYSTICK_ReportDesc[] = { 0x05, 0x01, // USAGE_PAGE (Generic Desktop) 0x09, 0x05, // USAGE (Game Pad) 0xa1, 0x01, // COLLECTION (Application) 0x09, 0x01, // USAGE (Pointer) 0xa1, 0x00, // COLLECTION (Physical) 0x05, 0x09, // USAGE_PAGE (Button) 0x75, 0x01, // REPORT_SIZE (1) 0x14, // LOGICAL_MINIMUM (0) 0x25, 0x01, // LOGICAL_MAXIMUM (1) 0x19, 0x01, // USAGE_MINIMUM (Button 1) 0x29, 0x18, // USAGE_MAXIMUM (Button 24) 0x95, 0x18, // REPORT_COUNT (24) 0x81, 0x02, // INPUT (Data,Var,Abs) 0x05, 0x01, // USAGE_PAGE (Generic Desktop) 0x09, 0x30, // USAGE (X) 0x09, 0x31, // USAGE (Y) 0x09, 0x32, // USAGE (Z) 0x09, 0x33, // USAGE (Rx) 0x09, 0x34, // USAGE (Ry) 0x09, 0x35, // USAGE (Rz) 0x09, 0x36, // USAGE (Slider) 0x09, 0x37, // USAGE (Dial) 0x14, // LOGICAL_MINIMUM (0) 0x26, 0x00, 0x08, // LOGICAL_MAXIMUM (2048) 0x75, 0x10, // REPORT_SIZE (16) 0x95, 0x08, // REPORT_COUNT (8) 0x81, 0x02, // INPUT (Data,Var,Abs) 0xc0, // END_COLLECTION 0x05, 0x06, // USAGE_PAGE (Generic Dev Ctrls) 0x09, 0x20, // USAGE (Battery Strength) 0x14, // LOGICAL_MINIMUM (0) 0x25, 0x64, // LOGICAL_MAXIMUM (100) 0x75, 0x08, // REPORT_SIZE (8) 0x95, 0x01, // REPORT_COUNT (1) 0x81, 0x02, // INPUT (Data,Var,Abs) 0xc0 // END_COLLECTION }; // clang-format on // clang-format off /* This partial HID description defines an Android compliant dpad / hat switch https://source.android.com/docs/compatibility/14/android-14-cdd#726_game_controller_support In addtion the D-Pad keys are mapped for applications using custom HID raw processing. */ static const uint8_t HID_JOYSTICK_DpadDesc[] = { 0x14, // LOGICAL_MINIMUM 0 0x25, 0x07, // LOGICAL_MAXIMUM 7 0x34, // PHYSICAL_MINIMUM 0 0x46, 0x3B, 0x01, // PHYSICAL_MAXIMUM 315 0x75, 0x04, // REPORT_SIZE 4 Number of bits per field 0x95, 0x01, // REPORT_COUNT 1 Number of fields 0x05, 0x01, // USAGE_PAGE Generic Desktop Page 0x09, 0x39, // USAGE Hat switch (Dynamic Value) 0x65, 0x14, // UNIT Rotation in degrees [1° units] (4=System=English Rotation, 1=Rotation=Degrees) 0x81, 0x42, // INPUT (1 field x 4 bits) 1=Variable 1=Null 0x19, 0x90, // USAGE_MINIMUM 0x00010090 D-pad Up 0x29, 0x93, // USAGE_MAXIMUM 0x00010093 D-pad Left 0x25, 0x01, // LOGICAL_MAXIMUM 0x01 (1) 0x44, // PHYSICAL_MAXIMUM (0) 0x64, // UNIT No unit (0=None) 0x75, 0x01, // REPORT_SIZE 0x01 (1) Number of bits per field 0x95, 0x04, // REPORT_COUNT 0x04 (4) Number of fields 0x81, 0x02, // INPUT 0x00000002 (4 fields x 1 bit) 0=Data 1=Variable 0=Absolute 0=NoWrap 0=Linear 0=PrefState 0=NoNull 0=NonVolatile 0=Bitmap }; // clang-format on static bool isUniqueAxisType(int type) { return type < 6; } static bool isUniqueSimType(int type) { return true; } int usbJoystickExtMode() { return g_model.usbJoystickExtMode; } int usbJoystickActive() { return (usbPlugged() && getSelectedUsbMode() == USB_JOYSTICK_MODE); } int usbJoystickSettingsChanged() { static uint32_t settingsChecksum = 0; if (!usbJoystickActive()) return false; if ((_usbJoystickIfMode != g_model.usbJoystickIfMode) || (_usbJoystickCircularCut != g_model.usbJoystickCircularCut)) return true; uint32_t oldChecksum = settingsChecksum; settingsChecksum = hash((uint8_t*)&g_model.usbJoystickCh, sizeof(g_model.usbJoystickCh)); return oldChecksum != settingsChecksum; } int setupUSBJoystick() { static const uint8_t axisTypeCodes[USBJOYS_AXIS_LAST + 1] = { 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38}; static const uint8_t simTypeCodes[USBJOYS_SIM_LAST + 1] = { 0xb0, 0xb8, 0xba, 0xbb, 0xc4, 0xc5, 0xc8, 0x00}; if (_hidReportDesc == nullptr) { _hidReportDesc = (uint8_t*)malloc(MAX_HID_REPORTDESC); if (_hidReportDesc == nullptr) return false; } if (_hidReport == nullptr) { _hidReport = (uint8_t*)malloc(MAX_HID_REPORT); if (_hidReport == nullptr) return false; } // properties to detect change uint8_t oldHIDReportDescSize = _hidReportDescSize; uint32_t oldChecksum = hash(_hidReportDesc, _hidReportDescSize); // Init data memset(_hidReportDesc, 0, MAX_HID_REPORTDESC); _hidReportDescSize = 0; memset(_hidReport, 0, MAX_HID_REPORT); _hidReportSize = 0; if (!usbJoystickExtMode()) { // Classic USB Joystick report description memcpy(_hidReportDesc, HID_JOYSTICK_ReportDesc, sizeof(HID_JOYSTICK_ReportDesc)); _hidReportDescSize = sizeof(HID_JOYSTICK_ReportDesc); _hidReportSize = 20; } else { // Init data only required for advanced mode if (_usbJS == nullptr) { _usbJS = (struct _usbJSData*)malloc(sizeof(struct _usbJSData)); if (_usbJS == nullptr) return false; } memset(_buttonState, 0, ((USBJ_BUTTON_SIZE + 7) >> 3)); memset(_usbJS, 0, sizeof(struct _usbJSData)); memset(_usbJS->_usbLastChannelOutput, 0xff, sizeof(_usbJS->_usbLastChannelOutput)); memset(_usbJoystickAxisPairs, 0xff, sizeof(_usbJoystickAxisPairs)); // copy the model snapshot _usbJoystickIfMode = g_model.usbJoystickIfMode; _usbJoystickCircularCut = g_model.usbJoystickCircularCut; // prepeare for circularCutoutValue for (uint8_t i = 0; i < USBJ_MAX_JOYSTICK_CHANNELS; i++) { uint8_t mode = g_model.usbJoystickCh[i].mode; if (mode == USBJOYS_CH_AXIS) { uint8_t submode = g_model.usbJoystickCh[i].param; // save axis pairs - not pretty but "if" is even more unreadeable switch (submode) { case USBJOYS_AXIS_X: switch (_usbJoystickCircularCut) { case USBJOYS_CC_XYZrX: case USBJOYS_CC_XYrXrY: case USBJOYS_CC_XYZrZ: _usbJoystickAxisPairs[0][0] = i; break; default: break; } break; case USBJOYS_AXIS_Y: switch (_usbJoystickCircularCut) { case USBJOYS_CC_XYZrX: case USBJOYS_CC_XYrXrY: case USBJOYS_CC_XYZrZ: _usbJoystickAxisPairs[0][1] = i; break; default: break; } break; case USBJOYS_AXIS_Z: switch (_usbJoystickCircularCut) { case USBJOYS_CC_XYZrX: case USBJOYS_CC_XYZrZ: _usbJoystickAxisPairs[1][0] = i; break; default: break; } break; case USBJOYS_AXIS_RX: switch (_usbJoystickCircularCut) { case USBJOYS_CC_XYZrX: _usbJoystickAxisPairs[1][1] = i; break; case USBJOYS_CC_XYrXrY: _usbJoystickAxisPairs[1][0] = i; break; default: break; } break; case USBJOYS_AXIS_RY: switch (_usbJoystickCircularCut) { case USBJOYS_CC_XYrXrY: _usbJoystickAxisPairs[1][1] = i; break; default: break; } break; case USBJOYS_AXIS_RZ: switch (_usbJoystickCircularCut) { case USBJOYS_CC_XYZrZ: _usbJoystickAxisPairs[1][1] = i; break; default: break; } break; default: break; } } } // start with classic descriptor memcpy(_hidReportDesc, HID_JOYSTICK_ReportDesc, 6); _hidReportDescSize = 6; uint8_t pageCurrent = 0x01; // HID support for game buttons is tricky : // a) some applications simply count buttons and ignore button IDs // b) in addition to a) some reverse the HID descriptor order // c) yet others correctly use the button IDs // // USAGE_MINIMUM / USAGE_MAXIMUM might result in some ghost buttons // without mixer input but everyone seems to process that consistently _usbJoystickButtonCount = 0; int8_t buttonMaxId = -1; // HID support for axis is tricky : // a) some applications simply count axis // b) in addition to a) some reverse the HID descriptor order // c) some applications ignore axis with the same IDs // d) some applications first process plain axis and then sim axis // e) some combine a), b), c) and d) in various ways // f) yet others correctly use the axis IDs // // advanced users know what they need // -> don't try to order axis // -> encode all axis (plain and sim) in one block // -> Dpad / hat switch gets special handling any way _usbJoystickAxisCount = 0; _usbJoystickDpadChannel = -1; for (uint8_t channelIx = 0; channelIx < USBJ_MAX_JOYSTICK_CHANNELS; channelIx++) { int8_t mode = g_model.usbJoystickCh[channelIx].mode; uint8_t typeIx = g_model.usbJoystickCh[channelIx].param; uint8_t pageTarget = 0; uint8_t usage; if (mode == USBJOYS_CH_BUTTON) { uint8_t btn_num = g_model.usbJoystickCh[channelIx].btn_num; uint8_t bits = g_model.usbJoystickCh[channelIx].switch_npos; uint8_t id = btn_num + bits + 1; if (id > buttonMaxId) { buttonMaxId = id; } _usbJS->_usbJoystickButtonChannels[_usbJoystickButtonCount++] = channelIx; } else if (mode == USBJOYS_CH_AXIS && typeIx <= USBJOYS_AXIS_LAST) { usage = axisTypeCodes[typeIx]; pageTarget = 0x01; // Generic Desktop Page (0x01) } else if (mode == USBJOYS_CH_SIM && typeIx <= USBJOYS_SIM_LAST) { usage = simTypeCodes[typeIx]; if (usage == 0x00) { // it is the special dpad / hat switch _usbJoystickDpadChannel = channelIx; } else { pageTarget = 0x02; // USAGE_PAGE Simulation Controls Page (0x02) } } if (pageTarget) { if (_usbJoystickAxisCount < 1) { // USAGE (Pointer) _hidReportDesc[_hidReportDescSize++] = 0x09; _hidReportDesc[_hidReportDescSize++] = 0x01; // COLLECTION (Physical) _hidReportDesc[_hidReportDescSize++] = 0xa1; _hidReportDesc[_hidReportDescSize++] = 0x00; } if (pageCurrent != pageTarget) { // USAGE_PAGE _hidReportDesc[_hidReportDescSize++] = 0x05; _hidReportDesc[_hidReportDescSize++] = pageTarget; pageCurrent = pageTarget; } // USAGE _hidReportDesc[_hidReportDescSize++] = 0x09; _hidReportDesc[_hidReportDescSize++] = usage; _usbJS->_usbJoystickAxisChannels[_usbJoystickAxisCount++] = channelIx; } } if (_usbJoystickAxisCount) { // report count and input - DO end collection memcpy(_hidReportDesc + _hidReportDescSize, HID_JOYSTICK_ReportDesc + 43, 11); _hidReportDesc[_hidReportDescSize + 7] = _usbJoystickAxisCount; _hidReportDescSize += 11; _hidReportSize += _usbJoystickAxisCount * 2; } // // encode buttons // uint8_t joystickType; switch (_usbJoystickIfMode) { case USBJOYS_GAMEPAD: // 0x00010005 Game Pad (Application Collection) joystickType = 0x05; // USB HID Usage Tables 1.21 page 33 : at least 4 buttons buttonMaxId = buttonMaxId < 4 ? 4 : buttonMaxId; break; case USBJOYS_MULTIAXIS: // 0x00010008 Multi-axis Controller (Application Collection) joystickType = 0x08; break; default: // Joystick (Application Collection) joystickType = 0x04; // USB HID Usage Tables 1.21 page 33 : at least 2 buttons buttonMaxId = buttonMaxId < 2 ? 2 : buttonMaxId; break; } // set Apllication Collection's usage _hidReportDesc[3] = joystickType; if (buttonMaxId) { memcpy(_hidReportDesc + _hidReportDescSize, HID_JOYSTICK_ReportDesc + 10, 15); _hidReportDesc[_hidReportDescSize + 10] = buttonMaxId; _hidReportDesc[_hidReportDescSize + 12] = buttonMaxId; _hidReportDescSize += 15; } // pad remaining space if (buttonMaxId < USBJ_BUTTON_SIZE) { _hidReportDesc[_hidReportDescSize++] = 0x75; _hidReportDesc[_hidReportDescSize++] = USBJ_BUTTON_SIZE - buttonMaxId; _hidReportDesc[_hidReportDescSize++] = 0x95; _hidReportDesc[_hidReportDescSize++] = 0x01; _hidReportDesc[_hidReportDescSize++] = 0x81; _hidReportDesc[_hidReportDescSize++] = 0x03; } _hidReportSize += ((USBJ_BUTTON_SIZE + 7) / 8); // battery status memcpy(_hidReportDesc + _hidReportDescSize, HID_JOYSTICK_ReportDesc + 54, 13); _hidReportDescSize += 13; _hidReportSize += 1; // special dpad / hat switch if (0 <= _usbJoystickDpadChannel) { memcpy(_hidReportDesc + _hidReportDescSize, HID_JOYSTICK_DpadDesc, sizeof(HID_JOYSTICK_DpadDesc)); _hidReportDescSize += sizeof(HID_JOYSTICK_DpadDesc); _hidReportSize += 1; } // END_COLLECTION _hidReportDesc[_hidReportDescSize++] = 0xc0; // end of report desc } // compare with the old description if (_hidReportDescSize != oldHIDReportDescSize) return true; uint32_t newChecksum = hash(_hidReportDesc, _hidReportDescSize); return oldChecksum != newChecksum; } extern "C" struct usbReport_t usbReportDesc() { usbReport_t res = {_hidReportDesc, _hidReportDescSize}; return res; } static void setBatteryBits(int hid_pos) { // vBatMin / vBatMax are encoded with offsets 90 / 120 uint8_t percent = limit( 0, divRoundClosest((g_vbat100mV - g_eeGeneral.vBatMin - 90) * 100, g_eeGeneral.vBatMax - g_eeGeneral.vBatMin + 30), 100); _hidReport[hid_pos] = percent; } static void setDpadBits(int hid_pos, int channelIx) { // Android's POV encoding: // 0 = North // 1 = North-East // 2 = East // 3 = South-East // 4 = South // 5 = South-West // 6 = West // 7 = North-West // 8-15 = no direction int16_t value = channelOutputs[channelIx] + 1024; // a span of 120 results in overflow at the top range // a span of 121 results in a too small top range // -> use 120 and slightly enlarge top and button ranges if (value > 2039) { value = 7; } else { value = ((value - 3) / 120) % 9; } // add explicit D-Pad keys for // applications using HID raw processing const uint8_t NORTH = 0x10; const uint8_t SOUTH = 0x20; const uint8_t EAST = 0x40; const uint8_t WEST = 0x80; // clang-format off const uint8_t key_lookup[] = { 0 | NORTH, 1 | NORTH | EAST, 2 | EAST, 3 | SOUTH | EAST, 4 | SOUTH, 5 | SOUTH | WEST, 6 | WEST, 7 | NORTH | WEST, 8 }; // clang-format on _hidReport[hid_pos] = key_lookup[value & 0x0F]; } void usbClassicStateUpdate() { if (_hidReport == nullptr) return; memset(_hidReport, 0, MAX_HID_REPORT); // buttons _hidReport[0] = 0; _hidReport[1] = 0; _hidReport[2] = 0; for (int i = 0; i < 8; ++i) { if (channelOutputs[i + 8] > 0) { _hidReport[0] |= (1 << i); } if (channelOutputs[i + 16] > 0) { _hidReport[1] |= (1 << i); } if (channelOutputs[i + 24] > 0) { _hidReport[2] |= (1 << i); } } // analog values // uint8_t * p = HID_Buffer + 1; for (int i = 0; i < 8; ++i) { int16_t value = limit(0, channelOutputs[i] + 1024, 2048); _hidReport[i * 2 + 3] = static_cast(value & 0xFF); _hidReport[i * 2 + 4] = static_cast(value >> 8); } // battery values setBatteryBits(8 * 2 + 3); } static void setBtnBits(uint8_t ix, uint8_t value, uint8_t size) { for (uint8_t i = 0; i < size; i++) { uint8_t bitix = ix + i; if (bitix >= USBJ_BUTTON_SIZE) break; if (value & (1 << i)) { _buttonState[(bitix / 8)] |= 1 << (bitix % 8); } else { _buttonState[(bitix / 8)] &= ~(1 << (bitix % 8)); } } } static void toggleBtnBit(uint8_t bitix) { if (bitix >= USBJ_BUTTON_SIZE) return; _buttonState[(bitix / 8)] ^= 1 << (bitix % 8); } #define g_usbTmr10ms (*(uint8_t*)&g_tmr10ms) #define BTNPUSH_TIME 20 /* x10ms */ static int16_t circularCutoutValue(uint8_t chix) { int32_t value = channelOutputs[chix]; uint8_t chpair = 0xff; const int32_t limit = 1048576; /* 1024^2 */ if (chix == _usbJoystickAxisPairs[0][0]) chpair = _usbJoystickAxisPairs[0][1]; else if (chix == _usbJoystickAxisPairs[0][1]) chpair = _usbJoystickAxisPairs[0][0]; else if (chix == _usbJoystickAxisPairs[1][0]) chpair = _usbJoystickAxisPairs[1][1]; else if (chix == _usbJoystickAxisPairs[1][1]) chpair = _usbJoystickAxisPairs[1][0]; if (chpair != 0xff) { int32_t pval = channelOutputs[chpair]; int32_t sum = ((value * value) + (pval * pval)); if (sum > limit) { double ratio = sqrt((double)limit / (double)sum); value *= ratio; } } return value; } void usbStateUpdate() { if (_hidReport == nullptr) return; const uint8_t button_ix = _usbJoystickAxisCount * 2; const uint8_t axis_ix = 0; const uint8_t battery_ix = button_ix + ((USBJ_BUTTON_SIZE + 7) >> 3); memset(_hidReport, 0, MAX_HID_REPORT); uint8_t chix = 0; int16_t value = 0; uint8_t swpos = 0; uint8_t btnval = 0; for (uint8_t i = 0; i < _usbJoystickButtonCount; i++) { chix = _usbJS->_usbJoystickButtonChannels[i]; value = channelOutputs[chix] + 1024; if (value > 2047) value = 2047; else if (value < 0) value = 0; if (g_model.usbJoystickCh[chix].inversion) value = 2047 - value; swpos = g_model.usbJoystickCh[chix].switch_npos + 1; // Quantization of the values if ((g_model.usbJoystickCh[chix].param == USBJOYS_BTN_MODE_NORMAL) || (g_model.usbJoystickCh[chix].param == USBJOYS_BTN_MODE_ON_PULSE)) { if (swpos == 1) { btnval = (value > 1024); } else { btnval = static_cast(value) / (2048 / swpos); if (btnval >= swpos) btnval = swpos - 1; } } else if (g_model.usbJoystickCh[chix].param == USBJOYS_BTN_MODE_SW_EMU) { btnval = (value > 1024); } else if (g_model.usbJoystickCh[chix].param == USBJOYS_BTN_MODE_DELTA) { btnval = (value >> 6); } else if (g_model.usbJoystickCh[chix].param == USBJOYS_BTN_MODE_COMPANION) { if (swpos == 1 || swpos == 2) { btnval = (value > 1024); } else if (swpos == 3) { btnval = (value < 1024) ? 1 : (value > 1024) ? 2 : 0; } else { btnval = static_cast(value) / (2048 / swpos); if (btnval >= swpos) btnval = swpos - 1; } } // Channel Output value changed if ((_usbJS->_usbLastChannelOutput[chix] == 0xffff) || (_usbJS->_usbLastChannelOutput[chix] != btnval)) { if ((g_model.usbJoystickCh[chix].param == USBJOYS_BTN_MODE_NORMAL) || (g_model.usbJoystickCh[chix].param == USBJOYS_BTN_MODE_ON_PULSE)) { if (swpos == 1) { setBtnBits(g_model.usbJoystickCh[chix].btn_num, btnval, 1); } else { setBtnBits(g_model.usbJoystickCh[chix].btn_num, 1 << btnval, swpos); } // Timer if (g_model.usbJoystickCh[chix].param == USBJOYS_BTN_MODE_ON_PULSE) { _usbJS->_usbChannelTimerActive[chix] = 1; _usbJS->_usbChannelTimer[chix] = g_usbTmr10ms; } } else if (g_model.usbJoystickCh[chix].param == USBJOYS_BTN_MODE_SW_EMU) { if ((_usbJS->_usbLastChannelOutput[chix] != 0xffff) && (_usbJS->_usbLastChannelOutput[chix] < btnval)) { toggleBtnBit(g_model.usbJoystickCh[chix].btn_num); } } else if (g_model.usbJoystickCh[chix].param == USBJOYS_BTN_MODE_DELTA) { if (_usbJS->_usbLastChannelOutput[chix] != 0xffff) { if (_usbJS->_usbLastChannelOutput[chix] < btnval) { setBtnBits(g_model.usbJoystickCh[chix].btn_num, 2, 2); } else if (_usbJS->_usbLastChannelOutput[chix] > btnval) { setBtnBits(g_model.usbJoystickCh[chix].btn_num, 1, 2); } _usbJS->_usbChannelTimerActive[chix] = 1; _usbJS->_usbChannelTimer[chix] = g_usbTmr10ms; } } else if (g_model.usbJoystickCh[chix].param == USBJOYS_BTN_MODE_COMPANION) { if (swpos == 1 || swpos == 2) { setBtnBits(g_model.usbJoystickCh[chix].btn_num, btnval, 1); } else if (swpos == 3) { setBtnBits(g_model.usbJoystickCh[chix].btn_num, btnval, 2); } else { setBtnBits(g_model.usbJoystickCh[chix].btn_num, 1 << btnval, swpos); } } _usbJS->_usbLastChannelOutput[chix] = btnval; } // Timer check if ((_usbJS->_usbChannelTimerActive[chix]) && (((uint8_t)(g_usbTmr10ms - _usbJS->_usbChannelTimer[chix]) >= BTNPUSH_TIME))) { _usbJS->_usbChannelTimerActive[chix] = 0; if (g_model.usbJoystickCh[chix].param == USBJOYS_BTN_MODE_ON_PULSE) { setBtnBits(g_model.usbJoystickCh[chix].btn_num, 0, swpos); } else if (g_model.usbJoystickCh[chix].param == USBJOYS_BTN_MODE_DELTA) { setBtnBits(g_model.usbJoystickCh[chix].btn_num, 0, 2); } } } memcpy(_hidReport + button_ix, _buttonState, ((USBJ_BUTTON_SIZE + 7) >> 3)); for (uint8_t i = 0; i < _usbJoystickAxisCount; i++) { chix = _usbJS->_usbJoystickAxisChannels[i]; value = limit(0, circularCutoutValue(chix) + 1024, 2048); if (g_model.usbJoystickCh[chix].inversion) value = 2048 - value; _hidReport[i * 2 + axis_ix] = static_cast(value & 0xFF); _hidReport[i * 2 + axis_ix + 1] = static_cast(value >> 8); } // battery values setBatteryBits(battery_ix); // dpad switch if (0 <= _usbJoystickDpadChannel) { setDpadBits(battery_ix + 1, _usbJoystickDpadChannel); } } uint8_t usbReportSize() { return _hidReportSize; } struct usbReport_t usbReport() { if (!usbJoystickExtMode()) { // Classic USB Joystick report usbClassicStateUpdate(); } else { usbStateUpdate(); } usbReport_t res = {_hidReport, _hidReportSize}; return res; } void onUSBJoystickModelChanged() { if (!usbJoystickActive()) return; if (setupUSBJoystick()) { #if defined(STM32) && !defined(SIMU) usbJoystickRestart(); #endif } } int isUSBAxisCollision(uint8_t chIdx) { USBJoystickChData* cch = usbJChAddress(chIdx); // non axis if (cch->mode != USBJOYS_CH_AXIS) return false; // non-unique axis check if (!isUniqueAxisType(cch->param)) { return false; } // collision check for (int i = 0; i < USBJ_MAX_JOYSTICK_CHANNELS; i++) { if (i == chIdx) continue; USBJoystickChData* och = usbJChAddress(i); if (cch->mode != och->mode) continue; if (cch->param == och->param) { return true; } } return false; } int isUSBSimCollision(uint8_t chIdx) { USBJoystickChData* cch = usbJChAddress(chIdx); // non axis if (cch->mode != USBJOYS_CH_SIM) return false; // non-unique axis check if (!isUniqueSimType(cch->param)) { return false; } // collision check for (int i = 0; i < USBJ_MAX_JOYSTICK_CHANNELS; i++) { if (i == chIdx) continue; USBJoystickChData* och = usbJChAddress(i); if (cch->mode != och->mode) continue; if (cch->param == och->param) { return true; } } return false; } int isUSBBtnNumCollision(uint8_t chIdx) { USBJoystickChData* cch = usbJChAddress(chIdx); // non button if (cch->mode != USBJOYS_CH_BUTTON) return false; // collision check uint8_t cfirst = cch->btn_num; uint8_t clast = cch->lastBtnNum(); for (int i = 0; i < USBJ_MAX_JOYSTICK_CHANNELS; i++) { if (i == chIdx) continue; USBJoystickChData* och = usbJChAddress(i); if (och->mode != USBJOYS_CH_BUTTON) continue; uint8_t ofirst = och->btn_num; uint8_t olast = och->lastBtnNum(); // overlap if ((cfirst <= olast) && (ofirst <= clast)) return true; } return false; }