Как использовать защитный экран USB-хост с различными джойстиками HID

arduino-uno usb joystick

Некоторое время назад я купил хост-щит Sparkfun USB и использовал его с джойстиком Logitech Extreme 3D Pro. Для этого есть пример кода, с которым приятно и легко работать. Также приведен пример кода универсального USB-джойстика HID.

Моя цель — иметь возможность использовать любой джойстик со щитом. Все каналы на каждом из моих джойстиков обнаруживаются, но все они имеют проблемы, такие как неправильное сопоставление и не полный диапазон движения.

Я понимаю, что мне нужно использовать USBHID_desc, чтобы получить дескриптор USB HID (уникальный для каждого джойстика, верно?) и вставить эти числа куда-нибудь в код USBHIDJoystick, но я не знаю, куда поместить эту информацию.

Моя проблема сводится к тому, как мне использовать данные, восстановленные из программы USBHID_desc.

Буду признателен за любую помощь, так как я немного растерялся

Спасибо

Дескриптор отчета для Random Flight Stick

Start
0000: 05 01 09 04 A1 01 09 01 A1 00 05 01 09 30 09 31 
0010: 15 00 26 FF 03 35 00 46 FF 03 65 00 75 0A 95 02 
0020: 81 02 09 35 09 32 15 00 26 FF 01 35 00 46 FF 01 
0030: 65 00 75 09 95 02 81 02 75 01 95 02 81 01 09 39 
0040: 15 01 25 08 35 00 46 3B 01 65 14 75 08 95 01 81 
0050: 02 05 09 19 01 29 06 15 00 25 01 35 00 45 01 75 
0060: 01 95 06 81 02 75 01 95 0A 81 01 C0 C0 
Usage Page Gen Desktop Ctrls(01)
Usage Game Pad
Collection Application
Usage Pointer
Collection Physical
Usage Page Gen Desktop Ctrls(01)
Usage X
Usage Y
Logical Min(00)
Logical Max(FF03)
Physical Min(00)
Physical Max(FF03)
Unit(00)
Report Size(0A)
Report Count(02)
Input(00000010)
Usage Rz
Usage Z
Logical Min(00)
Logical Max(FF01)
Physical Min(00)
Physical Max(FF01)
Unit(00)
Report Size(09)
Report Count(02)
Input(00000010)
Report Size(01)
Report Count(02)
Input(00000001)
Usage Hat Switch
Logical Min(01)
Logical Max(08)
Physical Min(00)
Physical Max(3B01)
Unit(14)
Report Size(08)
Report Count(01)
Input(00000010)
Usage Page Button(09)
Usage Min(01)
Usage Max(06)
Logical Min(00)
Logical Max(01)
Physical Min(00)
Physical Max(01)
Report Size(01)
Report Count(06)
Input(00000010)
Report Size(01)
Report Count(0A)
Input(00000001)
End Collection
End Collection Game Pad Pointer X Y(02)(08)
 Rz Z(00)(E3)
(00)(00)
 Hat Switch(00)
 Btn0001
(00) Btn0002
(01) Btn0003
(00) Btn0004
(00) Btn0005
(00) Btn0006
(00)
(00)(00)(00)(00)(00)(00)(00)(00)(00)(00)

USB-джойстик HID .INO

#include <usbhid.h>
#include <hiduniversal.h>
#include <usbhub.h>

// Удовлетворить IDE, которой нужно только увидеть статус включения в файле ino.
#ifdef dobogusinclude
#include <spi4teensy3.h>
#endif
#include <SPI.h>

#include "hidjoystickrptparser.h"

USB Usb;
USBHub Hub(&Usb);
HIDUniversal Hid(&Usb);
JoystickEvents JoyEvents;
JoystickReportParser Joy(&JoyEvents);

void setup() {
        Serial.begin(115200);
#if !defined(__MIPSEL__)
        while (!Serial); // Дождитесь подключения последовательного порта — используется на платах Leonardo, Teensy и других со встроенным последовательным соединением USB CDC
#endif
        Serial.println("Start");

        if (Usb.Init() == -1)
                Serial.println("OSC did not start.");

        delay(200);

        if (!Hid.SetReportParser(0, &Joy))
                ErrorMessage<uint8_t > (PSTR("SetReportParser"), 1);
}

void loop() {
        Usb.Task();
}

USB-джойстик HID .cpp

#include "hidjoystickrptparser.h"

JoystickReportParser::JoystickReportParser(JoystickEvents *evt) :
joyEvents(evt),
oldHat(0xDE),
oldButtons(0) {
        for (uint8_t i = 0; i < RPT_GEMEPAD_LEN; i++)
                oldPad[i] = 0xD;
}

void JoystickReportParser::Parse(USBHID *hid, bool is_rpt_id, uint8_t len, uint8_t *buf) {
        bool match = true;

        // Проверяем, есть ли изменения в отчете с момента последнего вызова метода
        for (uint8_t i = 0; i < RPT_GEMEPAD_LEN; i++)
                if (buf[i] != oldPad[i]) {
                        match = false;
                        break;
                }

        // Вызов обработчика событий Game Pad
        if (!match && joyEvents) {
                joyEvents->OnGamePadChanged((const GamePadEventData*)buf);

                for (uint8_t i = 0; i < RPT_GEMEPAD_LEN; i++) oldPad[i] = buf[i];
        }

        uint8_t hat = (buf[5] & 0xF);

        // Вызов обработчика события Hat Switch
        if (hat != oldHat && joyEvents) {
                joyEvents->OnHatSwitch(hat);
                oldHat = hat;
        }

        uint16_t buttons = (0x0000 | buf[6]);
        buttons <<= 4;
        buttons |= (buf[5] >> 4);
        uint16_t changes = (buttons ^ oldButtons);

        // Вызов обработчика событий кнопки для каждой измененной кнопки
        if (changes) {
                for (uint8_t i = 0; i < 0x0C; i++) {
                        uint16_t mask = (0x0001 << i);

                        if (((mask & changes) > 0) && joyEvents) {
                                if ((buttons & mask) > 0)
                                        joyEvents->OnButtonDn(i + 1);
                                else
                                        joyEvents->OnButtonUp(i + 1);
                        }
                }
                oldButtons = buttons;
        }
}

void JoystickEvents::OnGamePadChanged(const GamePadEventData *evt) {
        Serial.print("X1: ");
        PrintHex<uint8_t > (evt->X, 0x80);
        Serial.print("\tY1: ");
        PrintHex<uint8_t > (evt->Y, 0x80);
        Serial.print("\tX2: ");
        PrintHex<uint8_t > (evt->Z1, 0x80);
        Serial.print("\tY2: ");
        PrintHex<uint8_t > (evt->Z2, 0x80);
        Serial.print("\tRz: ");
        PrintHex<uint8_t > (evt->Rz, 0x80);
        Serial.println("");
}

void JoystickEvents::OnHatSwitch(uint8_t hat) {
        Serial.print("Hat Switch: ");
        PrintHex<uint8_t > (hat, 0x80);
        Serial.println("");
}

void JoystickEvents::OnButtonUp(uint8_t but_id) {
        Serial.print("Up: ");
        Serial.println(but_id, DEC);
}

void JoystickEvents::OnButtonDn(uint8_t but_id) {
        Serial.print("Dn: ");
        Serial.println(but_id, DEC);
}

USB-джойстик HID .h

#if !defined(__HIDJOYSTICKRPTPARSER_H__)
#define __HIDJOYSTICKRPTPARSER_H__

#include <usbhid.h>

struct GamePadEventData {
        uint8_t X, Y, Z1, Z2, Rz;
};

class JoystickEvents {
public:
        virtual void OnGamePadChanged(const GamePadEventData *evt);
        virtual void OnHatSwitch(uint8_t hat);
        virtual void OnButtonUp(uint8_t but_id);
        virtual void OnButtonDn(uint8_t but_id);
};

#define RPT_GEMEPAD_LEN     5

class JoystickReportParser : public HIDReportParser {
        JoystickEvents *joyEvents;

        uint8_t oldPad[RPT_GEMEPAD_LEN];
        uint8_t oldHat;
        uint16_t oldButtons;

public:
        JoystickReportParser(JoystickEvents *evt);

        virtual void Parse(USBHID *hid, bool is_rpt_id, uint8_t len, uint8_t *buf);
};

#endif // __HIDJOYSTICKRPTPARSER_H__

Редактирование текущей проблемы: (Это было исправлено методом проб и ошибок в упакованной структуре)

X: 512 Y: 512 Hat Switch: 0 Twist: 768 Slider: 108 Buttons A: 0 Buttons B: 0
X: 512 Y: 512 Hat Switch: 0 Twist: 256 Slider: 108 Buttons A: 0 Buttons B: 0
X: 512 Y: 512 Hat Switch: 0 Twist: 768 Slider: 108 Buttons A: 0 Buttons B: 0
X: 512 Y: 512 Hat Switch: 0 Twist: 256 Slider: 108 Buttons A: 0 Buttons B: 0
X: 512 Y: 512 Hat Switch: 0 Twist: 768 Slider: 108 Buttons A: 0 Buttons B: 0
X: 512 Y: 512 Hat Switch: 0 Twist: 256 Slider: 108 Buttons A: 0 Buttons B: 0
X: 512 Y: 512 Hat Switch: 0 Twist: 768 Slider: 108 Buttons A: 0 Buttons B: 0
X: 512 Y: 512 Hat Switch: 0 Twist: 256 Slider: 108 Buttons A: 0 Buttons B: 0
X: 512 Y: 512 Hat Switch: 0 Twist: 768 Slider: 108 Buttons A: 0 Buttons B: 0
X: 512 Y: 512 Hat Switch: 0 Twist: 256 Slider: 108 Buttons A: 0 Buttons B: 0
X: 512 Y: 512 Hat Switch: 0 Twist: 768 Slider: 108 Buttons A: 0 Buttons B: 0

Редактировать для ошибки оператора if: Ниже приведен раздел того, к чему в значительной степени сводится моя проблема. Теперь я предполагаю, что структуры действуют аналогично словарям в python, что, я думаю, является огромным упрощением, поскольку они также работают как класс? Во всяком случае, я работал над тем, чтобы получить первую структуру с данными для определенного джойстика, а затем использовать данные из этого первого для заполнения второго, который будет использоваться несколько раз в коде, но я думаю, что это худший метод то, что у меня есть ниже, и я все равно не смог заставить его работать.

.cpp

#include "le3dp_rptparser.h"
#define __HIDJOYSTICKRPTPARSER_H__

#include <usbhid.h>

struct LogitechGamePro
{
  union { //оси и переключатель хижины
    uint32_t axes;
    struct {
      uint32_t x : 10;
      uint32_t y : 10;
      uint32_t twist : 9;
      uint32_t slider : 10;   
    };
  };
  uint8_t hat;
  uint8_t buttons_a;
  uint8_t buttons_b;
};



struct GamePadEventData
{
  union { //оси и переключатель хижины
    uint32_t axes;
    struct {
      uint32_t x : 10;
      uint32_t y : 10;
      uint32_t twist : 9;
      uint32_t slider : 10;   
    };
  };
  uint8_t hat;
  uint8_t buttons_a;
  uint8_t buttons_b;
};





JoystickReportParser::JoystickReportParser(JoystickEvents *evt) :
    joyEvents(evt)
{}

void JoystickReportParser::Parse(USBHID *hid, bool is_rpt_id, uint8_t len, uint8_t *buf)
{

  
    
    bool match = true;

    // Проверяем, есть ли изменения в отчете с момента последнего вызова метода
    for (uint8_t i=0; i<RPT_GAMEPAD_LEN; i++) {
        if( buf[i] != oldPad[i] ) {
            match = false;
            break;
        }
  }
    // Вызов обработчика событий Game Pad
    if (!match && joyEvents) {
        joyEvents->OnGamePadChanged((const GamePadEventData*)buf);

        for (uint8_t i=0; i<RPT_GAMEPAD_LEN; i++) oldPad[i] = buf[i];
    }
 
}



void JoystickEvents::OnGamePadChanged(const GamePadEventData *evt)
{
    Serial.print("X: ");
    PrintHex<uint16_t>(evt->x, 0x80);
    Serial.print(" Y: ");
    PrintHex<uint16_t>(evt->y, 0x80);
    Serial.print(" Hat Switch: ");
    PrintHex<uint8_t>(evt->hat, 0x80);
    Serial.print(" Twist: ");
    PrintHex<uint8_t>(evt->twist, 0x80);
    Serial.print(" Slider: ");
    PrintHex<uint8_t>(evt->slider, 0x80);
  Serial.print(" Buttons A: ");
    PrintHex<uint8_t>(evt->buttons_a, 0x80);
    Serial.print(" Buttons B: ");
    PrintHex<uint8_t>(evt->buttons_b, 0x80);
    Serial.println("");
}

.h

#if !defined(__HIDJOYSTICKRPTPARSER_H__)
#define __HIDJOYSTICKRPTPARSER_H__

#include <usbhid.h>

struct GamePadEventData {};


struct ST290 {};
struct LogitechGamePro {};


class JoystickEvents
{
public:
    virtual void OnGamePadChanged(const GamePadEventData *evt);
};

#define RPT_GAMEPAD_LEN sizeof(GamePadEventData)/sizeof(uint8_t)

class JoystickReportParser : public HIDReportParser
{
    JoystickEvents      *joyEvents;

  uint8_t oldPad[RPT_GAMEPAD_LEN];

public:
    JoystickReportParser(JoystickEvents *evt);

    virtual void Parse(USBHID *hid, bool is_rpt_id, uint8_t len, uint8_t *buf);
};

#endif // __HIDJOYSTICKRPTPARSER_H__

Ошибка из примера кода 2:

sketch\le3dp_rptparser.cpp: In member function 'virtual void JoystickReportParser::Parse(USBHID*, bool, uint8_t, uint8_t*)':
le3dp_rptparser.cpp:36:54: error: no matching function for call to 'JoystickEvents::OnGamePadChanged(GamePadEventData&)'
             joyEvents->OnGamePadChanged(currentValues);
                                                      ^
In file included from sketch\le3dp_rptparser.cpp:1:0:
sketch\le3dp_rptparser.h:43:15: note: candidate: virtual void JoystickEvents::OnGamePadChanged(const GamePadEventData*)
  virtual void OnGamePadChanged(const GamePadEventData *evt);
               ^~~~~~~~~~~~~~~~
sketch\le3dp_rptparser.h:43:15: note:   no known conversion for argument 1 from 'GamePadEventData' to 'const GamePadEventData*'
sketch\le3dp_rptparser.cpp: At global scope:
le3dp_rptparser.cpp:46:6: error: prototype for 'void JoystickEvents::OnGamePadChanged(GamePadEventData*)' does not match any in class 'JoystickEvents'
 void JoystickEvents::OnGamePadChanged(GamePadEventData *evt)
      ^~~~~~~~~~~~~~
In file included from sketch\le3dp_rptparser.cpp:1:0:
le3dp_rptparser.h:43:15: error: candidate is: virtual void JoystickEvents::OnGamePadChanged(const GamePadEventData*)
  virtual void OnGamePadChanged(const GamePadEventData *evt);
               ^~~~~~~~~~~~~~~~
exit status 1
no matching function for call to 'JoystickEvents::OnGamePadChanged(GamePadEventData&)'

, 👍2


1 ответ


Лучший ответ:

3

Чтение и понимание дескриптора отчета иногда может быть чем-то вроде черной магии. На первый взгляд они довольно загадочны, но на самом деле в них есть смысл.

Если вы думаете о каждой записи (кроме "входных данных" и "выходных данных") как о настройке некоторого значения конфигурации, а "входные данные" и "выход" записей, поскольку они используют эти значения конфигурации, это имеет больше смысла.

Поскольку вы работаете только с геймпадами и джойстиками (по сути, одно и то же), большая часть дескриптора может быть проигнорирована (однако вам все равно придется анализировать его, чтобы добраться до других вещей). Итак, давайте возьмем ваш дескриптор примера построчно (или группу строк) и посмотрим, что они все означают и что они означают для вас.

Usage Page Gen Desktop Ctrls(01)
Usage Game Pad
Collection Application
Usage Pointer
Collection Physical
Usage Page Gen Desktop Ctrls(01)

С точки зрения данных вас это мало волнует, но они определяют, как интерпретируются следующие данные. Это означает, что "следующие данные предназначены для настройки положения указателя (направления, а не указателя мыши) геймпада.

Usage X
Usage Y

Это параметры, которые мы сейчас настраиваем (движение джойстика по осям X и Y). Мы группируем их вместе, потому что они имеют общие настройки и параметры. Эти параметры скоро появятся.

Logical Min(00)
Logical Max(FF03)
Physical Min(00)
Physical Max(FF03)

Это диапазон значений, о которых мы собираемся сообщать, и то, что этот диапазон означает. В этом случае существует сопоставление 1:1 между значениями, которые сообщает джойстик (логические), и тем, на что эти значения сопоставляются (физические). Позже мы увидим лучшее использование этого. Числа "с прямым порядком байтов", что означает, что "FF03" на самом деле "0x03FF", поэтому джойстик сообщает значения от 0 до 1023.

Unit(00)

Определяет физические единицы сообщаемого значения. В данном случае единиц нет. Мы снова увидим лучшее использование этого позже.

Report Size(0A)
Report Count(02)

Теперь мы перейдем к мельчайшим деталям отправляемых данных. Это говорит нам о том, что каждое значение отчета имеет размер 0x0A (10) бит, а их 2. Это 1 отчет для 10-битного значения X и 1 отчет для 10-битного значения Y (именно в таком порядке, поскольку это порядок строк "Использование" выше).

На данный момент у нас есть 20 бит данных в нашем отчете, которые расположены в байтах следующим образом:

XXXXXXXX
YYYYYYXX
....YYYY

В наших байтах еще не заполнены 4 бита, но не волнуйтесь, они скоро появятся. Далее идет "Ввод" запись:

Input(00000010)

Здесь фактически создаются данные в отчете в соответствии с настройками, которые мы установили выше. 00000010 говорит нам, что это значение данных переменной, которое отправляется.

Теперь у нас есть еще один аналогичный фрагмент для значений Z (дроссель) и Rz (поворот Z):

Usage Rz
Usage Z
Logical Min(00)
Logical Max(FF01)
Physical Min(00)
Physical Max(FF01)
Unit(00)
Report Size(09)
Report Count(02)
Input(00000010)

Это почти то же самое, что и выше, но цифры другие. У нас есть 2 использования Rz и Z (в таком порядке), и они сообщают значения от 0 до 0x01FF (511). Опять же, в этих числах нет единиц. Размер отчёта 9 бит (кстати, 29-1 это 511), и есть два отчёта, один для Rz и один для Z.

Итак, давайте добавим их в наши байты, 9 бит z (я буду использовать "z" для Rz) и 9 бит для Z:

XXXXXXXX
YYYYYYXX
zzzzYYYY
ZZZzzzzz
..ZZZZZZ

Видите ли, у нас еще есть два неиспользуемых бита в наших байтах. Отчеты HID не любят неиспользуемые биты, поэтому нам нужно дополнить их, прежде чем переходить к новой интересующей области:

Report Size(01)
Report Count(02)
Input(00000001)

Здесь мы определяем размер отчета 1 бит, и есть два отчета. Это 2 бита. Это наши биты заполнения, чтобы заполнить эти дополнительные два бита выше. Вы видите, что входная запись имеет другой номер. 00000001 говорит нам, что это постоянные данные. Это ничего не изменит — просто дополнение.

Теперь наш отчет HID выглядит так:

XXXXXXXX
YYYYYYXX
zzzzYYYY
ZZZzzzzz
00ZZZZZZ

Теперь мы переходим к "Переключателю шляпы", который чем-то похож, но не совсем:

Usage Hat Switch
Logical Min(01)
Logical Max(08)
Physical Min(00)
Physical Max(3B01)
Unit(14)
Report Size(08)
Report Count(01)
Input(00000010)

Здесь у нас есть 1 отчет из 8 бит. Мы отправляем значение от 1 до 8 для 8 направлений переключателя шляпы (у вас может быть только 1 направление за раз), и мы используем целый байт для отправки этого значения. Звучит расточительно, учитывая, что нам нужно только 4 бита для представления числа 8, но помните, что нам нужно будет дополнить остальные 4 бита постоянным значением, чтобы довести его до круглого байта. Так что в этом случае проще просто использовать весь байт целиком и избавить дескриптор от заполнения.

Вы заметили, что логические и физические минимальные и максимальные значения в этом разделе различаются. Это показывает вам, что вы бы сопоставили логические значения в "реальном мире" ситуация. В этом случае значения 1-8 сопоставляются с реальными значениями 0-315 (0x01b3). Вам решать, действительно ли вы делаете это сопоставление или нет. Хотя что это за цифры? Что ж, если вы разделите 315 на 8 (количество позиций шляпы), вы получите 45. И каждая позиция шляпы — это приращение в 45°. Поэтому они должны отображаться в градусах. И смотри, "Единица" hs значение 0x14 в нем. Если мы посмотрим на это, то увидим, что 0x14 соответствует «английской угловой позиции вращения» или «градусам». в обычной речи.

Теперь мы можем добавить это в формат нашего отчета:

XXXXXXXX
YYYYYYXX
zzzzYYYY
ZZZzzzzz
00ZZZZZZ
HHHHHHHH

И, наконец, у нас есть 6 кнопок:

Usage Page Button(09)
Usage Min(01)
Usage Max(06)
Logical Min(00)
Logical Max(01)
Physical Min(00)
Physical Max(01)
Report Size(01)
Report Count(06)
Input(00000010)

Нет "Кнопки 1" и т. д. в HID, но вы можете определить диапазон использования, что и было сделано здесь. По сути, поскольку мы находимся в "кнопках" На странице использования мы определяем диапазон от 1 до 6 для кнопок, то есть от кнопки 1 до кнопки 6 включительно.

Каждая кнопка имеет значение 0 или 1 (не нажата или не нажата), и это то же самое "В реальном мире". Каждая кнопка имеет 1-битный отчет, так как это все, что вам нужно, и есть 6 отчетов, по одному для каждой кнопки.

Снова мы используем для этого только 6 из 8 бит байта, поэтому мы дополняем его еще двумя постоянными битами:

Report Size(01)
Report Count(0A)
Input(00000001)

Поэтому, если мы сейчас добавим эти кнопки, мы получим окончательный формат отчета:

XXXXXXXX
YYYYYYXX
zzzzYYYY
ZZZzzzzz
00ZZZZZZ
HHHHHHHH
00654321

Итак, у вас есть 7 байтов данных, которые затем нужно правильно разделить и рекомбинировать со всеми видами битового сдвига и маскирования для извлечения реальных значений. И, конечно, это варьируется от джойстика к джойстику.

Как вы можете догадаться, написать парсер не только для самого HID-дескриптора, но и для извлечения данных в соответствии с этим дескриптором будет довольно сложной задачей. Для некоторых джойстиков все значения будут упакованы вместе, для некоторых — нет. Кому-то нужно будет читать в одном порядке, а кому-то в другом. У некоторых будет больше кнопок или больше осей движения, чем у других.

Вы можете прочитать спецификацию HID 1.11 здесь, которая расскажет вам все о том, как необработанные числа соответствуют разным вещам.

Существующий код синтаксического анализатора ищет вещи в жестко запрограммированных позициях во входящих данных и делает это очень простыми способами. Он никоим образом не учитывает дескриптор отчета, а вместо этого просто принимает каждый байт как ось.

Вам потребуется переписать всю эту функцию Parse() (или создать дочерний класс, который переопределяет ее), чтобы использовать информацию, которую вы проанализировали из дескриптора отчета, для правильного извлечения значений из байтов данных. Учитывая множество способов, которыми это можно сделать, перед вами будет стоять довольно сложная задача. Было бы проще просто поддерживать определенные джойстики и жестко запрограммировать для них параметры, возможно, создать отдельный подкласс для каждого джойстика, каждый со своей собственной переопределенной функцией Parse(). Затем вы можете использовать различные классы в своем коде по мере необходимости.


Чтобы решить другие проблемы:

  • В заголовочном файле есть определения для ваших структур:
struct LogitechGamePro
{
  union { //оси и переключатель хижины
    uint32_t axes;
    struct {
      uint32_t x : 10;
      uint32_t y : 10;
      uint32_t twist : 9;
      uint32_t slider : 10;   
    };
  };
  uint8_t hat;
  uint8_t buttons_a;
  uint8_t buttons_b;
};



struct GamePadEventData
{
  union { //оси и переключатель хижины
    uint32_t axes;
    struct {
      uint32_t x : 10;
      uint32_t y : 10;
      uint32_t twist : 9;
      uint32_t slider : 10;   
    };
  };
  uint8_t hat;
  uint8_t buttons_a;
  uint8_t buttons_b;
};

В файле .cpp используйте те, которые используются для приведения и заполнения:



void JoystickReportParser::Parse(USBHID *hid, bool is_rpt_id, uint8_t len, uint8_t *buf)
{

    struct GamePadEventData currentValues;
  
    
    bool match = true;

    // Checking if there are changes in report since the method was last called
    for (uint8_t i=0; i<RPT_GAMEPAD_LEN; i++) {
        if( buf[i] != oldPad[i] ) {
            match = false;
            break;
        }
    }
    // Calling Game Pad event handler
    if (!match && joyEvents) {
        // if (joystick is the logitech game pro) {
            // Overlay the struct on top of the data by casting it to
            // a new variable
            struct LogitechGamePro *jsdata = (LogitechGamePro *)buf;
 
            // Copy values from the temporary overlaid struct into
            // a real variable of the right type for the callback
            currentValues.x = jsdata->x;
            currentValues.y = jsdata->y;
            currentValues.z = jsdata->z;
            // etc

            // Call the callback with that real variable
            joyEvents->OnGamePadChanged(&currentValues);
        // } else if (joystick is some other joystick) {
        //     do the same as above with a different joystick struct
        // } ... etc ...

        for (uint8_t i=0; i<RPT_GAMEPAD_LEN; i++) oldPad[i] = buf[i];
    }
 
}
,

Большое спасибо, это помогло мне понять описание отчета. К сожалению, у меня нет навыков кодирования, чтобы переписать функцию синтаксического анализа для использования дескриптора отчета. Я предполагаю, что для изменения конкретных параметров для каждого джойстика мне нужно будет изменить struct GamePadEventData? я бы сделал что-то вроде этого: (из примера) uint32_t x : 10; uint32_t : 10; и изменить номера после? Я, вероятно, ошибаюсь, но соответствуют ли эти числа после диапазона каждой оси? (мин/макс), еще раз спасибо, @Vosem Media

Да, использование такой упакованной структуры — хороший способ извлечения данных., @Majenko

Здравствуйте, я использовал структуру, и она почти отлично работает. Единственная проблема заключается в том, что поворот колеблется между 256 и 768. Я отредактировал образец в своем исходном вопросе из серийного монитора. Это происходит всякий раз, когда слайд не находится на 0, и происходит гораздо быстрее, когда слайд находится в движении. Любой совет приветствуется спасибо, @Vosem Media

Хорошо, я разобрался с проблемой. Для тех, кто интересуется, у меня была немного неправильная упакованная структура. Метод проб и ошибок в течение минуты помог мне разобраться, но вместо этого вы можете сделать это с помощью дескриптора отчета., @Vosem Media

Моя проблема теперь заключается в выборе между структурами. Мой первоначальный план состоял в том, чтобы иметь массив для каждого типа джойстика, а затем выбирать, какой массив использовать для заполнения чисел для структуры. К сожалению, порядок каждой оси имеет значение, так что это не годится. Я не силен в c, но знаю основы. Как бы вы предложили переход между упакованными структурами? Спасибо, @Vosem Media

Вы, вероятно, захотите, чтобы большой if-then-else (или переключатель/случай) наложил структуру на массив байтов входящих данных (привел его), а затем скопировал значения из структуры в какую-то стандартную другую структуру/массив в использовать позже в вашей программе., @Majenko

А, ладно, спасибо, это проясняет., @Vosem Media

Эй, я пытаюсь обернуть структуру в оператор if, но это просто дает мне «ожидаемый неквалифицированный идентификатор перед «if». Есть идеи? Спасибо, @Vosem Media

Не без просмотра вашего кода., @Majenko

хороший момент, я отредактирую его в вопросе с более подробной информацией, @Vosem Media

Я добавил раздел простой версии того, что я пытаюсь сделать, с некоторыми деталями другого способа, которым я это сделал., @Vosem Media

@VosemMedia В этом фрагменте нет ничего плохого. Он компилируется нормально. И это совершенно правильный способ сделать это., @Majenko

Кажется, он не компилируется для меня, возможно, потому, что он находится в заголовочном файле? Я переместил заголовок/cpp в начало ino, но он все равно не скомпилировался. Я добавил большую часть своего кода. Спасибо., @Vosem Media

Ваш if должен быть внутри функции. Исполняемый код вне функции недействителен C., @Majenko

хм интересно, спасибо. Я добавил функцию void (отредактированную в исходном вопросе), и теперь она дает мне «GamePadEventData» не называет тип из файла .cpp. Любые дальнейшие идеи? Я не знаю, почему размещение его в операторе function/if вызывает эти проблемы. еще раз спасибо, @Vosem Media

Ваша структура существует только в той области, в которой она определена: в данном случае это функция, в которую вы ее только что обернули., @Majenko

поэтому я должен поместить все в этом файле в одну функцию?, @Vosem Media

Вы должны положить вещи в тех местах, где они необходимы. Сначала вы должны определить несколько структур с разными именами. Затем в декодере пакетов вы передаете входящие данные в одну из структур в зависимости от того, какой джойстик подключен. Затем вы копируете свои данные из этой структуры переменной приведения в свою структуру живых данных. Именно эти последние биты входят в if, а не в определение структуры., @Majenko

Итак, в моем файле .h я должен объявить несколько структур для каждого типа джойстика, а затем куда и как я должен переместить данные из выбранной структуры (например, джойстика logitech) в живую структуру. Должен ли я сделать это в файле cpp? если да, то должен ли я обернуть все области, требующие структуры, в одну функцию? Спасибо, @Vosem Media

Да, сделайте это в файле CPP. В функции разбора. Используйте приведение массива данных: if (joystick == LOGITECH_GAME_PRO) {struct LogitechGamePro *jsdata = (struct LogitechGamePro *)data; mydata.x = jsdata->x; ... } например., @Majenko

Спасибо за код, есть пара вопросов. Если я объявлю свои структуры в файле CPP и изменю данные в активной структуре в функции CPP, как функции .h получат к ней доступ? Это дает мне данные геймпада, не дает тип, предположительно потому, что он не определен в файле .h. Как .h может получить доступ к нему, определенному в cpp? Кроме того, я бы отредактировал ваш код, чтобы он выглядел примерно так: struct GamePadEventData (живой) *jsdata = (struct LogitechGamePro (тот, на который мы хотим изменить живой) *)data; mydata.x = jsdata->x; а также добавить больше, например, mydata.y = ..., @Vosem Media

Нет. GamePadEventData — это структура, определяющая переменную, которую вы хотите скопировать *в* (в этом примере — mydata)., @Majenko

Хорошо, это имеет больше смысла, так что mydata — это пример структуры, в которую вы копируете. Я все еще получаю `virtual void OnGamePadChanged(const GamePadEventData *evt); статус выхода 1 «GamePadEventData» не указывает тип из заголовка. Это потому, что я объявляю структуры в CPP. Что вы рекомендуете? Спасибо, @Vosem Media

Объявите структуры в файле .h (то есть только часть struct foo {...};). Используйте структуры в файле CPP в качестве типов переменных., @Majenko

Хорошо, я удалил всю ерунду, которая у меня была раньше, и пытаюсь заставить работать голые кости. Я удалил приведение структур, потому что это отдельная проблема, которую я буду решать после этой первой. Я объявил структуры в .h, как вы рекомендовали, но заполнил их в .cpp (как вы можете видеть в коде, который я редактировал). К сожалению, я получаю сообщение об ошибке повторного объявления (в .cpp), потому что я ранее создал их в .h. Я бы подумал, что простое удаление типа переменной struct отсортировало бы его, но я не думаю, что вы можете сделать это в C. Спасибо., @Vosem Media

Ваши пустые структуры в .h должны быть полными структурами, которые в настоящее время находятся в .cpp. Структуры в .cpp не должны существовать., @Majenko

Я отредактировал пример кода в конце своего ответа., @Majenko

Спасибо за весь этот пример кода, я думаю, что наконец-то все понял. выдает одну ошибку: «Нет соответствующей функции для вызова JoystickEvents::OnGamePadChanged(GamePadEventData&)» из строки «joyEvents->OnGamePadChanged(currentValues);», спасибо. Я также добавил полную ошибку в конец моего вопроса., @Vosem Media

Вероятно, на самом деле должен быть указатель..., @Majenko

Большое спасибо, все заработало идеально. Я знаю, что вопрос был очень старым, когда я разместил новый комментарий, и я бы понял, если бы вы просто проигнорировали его. Спасибо, что помогли мне, это решило все мои проблемы :), @Vosem Media

Рад помочь., @Majenko