/* * 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 #include "opentx.h" #include "hal/usb_driver.h" #include "usb_joystick.h" #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; #define USBJ_CC_XYZrX 1 #define USBJ_CC_XYrXrZ 2 static uint8_t _usbJoystickButtonCount = 0; static uint8_t _usbJoystickAxisCount = 0; static uint8_t _usbJoystickAxisPairs[3][2] = { }; /* This USB HID endpoint report description defines a device with: * 24 digital buttons * 8 analog axes with 16bit 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) 0x19, 0x01, // USAGE_MINIMUM (Button 1) 0x29, 0x18, // USAGE_MAXIMUM (Button 24) 0x14, // LOGICAL_MINIMUM (0) 0x25, 0x01, // LOGICAL_MAXIMUM (1) 0x95, 0x18, // REPORT_COUNT (24) 0x75, 0x01, // REPORT_SIZE (1) 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, 0xFF, 0x07, // LOGICAL_MAXIMUM (2047) 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 }; 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 }; 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; } if (_usbJS == nullptr) { _usbJS = (struct _usbJSData*)malloc(sizeof(struct _usbJSData)); if (_usbJS == nullptr) return false; } // properties to detect change uint8_t oldHIDReportDescSize = _hidReportDescSize; uint32_t oldChecksum = hash(_hidReportDesc, _hidReportDescSize); // copy the model snapshot _usbJoystickIfMode = g_model.usbJoystickIfMode; _usbJoystickCircularCut = g_model.usbJoystickCircularCut; // Init data memset(_hidReportDesc, 0, MAX_HID_REPORTDESC); _hidReportDescSize = 0; memset(_hidReport, 0, MAX_HID_REPORT); _hidReportSize = 0; memset(_buttonState, 0, ((USBJ_BUTTON_SIZE+7) >> 3)); memset(_usbJS, 0, sizeof(struct _usbJSData)); memset(_usbJS->_usbLastChannelOutput, 0xff, sizeof(_usbJS->_usbLastChannelOutput)); _usbJoystickButtonCount = 0; _usbJoystickAxisCount = 0; memset(_usbJoystickAxisPairs, 0xff, sizeof(_usbJoystickAxisPairs)); if (!usbJoystickExtMode()) { // Classic USB Joystick report description memcpy(_hidReportDesc, HID_JOYSTICK_ReportDesc, sizeof(HID_JOYSTICK_ReportDesc)); _hidReportDescSize = sizeof(HID_JOYSTICK_ReportDesc); _hidReportSize = 20; } else { uint8_t genAxisCount = 0; uint8_t simAxisCount = 0; uint8_t buttonCount = 0; // sort channels by type uint8_t typeCount[USBJOYS_CH_LAST + 1] = { }; uint8_t axisCount[USBJOYS_AXIS_LAST + 1] = { }; uint8_t simCount[USBJOYS_SIM_LAST + 1] = { }; uint8_t axisIx[USBJOYS_AXIS_LAST + 1] = { }; uint8_t simIx[USBJOYS_SIM_LAST + 1] = { }; uint8_t mode = 0; uint8_t submode = 0; for (uint8_t i = 0; i < USBJ_MAX_JOYSTICK_CHANNELS; i++) { mode = g_model.usbJoystickCh[i].mode; if (g_model.usbJoystickCh[i].mode == USBJOYS_CH_BUTTON) { buttonCount += g_model.usbJoystickCh[i].btnCount(); typeCount[mode]++; } else if (g_model.usbJoystickCh[i].mode == USBJOYS_CH_AXIS) { submode = g_model.usbJoystickCh[i].param; if (submode <= USBJOYS_AXIS_LAST) { if (!isUniqueAxisType(submode) || (axisCount[submode] == 0)) { axisCount[submode]++; typeCount[mode]++; } } } else if (g_model.usbJoystickCh[i].mode == USBJOYS_CH_SIM) { submode = g_model.usbJoystickCh[i].param; if (submode <= USBJOYS_SIM_LAST) { if (!isUniqueSimType(submode) || (simCount[submode] == 0)) { simCount[submode]++; typeCount[mode]++; } } } } uint8_t ixcount = 0; for (uint8_t i = 0; i <= USBJOYS_AXIS_LAST; i++) { axisIx[i] = ixcount; ixcount += axisCount[i]; } ixcount = 0; for (uint8_t i = 0; i <= USBJOYS_SIM_LAST; i++) { simIx[i] = ixcount; ixcount += simCount[i]; } ixcount = 0; for (uint8_t i = 0; i < USBJ_MAX_JOYSTICK_CHANNELS; i++) { mode = g_model.usbJoystickCh[i].mode; if (g_model.usbJoystickCh[i].mode == USBJOYS_CH_BUTTON) { _usbJS->_usbJoystickButtonChannels[ixcount] = i; ixcount++; } else if (g_model.usbJoystickCh[i].mode == USBJOYS_CH_AXIS) { submode = g_model.usbJoystickCh[i].param; if (submode <= USBJOYS_AXIS_LAST) { _usbJS->_usbJoystickAxisChannels[axisIx[submode]] = i; if (!isUniqueAxisType(submode)) { axisIx[submode]++; } // save axis pairs if(submode == USBJOYS_AXIS_X) { _usbJoystickAxisPairs[0][0] = i; } else if(submode == USBJOYS_AXIS_Y) { _usbJoystickAxisPairs[0][1] = i; } else if(submode == USBJOYS_AXIS_Z) { _usbJoystickAxisPairs[1][0] = i; } else if(submode == USBJOYS_AXIS_RX) { _usbJoystickAxisPairs[1][1] = i; _usbJoystickAxisPairs[2][0] = i; } else if(submode == USBJOYS_AXIS_RY) { _usbJoystickAxisPairs[2][1] = i; } } } else if (g_model.usbJoystickCh[i].mode == USBJOYS_CH_SIM) { submode = g_model.usbJoystickCh[i].param; if (submode <= USBJOYS_SIM_LAST) { _usbJS->_usbJoystickAxisChannels[simIx[submode]] = i; if (!isUniqueSimType(submode)) { simIx[submode]++; } } } } // store counts _usbJoystickButtonCount = typeCount[USBJOYS_CH_BUTTON]; genAxisCount = typeCount[USBJOYS_CH_AXIS]; simAxisCount = typeCount[USBJOYS_CH_SIM]; _usbJoystickAxisCount = typeCount[USBJOYS_CH_AXIS] + typeCount[USBJOYS_CH_SIM]; // generate report desc // USAGE_PAGE (Generic Desktop) memcpy(_hidReportDesc, HID_JOYSTICK_ReportDesc, 25); _hidReportDescSize = 25; // USAGE (Joystick=0x04, Gamepad=0x05, Multi-axis Controller=0x08) uint8_t joystickType; if (_usbJoystickIfMode == USBJOYS_GAMEPAD) { joystickType = 0x05; // USB HID Usage Tables 1.21 page 33 : at least 4 buttons buttonCount = buttonCount >= 4 ? buttonCount : 4; } else if (_usbJoystickIfMode == USBJOYS_MULTIAXIS) { joystickType = 0x08; } else { joystickType = 0x04; // USB HID Usage Tables 1.21 page 33 : at least 2 buttons buttonCount = buttonCount >= 2 ? buttonCount : 2; } _hidReportDesc[3] = joystickType; _hidReportDesc[15] = buttonCount; _hidReportDesc[20] = buttonCount; if (buttonCount == 0) _hidReportDescSize -= 15; // padding for missing buttons if (buttonCount < USBJ_BUTTON_SIZE) { _hidReportDesc[_hidReportDescSize++] = 0x75; _hidReportDesc[_hidReportDescSize++] = USBJ_BUTTON_SIZE - buttonCount; _hidReportDesc[_hidReportDescSize++] = 0x95; _hidReportDesc[_hidReportDescSize++] = 0x01; _hidReportDesc[_hidReportDescSize++] = 0x81; _hidReportDesc[_hidReportDescSize++] = 0x03; } // generic axis types if (genAxisCount > 0) { // USAGE_PAGE (Generic Desktop) _hidReportDesc[_hidReportDescSize++] = 0x05; _hidReportDesc[_hidReportDescSize++] = 0x01; for (uint8_t i = 0; i <= USBJOYS_AXIS_LAST; i++) { for (uint8_t cnt = 0; cnt < axisCount[i]; cnt++) { // USAGE (#) _hidReportDesc[_hidReportDescSize++] = 0x09; _hidReportDesc[_hidReportDescSize++] = axisTypeCodes[i]; } } memcpy(_hidReportDesc+_hidReportDescSize, HID_JOYSTICK_ReportDesc+43, 10); _hidReportDesc[_hidReportDescSize+7] = genAxisCount; _hidReportDescSize += 10; } // sim axis types if (simAxisCount > 0) { // USAGE_PAGE (Simulation Controls) _hidReportDesc[_hidReportDescSize++] = 0x05; _hidReportDesc[_hidReportDescSize++] = 0x02; for (uint8_t i = 0; i <= USBJOYS_SIM_LAST; i++) { for (uint8_t cnt = 0; cnt < simCount[i]; cnt++) { // USAGE (#) _hidReportDesc[_hidReportDescSize++] = 0x09; _hidReportDesc[_hidReportDescSize++] = simTypeCodes[i]; } } memcpy(_hidReportDesc+_hidReportDescSize, HID_JOYSTICK_ReportDesc+43, 10); _hidReportDesc[_hidReportDescSize+7] = simAxisCount; _hidReportDescSize += 10; } // END_COLLECTION _hidReportDesc[_hidReportDescSize++] = 0xc0; // battery status memcpy(_hidReportDesc+_hidReportDescSize, HID_JOYSTICK_ReportDesc+54, 13); _hidReportDescSize += 13; // END_COLLECTION _hidReportDesc[_hidReportDescSize++] = 0xc0; // end of report desc _hidReportSize = ((USBJ_BUTTON_SIZE+7) / 8) + (_usbJoystickAxisCount * 2) + 1; } //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; } 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 = channelOutputs[i] + 1024; if ( value > 2047 ) value = 2047; else if ( value < 0 ) value = 0; _hidReport[i*2 +3] = static_cast(value & 0xFF); _hidReport[i*2 +4] = static_cast((value >> 8) & 0x07); } //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(_usbJoystickCircularCut == USBJ_CC_XYZrX) { 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]; } else if(_usbJoystickCircularCut == USBJ_CC_XYrXrZ) { if(chix == _usbJoystickAxisPairs[0][0]) chpair = _usbJoystickAxisPairs[0][1]; else if(chix == _usbJoystickAxisPairs[0][1]) chpair = _usbJoystickAxisPairs[0][0]; else if(chix == _usbJoystickAxisPairs[2][0]) chpair = _usbJoystickAxisPairs[2][1]; else if(chix == _usbJoystickAxisPairs[2][1]) chpair = _usbJoystickAxisPairs[2][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 = 0; const uint8_t axis_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 = circularCutoutValue(chix) + 1024; if ( value > 2047 ) value = 2047; else if ( value < 0 ) value = 0; if (g_model.usbJoystickCh[chix].inversion) value = 2047 - value; _hidReport[i*2 + axis_ix] = static_cast(value & 0xFF); _hidReport[i*2 + axis_ix+1] = static_cast((value >> 8) & 0x07); } //battery values setBatteryBits(_usbJoystickAxisCount*2 + axis_ix); } 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; }