sonik-br / lgff_wheel_adapter

Adapter for Logitech force feedback wheels
GNU General Public License v3.0
22 stars 2 forks source link

Using as wheel input #11

Open kallaspriit opened 1 day ago

kallaspriit commented 1 day ago

Could I use this library as a Logitech G29 PC wheel input without emulating another wheel?

Basically I want to use the wheel as input to controlling a RC car over ExpressLRS. I just want to get the wheel and pedals positions and ideally also control the force feedback.

I have tried modifying the original example removing everything related to emulating a wheel and replaced that with just a serial over the main USB but I don't seem to be getting any event from the wheel.

Here's my protoboard setup, just connecting the USB-A port to pico pin 0, 1 along with power and ground. pico1 pico2

Maybe it would be possible to add an example that just connects to the wheel, outputs wheel state to console and applies +90 degrees limit using force feedback etc?

sonik-br commented 1 day ago

I can help with the code. Start with something more clean like this one. This is untested. Let me know if it works.

#include "pio_usb.h"
#include "Adafruit_TinyUSB.h"

// USB DP pin
#define PIN_USB_HOST_DP 0

// validation
#if !defined(USE_TINYUSB) || defined(USE_TINYUSB_HOST)
  #error USB Stack must be configured as "Tools -> USB Stack -> Adafruit TinyUSB"
#endif

#if F_CPU != 120000000 && F_CPU != 240000000
  #error PIO USB require CPU Speed must be 120 or 240 MHz
#endif

//G29 Racing Wheel (PS3/PS4 switch set to "PS3")
//also G923 when in PS3 mode
typedef struct TU_ATTR_PACKED {
  // byte 1: hat/buttons
  uint8_t hat : 4;
  uint8_t cross : 1;
  uint8_t square : 1;
  uint8_t circle : 1;
  uint8_t triangle : 1;

  uint8_t R1 : 1;
  uint8_t L1 : 1;
  uint8_t R2 : 1;
  uint8_t L2 : 1;
  uint8_t share : 1;
  uint8_t options : 1;
  uint8_t R3 : 1;
  uint8_t L3 : 1;

  uint8_t shifter_1 : 1;
  uint8_t shifter_2 : 1;
  uint8_t shifter_3 : 1;
  uint8_t shifter_4 : 1;
  uint8_t shifter_5 : 1;
  uint8_t shifter_6 : 1;
  uint8_t shifter_r : 1;
  uint8_t plus : 1;

  uint8_t minus : 1;
  uint8_t dial_cw : 1;
  uint8_t dial_ccw : 1;
  uint8_t enter : 1;
  uint8_t PS : 1;
  uint8_t : 3; // unknown

  uint16_t wheel : 16;

  uint8_t gasPedal;
  uint8_t brakePedal;
  uint8_t clutchPedal;

  uint8_t shifter_x;
  uint8_t shifter_y;

  uint8_t : 1; // unknown
  uint8_t pedal_disconnected : 1; // tested with a G923
  uint8_t power_connected : 1; //(power or calibrated? , not sure) tested with a G923
  uint8_t : 1; // unknown
  uint8_t unknown1: 1; // unknown (0x1 on G923)
  uint8_t : 1; // unknown
  uint8_t shifter_stick_down : 1;
  uint8_t unknown2: 1; // unknown (0x1 on G923)

} g29_report_t;

// USB Host object
Adafruit_USBH_Host USBHost;

void setup() {
  rp2040.enableDoubleResetBootloader();
  Serial.begin(115200);

  // USB Host (PIO)
  pio_usb_configuration_t pio_cfg = PIO_USB_DEFAULT_CONFIG;
  pio_cfg.pin_dp = PIN_USB_HOST_DP;

#if defined(ARDUINO_RASPBERRY_PI_PICO_W)
  /* https://github.com/sekigon-gonnoc/Pico-PIO-USB/issues/46 */
  pio_cfg.sm_tx = 3;
  pio_cfg.sm_rx = 2;
  pio_cfg.sm_eop = 3;
  pio_cfg.pio_rx_num = 0;
  pio_cfg.pio_tx_num = 1;
  pio_cfg.tx_ch = 9;
#endif /* ARDUINO_RASPBERRY_PI_PICO_W */

  USBHost.configure_pio_usb(1, &pio_cfg);

  // run host stack on controller (rhport) 1
  USBHost.begin(1);
}

void loop() {
 USBHost.task();
}

void tuh_hid_mount_cb(uint8_t dev_addr, uint8_t idx, uint8_t const* report_desc, uint16_t desc_len) {
  Serial.println("tuh_hid_mount_cb");
  tuh_hid_receive_report(dev_addr, idx);
}

void tuh_hid_umount_cb(uint8_t dev_addr, uint8_t idx) {
  Serial.println("tuh_hid_umount_cb");
}

void tuh_hid_report_received_cb(uint8_t dev_addr, uint8_t idx, uint8_t const* report, uint16_t len) {
  g29_report_t* input_report = (g29_report_t*)report;

  Serial.println("tuh_hid_report_received_cb");
  Serial.print("wheel ");       Serial.println(input_report->wheel);
  Serial.print("gasPedal ");    Serial.println(input_report->gasPedal);
  Serial.print("brakePedal ");  Serial.println(input_report->brakePedal);
  Serial.print("cross  ");      Serial.println(input_report->cross);
  Serial.println();

  tuh_hid_receive_report(dev_addr, idx);
}
kallaspriit commented 1 day ago

Hey, thanks a lot, I tried this but not getting anything in the logs.

I just discovered that I don't actually have the G29 wheel but rather the G920, maybe that's why?

I actually already got something working based on Your other project xid_wheel_adapter.

/**
 * Based on
 * https://github.com/sonik-br/xid_wheel_adapter
 *
 * Make sure to set
 * - Tools > CPU Speed > 120MHz
 * - Tools > USB Stack > Adafuit TinyUSB
 *
 * Install correct board:
 * - Raspberry Pi Pico/RP2040 by Earle F. Philhower 3.7.2
 *
 * And install correct versions of libraries (newer versions did not seem to work):
 * - Pico PIO USB by sekigon-gonnoc 0.5.3
 * - Adafruit TinyUSB Library by Adafruit 3.1.3
 *
 * Modify C:/Users/xxx/Documents/Adafruit_TinyUSB_Library/src/tusb_config.h and add
 * - #define CFG_TUH_XINPUT
**/

#include "pio_usb.h"
#include "Adafruit_TinyUSB.h"
#include "xinput_host.h"

// USB D+ pin (D- must be DP +1)
#define PIN_USB_HOST_DP 0

// add "#define CFG_TUH_XINPUT 1" to Adafruit_TinyUSB_Library/src/tusb_config.h
#ifndef CFG_TUH_XINPUT
#error CFG_TUH_XINPUT not enabled. define it at tusb_config.h
#endif

// verify correct CPU frequency
#if F_CPU != 120000000 && F_CPU != 240000000
#error PIO USB require CPU Speed must be 120 or 240 MHz
#endif

// usb host
Adafruit_USBH_Host USBHost;

// sets built-in led state
void set_led(bool state) {
  gpio_put(LED_BUILTIN, state);
}

void setup() {
  // setup serial
  Serial.begin(115200);
  Serial.print("Setup.. ");

  // configure led pin
  gpio_init(LED_BUILTIN);
  gpio_set_dir(LED_BUILTIN, 1);
  gpio_put(LED_BUILTIN, LOW);

  // configure usb host pio
  pio_usb_configuration_t pio_cfg = PIO_USB_DEFAULT_CONFIG;
  pio_cfg.pin_dp = PIN_USB_HOST_DP;
  USBHost.configure_pio_usb(1, &pio_cfg);

  // start the host on port 1 on pins DP: 0, DN: 1
  USBHost.begin(1);

  Serial.println("done!");
}

void loop() {
  // update the usb host task
  USBHost.task();
}

const usbh_class_driver_t *usbh_app_driver_get_cb(uint8_t *driver_count) {
  *driver_count = 1;

  return &usbh_xinput_driver;
}

void tuh_xinput_report_received_cb(uint8_t dev_addr, uint8_t instance, xinputh_interface_t const *xid_itf, uint16_t len) {
  const xinput_gamepad_t *p = &xid_itf->pad;

  // skip if no new data available
  if (xid_itf->last_xfer_result != XFER_RESULT_SUCCESS || !xid_itf->connected || !xid_itf->new_pad_data) {
    tuh_xinput_receive_report(dev_addr, instance);

    return;
  }

  // read controller state
  bool buttonDpadUp = p->wButtons & XINPUT_GAMEPAD_DPAD_UP;
  bool buttonDpadDown = p->wButtons & XINPUT_GAMEPAD_DPAD_DOWN;
  bool buttonDpadLeft = p->wButtons & XINPUT_GAMEPAD_DPAD_LEFT;
  bool buttonDpadRight = p->wButtons & XINPUT_GAMEPAD_DPAD_RIGHT;
  bool buttonStart = p->wButtons & XINPUT_GAMEPAD_START;
  bool buttonBack = p->wButtons & XINPUT_GAMEPAD_BACK;
  // these are messed up for some reason
  bool buttonLeftThumb = p->wButtons & XINPUT_GAMEPAD_RIGHT_THUMB;
  bool buttonRightThumb = p->wButtons & XINPUT_GAMEPAD_LEFT_THUMB;
  bool buttonA = p->wButtons & XINPUT_GAMEPAD_A;
  bool buttonB = p->wButtons & XINPUT_GAMEPAD_B;
  bool buttonX = p->wButtons & XINPUT_GAMEPAD_X;
  bool buttonY = p->wButtons & XINPUT_GAMEPAD_Y;
  bool buttonLeftShoulder = p->wButtons & XINPUT_GAMEPAD_LEFT_SHOULDER;
  bool buttonRightShoulder = p->wButtons & XINPUT_GAMEPAD_RIGHT_SHOULDER;
  float axisBrake = (float)p->bLeftTrigger / (float)UINT8_MAX;      // 0-255
  float axisThrottle = (float)p->bRightTrigger / (float)UINT8_MAX;  // 0-25
  float axisSteering = (float)p->sThumbLX / (float)INT16_MAX;       // 0-32768 INT16_MIN-INT16_MAX

  // these seem to be always zero
  // int axisUnknown1 = p->sThumbLY;
  // int axisUnknown2 = p->sThumbRX;
  // int axisUnknown3 = p->sThumbRY;

  Serial.print("Steering: ");
  Serial.print(axisSteering);
  Serial.print(", throttle: ");
  Serial.print(axisThrottle);
  Serial.print(", brake: ");
  Serial.print(axisBrake);
  Serial.print(", d-pad up: ");
  Serial.print(buttonDpadUp ? "Y" : "N");
  Serial.print(", d-pad down: ");
  Serial.print(buttonDpadDown ? "Y" : "N");
  Serial.print(", d-pad left: ");
  Serial.print(buttonDpadLeft ? "Y" : "N");
  Serial.print(", d-pad right: ");
  Serial.print(buttonDpadRight ? "Y" : "N");
  Serial.print(", start: ");
  Serial.print(buttonStart ? "Y" : "N");
  Serial.print(", back: ");
  Serial.print(buttonBack ? "Y" : "N");
  Serial.print(", left thumb: ");
  Serial.print(buttonLeftThumb ? "Y" : "N");
  Serial.print(", right thumb: ");
  Serial.print(buttonRightThumb ? "Y" : "N");
  Serial.print(", A: ");
  Serial.print(buttonA ? "Y" : "N");
  Serial.print(", B: ");
  Serial.print(buttonB ? "Y" : "N");
  Serial.print(", X: ");
  Serial.print(buttonX ? "Y" : "N");
  Serial.print(", Y: ");
  Serial.print(buttonY ? "Y" : "N");
  Serial.print(", left shoulder: ");
  Serial.print(buttonLeftShoulder ? "Y" : "N");
  Serial.print(", right shoulder: ");
  Serial.print(buttonRightShoulder ? "Y" : "N");

  Serial.println();

  // request data from device
  tuh_xinput_receive_report(dev_addr, instance);
}

void tuh_xinput_mount_cb(uint8_t dev_addr, uint8_t instance, const xinputh_interface_t *xinput_itf) {
  Serial.println("Wheel connected");

  set_led(HIGH);

  uint16_t PID, VID;
  tuh_vid_pid_get(dev_addr, &VID, &PID);

  // request data from device
  tuh_xinput_receive_report(dev_addr, instance);
}

void tuh_xinput_umount_cb(uint8_t dev_addr, uint8_t instance) {
  Serial.println("Wheel disconnected");

  set_led(LOW);
}

Outputs data like this in the console: Steering: -0.26, throttle: 0.00, brake: 0.16, d-pad up: N, d-pad down: N, d-pad left: N, d-pad right: N, start: N, back: N, left thumb: N, right thumb: N, A: N, B: N, X: N, Y: N, left shoulder: N, right shoulder: N

Does this look sensible? Also is there a way to control the steering force feedback?

sonik-br commented 18 hours ago

It's a G920 XBOX version?

kallaspriit commented 18 hours ago

No it's a PC version.

sonik-br commented 18 hours ago

I don't think there's a "PC only" version. Taken now from logitech site:

image

image

kallaspriit commented 18 hours ago

Yeah You're right I guess.

It's this one, just used it on PC before. image

So it should still work with your code as well? Does it need to be switched to xbox mode somehow or..?

sonik-br commented 18 hours ago

So... AFAIK there's no open source software that can control it's FFB. I don't have a clue on how it works. There's a linux driver for it but too complex to port over to pico. You won't have FFB with my code.

That said, for just reading it's data, it works just as an xbox controller. And your code above does that :)

If you really want FFB, you could use some other adapter that converts it into a playstation wheel (like a DriveHub), then the pico would read it that way.