Plug a Logitech racing wheel into an ESP32‑S3 and read it. Clean, normalized steering, pedals, buttons and shifter — with the tricky Logitech native‑mode switch handled for you.
Read a Logitech Driving Force racing wheel (G29, G923, G920, G27, G25, DFGT, Driving Force Pro) on an ESP32‑S3 over USB host, and get clean, normalized inputs: steering -1..+1, pedals 0..1, buttons, D‑pad, and optionally the H‑shifter gear.
MIT licensed. Header‑only. Built on the excellent EspUsbHost library.
- 🎛️ Normalized inputs —
steer−1..+1, pedals 0..1, no raw‑byte wrangling - 🔌 Native‑mode switch handled — including the painful G923 PlayStation→PC switch
- 🕹️ Whole Driving Force family — G29 / G923 / G920 / G27 / G25 / DFGT / DFP
- 💡 G923 RPM LED bar control in one call
- 🪶 Header‑only, ~1 dependency (EspUsbHost), MIT
Logitech wheels don't just stream their axes when you plug them in. They boot in a restricted mode that only reports neutral or combined‑axis data until the host sends one HID output report to unlock separate pedals, full 16‑bit steering, all the buttons and the shifter. The G923 is worse: in its default PlayStation mode it streams only neutral reports (it's waiting for a PlayStation authentication a generic host can't provide) until you switch it to PC ("Classic") mode.
Figuring those commands out is the hard part. This library ships them per‑wheel, so a G29 or G923 just works — plug it into an ESP32‑S3 and read steer / throttle / brake.
Anything in the Logitech Driving Force family (VID 0x046D): G29, G920, G923 (PS/Xbox/PC), G25, G27, Driving Force Pro, Driving Force GT. Others in the family will likely work — turn on the debug dump and check.
- An ESP32‑S3 (its native USB can act as a host; a plain ESP32 or C3 cannot).
- The wheel's USB D+/D‑ wired to the S3's native‑USB D+/D‑.
- 5V injected onto the wheel's VBUS — the S3 can't power a wheel.
- A common ground between the S3 and the wheel's 5V supply.
See WIRING.md for the full details.
- Install the dependency (Arduino IDE Library Manager, or CLI):
arduino-cli lib install "EspUsbHost" # tested with 2.7.2 - Install this library: Sketch → Include Library → Add .ZIP Library…, or drop the folder into your Arduino
libraries/, or:arduino-cli lib install --git-url https://github.com/botsofcog/LogiWheelHost - In the IDE, select an ESP32‑S3 board and leave USB Mode at the default OTG mode so the native USB can host.
#include <LogiWheelHost.h>
LogiWheelHost wheel;
void setup() {
Serial.begin(115200);
wheel.begin(); // or wheel.begin(onInput) for a callback
}
void loop() {
wheel.tick(); // services the G923 mode switch
if (wheel.connected()) {
LogiWheelInput in = wheel.latest();
// in.steer -1.0 .. +1.0
// in.throttle / in.brake / in.clutch 0.0 .. 1.0
myServo.write(90 + (int)(in.steer * 90));
myMotor.setDuty(in.throttle - in.brake);
}
}See examples/SerialMonitor for a complete sketch that prints every input and mirrors the throttle onto the G923 RPM LED bar.
| field | type | meaning |
|---|---|---|
steer |
float |
-1.0 full left … +1.0 full right (deadzone + invert applied) |
throttle brake clutch |
float |
0.0 released … 1.0 floored |
steerRaw |
uint16_t |
raw 16‑bit steering (centre ~32768) |
throttleRaw brakeRaw clutchRaw |
uint8_t |
raw pedals (idle 255) |
buttons |
uint32_t |
raw button bitfield (report[0..3]) |
hat |
uint8_t |
D‑pad: 0=N 2=E 4=S 6=W 8=centre |
gear |
int8_t |
H‑shifter gear (if LOGIWHEEL_SHIFTER=1), else UNKNOWN |
stampMs |
uint32_t |
millis() when this reading arrived |
| method | does |
|---|---|
begin(cb = nullptr) |
start the USB host; optional callback void(const LogiWheelInput&) fired on each reading |
tick() |
call every loop() — services the deferred G923 PS→PC switch |
connected() |
is a recognised wheel plugged in? |
latest() |
thread‑safe snapshot of the last reading |
setRpmLeds(0..6) |
light N LEDs on the G923 RPM bar (ignored on wheels without it) |
pressed(in, LOGIWHEEL_BTN_*) |
static helper: is that button set in in.buttons? |
The optional callback runs in EspUsbHost's background task — keep it short, or just poll
latest()fromloop().
Named button masks (LOGIWHEEL_BTN_X, _TRIANGLE, _L_PADDLE, _BACK, …) are provided as a starting point confirmed on a G923 in PC mode. Verify them on your wheel with the debug dump.
Every knob is a -D build flag (override in your build, no library edits). Common ones:
| flag | default | what |
|---|---|---|
LOGIWHEEL_STEER_USABLE |
32767 |
raw counts each side of centre that map to full lock (±1.0). Lower = a smaller turn reaches full lock. |
LOGIWHEEL_STEER_DEADZONE |
300 |
raw counts around centre ignored |
LOGIWHEEL_STEER_INVERT |
0 |
1 if left/right come out reversed |
LOGIWHEEL_PEDAL_DEADZONE |
10 |
slack at the top of pedal travel |
LOGIWHEEL_SHIFTER |
0 |
1 to decode a Driving Force Shifter's gear |
LOGIWHEEL_DEBUG |
0 |
1 prints the raw HID report (calibration) |
LOGIWHEEL_OFF_STEER_LO/HI, _THROTTLE, _BRAKE, _CLUTCH |
4/5/6/7/8 | report byte offsets — fix these if an axis reads wrong |
If an axis reads wrong on your wheel, build once with -DLOGIWHEEL_DEBUG=1, open the Serial Monitor, and press each control one at a time. The dump prints only when the report changes, so you'll see exactly which byte moves — then set the matching LOGIWHEEL_OFF_* offset. The mode‑switch commands and PID list are already handled for you.
- USB host layer: EspUsbHost by TANAKA Masayuki.
- The Logitech native‑mode command family is documented by the Linux new‑lg4ff driver and the GIMX project; the G923 PS→PC switch mirrors the
usb_modeswitchentry for046d:c267. - Extracted and generalized from an ESP32 arcade steering‑wheel controller.
MIT — do whatever you like; attribution appreciated, no warranty.