From 03ad2361fbaccb259b2df92fa6b9f3c9bef2c785 Mon Sep 17 00:00:00 2001 From: David Madison Date: Fri, 7 Jun 2024 22:20:11 -0400 Subject: [PATCH 01/39] Change detection 'invert' to 'activeLow' This more accurately describes what the constructor is *asking*, if not how the code actually works. --- src/SimRacing.cpp | 4 ++-- src/SimRacing.h | 9 +++++---- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/SimRacing.cpp b/src/SimRacing.cpp index e3472c4..82b7891 100644 --- a/src/SimRacing.cpp +++ b/src/SimRacing.cpp @@ -176,9 +176,9 @@ static void readFloat(float& value, Stream& client) { // DeviceConnection # //######################################################### -DeviceConnection::DeviceConnection(PinNum pin, bool invert, unsigned long detectTime) +DeviceConnection::DeviceConnection(PinNum pin, bool activeLow, unsigned long detectTime) : - pin(sanitizePin(pin)), inverted(invert), stablePeriod(detectTime), // constants(ish) + pin(sanitizePin(pin)), inverted(activeLow), stablePeriod(detectTime), // constants(ish) /* Assume we're connected on first call */ diff --git a/src/SimRacing.h b/src/SimRacing.h index b1cea20..b13183c 100644 --- a/src/SimRacing.h +++ b/src/SimRacing.h @@ -75,12 +75,13 @@ namespace SimRacing { /** * Class constructor * - * @param pin the pin number being read. Can be 'UnusedPin' to disable. - * @param invert whether the input is inverted, so 'LOW' is detected instead of 'HIGH' + * @param pin the pin number being read. Can be 'UnusedPin' to disable. + * @param activeLow whether the device is detected on a high signal (false, + * default) or a low signal (true) * @param detectTime the amount of time, in ms, the input must be stable for - * before it's interpreted as 'detected' + * before it's interpreted as 'detected' */ - DeviceConnection(PinNum pin, bool invert = false, unsigned long detectTime = 250); + DeviceConnection(PinNum pin, bool activeLow = false, unsigned long detectTime = 250); /** * Checks if the pin detects a connection. This polls the input and checks From 80d083eb40e5de8964c3b4cb1673571676fcf8c5 Mon Sep 17 00:00:00 2001 From: David Madison Date: Fri, 7 Jun 2024 22:54:55 -0400 Subject: [PATCH 02/39] Add activeLow option to peripherals Although all current peripherals use active-high detection, that may not be the case in the future. Changing this now for future compatibility. The Logitech classes maintain their original arguments, as we know they are active-high. Because the added argument includes a default, this does not technically break the public API. --- src/SimRacing.cpp | 35 ++++++++++------- src/SimRacing.h | 98 +++++++++++++++++++++++++++++++++++------------ 2 files changed, 96 insertions(+), 37 deletions(-) diff --git a/src/SimRacing.cpp b/src/SimRacing.cpp index 82b7891..2f4d984 100644 --- a/src/SimRacing.cpp +++ b/src/SimRacing.cpp @@ -350,11 +350,11 @@ void AnalogInput::setCalibration(AnalogInput::Calibration newCal) { // Pedals # //######################################################### -Pedals::Pedals(AnalogInput* dataPtr, uint8_t nPedals, PinNum detectPin) +Pedals::Pedals(AnalogInput* dataPtr, uint8_t nPedals, PinNum detectPin, bool detectActiveLow) : pedalData(dataPtr), NumPedals(nPedals), - detector(detectPin), + detector(detectPin, detectActiveLow), changed(false) {} @@ -555,8 +555,8 @@ void Pedals::serialCalibration(Stream& iface) { } -TwoPedals::TwoPedals(PinNum gasPin, PinNum brakePin, PinNum detectPin) - : Pedals(pedalData, NumPedals, detectPin), +TwoPedals::TwoPedals(PinNum gasPin, PinNum brakePin, PinNum detectPin, bool detectActiveLow) + : Pedals(pedalData, NumPedals, detectPin, detectActiveLow), pedalData{ AnalogInput(gasPin), AnalogInput(brakePin) } {} @@ -566,8 +566,8 @@ void TwoPedals::setCalibration(AnalogInput::Calibration gasCal, AnalogInput::Cal } -ThreePedals::ThreePedals(PinNum gasPin, PinNum brakePin, PinNum clutchPin, PinNum detectPin) - : Pedals(pedalData, NumPedals, detectPin), +ThreePedals::ThreePedals(PinNum gasPin, PinNum brakePin, PinNum clutchPin, PinNum detectPin, bool detectActiveLow) + : Pedals(pedalData, NumPedals, detectPin, detectActiveLow), pedalData{ AnalogInput(gasPin), AnalogInput(brakePin), AnalogInput(clutchPin) } {} @@ -580,7 +580,10 @@ void ThreePedals::setCalibration(AnalogInput::Calibration gasCal, AnalogInput::C LogitechPedals::LogitechPedals(PinNum gasPin, PinNum brakePin, PinNum clutchPin, PinNum detectPin) - : ThreePedals(gasPin, brakePin, clutchPin, detectPin) + : ThreePedals( + gasPin, brakePin, clutchPin, + detectPin, false // active high + ) { // taken from calibrating my own pedals. the springs are pretty stiff so while // this covers the whole travel range, users may want to back it down for casual @@ -589,7 +592,10 @@ LogitechPedals::LogitechPedals(PinNum gasPin, PinNum brakePin, PinNum clutchPin, } LogitechDrivingForceGT_Pedals::LogitechDrivingForceGT_Pedals(PinNum gasPin, PinNum brakePin, PinNum detectPin) - : TwoPedals(gasPin, brakePin, detectPin) + : TwoPedals( + gasPin, brakePin, + detectPin, false // active high + ) { this->setCalibration({ 646, 0 }, { 473, 1023 }); // taken from calibrating my own pedals } @@ -674,7 +680,7 @@ const float AnalogShifter::CalEngagementPoint = 0.70; const float AnalogShifter::CalReleasePoint = 0.50; const float AnalogShifter::CalEdgeOffset = 0.60; -AnalogShifter::AnalogShifter(PinNum pinX, PinNum pinY, PinNum pinRev, PinNum detectPin) +AnalogShifter::AnalogShifter(PinNum pinX, PinNum pinY, PinNum pinRev, PinNum detectPin, bool detectActiveLow) : /* In initializing the Shifter, the lowest gear is going to be '-1' if a pin * exists for reverse, otherwise it's going to be '0' (neutral). @@ -685,7 +691,7 @@ AnalogShifter::AnalogShifter(PinNum pinX, PinNum pinY, PinNum pinRev, PinNum det analogAxis{ AnalogInput(pinX), AnalogInput(pinY) }, pinReverse(sanitizePin(pinRev)), - detector(detectPin, false) // not inverted + detector(detectPin, detectActiveLow) {} void AnalogShifter::begin() { @@ -997,7 +1003,10 @@ void AnalogShifter::serialCalibration(Stream& iface) { } LogitechShifter::LogitechShifter(PinNum pinX, PinNum pinY, PinNum pinRev, PinNum detectPin) - : AnalogShifter(pinX, pinY, pinRev, detectPin) + : AnalogShifter( + pinX, pinY, pinRev, + detectPin, false // active high + ) { this->setCalibration({ 490, 440 }, { 253, 799 }, { 262, 86 }, { 460, 826 }, { 470, 76 }, { 664, 841 }, { 677, 77 }); } @@ -1006,10 +1015,10 @@ LogitechShifter::LogitechShifter(PinNum pinX, PinNum pinY, PinNum pinRev, PinNum // Handbrake # //######################################################### -Handbrake::Handbrake(PinNum pinAx, PinNum detectPin) +Handbrake::Handbrake(PinNum pinAx, PinNum detectPin, boolean detectActiveLow) : analogAxis(pinAx), - detector(detectPin), + detector(detectPin, detectActiveLow), changed(false) {} diff --git a/src/SimRacing.h b/src/SimRacing.h index b13183c..aa8fd34 100644 --- a/src/SimRacing.h +++ b/src/SimRacing.h @@ -294,11 +294,14 @@ namespace SimRacing { /** * Class constructor * - * @param dataPtr pointer to the analog input data managed by the class, stored elsewhere - * @param nPedals the number of pedals stored in said data pointer - * @param detectPin the digital pin for device detection (high is detected) + * @param dataPtr pointer to the analog input data managed by the class, + * stored elsewhere + * @param nPedals the number of pedals stored in said data pointer + * @param detectPin the digital pin for device detection + * @param detectActiveLow whether the device is detected on a high signal (false, + * default) or a low signal (true) */ - Pedals(AnalogInput* dataPtr, uint8_t nPedals, PinNum detectPin); + Pedals(AnalogInput* dataPtr, uint8_t nPedals, PinNum detectPin, bool detectActiveLow = false); /** @copydoc Peripheral::begin() */ virtual void begin(); @@ -394,11 +397,16 @@ namespace SimRacing { /** * Class constructor * - * @param pinGas the analog pin for the gas pedal potentiometer - * @param pinBrake the analog pin for the brake pedal potentiometer - * @param pinDetect the digital pin for device detection (high is detected) + * @param pinGas the analog pin for the gas pedal potentiometer + * @param pinBrake the analog pin for the brake pedal potentiometer + * @param pinDetect the digital pin for device detection + * @param detectActiveLow whether the device is detected on a high signal (false, + * default) or a low signal (true) */ - TwoPedals(PinNum pinGas, PinNum pinBrake, PinNum pinDetect = UnusedPin); + TwoPedals( + PinNum pinGas, PinNum pinBrake, + PinNum pinDetect = UnusedPin, bool detectActiveLow = false + ); /** * Sets the calibration data (min/max) for the pedals @@ -422,12 +430,17 @@ namespace SimRacing { /** * Class constructor * - * @param pinGas the analog pin for the gas pedal potentiometer - * @param pinBrake the analog pin for the brake pedal potentiometer - * @param pinClutch the analog pin for the clutch pedal potentiometer - * @param pinDetect the digital pin for device detection (high is detected) + * @param pinGas the analog pin for the gas pedal potentiometer + * @param pinBrake the analog pin for the brake pedal potentiometer + * @param pinClutch the analog pin for the clutch pedal potentiometer + * @param pinDetect the digital pin for device detection + * @param detectActiveLow whether the device is detected on a high signal (false, + * default) or a low signal (true) */ - ThreePedals(PinNum pinGas, PinNum pinBrake, PinNum pinClutch, PinNum pinDetect = UnusedPin); + ThreePedals( + PinNum pinGas, PinNum pinBrake, PinNum pinClutch, + PinNum pinDetect = UnusedPin, bool detectActiveLow = false + ); /** * Sets the calibration data (min/max) for the pedals @@ -550,12 +563,18 @@ namespace SimRacing { /** * Class constructor * - * @param pinX the analog input pin for the X axis - * @param pinY the analog input pin for the Y axis - * @param pinRev the digital input pin for the 'reverse' button - * @param pinDetect the digital pin for device detection (high is detected) + * @param pinX the analog input pin for the X axis + * @param pinY the analog input pin for the Y axis + * @param pinRev the digital input pin for the 'reverse' button + * @param pinDetect the digital pin for device detection + * @param detectActiveLow whether the device is detected on a high signal (false, + * default) or a low signal (true) */ - AnalogShifter(PinNum pinX, PinNum pinY, PinNum pinRev = UnusedPin, PinNum pinDetect = UnusedPin); + AnalogShifter( + PinNum pinX, PinNum pinY, + PinNum pinRev = UnusedPin, + PinNum pinDetect = UnusedPin, bool detectActiveLow = false + ); /** * Initializes the hardware pins for reading the gear states. @@ -691,10 +710,15 @@ namespace SimRacing { /** * Class constructor * - * @param pinAx analog pin number for the handbrake axis - * @param pinDetect the digital pin for device detection (high is detected) + * @param pinAx analog pin number for the handbrake axis + * @param pinDetect the digital pin for device detection + * @param detectActiveLow whether the device is detected on a high signal (false, + * default) or a low signal (true) */ - Handbrake(PinNum pinAx, PinNum pinDetect = UnusedPin); + Handbrake( + PinNum pinAx, + PinNum pinDetect = UnusedPin, boolean detectActiveLow = false + ); /** * Initializes the pin for reading from the handbrake. @@ -760,7 +784,15 @@ namespace SimRacing { */ class LogitechPedals : public ThreePedals { public: - /** @copydoc ThreePedals::ThreePedals */ + /** + * Class constructor + * + * @param pinGas the analog pin for the gas pedal potentiometer, DE-9 pin 2 + * @param pinBrake the analog pin for the brake pedal potentiometer, DE-9 pin 3 + * @param pinClutch the analog pin for the clutch pedal potentiometer, DE-9 pin 4 + * @param pinDetect the digital pin for device detection, DE-9 pin 6. Requires a + * pull-down resistor. + */ LogitechPedals(PinNum pinGas, PinNum pinBrake, PinNum pinClutch, PinNum pinDetect = UnusedPin); }; @@ -775,7 +807,14 @@ namespace SimRacing { */ class LogitechDrivingForceGT_Pedals : public TwoPedals { public: - /** @copydoc TwoPedals::TwoPedals */ + /** + * Class constructor + * + * @param pinGas the analog pin for the gas pedal potentiometer, DE-9 pin 2 + * @param pinBrake the analog pin for the brake pedal potentiometer, DE-9 pin 3 + * @param pinDetect the digital pin for device detection, DE-9 pin 4. Requires a + * pull-down resistor. + */ LogitechDrivingForceGT_Pedals(PinNum pinGas, PinNum pinBrake, PinNum pinDetect = UnusedPin); }; @@ -787,7 +826,18 @@ namespace SimRacing { */ class LogitechShifter : public AnalogShifter { public: - /** @copydoc AnalogShifter::AnalogShifter */ + /** + * Class constructor + * + * @param pinX the analog input pin for the X axis, DE-9 pin 4 + * @param pinY the analog input pin for the Y axis, DE-9 pin 8 + * @param pinRev the digital input pin for the 'reverse' button, DE-9 pin 2 + * @param pinDetect the digital pin for device detection, DE-9 pin 7. Requires + * a pull-down resistor. + * + * @note In order to get the 'reverse' signal from the shifter, the chip select + * pin (DE-9 pin 3) needs to be pulled up to VCC. + */ LogitechShifter(PinNum pinX, PinNum pinY, PinNum pinRev = UnusedPin, PinNum pinDetect = UnusedPin); }; From 5c4bac39ba34e1b1279a590c1bf93fbd8ca1778a Mon Sep 17 00:00:00 2001 From: David Madison Date: Fri, 7 Jun 2024 23:07:49 -0400 Subject: [PATCH 03/39] Create type alias for shifter gear numbers --- src/SimRacing.cpp | 8 ++++---- src/SimRacing.h | 21 +++++++++++++-------- 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/src/SimRacing.cpp b/src/SimRacing.cpp index 2f4d984..d3c6268 100644 --- a/src/SimRacing.cpp +++ b/src/SimRacing.cpp @@ -605,7 +605,7 @@ LogitechDrivingForceGT_Pedals::LogitechDrivingForceGT_Pedals(PinNum gasPin, PinN // Shifter # //######################################################### -Shifter::Shifter(int8_t min, int8_t max) +Shifter::Shifter(Gear min, Gear max) : MinGear(min), MaxGear(max) {} @@ -716,7 +716,7 @@ bool AnalogShifter::update() { // neutral and then immediately return case(DeviceConnection::Unplug): { - const int8_t previousGear = this->getGear(); + const Gear previousGear = this->getGear(); analogAxis[Axis::X].setPosition(calibration.neutralX); analogAxis[Axis::Y].setPosition(calibration.neutralY); @@ -736,13 +736,13 @@ bool AnalogShifter::update() { break; } - const int8_t previousGear = this->getGear(); + const Gear previousGear = this->getGear(); const bool prevOdd = ((previousGear != -1) && (previousGear & 1)); // were we previously in an odd gear const bool prevEven = (!prevOdd && previousGear != 0); // were we previously in an even gear const int x = analogAxis[Axis::X].getPosition(); const int y = analogAxis[Axis::Y].getPosition(); - int8_t newGear = 0; + Gear newGear = 0; // If we're below the 'release' thresholds, we must still be in the previous gear if ((prevOdd && y > calibration.oddRelease) || (prevEven && y < calibration.evenRelease)) { diff --git a/src/SimRacing.h b/src/SimRacing.h index aa8fd34..bffb808 100644 --- a/src/SimRacing.h +++ b/src/SimRacing.h @@ -470,13 +470,18 @@ namespace SimRacing { */ class Shifter : public Peripheral { public: + /** + * Type alias for gear numbers + */ + using Gear = int8_t; + /** * Class constructor * * @param min the lowest gear possible * @param max the highest gear possible */ - Shifter(int8_t min, int8_t max); + Shifter(Gear min, Gear max); /** * Returns the currently selected gear. @@ -486,7 +491,7 @@ namespace SimRacing { * * @return current gear index */ - int8_t getGear() const { return currentGear; } + Gear getGear() const { return currentGear; } /** * Returns a character that represents the given gear. @@ -537,21 +542,21 @@ namespace SimRacing { * * @return the lowest gear index */ - int8_t getGearMin() { return MinGear; } + Gear getGearMin() { return MinGear; } /** * Retrieves the maximum possible gear index. * * @return the highest gear index */ - int8_t getGearMax() { return MaxGear; } + Gear getGearMax() { return MaxGear; } protected: - const int8_t MinGear; ///< the lowest selectable gear - const int8_t MaxGear; ///< the highest selectable gear + const Gear MinGear; ///< the lowest selectable gear + const Gear MaxGear; ///< the highest selectable gear - int8_t currentGear; ///< index of the current gear - bool changed; ///< whether the gear has changed since the previous update + Gear currentGear; ///< index of the current gear + bool changed; ///< whether the gear has changed since the previous update }; From a11f9ec437dbc4a166cf6f0b1fc3ce1dd9d1641b Mon Sep 17 00:00:00 2001 From: David Madison Date: Fri, 7 Jun 2024 23:23:24 -0400 Subject: [PATCH 04/39] Remove direct gear access in Shifter base Changes out the direct access to 'currentGear' and the 'changed' flag with a buffer function that handles that for us. Much cleaner and safer. Not sure what I was thinking the first time around... --- src/SimRacing.cpp | 38 ++++++++++++++++++++++++-------------- src/SimRacing.h | 18 ++++++++++++++++-- 2 files changed, 40 insertions(+), 16 deletions(-) diff --git a/src/SimRacing.cpp b/src/SimRacing.cpp index d3c6268..8a0db13 100644 --- a/src/SimRacing.cpp +++ b/src/SimRacing.cpp @@ -607,7 +607,19 @@ LogitechDrivingForceGT_Pedals::LogitechDrivingForceGT_Pedals(PinNum gasPin, PinN Shifter::Shifter(Gear min, Gear max) : MinGear(min), MaxGear(max) -{} +{ + this->currentGear = this->previousGear = 0; // neutral +} + +void Shifter::setGear(Gear gear) { + // if gear is out of range, set it to neutral + if (gear < MinGear || gear > MaxGear) { + gear = 0; + } + + this->previousGear = this->currentGear; + this->currentGear = gear; +} char Shifter::getGearChar(int gear) { char c = '?'; @@ -715,24 +727,22 @@ bool AnalogShifter::update() { // on an unplug event, we want to reset our position back to // neutral and then immediately return case(DeviceConnection::Unplug): - { - const Gear previousGear = this->getGear(); + // set axis values to calibrated neutral analogAxis[Axis::X].setPosition(calibration.neutralX); analogAxis[Axis::Y].setPosition(calibration.neutralY); - if (previousGear != 0) changed = true; - currentGear = 0; - return changed; + // set gear to neutral + this->setGear(0); + + return this->gearChanged(); break; - } - // if the device is either disconnected or just plugged in and unstable, set gear - // 'changed' to false and then immediately return false to save on processing + // if the device is either disconnected or just plugged in and unstable, + // immediately return false to save on processing case(DeviceConnection::PlugIn): case(DeviceConnection::Disconnected): - changed = false; - return changed; + return false; break; } @@ -783,10 +793,10 @@ bool AnalogShifter::update() { } } - changed = (newGear != previousGear) ? 1 : 0; - currentGear = newGear; + // finally, store the newly calculated gear + this->setGear(newGear); - return changed; + return this->gearChanged(); } long AnalogShifter::getPosition(Axis ax, long min, long max) const { diff --git a/src/SimRacing.h b/src/SimRacing.h index bffb808..f144789 100644 --- a/src/SimRacing.h +++ b/src/SimRacing.h @@ -535,7 +535,9 @@ namespace SimRacing { * * @return 'true' if gear has changed, 'false' otherwise */ - bool gearChanged() const { return changed; } + bool gearChanged() const { + return this->currentGear != this->previousGear; + } /** * Retrieves the minimum possible gear index. @@ -552,11 +554,23 @@ namespace SimRacing { Gear getGearMax() { return MaxGear; } protected: + /** + * Changes the currently set gear, internally + * + * This function sanitizes the newly selected gear with MinGear / MaxGear, + * and handles caching the previous value for checking if the gear has + * changed. + * + * @param gear the new gear value to set + */ + void setGear(Gear gear); + + private: const Gear MinGear; ///< the lowest selectable gear const Gear MaxGear; ///< the highest selectable gear Gear currentGear; ///< index of the current gear - bool changed; ///< whether the gear has changed since the previous update + Gear previousGear; ///< index of the last selected gear }; From bdc34288e86d44bc19beafc89be7667dfedd445d Mon Sep 17 00:00:00 2001 From: David Madison Date: Fri, 7 Jun 2024 23:50:55 -0400 Subject: [PATCH 05/39] Add gear range arguments to AnalogShifter This allows for limiting the shifter range, say if you want to build a 5-speed analog shifter. More importantly it allows us to put the shifter in reverse without having to define a pin to read the reverse signal from. --- src/SimRacing.cpp | 13 +++++++------ src/SimRacing.h | 13 +++++++++++-- 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/src/SimRacing.cpp b/src/SimRacing.cpp index 8a0db13..0cb2b84 100644 --- a/src/SimRacing.cpp +++ b/src/SimRacing.cpp @@ -692,12 +692,12 @@ const float AnalogShifter::CalEngagementPoint = 0.70; const float AnalogShifter::CalReleasePoint = 0.50; const float AnalogShifter::CalEdgeOffset = 0.60; -AnalogShifter::AnalogShifter(PinNum pinX, PinNum pinY, PinNum pinRev, PinNum detectPin, bool detectActiveLow) - : - /* In initializing the Shifter, the lowest gear is going to be '-1' if a pin - * exists for reverse, otherwise it's going to be '0' (neutral). - */ - Shifter(sanitizePin(pinRev) != UnusedPin ? -1 : 0, 6), +AnalogShifter::AnalogShifter( + Gear gearMin, Gear gearMax, + PinNum pinX, PinNum pinY, PinNum pinRev, + PinNum detectPin, bool detectActiveLow +) : + Shifter(gearMin, gearMax), /* Two axes, X and Y */ analogAxis{ AnalogInput(pinX), AnalogInput(pinY) }, @@ -1014,6 +1014,7 @@ void AnalogShifter::serialCalibration(Stream& iface) { LogitechShifter::LogitechShifter(PinNum pinX, PinNum pinY, PinNum pinRev, PinNum detectPin) : AnalogShifter( + -1, 6, // includes reverse and gears 1-6 pinX, pinY, pinRev, detectPin, false // active high ) diff --git a/src/SimRacing.h b/src/SimRacing.h index f144789..26a1ff6 100644 --- a/src/SimRacing.h +++ b/src/SimRacing.h @@ -582,14 +582,23 @@ namespace SimRacing { /** * Class constructor * + * @param gearMin the lowest gear possible + * @param gearMax the highest gear possible * @param pinX the analog input pin for the X axis * @param pinY the analog input pin for the Y axis * @param pinRev the digital input pin for the 'reverse' button * @param pinDetect the digital pin for device detection - * @param detectActiveLow whether the device is detected on a high signal (false, - * default) or a low signal (true) + * @param detectActiveLow whether the device is detected on a high signal + * (false, default) or a low signal (true) + * + * @note With the way the class is designed, the lowest possible gear is + * -1 (reverse), and the highest possible gear is 6. Setting the + * arguments lower/higher than this will have no effect. Setting + * the arguments within this range will limit to those gears, + * and selecting gears out of range will result in neutral. */ AnalogShifter( + Gear gearMin, Gear gearMax, PinNum pinX, PinNum pinY, PinNum pinRev = UnusedPin, PinNum pinDetect = UnusedPin, bool detectActiveLow = false From d0cefacaf21ec8503ddea796aa9246055b0908fd Mon Sep 17 00:00:00 2001 From: David Madison Date: Thu, 13 Jun 2024 17:25:39 -0400 Subject: [PATCH 06/39] Move detector implementation to base class In implementing the G25 shifter I was running into synchronicity issues since each 'update()' function polled the connection state independently. In some updates the buttons would be connected while the shifter wasn't, in other updates it was the other way around. Moving the detector behavior to the base class makes it so that the detector is only polled once per update(), and it's *always* polled per update(). Using a pointer here also allows future classes to take advantage of polymorphism for more complicated 'detection' systems. --- src/SimRacing.cpp | 175 +++++++++++++++++++++++++++------------------- src/SimRacing.h | 168 ++++++++++++++++++++++++++------------------ 2 files changed, 205 insertions(+), 138 deletions(-) diff --git a/src/SimRacing.cpp b/src/SimRacing.cpp index 0cb2b84..9d2b4d1 100644 --- a/src/SimRacing.cpp +++ b/src/SimRacing.cpp @@ -345,16 +345,53 @@ void AnalogInput::setCalibration(AnalogInput::Calibration newCal) { this->cal = newCal; } +//######################################################### +// Peripheral # +//######################################################### + +Peripheral::Peripheral(DeviceConnection* detector) + : detector(detector) +{} + +bool Peripheral::update() { + // if the detector exists, poll for state + if (this->detector) { + this->detector->poll(); + } + + // get the connected state from the detector + const bool connected = this->isConnected(); + + // call the derived class update function + return this->updateState(connected); +} + +bool Peripheral::isConnected() { + // if detector exists, return state + if (this->detector) { + return this->detector->isConnected(); + } + + // otherwise, assume always connected + return true; +} + +void Peripheral::setStablePeriod(unsigned long t) { + // if detector exists, set the stable period + if (this->detector) { + this->detector->setStablePeriod(t); + } +} //######################################################### // Pedals # //######################################################### -Pedals::Pedals(AnalogInput* dataPtr, uint8_t nPedals, PinNum detectPin, bool detectActiveLow) +Pedals::Pedals(AnalogInput* dataPtr, uint8_t nPedals, DeviceConnection* detector) : + Peripheral(detector), pedalData(dataPtr), NumPedals(nPedals), - detector(detectPin, detectActiveLow), changed(false) {} @@ -362,26 +399,29 @@ void Pedals::begin() { update(); // set initial pedal position } -bool Pedals::update() { - changed = false; +bool Pedals::updateState(bool connected) { + this->changed = false; - detector.poll(); - if (detector.getState() == DeviceConnection::Connected) { - // if connected, read all pedal positions + // if we're connected, read all pedal positions + if (connected) { for (int i = 0; i < getNumPedals(); ++i) { changed |= pedalData[i].read(); } } - else if (detector.getState() == DeviceConnection::Unplug) { - // on unplug event, zero all pedals + + // otherwise, zero all pedals + else { for (int i = 0; i < getNumPedals(); ++i) { const int min = pedalData[i].getMin(); - pedalData[i].setPosition(min); + const int prev = pedalData[i].getPositionRaw(); + if (min != prev) { + pedalData[i].setPosition(min); + changed = true; + } } - changed = true; // set flag so we know everything moved to 0 } - return changed; + return this->changed; } long Pedals::getPosition(PedalID pedal, long rMin, long rMax) const { @@ -555,8 +595,8 @@ void Pedals::serialCalibration(Stream& iface) { } -TwoPedals::TwoPedals(PinNum gasPin, PinNum brakePin, PinNum detectPin, bool detectActiveLow) - : Pedals(pedalData, NumPedals, detectPin, detectActiveLow), +TwoPedals::TwoPedals(PinNum gasPin, PinNum brakePin, DeviceConnection* detector) + : Pedals(pedalData, NumPedals, detector), pedalData{ AnalogInput(gasPin), AnalogInput(brakePin) } {} @@ -566,8 +606,8 @@ void TwoPedals::setCalibration(AnalogInput::Calibration gasCal, AnalogInput::Cal } -ThreePedals::ThreePedals(PinNum gasPin, PinNum brakePin, PinNum clutchPin, PinNum detectPin, bool detectActiveLow) - : Pedals(pedalData, NumPedals, detectPin, detectActiveLow), +ThreePedals::ThreePedals(PinNum gasPin, PinNum brakePin, PinNum clutchPin, DeviceConnection* detector) + : Pedals(pedalData, NumPedals, detector), pedalData{ AnalogInput(gasPin), AnalogInput(brakePin), AnalogInput(clutchPin) } {} @@ -578,12 +618,10 @@ void ThreePedals::setCalibration(AnalogInput::Calibration gasCal, AnalogInput::C } - LogitechPedals::LogitechPedals(PinNum gasPin, PinNum brakePin, PinNum clutchPin, PinNum detectPin) - : ThreePedals( - gasPin, brakePin, clutchPin, - detectPin, false // active high - ) + : + ThreePedals(gasPin, brakePin, clutchPin, &this->detectObj), + detectObj(detectPin, false) // active high { // taken from calibrating my own pedals. the springs are pretty stiff so while // this covers the whole travel range, users may want to back it down for casual @@ -592,10 +630,9 @@ LogitechPedals::LogitechPedals(PinNum gasPin, PinNum brakePin, PinNum clutchPin, } LogitechDrivingForceGT_Pedals::LogitechDrivingForceGT_Pedals(PinNum gasPin, PinNum brakePin, PinNum detectPin) - : TwoPedals( - gasPin, brakePin, - detectPin, false // active high - ) + : + TwoPedals(gasPin, brakePin, &this->detectObj), + detectObj(detectPin, false) // active high { this->setCalibration({ 646, 0 }, { 473, 1023 }); // taken from calibrating my own pedals } @@ -605,8 +642,10 @@ LogitechDrivingForceGT_Pedals::LogitechDrivingForceGT_Pedals(PinNum gasPin, PinN // Shifter # //######################################################### -Shifter::Shifter(Gear min, Gear max) - : MinGear(min), MaxGear(max) +Shifter::Shifter(Gear min, Gear max, DeviceConnection* detector) + : + Peripheral(detector), + MinGear(min), MaxGear(max) { this->currentGear = this->previousGear = 0; // neutral } @@ -695,15 +734,14 @@ const float AnalogShifter::CalEdgeOffset = 0.60; AnalogShifter::AnalogShifter( Gear gearMin, Gear gearMax, PinNum pinX, PinNum pinY, PinNum pinRev, - PinNum detectPin, bool detectActiveLow + DeviceConnection* detector ) : - Shifter(gearMin, gearMax), + Shifter(gearMin, gearMax, detector), /* Two axes, X and Y */ analogAxis{ AnalogInput(pinX), AnalogInput(pinY) }, - pinReverse(sanitizePin(pinRev)), - detector(detectPin, detectActiveLow) + pinReverse(sanitizePin(pinRev)) {} void AnalogShifter::begin() { @@ -713,21 +751,10 @@ void AnalogShifter::begin() { update(); // set initial gear position } -bool AnalogShifter::update() { - detector.poll(); - - switch (detector.getState()) { - - // connected! poll the ADC for new analog axis data - case(DeviceConnection::Connected): - analogAxis[Axis::X].read(); - analogAxis[Axis::Y].read(); - break; - - // on an unplug event, we want to reset our position back to - // neutral and then immediately return - case(DeviceConnection::Unplug): - +bool AnalogShifter::updateState(bool connected) { + // if not connected, reset our position back to neutral + // and immediately return + if (!connected) { // set axis values to calibrated neutral analogAxis[Axis::X].setPosition(calibration.neutralX); analogAxis[Axis::Y].setPosition(calibration.neutralY); @@ -735,23 +762,21 @@ bool AnalogShifter::update() { // set gear to neutral this->setGear(0); + // status changed if gear changed return this->gearChanged(); - break; - - // if the device is either disconnected or just plugged in and unstable, - // immediately return false to save on processing - case(DeviceConnection::PlugIn): - case(DeviceConnection::Disconnected): - return false; - break; } + // poll the analog axes for new data + analogAxis[Axis::X].read(); + analogAxis[Axis::Y].read(); + const int x = analogAxis[Axis::X].getPosition(); + const int y = analogAxis[Axis::Y].getPosition(); + + // check previous gears for comparison const Gear previousGear = this->getGear(); const bool prevOdd = ((previousGear != -1) && (previousGear & 1)); // were we previously in an odd gear const bool prevEven = (!prevOdd && previousGear != 0); // were we previously in an even gear - - const int x = analogAxis[Axis::X].getPosition(); - const int y = analogAxis[Axis::Y].getPosition(); + Gear newGear = 0; // If we're below the 'release' thresholds, we must still be in the previous gear @@ -812,7 +837,7 @@ int AnalogShifter::getPositionRaw(Axis ax) const { bool AnalogShifter::getReverseButton() const { // if the reverse pin is not set *or* if the device is not currently // connected, avoid reading the floating input and just return 'false' - if (pinReverse == UnusedPin || detector.getState() != DeviceConnection::Connected) { + if (pinReverse == UnusedPin) { return false; } return digitalRead(pinReverse); @@ -1015,9 +1040,9 @@ void AnalogShifter::serialCalibration(Stream& iface) { LogitechShifter::LogitechShifter(PinNum pinX, PinNum pinY, PinNum pinRev, PinNum detectPin) : AnalogShifter( -1, 6, // includes reverse and gears 1-6 - pinX, pinY, pinRev, - detectPin, false // active high - ) + pinX, pinY, pinRev, &this->detectObj), + + detectObj(detectPin, false) // active high { this->setCalibration({ 490, 440 }, { 253, 799 }, { 262, 86 }, { 460, 826 }, { 470, 76 }, { 664, 841 }, { 677, 77 }); } @@ -1028,8 +1053,9 @@ LogitechShifter::LogitechShifter(PinNum pinX, PinNum pinY, PinNum pinRev, PinNum Handbrake::Handbrake(PinNum pinAx, PinNum detectPin, boolean detectActiveLow) : + Peripheral(&this->detectObj), analogAxis(pinAx), - detector(detectPin, detectActiveLow), + detectObj(detectPin, detectActiveLow), changed(false) {} @@ -1037,19 +1063,26 @@ void Handbrake::begin() { update(); // set initial handbrake position } -bool Handbrake::update() { - changed = false; +bool Handbrake::updateState(bool connected) { + this->changed = false; - detector.poll(); - if (detector.getState() == DeviceConnection::Connected) { - changed = analogAxis.read(); + // if connected, read state of the axis + if (connected) { + this->changed = this->analogAxis.read(); } - else if (detector.getState() == DeviceConnection::Unplug) { - analogAxis.setPosition(analogAxis.getMin()); - changed = true; + + // otherwise, set axis to its minimum (idle) position + else { + const int min = this->analogAxis.getMin(); + const int prev = this->analogAxis.getPositionRaw(); + + if (min != prev) { + this->analogAxis.setPosition(min); + this->changed = true; + } } - return changed; + return this->changed; } long Handbrake::getPosition(long rMin, long rMax) const { diff --git a/src/SimRacing.h b/src/SimRacing.h index 26a1ff6..f606e3e 100644 --- a/src/SimRacing.h +++ b/src/SimRacing.h @@ -106,7 +106,8 @@ namespace SimRacing { bool isConnected() const; /** - * Allows the user to change the stable period of the detector. + * Set how long the detection pin must be stable for before the device + * is considered to be 'connected' * * @param t the amount of time, in ms, the input must be stable for * (no changes) before it's interpreted as 'detected' @@ -249,6 +250,19 @@ namespace SimRacing { */ class Peripheral { public: + /** + * Class Constructor + * + * @param detector pointer to a device connection instance, to use for + * determining if the peripheral is connected + */ + Peripheral(DeviceConnection* detector = nullptr); + + /** + * Class destructor + */ + virtual ~Peripheral() {} + /** * Initialize the hardware (if necessary) */ @@ -257,12 +271,38 @@ namespace SimRacing { /** * Perform a poll of the hardware to refresh the class state * - * @return 'true' if device state changed, 'false' otherwise + * @returns 'true' if device state changed, 'false' otherwise */ - virtual bool update() = 0; + bool update(); - /** @copydoc DeviceConnection::isConnected() */ - virtual bool isConnected() const { return true; } + /** + * Check if the device is physically connected to the board. That means + * it is both present and detected long enough to be considered 'stable'. + * + * @returns 'true' if the device is connected, 'false' otherwise + */ + bool isConnected(); + + /** @copydoc DeviceConnection::setStablePeriod(unsigned long) */ + void setStablePeriod(unsigned long t); + + protected: + /** + * Perform an internal poll of the hardware to refresh the class state + * + * This function is called from within the public update() in order to + * refresh the cached state of the peripheral. It needs to be defined + * in every derived class. This function is the *only* place where the + * cached device state should be changed. + * + * @param connected the state of the device connection + * + * @returns 'true' if device state changed, 'false' otherwise + */ + virtual bool updateState(bool connected) = 0; + + private: + DeviceConnection* detector; ///< Pointer to a device connection instance }; @@ -294,21 +334,20 @@ namespace SimRacing { /** * Class constructor * - * @param dataPtr pointer to the analog input data managed by the class, - * stored elsewhere - * @param nPedals the number of pedals stored in said data pointer - * @param detectPin the digital pin for device detection - * @param detectActiveLow whether the device is detected on a high signal (false, - * default) or a low signal (true) + * @param dataPtr pointer to the analog input data managed by the class, + * stored elsewhere + * @param nPedals the number of pedals stored in said data pointer + * @param detector pointer to a device connection instance, to use for + * determining if the peripheral is connected */ - Pedals(AnalogInput* dataPtr, uint8_t nPedals, PinNum detectPin, bool detectActiveLow = false); + Pedals( + AnalogInput* dataPtr, uint8_t nPedals, + DeviceConnection* detector = nullptr + ); /** @copydoc Peripheral::begin() */ virtual void begin(); - /** @copydoc Peripheral::update() */ - virtual bool update(); - /** * Retrieves the buffered position for the pedal, rescaled to a * nominal range using the calibration values. @@ -370,9 +409,6 @@ namespace SimRacing { */ void serialCalibration(Stream& iface = Serial); - /** @copydoc Peripheral::isConnected() */ - bool isConnected() const { return detector.isConnected(); } - /** * Utility function to get the string name for each pedal. * @@ -381,10 +417,13 @@ namespace SimRacing { */ static String getPedalName(PedalID pedal); + protected: + /** @copydoc Peripheral::updateState(bool) */ + virtual bool updateState(bool connected); + private: AnalogInput* pedalData; ///< pointer to the pedal data const int NumPedals; ///< number of pedals managed by this class - DeviceConnection detector; ///< detector instance for checking if the pedals are connected bool changed; ///< whether the pedal position has changed since the previous update }; @@ -397,15 +436,14 @@ namespace SimRacing { /** * Class constructor * - * @param pinGas the analog pin for the gas pedal potentiometer - * @param pinBrake the analog pin for the brake pedal potentiometer - * @param pinDetect the digital pin for device detection - * @param detectActiveLow whether the device is detected on a high signal (false, - * default) or a low signal (true) + * @param pinGas the analog pin for the gas pedal potentiometer + * @param pinBrake the analog pin for the brake pedal potentiometer + * @param detector pointer to a device connection instance, to use for + * determining if the peripheral is connected */ TwoPedals( PinNum pinGas, PinNum pinBrake, - PinNum pinDetect = UnusedPin, bool detectActiveLow = false + DeviceConnection* detector = nullptr ); /** @@ -430,16 +468,15 @@ namespace SimRacing { /** * Class constructor * - * @param pinGas the analog pin for the gas pedal potentiometer - * @param pinBrake the analog pin for the brake pedal potentiometer - * @param pinClutch the analog pin for the clutch pedal potentiometer - * @param pinDetect the digital pin for device detection - * @param detectActiveLow whether the device is detected on a high signal (false, - * default) or a low signal (true) + * @param pinGas the analog pin for the gas pedal potentiometer + * @param pinBrake the analog pin for the brake pedal potentiometer + * @param pinClutch the analog pin for the clutch pedal potentiometer + * @param detector pointer to a device connection instance, to use for + * determining if the peripheral is connected */ ThreePedals( PinNum pinGas, PinNum pinBrake, PinNum pinClutch, - PinNum pinDetect = UnusedPin, bool detectActiveLow = false + DeviceConnection* detector = nullptr ); /** @@ -478,10 +515,12 @@ namespace SimRacing { /** * Class constructor * - * @param min the lowest gear possible - * @param max the highest gear possible + * @param min the lowest gear possible + * @param max the highest gear possible + * @param detector pointer to a device connection instance, to use for + * determining if the peripheral is connected */ - Shifter(Gear min, Gear max); + Shifter(Gear min, Gear max, DeviceConnection* detector = nullptr); /** * Returns the currently selected gear. @@ -582,14 +621,13 @@ namespace SimRacing { /** * Class constructor * - * @param gearMin the lowest gear possible - * @param gearMax the highest gear possible - * @param pinX the analog input pin for the X axis - * @param pinY the analog input pin for the Y axis - * @param pinRev the digital input pin for the 'reverse' button - * @param pinDetect the digital pin for device detection - * @param detectActiveLow whether the device is detected on a high signal - * (false, default) or a low signal (true) + * @param gearMin the lowest gear possible + * @param gearMax the highest gear possible + * @param pinX the analog input pin for the X axis + * @param pinY the analog input pin for the Y axis + * @param pinRev the digital input pin for the 'reverse' button + * @param detector pointer to a device connection instance, to use for + * determining if the peripheral is connected * * @note With the way the class is designed, the lowest possible gear is * -1 (reverse), and the highest possible gear is 6. Setting the @@ -601,7 +639,7 @@ namespace SimRacing { Gear gearMin, Gear gearMax, PinNum pinX, PinNum pinY, PinNum pinRev = UnusedPin, - PinNum pinDetect = UnusedPin, bool detectActiveLow = false + DeviceConnection* detector = nullptr ); /** @@ -611,13 +649,6 @@ namespace SimRacing { */ virtual void begin(); - /** - * Polls the hardware to update the current gear state. - * - * @return 'true' if the gear has changed, 'false' otherwise - */ - virtual bool update(); - /** @copydoc AnalogInput::getPosition() * @param ax the axis to get the position of */ @@ -685,8 +716,9 @@ namespace SimRacing { */ void serialCalibration(Stream& iface = Serial); - /** @copydoc Peripheral::isConnected() */ - bool isConnected() const { return detector.isConnected(); } + protected: + /** @copydoc Peripheral::updateState(bool) */ + virtual bool updateState(bool connected); private: /** @@ -724,7 +756,6 @@ namespace SimRacing { AnalogInput analogAxis[2]; ///< Axis data for X and Y PinNum pinReverse; ///< The pin for the reverse gear button - DeviceConnection detector; ///< detector instance for checking if the shifter is connected }; /// @} Shifters @@ -753,13 +784,6 @@ namespace SimRacing { */ virtual void begin(); - /** - * Polls the handbrake to update its position. - * - * @return 'true' if the gear has changed, 'false' otherwise - */ - virtual bool update(); - /** * Retrieves the buffered position for the handbrake axis, rescaled to a * nominal range using the calibration values. @@ -786,7 +810,7 @@ namespace SimRacing { * * @return 'true' if the position has changed, 'false' otherwise */ - bool positionChanged() const { return changed; } + bool positionChanged() const { return this->changed; } /// @copydoc AnalogInput::setCalibration() void setCalibration(AnalogInput::Calibration newCal); @@ -794,13 +818,14 @@ namespace SimRacing { /// @copydoc AnalogShifter::serialCalibration() void serialCalibration(Stream& iface = Serial); - /** @copydoc Peripheral::isConnected() */ - bool isConnected() const { return detector.isConnected(); } + protected: + /** @copydoc Peripheral::updateState(bool) */ + virtual bool updateState(bool connected); private: - AnalogInput analogAxis; ///< axis data for the handbrake's position - DeviceConnection detector; ///< detector instance for checking if the handbrake is connected - bool changed; ///< whether the handbrake position has changed since the previous update + AnalogInput analogAxis; ///< axis data for the handbrake's position + DeviceConnection detectObj; ///< detector instance for checking if the handbrake is connected + bool changed; ///< whether the handbrake position has changed since the previous update }; @@ -822,6 +847,9 @@ namespace SimRacing { * pull-down resistor. */ LogitechPedals(PinNum pinGas, PinNum pinBrake, PinNum pinClutch, PinNum pinDetect = UnusedPin); + + private: + DeviceConnection detectObj; ///< detector instance for checking if the pedals are connected }; /** @@ -844,6 +872,9 @@ namespace SimRacing { * pull-down resistor. */ LogitechDrivingForceGT_Pedals(PinNum pinGas, PinNum pinBrake, PinNum pinDetect = UnusedPin); + + private: + DeviceConnection detectObj; ///< detector instance for checking if the pedals are connected }; /** @@ -867,6 +898,9 @@ namespace SimRacing { * pin (DE-9 pin 3) needs to be pulled up to VCC. */ LogitechShifter(PinNum pinX, PinNum pinY, PinNum pinRev = UnusedPin, PinNum pinDetect = UnusedPin); + + private: + DeviceConnection detectObj; ///< detector instance for checking if the shifter is connected }; From ea86a3b93e4438fcb70dfa679d03e6260fb30747 Mon Sep 17 00:00:00 2001 From: David Madison Date: Thu, 13 Jun 2024 17:45:26 -0400 Subject: [PATCH 07/39] Cache reverse button state for shifter With the implementation of connection in the base class, caching this value allows us to clear it on disconnect. It also reinforces the idiom that peripheral state is only changed on 'update()'. --- src/SimRacing.cpp | 21 +++++++++++++++++---- src/SimRacing.h | 12 ++++++++++++ 2 files changed, 29 insertions(+), 4 deletions(-) diff --git a/src/SimRacing.cpp b/src/SimRacing.cpp index 9d2b4d1..3d397f8 100644 --- a/src/SimRacing.cpp +++ b/src/SimRacing.cpp @@ -741,7 +741,8 @@ AnalogShifter::AnalogShifter( /* Two axes, X and Y */ analogAxis{ AnalogInput(pinX), AnalogInput(pinY) }, - pinReverse(sanitizePin(pinRev)) + pinReverse(sanitizePin(pinRev)), + reverseState(false) {} void AnalogShifter::begin() { @@ -759,6 +760,9 @@ bool AnalogShifter::updateState(bool connected) { analogAxis[Axis::X].setPosition(calibration.neutralX); analogAxis[Axis::Y].setPosition(calibration.neutralY); + // set reverse state to unpressed + this->reverseState = false; + // set gear to neutral this->setGear(0); @@ -772,6 +776,9 @@ bool AnalogShifter::updateState(bool connected) { const int x = analogAxis[Axis::X].getPosition(); const int y = analogAxis[Axis::Y].getPosition(); + // poll the reverse button and cache in the class + this->reverseState = this->readReverseButton(); + // check previous gears for comparison const Gear previousGear = this->getGear(); const bool prevOdd = ((previousGear != -1) && (previousGear & 1)); // were we previously in an odd gear @@ -834,15 +841,21 @@ int AnalogShifter::getPositionRaw(Axis ax) const { return analogAxis[ax].getPositionRaw(); } -bool AnalogShifter::getReverseButton() const { - // if the reverse pin is not set *or* if the device is not currently - // connected, avoid reading the floating input and just return 'false' +bool AnalogShifter::readReverseButton() { + // if the reverse pin is not set, avoid reading the + // floating input and just return 'false' if (pinReverse == UnusedPin) { return false; } return digitalRead(pinReverse); } +bool AnalogShifter::getReverseButton() const { + // return the cached reverse state from updateState(bool) + // do NOT poll the button! + return this->reverseState; +} + void AnalogShifter::setCalibration( GearPosition neutral, GearPosition g1, GearPosition g2, GearPosition g3, GearPosition g4, GearPosition g5, GearPosition g6, diff --git a/src/SimRacing.h b/src/SimRacing.h index f606e3e..f70786f 100644 --- a/src/SimRacing.h +++ b/src/SimRacing.h @@ -721,6 +721,17 @@ namespace SimRacing { virtual bool updateState(bool connected); private: + /** + * Read the state of the reverse button + * + * This function should *only* be called as part of updateState(bool), + * to update the state of the device. + * + * @returns the state of the reverse button, 'true' if pressed, + * 'false' otherwise + */ + virtual bool readReverseButton(); + /** * Distance from neutral on Y to register a gear as * being engaged (as a percentage of distance from @@ -756,6 +767,7 @@ namespace SimRacing { AnalogInput analogAxis[2]; ///< Axis data for X and Y PinNum pinReverse; ///< The pin for the reverse gear button + bool reverseState; ///< Buffered value for the state of the reverse gear button }; /// @} Shifters From f22ad323294417030ec2ed41cdb09c9cd88e43b8 Mon Sep 17 00:00:00 2001 From: David Madison Date: Thu, 13 Jun 2024 20:57:24 -0400 Subject: [PATCH 08/39] Add Logitech G27 shifter support Extends the LogitechShifter class, and adds support for reading the shift registers connected to the shifter's additional buttons. --- src/SimRacing.cpp | 225 ++++++++++++++++++++++++++++++++++++++++++++++ src/SimRacing.h | 161 +++++++++++++++++++++++++++++++++ 2 files changed, 386 insertions(+) diff --git a/src/SimRacing.cpp b/src/SimRacing.cpp index 3d397f8..5c3f71d 100644 --- a/src/SimRacing.cpp +++ b/src/SimRacing.cpp @@ -1060,6 +1060,231 @@ LogitechShifter::LogitechShifter(PinNum pinX, PinNum pinY, PinNum pinRev, PinNum this->setCalibration({ 490, 440 }, { 253, 799 }, { 262, 86 }, { 460, 826 }, { 470, 76 }, { 664, 841 }, { 677, 77 }); } + +LogitechShifterG27::LogitechShifterG27( + PinNum pinX, PinNum pinY, + PinNum pinLatch, PinNum pinClock, PinNum pinData, + PinNum pinDetect, + PinNum pinLed +) : + LogitechShifter(pinX, pinY, UnusedPin, pinDetect), + + pinLatch(sanitizePin(pinLatch)), pinClock(sanitizePin(pinClock)), pinData(sanitizePin(pinData)), + pinLed(sanitizePin(pinLed)) +{ + this->pinModesSet = false; + this->buttonStates = this->previousButtons = 0x0000; // zero all button data +} + +void LogitechShifterG27::cacheButtons(uint16_t newStates) { + this->previousButtons = this->buttonStates; // save current to previous + this->buttonStates = newStates; // replace current with new value +} + +void LogitechShifterG27::setPinModes(bool enabled) { + // check if pins are valid. if one or more pins is unused, + // this isn't going to work and we shouldn't bother setting + // any of the pin states + if ( + this->pinData == UnusedPin || + this->pinLatch == UnusedPin || + this->pinClock == UnusedPin) + { + return; + } + + // set up data pin to read from regardless + pinMode(this->pinData, INPUT); + + // enabled = drive the output pins + if (enabled) { + // note: writing the output before setting the + // pin mode so that we don't accidentally drive + // the wrong direction momentarily + + // set latch pin as output, HIGH on idle + digitalWrite(this->pinLatch, HIGH); + pinMode(this->pinLatch, OUTPUT); + + // set clock pin as output, LOW on idle + digitalWrite(this->pinClock, LOW); + pinMode(this->pinClock, OUTPUT); + + // if we have an LED pin, set it to output and turn + // the LED on (active low) + if (this->pinLed != UnusedPin) { + digitalWrite(this->pinLed, LOW); + pinMode(this->pinLed, OUTPUT); + } + } + + // disabled = leave output pins as high-z + else { + // note: setting the mode before writing the + // output for the same reason; changing in + // high-z mode is safer + + // set latch pin as high impedance, with pull-up + pinMode(this->pinLatch, INPUT); + digitalWrite(this->pinLatch, HIGH); + + // set clock pin as high impedance, no pull-up + pinMode(this->pinClock, INPUT); + digitalWrite(this->pinClock, LOW); + + // if we have an LED pin, set it to input, LOW on idle + if (this->pinLed != UnusedPin) { + pinMode(this->pinLed, INPUT); + digitalWrite(this->pinLed, LOW); + } + } + + this->pinModesSet = enabled; +} + +uint16_t LogitechShifterG27::readShiftRegisters() { + // if the pin outputs are not set, quit (none pressed) + if (!this->pinModesSet) return 0x0000; + + uint16_t data = 0x0000; + + // pulse shift register latch from high to low to high, 12 us + // (this timing is *completely* arbitrary, but it's nice to have + // *some* delay so that much faster MCUs don't blow through it) + digitalWrite(this->pinLatch, LOW); + delayMicroseconds(12); + digitalWrite(this->pinLatch, HIGH); + delayMicroseconds(12); + + // clock is pullsed from LOW to HIGH on every bit, + // and then left to idle low + for (int i = 0; i < 16; ++i) { + digitalWrite(this->pinClock, LOW); + const bool state = digitalRead(this->pinData); + if (state) data |= 1 << (15 - i); // store data in word, MSB-first + digitalWrite(this->pinClock, HIGH); + delayMicroseconds(6); + } + digitalWrite(this->pinClock, LOW); + + return data; +} + +void LogitechShifterG27::begin() { + // disable pin outputs. this sets the initial + // 'safe' state. the outputs will be enabled + // by the 'updateState(bool)' function when needed. + this->setPinModes(0); + + // call the begin() class of the base, which will also + // poll 'update()' on our behalf + this->AnalogShifter::begin(); +} + +bool LogitechShifterG27::updateState(bool connected) { + bool changed = false; + + // if we're connected, set the pin modes, read the + // shift registers, and cache the data + if (connected) { + if (!this->pinModesSet) { + this->setPinModes(1); + } + + const uint16_t data = this->readShiftRegisters(); + this->cacheButtons(data); + changed |= this->buttonsChanged(); + } + + // if we're *not* connected, reset the pin modes and + // set no buttons pressed + else { + if (this->pinModesSet) { + this->setPinModes(0); + } + + this->cacheButtons(0x0000); + changed |= this->buttonsChanged(); + } + + // we also need to update the data for the analog shifter + changed |= AnalogShifter::updateState(connected); + + return changed; +} + +bool LogitechShifterG27::buttonsChanged() const { + return this->buttonStates != this->previousButtons; +} + +bool LogitechShifterG27::getButton(Button button) const { + return this->extractButton(button, this->buttonStates); +} + +bool LogitechShifterG27::getButtonChanged(Button button) const { + return this->getButton(button) != this->extractButton(button, this->previousButtons); +} + +int LogitechShifterG27::getDpadAngle() const { + const Button pads[4] = { + DPAD_UP, + DPAD_RIGHT, + DPAD_DOWN, + DPAD_LEFT, + }; + + // combine pads to a bitfield (nybble) + uint8_t dpad = 0x00; + for (uint8_t i = 0; i < 4; ++i) { + dpad |= (this->getButton(pads[i]) << i); + } + + // The hatswitch value is from 0-7 proceeding clockwise + // from top (0 is 'up', 1 is 'up + right', etc.). I don't + // know of a great way to do this, so have this naive + // lookup table with a built-in SOCD cleaner + + // For this, simultaneous opposing cardinal directions + // are neutral (because this is presumably used for + // navigation only, and not fighting games. Probably). + + // bitfield to hatswitch lookup table + const uint8_t hat_table[16] = { + 8, // 0b0000, Unpressed + 0, // 0b0001, Up + 2, // 0b0010, Right + 1, // 0b0011, Right + Up + 4, // 0b0100, Down + 8, // 0b0101, Down + Up (SOCD None) + 3, // 0b0110, Down + Right + 2, // 0b0111, Down + Right + Up (SOCD Right) + 6, // 0b1000, Left + 7, // 0b1001, Left + Up + 8, // 0b1010, Left + Right (SOCD None) + 0, // 0b1011, Left + Right + Up (SOCD Up) + 5, // 0b1100, Left + Down + 6, // 0b1101, Left + Down + Up (SOCD Left) + 4, // 0b1110, Left + Down + Right (SOCD Down) + 8, // 0b1111, Left + Down + Right + Up (SOCD None) + }; + + // multiply the 0-8 value by 45 to get it in degrees + int16_t angle = hat_table[dpad & 0x0F] * 45; + + // edge case: if no buttons are pressed, the angle is '-1' + if (angle == 360) angle = -1; + + return angle; +} + +bool LogitechShifterG27::readReverseButton() { + // this virtual function is provided for the sake of the AnalogShifter base + // class, which can use this to get the button state from the shift register + // without needing to interface with the shift registers themselves + return this->getButton(BUTTON_REVERSE); +} + + //######################################################### // Handbrake # //######################################################### diff --git a/src/SimRacing.h b/src/SimRacing.h index f70786f..92e382c 100644 --- a/src/SimRacing.h +++ b/src/SimRacing.h @@ -915,6 +915,167 @@ namespace SimRacing { DeviceConnection detectObj; ///< detector instance for checking if the shifter is connected }; + /** + * @brief Interface with the Logitech G27 shifter + * @ingroup Shifters + * + * The G27 shifter includes the same analog shifter as the Logitech Driving + * Force shifter (implemented in the LogitechShifter class), as well as + * a directional pad and eight buttons. + * + * @see https://en.wikipedia.org/wiki/Logitech_G27 + */ + class LogitechShifterG27 : public LogitechShifter { + public: + /** + * @brief Enumeration of button values + * + * Buttons 1-4 are the red buttons, from left to right. The directional + * pad is read as four separate buttons. The black buttons use cardinal + * directions. + * + * These values represent the bit offset from LSB. Data is read in + * MSB first. + */ + enum Button : uint8_t { + BUTTON_UNUSED1 = 15, ///< Unused shift register pin + BUTTON_REVERSE = 14, ///< Reverse button (press down on the shifter) + BUTTON_UNUSED2 = 13, ///< Unused shift register pin + BUTTON_SEQUENTIAL = 12, ///< Sequential mode button (turn the dial counter-clockwise) + BUTTON_3 = 11, ///< 3rd red button (mid right) + BUTTON_2 = 10, ///< 2nd red button (mid left) + BUTTON_4 = 9, ///< 4th red button (far right) + BUTTON_1 = 8, ///< 1st red button (far left) + BUTTON_NORTH = 7, ///< The top black button + BUTTON_EAST = 6, ///< The right black button + BUTTON_WEST = 5, ///< The left black button + BUTTON_SOUTH = 4, ///< The bottom black button + DPAD_RIGHT = 3, ///< Right button of the directional pad + DPAD_LEFT = 2, ///< Left button of the directional pad + DPAD_DOWN = 1, ///< Down button of the directional pad + DPAD_UP = 0, ///< Top button of the directional pad + }; + + /** + * Class constructor + * + * @param pinX analog input pin for the X axis, DE-9 pin 4 + * @param pinY analog input pin for the Y axis, DE-9 pin 8 + * @param pinLatch digital output pin to pulse to latch data, DE-9 pin 3 + * @param pinClock digital output pin to pulse as a clock, DE-9 pin 7 + * @param pinData digital input pin to use for reading data, DE-9 pin 2 + * @param pinDetect the digital input pin for device detection, DE-9 pin 1. + * Requires a pull-down resistor. + * @param pinLed digital output pin to light the power LED on connection, + * DE-9 pin 5. Requires a 100 Ohm series resistor. + */ + LogitechShifterG27( + PinNum pinX, PinNum pinY, + PinNum pinLatch, PinNum pinClock, PinNum pinData, + PinNum pinDetect = UnusedPin, + PinNum pinLed = UnusedPin + ); + + /** + * Initializes the hardware pins for reading the gear states and + * the buttons. + */ + virtual void begin(); + + /** + * Retrieve the state of a single button + * + * @param button The button to retrieve + * @returns The state of the button + */ + bool getButton(Button button) const; + + /** + * Checks whether a button has changed between updates + * + * @param button The button to check + * @returns 'true' if the button's state has changed, 'false' otherwise + */ + bool getButtonChanged(Button button) const; + + /** + * Get the directional pad angle in degrees + * + * This is useful for using the directional pad as a "hatswitch", in USB + * + * @returns the directional pad value in degrees (0-360), or '-1' if no + * directional pad buttons are pressed + */ + int getDpadAngle() const; + + /** + * Checks if any of the buttons have changed since the last update + * + * @returns 'true' if any buttons have changed state, 'false' otherwise + */ + bool buttonsChanged() const; + + protected: + /** @copydoc Peripheral::updateState(bool) */ + virtual bool updateState(bool connected); + + private: + /** + * Extracts a button value from a given data word + * + * @param button The button to extract state for + * @param data Packed data word containing button states + * + * @returns The state of the button + */ + static bool extractButton(Button button, uint16_t data) { + // convert button to single bit with offset, and perform + // a bitwise 'AND' to get the bit value + return data & (1 << (uint8_t) button); + } + + /** + * Store the current button data for reference and replace it with + * a new value + * + * @param newStates The new button states to store + */ + void cacheButtons(uint16_t newStates); + + /** + * Set the pin modes for all pins + * + * @param enabled 'true' to set the pins to their active configuration, + * 'false' to set them to idle / safe + */ + void setPinModes(bool enabled); + + /** + * Shift the button data out from the shift register + * + * @returns the 16-bit data from the shift registers + */ + uint16_t readShiftRegisters(); + + /** @copydoc AnalogShifter::readReverseButton() */ + virtual bool readReverseButton(); + + // Pins for the shift register interface + PinNum pinLatch; ///< Pin to pulse to latch data, DE-9 pin 3 + PinNum pinClock; ///< Pin to pulse as a clock, DE-9 pin 7 + PinNum pinData; ///< Pin to use for reading data, DE-9 pin 2 + + // Generic I/O pins + PinNum pinLed; ///< Pin to light the power LED, DE-9 pin 5 + + // I/O state + bool pinModesSet; ///< Flag for whether the output pins are enabled / driven + + // Button states + uint16_t buttonStates; ///< the state of the buttons, as a packed word (where 0 = unpressed and 1 = pressed) + uint16_t previousButtons; ///< the previous state of the buttons, for comparison + }; + #if defined(__AVR_ATmega32U4__) || defined(SIM_RACING_DOXYGEN) /** From 9d1fb718ee0955644c22c07bd0fa480b9ab16b71 Mon Sep 17 00:00:00 2001 From: David Madison Date: Tue, 18 Jun 2024 17:35:53 -0400 Subject: [PATCH 09/39] Add Logitech G25 shifter support Extends the LogitechShifterG27 class, and adds support for the sequential shifting feature --- src/SimRacing.cpp | 265 ++++++++++++++++++++++++++++++++++++++++++++++ src/SimRacing.h | 100 +++++++++++++++++ 2 files changed, 365 insertions(+) diff --git a/src/SimRacing.cpp b/src/SimRacing.cpp index 5c3f71d..46250dd 100644 --- a/src/SimRacing.cpp +++ b/src/SimRacing.cpp @@ -1285,6 +1285,271 @@ bool LogitechShifterG27::readReverseButton() { } +/* +* Static calibration constants +* These values are arbitrary - just what worked well with my own shifter. +*/ +const float LogitechShifterG25::CalEngagementPoint = 0.70; +const float LogitechShifterG25::CalReleasePoint = 0.50; + +LogitechShifterG25::LogitechShifterG25( + PinNum pinX, PinNum pinY, + PinNum pinLatch, PinNum pinClock, PinNum pinData, + PinNum pinDetect, + PinNum pinLed +) : + LogitechShifterG27( + pinX, pinY, + pinLatch, pinClock, pinData, + pinDetect, + pinLed + ), + + sequentialProcess(false), // not in sequential mode + sequentialState(0) // no sequential buttons pressed +{ + // using the values from my own shifter + this->setCalibrationSequential(425, 619, 257, 0.70, 0.50); +} + +void LogitechShifterG25::begin() { + this->sequentialProcess = false; // clear process flag + this->sequentialState = 0; // clear any pressed buttons + + this->LogitechShifterG27::begin(); // call base class begin() +} + +bool LogitechShifterG25::updateState(bool connected) { + // call the base class to update the state of the + // buttons and the H-pattern shifter + bool changed = this->LogitechShifterG27::updateState(connected); + + // if we're connected and in sequential mode... + if (connected && this->inSequentialMode()) { + + // clear 'changed', because this will falsely report a change + // if we've "shifted" into 2nd/4th in the process of sequential + // shifting + changed = false; + + // force neutral gear, ignoring the H-pattern selection + this->setGear(0); + + // edge case: if we've not just switched into sequential mode, + // we need to ignore the H-pattern gear change (to 2/4, and then + // set by us to neutral). We can do that, hackily, by setting to + // neutral again to clear the cached gear for comparison. + if (this->sequentialProcess) { + this->setGear(0); + } + + // read the raw y axis value, ignoring the H-pattern calibration + const int y = this->getPositionRaw(Axis::Y); + + // save the previous state for reference + const int8_t prevState = this->sequentialState; + + // if we're neutral, check for up/down shift + if (this->sequentialState == 0) { + if (y >= this->seqCalibration.upTrigger) this->sequentialState = 1; + else if (y <= this->seqCalibration.downTrigger) this->sequentialState = -1; + } + + // if we're in up-shift mode, check for release + else if ((this->sequentialState == 1) && (y < this->seqCalibration.upRelease)) { + this->sequentialState = 0; + } + + // if we're in down-shift mode, check for release + else if ((this->sequentialState == -1) && (y > this->seqCalibration.downRelease)) { + this->sequentialState = 0; + } + + // set the 'changed' flag if the sequential state changed + if (prevState != this->sequentialState) { + changed = true; + } + // otherwise, set 'changed' based on the buttons *only* + else { + changed = this->buttonsChanged(); + } + + // set 'process' flag to handle edge case on subsequent updates + this->sequentialProcess = true; + } + + // if we're not connected or if the sequential mode has been disabled, + // clear the sequential flags if they have been set + else { + if (this->sequentialProcess) { + this->sequentialProcess = false; // not in sequential mode + this->sequentialState = 0; // no sequential buttons pressed + changed = true; + } + } + + return changed; +} + +bool LogitechShifterG25::inSequentialMode() const { + return this->getButton(BUTTON_SEQUENTIAL); +} + +bool LogitechShifterG25::getShiftUp() const { + return this->sequentialState == 1; +} + +bool LogitechShifterG25::getShiftDown() const { + return this->sequentialState == -1; +} + +void LogitechShifterG25::setCalibrationSequential(int neutral, int up, int down, float engagePoint, float releasePoint) { + // limit percentage thresholds + engagePoint = floatPercent(engagePoint); + releasePoint = floatPercent(releasePoint); + + // prevent release point from being higher than engage + // (which will prevent the shifter from working at all) + if (releasePoint > engagePoint) { + releasePoint = engagePoint; + } + + // calculate ranges + const int upRange = up - neutral; + const int downRange = neutral - down; + + // calculate calibration points + this->seqCalibration.upTrigger = neutral + (upRange * engagePoint); + this->seqCalibration.upRelease = neutral + (upRange * releasePoint); + + this->seqCalibration.downTrigger = neutral - (downRange * engagePoint); + this->seqCalibration.downRelease = neutral - (downRange * releasePoint); +} + +void LogitechShifterG25::serialCalibrationSequential(Stream& iface) { + // err if not connected + if (this->isConnected() == false) { + iface.print(F("Error! Cannot perform calibration, ")); + iface.print(F("shifter")); + iface.println(F(" is not connected.")); + return; + } + + const char* separator = "------------------------------------"; + + iface.println(); + iface.println(F("Sim Racing Library G25 Sequential Shifter Calibration")); + iface.println(separator); + iface.println(); + + while (this->inSequentialMode() == false) { + iface.print(F("Please press down on the shifter and move the dial counter-clockwise to put the shifter into sequential mode")); + iface.print(F(". Send any character to continue.")); + iface.println(F(" Send 'q' to quit.")); + iface.println(); + + waitClient(iface); + this->update(); + + // quit if user sends 'q' + if (iface.read() == 'q') { + iface.println(F("Quitting sequential calibration! Goodbye <3")); + iface.println(); + return; + } + + // send an error if we're still not there + if (this->inSequentialMode() == false) { + iface.println(F("Error: The shifter is not in sequential mode")); + iface.println(); + } + } + + float engagementPoint = LogitechShifterG25::CalEngagementPoint; + float releasePoint = LogitechShifterG25::CalReleasePoint; + + const uint8_t NumPoints = 3; + const char* directions[2] = { + "up", + "down", + }; + int data[NumPoints]; + + int& neutral = data[0]; + int& yMax = data[1]; + int& yMin = data[2]; + + for (uint8_t i = 0; i < NumPoints; ++i) { + if (i == 0) { + iface.print(F("Leave the gear shifter in neutral")); + } + else { + iface.print(F("Please move the gear shifter to sequentially shift ")); + iface.print(directions[i - 1]); + iface.print(F(" and hold it there")); + } + iface.println(F(". Send any character to continue.")); + waitClient(iface); + + this->update(); + data[i] = this->getPositionRaw(Axis::Y); + iface.println(); // spacing + } + + iface.println(F("These settings are optional. Send 'y' to customize. Send any other character to continue with the default values.")); + + iface.print(F(" * Shift Engagement Point: \t")); + iface.println(engagementPoint); + + iface.print(F(" * Shift Release Point: \t")); + iface.println(releasePoint); + + iface.println(); + + waitClient(iface); + + if (iface.read() == 'y') { + iface.println(F("Set the engagement point as a floating point percentage. This is the percentage away from the neutral axis on Y to start shifting.")); + readFloat(engagementPoint, iface); + iface.println(); + + iface.println(F("Set the release point as a floating point percentage. This is the percentage away from the neutral axis on Y to stop shifting. It must be less than the engagement point.")); + readFloat(releasePoint, iface); + iface.println(); + } + + flushClient(iface); + + // apply and print + this->setCalibrationSequential(neutral, yMax, yMin, engagementPoint, releasePoint); + + iface.println(F("Here is your calibration:")); + iface.println(separator); + iface.println(); + + iface.print(F("shifter.setCalibrationSequential( ")); + + iface.print(neutral); + iface.print(", "); + iface.print(yMax); + iface.print(", "); + iface.print(yMin); + iface.print(", "); + + iface.print(engagementPoint); + iface.print(", "); + iface.print(releasePoint); + iface.print(");"); + iface.println(); + + iface.println(); + iface.println(separator); + iface.println(); + + iface.println(F("Paste this line into the setup() function to calibrate on startup.")); + iface.println(F("\n\nCalibration complete! :)\n")); +} + //######################################################### // Handbrake # //######################################################### diff --git a/src/SimRacing.h b/src/SimRacing.h index 92e382c..8ac0502 100644 --- a/src/SimRacing.h +++ b/src/SimRacing.h @@ -1076,6 +1076,106 @@ namespace SimRacing { uint16_t previousButtons; ///< the previous state of the buttons, for comparison }; + /** + * @brief Interface with the Logitech G25 shifter + * @ingroup Shifters + * + * The G25 shifter includes the same analog shifter as the Logitech Driving + * Force shifter (implemented in the LogitechShifter class), the buttons + * included with the G27 shifter (implemented in the LogitechShifterG27 + * class), and a mode switch between H-pattern and sequential shift modes. + * + * @see https://en.wikipedia.org/wiki/Logitech_G25 + */ + class LogitechShifterG25 : public LogitechShifterG27 { + public: + /** + * @copydoc LogitechShifterG27::LogitechShifterG27(PinNum, PinNum, + * PinNum, PinNum, PinNum, PinNum, PinNum) + */ + LogitechShifterG25( + PinNum pinX, PinNum pinY, + PinNum pinLatch, PinNum pinClock, PinNum pinData, + PinNum pinDetect = UnusedPin, + PinNum pinLed = UnusedPin + ); + + /** @copydoc LogitechShifterG27::begin() */ + virtual void begin(); + + /** + * Check if the shifter is in sequential shifting mode + * + * @returns 'true' if the shifter is in sequential shifting mode, + * 'false' if the shifter is in H-pattern shifting mode. + */ + bool inSequentialMode() const; + + /** + * Check if the sequential shifter is shifted up + * + * @returns 'true' if the sequential shifter is shifted up, + * 'false' otherwise + */ + bool getShiftUp() const; + + /** + * Check if the sequential shifter is shifted down + * + * @returns 'true' if the sequential shifter is shifted down, + * 'false' otherwise + */ + bool getShiftDown() const; + + /** + * Calibrate the sequential shifter for more accurate shifting. + * + * @param neutral the Y position of the shifter in neutral + * @param up the Y position of the shifter in sequential 'up' + * @param down the Y position of the shifter in sequential 'down' + * @param engagePoint distance from neutral on Y to register a gear as + * being engaged (as a percentage of distance from + * neutral to Y max, 0-1) + * @param releasePoint distance from neutral on Y to go back into neutral + * from an engaged gear (as a percentage of distance + * from neutral to Y max, 0-1) + */ + void setCalibrationSequential(int neutral, int up, int down, float engagePoint, float releasePoint); + + /** @copydoc AnalogShifter::serialCalibration(Stream&) */ + void serialCalibrationSequential(Stream& iface = Serial); + + protected: + /** @copydoc Peripheral::updateState(bool) */ + virtual bool updateState(bool connected); + + private: + /** + * Distance from neutral on Y to register a gear as + * being engaged (as a percentage of distance from + * neutral to Y max, 0-1). Used for calibration. + */ + static const float CalEngagementPoint; + + /** + * Distance from neutral on Y to go back into neutral + * from an engaged gear (as a percentage of distance + * from neutral to Y max, 0-1). Used for calibration. + */ + static const float CalReleasePoint; + + bool sequentialProcess; ///< Flag to indicate whether we are processing sequential shifts + int8_t sequentialState; ///< Tri-state flag for the shift direction. 1 (Up), 0 (Neutral), -1 (Down). + + /*** Internal calibration struct */ + struct SequentialCalibration { + int upTrigger; ///< Threshold to set the sequential shift as 'up' + int upRelease; ///< Threshold to clear the 'up' sequential shift + int downTrigger; ///< Threshold to set the sequential shift as 'down' + int downRelease; ///< Threshold to clear the 'down' sequential shift + } seqCalibration; + }; + #if defined(__AVR_ATmega32U4__) || defined(SIM_RACING_DOXYGEN) /** From 24c9742aa1409e728dc97fcea8790e807e136102 Mon Sep 17 00:00:00 2001 From: David Madison Date: Tue, 18 Jun 2024 19:53:37 -0400 Subject: [PATCH 10/39] Add Logitech G25 and G27 shifter examples --- .../LogitechShifterG25_Joystick.ino | 153 ++++++++++++++++++ .../LogitechShifterG25_Print.ino | 153 ++++++++++++++++++ .../LogitechShifterG27_Joystick.ino | 144 +++++++++++++++++ .../LogitechShifterG27_Print.ino | 143 ++++++++++++++++ 4 files changed, 593 insertions(+) create mode 100644 examples/Shifter/LogitechShifterG25/LogitechShifterG25_Joystick/LogitechShifterG25_Joystick.ino create mode 100644 examples/Shifter/LogitechShifterG25/LogitechShifterG25_Print/LogitechShifterG25_Print.ino create mode 100644 examples/Shifter/LogitechShifterG27/LogitechShifterG27_Joystick/LogitechShifterG27_Joystick.ino create mode 100644 examples/Shifter/LogitechShifterG27/LogitechShifterG27_Print/LogitechShifterG27_Print.ino diff --git a/examples/Shifter/LogitechShifterG25/LogitechShifterG25_Joystick/LogitechShifterG25_Joystick.ino b/examples/Shifter/LogitechShifterG25/LogitechShifterG25_Joystick/LogitechShifterG25_Joystick.ino new file mode 100644 index 0000000..d8f62e7 --- /dev/null +++ b/examples/Shifter/LogitechShifterG25/LogitechShifterG25_Joystick/LogitechShifterG25_Joystick.ino @@ -0,0 +1,153 @@ +/* + * Project Sim Racing Library for Arduino + * @author David Madison + * @link github.com/dmadison/Sim-Racing-Arduino + * @license LGPLv3 - Copyright (c) 2024 David Madison + * + * This file is part of the Sim Racing Library for Arduino. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + + /** + * @details Emulates the Logitech G25 shifter as a joystick over USB. + * @example LogitechShifterG25_Joystick.ino + */ + +// This example requires the Arduino Joystick Library +// Download Here: https://github.com/MHeironimus/ArduinoJoystickLibrary + +#include +#include + +const int Pin_ShifterX = A0; // DE-9 pin 4 +const int Pin_ShifterY = A2; // DE-9 pin 8 + +const int Pin_ShifterLatch = 5; // DE-9 pin 3 +const int Pin_ShifterClock = 6; // DE-9 pin 7 +const int Pin_ShifterData = 7; // DE-9 pin 2 + +// These pins require extra resistors! If you have made the proper +// connections, change the pin numbers to the ones you're using +const int Pin_ShifterDetect = SimRacing::UnusedPin; // DE-9 pin 1, requires pull-down resistor +const int Pin_ShifterLED = SimRacing::UnusedPin; // DE-9 pin 5, requires 100-120 Ohm series resistor + +SimRacing::LogitechShifterG25 shifter( + Pin_ShifterX, Pin_ShifterY, + Pin_ShifterLatch, Pin_ShifterClock, Pin_ShifterData, + Pin_ShifterDetect, Pin_ShifterLED +); + +// Set this option to 'true' to send the shifter's X/Y position +// as a joystick. This is not needed for most games. +const bool SendAnalogAxis = false; + +const int Gears[] = { 1, 2, 3, 4, 5, 6, -1 }; +const int NumGears = sizeof(Gears) / sizeof(Gears[0]); + +using ShifterButton = SimRacing::LogitechShifterG25::Button; +const ShifterButton Buttons[] = { + ShifterButton::BUTTON_SOUTH, + ShifterButton::BUTTON_EAST, + ShifterButton::BUTTON_WEST, + ShifterButton::BUTTON_NORTH, + ShifterButton::BUTTON_1, + ShifterButton::BUTTON_2, + ShifterButton::BUTTON_3, + ShifterButton::BUTTON_4, +}; +const int NumButtons = sizeof(Buttons) / sizeof(Buttons[0]); + +const int ADC_Max = 1023; // 10-bit on AVR + +Joystick_ Joystick( + JOYSTICK_DEFAULT_REPORT_ID, // default report (no additional pages) + JOYSTICK_TYPE_JOYSTICK, // so that this shows up in Windows joystick manager + NumGears + NumButtons + 2, // number of buttons (7 gears: reverse and 1-6, 8 buttons, 2 sequential gears) + 1, // number of hat switches (1, the directional pad) + SendAnalogAxis, SendAnalogAxis, // include X and Y axes for analog output, if set above + false, false, false, false, false, false, false, false, false); // no other axes + +void updateJoystick(); // forward-declared function for non-Arduino environments + + +void setup() { + shifter.begin(); + + // if you have one, your calibration line should go here + + Joystick.begin(false); // 'false' to disable auto-send + Joystick.setXAxisRange(0, ADC_Max); + Joystick.setYAxisRange(ADC_Max, 0); // invert axis so 'up' is up + + updateJoystick(); // send initial state +} + +void loop() { + bool dataChanged = shifter.update(); + + if (dataChanged || SendAnalogAxis == true) { + updateJoystick(); + } +} + +void updateJoystick() { + // keep track of which button we're updating + // in the joystick output + int currentButton = 0; + + // set the buttons corresponding to the gears + for (int i = 0; i < NumGears; i++) { + if (shifter.getGear() == Gears[i]) { + Joystick.pressButton(currentButton); + } + else { + Joystick.releaseButton(currentButton); + } + + currentButton++; + } + + // set the analog axes (if the option is set) + if (SendAnalogAxis == true) { + int x = shifter.getPosition(SimRacing::X, 0, ADC_Max); + int y = shifter.getPosition(SimRacing::Y, 0, ADC_Max); + Joystick.setXAxis(x); + Joystick.setYAxis(y); + } + + // set the buttons + for (int i = 0; i < NumButtons; i++) { + bool state = shifter.getButton(Buttons[i]); + Joystick.setButton(currentButton, state); + + currentButton++; + } + + // set the hatswitch (directional pad) + int angle = shifter.getDpadAngle(); + Joystick.setHatSwitch(0, angle); + + // set the sequential shifting buttons + bool shiftUp = shifter.getShiftUp(); + Joystick.setButton(currentButton, shiftUp); + currentButton++; + + bool shiftDown = shifter.getShiftDown(); + Joystick.setButton(currentButton, shiftDown); + currentButton++; + + // send the updated data via USB + Joystick.sendState(); +} diff --git a/examples/Shifter/LogitechShifterG25/LogitechShifterG25_Print/LogitechShifterG25_Print.ino b/examples/Shifter/LogitechShifterG25/LogitechShifterG25_Print/LogitechShifterG25_Print.ino new file mode 100644 index 0000000..537aeeb --- /dev/null +++ b/examples/Shifter/LogitechShifterG25/LogitechShifterG25_Print/LogitechShifterG25_Print.ino @@ -0,0 +1,153 @@ +/* + * Project Sim Racing Library for Arduino + * @author David Madison + * @link github.com/dmadison/Sim-Racing-Arduino + * @license LGPLv3 - Copyright (c) 2024 David Madison + * + * This file is part of the Sim Racing Library for Arduino. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + + /** + * @details Reads from the Logitech G25 shifter and prints the data over serial. + * @example LogitechShifterG25_Print.ino + */ + +#include + +const int Pin_ShifterX = A0; // DE-9 pin 4 +const int Pin_ShifterY = A2; // DE-9 pin 8 + +const int Pin_ShifterLatch = 5; // DE-9 pin 3 +const int Pin_ShifterClock = 6; // DE-9 pin 7 +const int Pin_ShifterData = 7; // DE-9 pin 2 + +// These pins require extra resistors! If you have made the proper +// connections, change the pin numbers to the ones you're using +const int Pin_ShifterDetect = SimRacing::UnusedPin; // DE-9 pin 1, requires pull-down resistor +const int Pin_ShifterLED = SimRacing::UnusedPin; // DE-9 pin 5, requires 100-120 Ohm series resistor + +SimRacing::LogitechShifterG25 shifter( + Pin_ShifterX, Pin_ShifterY, + Pin_ShifterLatch, Pin_ShifterClock, Pin_ShifterData, + Pin_ShifterDetect, Pin_ShifterLED +); + +// alias so we don't need to type so much +using ShifterButton = SimRacing::LogitechShifterG25::Button; + +// forward-declared functions for non-Arduino environments +void printConditional(bool state, char pressed); +void printButton(ShifterButton button, char pressed); +void printShifter(); + +const unsigned long PrintSpeed = 1500; // ms +unsigned long lastPrint = 0; + + +void setup() { + shifter.begin(); + + // if you have one, your calibration line should go here + + Serial.begin(115200); + while (!Serial); // wait for connection to open + + Serial.println("Logitech G25 Starting..."); +} + +void loop() { + // send some serial data to run conversational calibration + if (Serial.read() != -1) { + shifter.serialCalibration(); + shifter.serialCalibrationSequential(); + delay(2000); + } + + bool dataChanged = shifter.update(); + + // if data has changed, print immediately + if (dataChanged) { + Serial.print("! "); + printShifter(); + } + + // otherwise, print if we've been idle for awhile + if (millis() - lastPrint >= PrintSpeed) { + Serial.print(" "); + printShifter(); + } +} + +void printConditional(bool state, char pressed) { + if (state == true) { + Serial.print(pressed); + } + else { + Serial.print('_'); + } +} + +void printButton(ShifterButton button, char pressed) { + bool state = shifter.getButton(button); + printConditional(state, pressed); +} + +void printShifter() { + // if in sequential mode, print up/down + if (shifter.inSequentialMode()) { + Serial.print("S:["); + printConditional(shifter.getShiftUp(), '+'); + printConditional(shifter.getShiftDown(), '-'); + Serial.print(']'); + } + // otherwise in H-pattern mode, print the gear + else { + Serial.print("H: ["); + Serial.print(shifter.getGearChar()); + Serial.print("]"); + } + + // print X/Y position of shifter + Serial.print(" - XY: ("); + Serial.print(shifter.getPositionRaw(SimRacing::X)); + Serial.print(", "); + Serial.print(shifter.getPositionRaw(SimRacing::Y)); + Serial.print(") "); + + // print directional pad + printButton(ShifterButton::DPAD_LEFT, '<'); + printButton(ShifterButton::DPAD_UP, '^'); + printButton(ShifterButton::DPAD_DOWN, 'v'); + printButton(ShifterButton::DPAD_RIGHT, '>'); + Serial.print(' '); + + // print black buttons + printButton(ShifterButton::BUTTON_NORTH, 'N'); + printButton(ShifterButton::BUTTON_SOUTH, 'S'); + printButton(ShifterButton::BUTTON_EAST, 'E'); + printButton(ShifterButton::BUTTON_WEST, 'W'); + Serial.print(' '); + + // print red buttons + printButton(ShifterButton::BUTTON_1, '1'); + printButton(ShifterButton::BUTTON_2, '2'); + printButton(ShifterButton::BUTTON_3, '3'); + printButton(ShifterButton::BUTTON_4, '4'); + + Serial.println(); + + lastPrint = millis(); +} diff --git a/examples/Shifter/LogitechShifterG27/LogitechShifterG27_Joystick/LogitechShifterG27_Joystick.ino b/examples/Shifter/LogitechShifterG27/LogitechShifterG27_Joystick/LogitechShifterG27_Joystick.ino new file mode 100644 index 0000000..25463d2 --- /dev/null +++ b/examples/Shifter/LogitechShifterG27/LogitechShifterG27_Joystick/LogitechShifterG27_Joystick.ino @@ -0,0 +1,144 @@ +/* + * Project Sim Racing Library for Arduino + * @author David Madison + * @link github.com/dmadison/Sim-Racing-Arduino + * @license LGPLv3 - Copyright (c) 2024 David Madison + * + * This file is part of the Sim Racing Library for Arduino. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + + /** + * @details Emulates the Logitech G27 shifter as a joystick over USB. + * @example LogitechShifterG27_Joystick.ino + */ + +// This example requires the Arduino Joystick Library +// Download Here: https://github.com/MHeironimus/ArduinoJoystickLibrary + +#include +#include + +const int Pin_ShifterX = A0; // DE-9 pin 4 +const int Pin_ShifterY = A2; // DE-9 pin 8 + +const int Pin_ShifterLatch = 5; // DE-9 pin 3 +const int Pin_ShifterClock = 6; // DE-9 pin 7 +const int Pin_ShifterData = 7; // DE-9 pin 2 + +// These pins require extra resistors! If you have made the proper +// connections, change the pin numbers to the ones you're using +const int Pin_ShifterDetect = SimRacing::UnusedPin; // DE-9 pin 1, requires pull-down resistor +const int Pin_ShifterLED = SimRacing::UnusedPin; // DE-9 pin 5, requires 100-120 Ohm series resistor + +SimRacing::LogitechShifterG27 shifter( + Pin_ShifterX, Pin_ShifterY, + Pin_ShifterLatch, Pin_ShifterClock, Pin_ShifterData, + Pin_ShifterDetect, Pin_ShifterLED +); + +// Set this option to 'true' to send the shifter's X/Y position +// as a joystick. This is not needed for most games. +const bool SendAnalogAxis = false; + +const int Gears[] = { 1, 2, 3, 4, 5, 6, -1 }; +const int NumGears = sizeof(Gears) / sizeof(Gears[0]); + +using ShifterButton = SimRacing::LogitechShifterG27::Button; +const ShifterButton Buttons[] = { + ShifterButton::BUTTON_SOUTH, + ShifterButton::BUTTON_EAST, + ShifterButton::BUTTON_WEST, + ShifterButton::BUTTON_NORTH, + ShifterButton::BUTTON_1, + ShifterButton::BUTTON_2, + ShifterButton::BUTTON_3, + ShifterButton::BUTTON_4, +}; +const int NumButtons = sizeof(Buttons) / sizeof(Buttons[0]); + +const int ADC_Max = 1023; // 10-bit on AVR + +Joystick_ Joystick( + JOYSTICK_DEFAULT_REPORT_ID, // default report (no additional pages) + JOYSTICK_TYPE_JOYSTICK, // so that this shows up in Windows joystick manager + NumGears + NumButtons, // number of buttons (7 gears: reverse and 1-6, 8 buttons) + 1, // number of hat switches (1, the directional pad) + SendAnalogAxis, SendAnalogAxis, // include X and Y axes for analog output, if set above + false, false, false, false, false, false, false, false, false); // no other axes + +void updateJoystick(); // forward-declared function for non-Arduino environments + + +void setup() { + shifter.begin(); + + // if you have one, your calibration line should go here + + Joystick.begin(false); // 'false' to disable auto-send + Joystick.setXAxisRange(0, ADC_Max); + Joystick.setYAxisRange(ADC_Max, 0); // invert axis so 'up' is up + + updateJoystick(); // send initial state +} + +void loop() { + bool dataChanged = shifter.update(); + + if (dataChanged || SendAnalogAxis == true) { + updateJoystick(); + } +} + +void updateJoystick() { + // keep track of which button we're updating + // in the joystick output + int currentButton = 0; + + // set the buttons corresponding to the gears + for (int i = 0; i < NumGears; i++) { + if (shifter.getGear() == Gears[i]) { + Joystick.pressButton(currentButton); + } + else { + Joystick.releaseButton(currentButton); + } + + currentButton++; + } + + // set the analog axes (if the option is set) + if (SendAnalogAxis == true) { + int x = shifter.getPosition(SimRacing::X, 0, ADC_Max); + int y = shifter.getPosition(SimRacing::Y, 0, ADC_Max); + Joystick.setXAxis(x); + Joystick.setYAxis(y); + } + + // set the buttons + for (int i = 0; i < NumButtons; i++) { + bool state = shifter.getButton(Buttons[i]); + Joystick.setButton(currentButton, state); + + currentButton++; + } + + // set the hatswitch (directional pad) + int angle = shifter.getDpadAngle(); + Joystick.setHatSwitch(0, angle); + + // send the updated data via USB + Joystick.sendState(); +} diff --git a/examples/Shifter/LogitechShifterG27/LogitechShifterG27_Print/LogitechShifterG27_Print.ino b/examples/Shifter/LogitechShifterG27/LogitechShifterG27_Print/LogitechShifterG27_Print.ino new file mode 100644 index 0000000..8cd5312 --- /dev/null +++ b/examples/Shifter/LogitechShifterG27/LogitechShifterG27_Print/LogitechShifterG27_Print.ino @@ -0,0 +1,143 @@ +/* + * Project Sim Racing Library for Arduino + * @author David Madison + * @link github.com/dmadison/Sim-Racing-Arduino + * @license LGPLv3 - Copyright (c) 2024 David Madison + * + * This file is part of the Sim Racing Library for Arduino. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + + /** + * @details Reads from the Logitech G27 shifter and prints the data over serial. + * @example LogitechShifterG27_Print.ino + */ + +#include + +const int Pin_ShifterX = A0; // DE-9 pin 4 +const int Pin_ShifterY = A2; // DE-9 pin 8 + +const int Pin_ShifterLatch = 5; // DE-9 pin 3 +const int Pin_ShifterClock = 6; // DE-9 pin 7 +const int Pin_ShifterData = 7; // DE-9 pin 2 + +// These pins require extra resistors! If you have made the proper +// connections, change the pin numbers to the ones you're using +const int Pin_ShifterDetect = SimRacing::UnusedPin; // DE-9 pin 1, requires pull-down resistor +const int Pin_ShifterLED = SimRacing::UnusedPin; // DE-9 pin 5, requires 100-120 Ohm series resistor + +SimRacing::LogitechShifterG27 shifter( + Pin_ShifterX, Pin_ShifterY, + Pin_ShifterLatch, Pin_ShifterClock, Pin_ShifterData, + Pin_ShifterDetect, Pin_ShifterLED +); + +// alias so we don't need to type so much +using ShifterButton = SimRacing::LogitechShifterG27::Button; + +// forward-declared functions for non-Arduino environments +void printConditional(bool state, char pressed); +void printButton(ShifterButton button, char pressed); +void printShifter(); + +const unsigned long PrintSpeed = 1500; // ms +unsigned long lastPrint = 0; + + +void setup() { + shifter.begin(); + + // if you have one, your calibration line should go here + + Serial.begin(115200); + while (!Serial); // wait for connection to open + + Serial.println("Logitech G27 Starting..."); +} + +void loop() { + // send some serial data to run conversational calibration + if (Serial.read() != -1) { + shifter.serialCalibration(); + delay(2000); + } + + bool dataChanged = shifter.update(); + + // if data has changed, print immediately + if (dataChanged) { + Serial.print("! "); + printShifter(); + } + + // otherwise, print if we've been idle for awhile + if (millis() - lastPrint >= PrintSpeed) { + Serial.print(" "); + printShifter(); + } +} + +void printConditional(bool state, char pressed) { + if (state == true) { + Serial.print(pressed); + } + else { + Serial.print('_'); + } +} + +void printButton(ShifterButton button, char pressed) { + bool state = shifter.getButton(button); + printConditional(state, pressed); +} + +void printShifter() { + // print the H-pattern gear + Serial.print("H:["); + Serial.print(shifter.getGearChar()); + Serial.print("]"); + + // print X/Y position of shifter + Serial.print(" - XY: ("); + Serial.print(shifter.getPositionRaw(SimRacing::X)); + Serial.print(", "); + Serial.print(shifter.getPositionRaw(SimRacing::Y)); + Serial.print(") "); + + // print directional pad + printButton(ShifterButton::DPAD_LEFT, '<'); + printButton(ShifterButton::DPAD_UP, '^'); + printButton(ShifterButton::DPAD_DOWN, 'v'); + printButton(ShifterButton::DPAD_RIGHT, '>'); + Serial.print(' '); + + // print black buttons + printButton(ShifterButton::BUTTON_NORTH, 'N'); + printButton(ShifterButton::BUTTON_SOUTH, 'S'); + printButton(ShifterButton::BUTTON_EAST, 'E'); + printButton(ShifterButton::BUTTON_WEST, 'W'); + Serial.print(' '); + + // print red buttons + printButton(ShifterButton::BUTTON_1, '1'); + printButton(ShifterButton::BUTTON_2, '2'); + printButton(ShifterButton::BUTTON_3, '3'); + printButton(ShifterButton::BUTTON_4, '4'); + + Serial.println(); + + lastPrint = millis(); +} From 2e8eae116c590c56859a2650d98a045e85fb0e09 Mon Sep 17 00:00:00 2001 From: David Madison Date: Tue, 18 Jun 2024 19:59:15 -0400 Subject: [PATCH 11/39] Add Logitech G25 Shifter documentation page --- docs/pages/devices/logitech_shifter_g25.md | 88 ++++++++++++++++++++++ docs/pages/supported_devices.md | 1 + 2 files changed, 89 insertions(+) create mode 100644 docs/pages/devices/logitech_shifter_g25.md diff --git a/docs/pages/devices/logitech_shifter_g25.md b/docs/pages/devices/logitech_shifter_g25.md new file mode 100644 index 0000000..fdb1d9e --- /dev/null +++ b/docs/pages/devices/logitech_shifter_g25.md @@ -0,0 +1,88 @@ +# Logitech G25 / G27 Shifter {#logitech_shifter_g25} + +The [Logitech G25](https://en.wikipedia.org/wiki/Logitech_G25) and [Logitech G27](https://en.wikipedia.org/wiki/Logitech_G27) shifters are implemented using the SimRacing::LogitechShifterG25 and SimRacing::LogitechShifterG27 classes, respectively. The G25 and G27 shifters function identically, except for the lack of sequential mode on the G27. + +These notes are based off of disassembling my own G25 shifter, with the internal PCB marked "202339-0000 REV. A1". + +## Connector + +| ![DE-9_Male](DE9_Male.svg) | ![DE-9_Female](DE9_Female.svg) | +| :-----------------------: | :---------------------------: | +| DE-9 Male Connector | DE-9 Female connector | + +DE-9 graphic from [Aeroid](https://commons.wikimedia.org/wiki/User:Aeroid) @ [Wikimedia Commons](https://commons.wikimedia.org/wiki/File:DE9_Diagram.svg#/media/File:DE-9_Female.svg), modified for scale, colors, and creation of a complementary male version. These graphics are licensed under [CC BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/). + +Both the G25 and G27 Logitech shifters connect to the wheel base units using [a female DE-9 connector](https://en.wikipedia.org/wiki/D-subminiature). Note that most jumper wires with [DuPont headers](https://en.wikipedia.org/wiki/Jump_wire) will not fit snugly into a DE-9 connector. For reliability and ease of use it's recommended to use a mating male DE-9 connector when interfacing with the shifter. + +Note that the DE-9 connector is often erroneously referred to as DB-9. These are the same thing. + +## Pinout + +| Function | DE-9 Pin | Internal J11 Pin | Data Direction | Wire Color | Necessary | Recommended Pin | +|---------------------------|----------|------------------|----------------|------------------|-----------|-----------------| +| Power | 1 | 1 | - | Black | | | +| Data Output (SDO) | 2 | 7 | Out | Gray | X | 7 | +| Latch / Chip Select | 3 | 5 | In | Yellow | X | 5 | +| X Axis Wiper | 4 | 3 | Out | Orange | X | A0 | +| Data In (SDI) / Power LED | 5 | 2 | In | Red | | | +| Ground | 6 | 8 | - | Black (Sheathed) | X | GND | +| Clock (SCLK) | 7 | 6 | In | Purple | X | 6 | +| Y Axis Wiper | 8 | 4 | Out | Green | X | A2 | +| Power | 9 | 1 | - | Black | X | VCC | + +Pin #2 (Data Output) is connected directly to the output of the EEPROM, and connected to the output of the shift registers through a 1000 Ohm resistor. + +Pin #3 (Latch / Chip Select) is shared between the onboard EEPROM and the shift registers. It is normally HIGH. It must be pulsed once (HIGH / LOW / HIGH) to latch the data into the shift registers. Holding it low instructs the EEPROM to listen for commands. + +Pin #5 (Data In) is used exclusively by the EEPROM. It is also connected to the "Power" LED through a 330 Ohm resistor. Driving this pin LOW will turn on the LED. As the "Sequential Mode" LED is connected using a 470 Ohm resistor, I would recommend using a 100-120 Ohm resistor in series so that the pair are closer in brightness. + +Pin #7 (Clock) is connected to the clock inputs of both the EEPROM and the shift registers. It is normally LOW and must be pulsed HIGH to send or receive data. Be wary of driving this pin to ground without protection, as this pin is also used as a joint power input for the [Logitech Driving Force Shifter](@ref logitech_shifter). + +The power pins (#1 / #9) are connected together within the DE-9 connector. Either one can be used, but it is recommended to use #9 for better forwards-compatibility with the [Logitech Driving Force Shifter](@ref logitech_shifter). + +The shifter's electronics are theoretically compatibile with both 3.3V and 5V logic. Be sure to use the appropriate voltage for the logic level of your microcontroller. + +## Buttons + +The shifter includes 12 user-facing buttons: + +* Four black buttons in a diamond pattern +* One directional pad (D-Pad) +* Four red buttons in a straight line + +The shifter also contains two internal buttons: a button on the bottom of the shifter to indicate that it's in reverse, and a button on the sequential mode dial to indicate that it's in sequential mode. These buttons are implemented as part of the SimRacing::LogitechShifterButtons class. + +### Shift Registers + +These buttons are connected to the external DE-9 connector through a series of NXP 74HC165D parallel-to-serial shift registers. + +| Button | Register | Bit | Offset | Enum | +|-----------------------|----------|-----|--------|--------------------------------------------------| +| (Unused) | Bottom | D7 | 15 | SimRacing::LogitechShifterG27::BUTTON_UNUSED1 | +| Reverse | Bottom | D6 | 14 | SimRacing::LogitechShifterG27::BUTTON_REVERSE | +| (Unused) | Bottom | D5 | 13 | SimRacing::LogitechShifterG27::BUTTON_UNUSED2 | +| Sequential Mode | Bottom | D4 | 12 | SimRacing::LogitechShifterG27::BUTTON_SEQUENTIAL | +| Red #3 | Bottom | D3 | 11 | SimRacing::LogitechShifterG27::BUTTON_3 | +| Red #2 | Bottom | D2 | 10 | SimRacing::LogitechShifterG27::BUTTON_2 | +| Red #4 | Bottom | D1 | 9 | SimRacing::LogitechShifterG27::BUTTON_4 | +| Red #1 | Bottom | D0 | 8 | SimRacing::LogitechShifterG27::BUTTON_1 | +| Black Up | Top | D7 | 7 | SimRacing::LogitechShifterG27::BUTTON_NORTH | +| Black Right | Top | D6 | 6 | SimRacing::LogitechShifterG27::BUTTON_EAST | +| Black Left | Top | D5 | 5 | SimRacing::LogitechShifterG27::BUTTON_WEST | +| Black Down | Top | D4 | 4 | SimRacing::LogitechShifterG27::BUTTON_SOUTH | +| Directional Pad Right | Top | D3 | 3 | SimRacing::LogitechShifterG27::DPAD_RIGHT | +| Directional Pad Left | Top | D2 | 2 | SimRacing::LogitechShifterG27::DPAD_LEFT | +| Directional Pad Down | Top | D1 | 1 | SimRacing::LogitechShifterG27::DPAD_DOWN | +| Directional Pad Up | Top | D0 | 0 | SimRacing::LogitechShifterG27::DPAD_UP | + +Data from the shift registers can be read using the Data Output (DE-9 #2), Latch (DE-9 #3), and Clock (DE-9 #7) pins. The latch must be pulsed LOW (HIGH / LOW / HIGH), then data read out via the data output pin while the clock is pulsed repeatedly from LOW to HIGH. + +All buttons will report a '1' state if they are pressed, and a '0' state if they are unpressed. Internally, all of these buttons are held to ground with 10k pull-downs. + +The red buttons are numbered from left to right, 1-4. The black buttons use cardinal directions. + +## EEPROM Storage + +The Logitech shifter has an internal EEPROM chip, presumably for storing settings and calibration data. In my shifter this is an [ST Microelectronics M95010-W](https://www.st.com/resource/en/datasheet/m95010-w.pdf) in an SO8 package. It has 1 Kbit of memory and can be read and written to via the DE-9 connector. The EERPOM does not need to be used in order to retrieve the control surface data from the shifter. + +This library does not implement EEPROM support, either for reading from the EEPROM or utilizing its data. diff --git a/docs/pages/supported_devices.md b/docs/pages/supported_devices.md index 0761f5a..20bcbbf 100644 --- a/docs/pages/supported_devices.md +++ b/docs/pages/supported_devices.md @@ -11,3 +11,4 @@ - @subpage logitech_pedals - @subpage logitech_shifter +- @subpage logitech_shifter_g25 From 7ea3fcfdaad0ace5de65e10b4dd0646713b5e1ca Mon Sep 17 00:00:00 2001 From: David Madison Date: Tue, 18 Jun 2024 20:15:39 -0400 Subject: [PATCH 12/39] Add G923, G29, and G920 shifter aliases There seem to be a number of users confused about what the "Driving Force" shifter is. These aliases cover users who are using the shifter that came with their wheel and know their wheel model only. --- src/SimRacing.h | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/SimRacing.h b/src/SimRacing.h index 8ac0502..b245996 100644 --- a/src/SimRacing.h +++ b/src/SimRacing.h @@ -915,6 +915,30 @@ namespace SimRacing { DeviceConnection detectObj; ///< detector instance for checking if the shifter is connected }; + /** + * @brief Interface with the Logitech G923 shifter + * @ingroup Shifters + * + * @see https://www.logitechg.com/en-us/products/driving/g923-trueforce-sim-racing-wheel.html + */ + using LogitechShifterG923 = LogitechShifter; + + /** + * @brief Interface with the Logitech G29 shifter + * @ingroup Shifters + * + * @see https://en.wikipedia.org/wiki/Logitech_G29 + */ + using LogitechShifterG29 = LogitechShifter; + + /** + * @brief Interface with the Logitech G920 shifter + * @ingroup Shifters + * + * @see https://en.wikipedia.org/wiki/Logitech_G29 + */ + using LogitechShifterG920 = LogitechShifter; + /** * @brief Interface with the Logitech G27 shifter * @ingroup Shifters From 0791db724c12210333ad55eb62986a506d73f86a Mon Sep 17 00:00:00 2001 From: David Madison Date: Tue, 18 Jun 2024 20:40:49 -0400 Subject: [PATCH 13/39] Rename the Logitech shifter examples And puts them in their own folder, as the library now supports multiple different shifters (although they're all Logitech at the moment...) --- .../LogitechShifter_Joystick/LogitechShifter_Joystick.ino} | 5 +++-- .../LogitechShifter_Print/LogitechShifter_Print.ino} | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) rename examples/Shifter/{ShiftJoystick/ShiftJoystick.ino => LogitechShifter/LogitechShifter_Joystick/LogitechShifter_Joystick.ino} (95%) rename examples/Shifter/{ShiftPrint/ShiftPrint.ino => LogitechShifter/LogitechShifter_Print/LogitechShifter_Print.ino} (92%) diff --git a/examples/Shifter/ShiftJoystick/ShiftJoystick.ino b/examples/Shifter/LogitechShifter/LogitechShifter_Joystick/LogitechShifter_Joystick.ino similarity index 95% rename from examples/Shifter/ShiftJoystick/ShiftJoystick.ino rename to examples/Shifter/LogitechShifter/LogitechShifter_Joystick/LogitechShifter_Joystick.ino index 6b36ef6..6270fa8 100644 --- a/examples/Shifter/ShiftJoystick/ShiftJoystick.ino +++ b/examples/Shifter/LogitechShifter/LogitechShifter_Joystick/LogitechShifter_Joystick.ino @@ -21,8 +21,9 @@ */ /** - * @details Emulates the shifter as a joystick over USB. - * @example ShiftJoystick.ino + * @details Emulates the Logitech Driving Force shifter (included with + * the G923 / G920 / G29 wheels) as a joystick over USB. + * @example LogitechShifter_Joystick.ino */ // This example requires the Arduino Joystick Library diff --git a/examples/Shifter/ShiftPrint/ShiftPrint.ino b/examples/Shifter/LogitechShifter/LogitechShifter_Print/LogitechShifter_Print.ino similarity index 92% rename from examples/Shifter/ShiftPrint/ShiftPrint.ino rename to examples/Shifter/LogitechShifter/LogitechShifter_Print/LogitechShifter_Print.ino index dbabb6f..58443fc 100644 --- a/examples/Shifter/ShiftPrint/ShiftPrint.ino +++ b/examples/Shifter/LogitechShifter/LogitechShifter_Print/LogitechShifter_Print.ino @@ -21,8 +21,9 @@ */ /** - * @details Reads and prints the current gear over serial. - * @example ShiftPrint.ino + * @details Reads from the Logitech Driving Force shifter (included with + * the G923 / G920 / G29 wheels) and prints the data over serial. + * @example LogitechShifter_Print.ino */ #include From b46ecde136efcfd951486dd9afc314a99ae1196d Mon Sep 17 00:00:00 2001 From: David Madison Date: Tue, 18 Jun 2024 20:43:16 -0400 Subject: [PATCH 14/39] Add DE-9 pin numbers to shifter examples --- .../LogitechShifter_Joystick/LogitechShifter_Joystick.ino | 6 +++--- .../LogitechShifter_Print/LogitechShifter_Print.ino | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/examples/Shifter/LogitechShifter/LogitechShifter_Joystick/LogitechShifter_Joystick.ino b/examples/Shifter/LogitechShifter/LogitechShifter_Joystick/LogitechShifter_Joystick.ino index 6270fa8..81ad9e1 100644 --- a/examples/Shifter/LogitechShifter/LogitechShifter_Joystick/LogitechShifter_Joystick.ino +++ b/examples/Shifter/LogitechShifter/LogitechShifter_Joystick/LogitechShifter_Joystick.ino @@ -41,9 +41,9 @@ const bool SendAnalogAxis = false; // games, but can be useful for custom controller purposes. const bool SendReverseRaw = false; -const int Pin_ShifterX = A0; -const int Pin_ShifterY = A2; -const int Pin_ShifterRev = 2; +const int Pin_ShifterX = A0; // DE-9 pin 4 +const int Pin_ShifterY = A2; // DE-9 pin 8 +const int Pin_ShifterRev = 2; // DE-9 pin 2 SimRacing::LogitechShifter shifter(Pin_ShifterX, Pin_ShifterY, Pin_ShifterRev); //SimRacing::LogitechShifter shifter(SHIFTER_SHIELD_V1_PINS); diff --git a/examples/Shifter/LogitechShifter/LogitechShifter_Print/LogitechShifter_Print.ino b/examples/Shifter/LogitechShifter/LogitechShifter_Print/LogitechShifter_Print.ino index 58443fc..ee91f83 100644 --- a/examples/Shifter/LogitechShifter/LogitechShifter_Print/LogitechShifter_Print.ino +++ b/examples/Shifter/LogitechShifter/LogitechShifter_Print/LogitechShifter_Print.ino @@ -28,9 +28,9 @@ #include -const int Pin_ShifterX = A0; -const int Pin_ShifterY = A2; -const int Pin_ShifterRev = 2; +const int Pin_ShifterX = A0; // DE-9 pin 4 +const int Pin_ShifterY = A2; // DE-9 pin 8 +const int Pin_ShifterRev = 2; // DE-9 pin 2 SimRacing::LogitechShifter shifter(Pin_ShifterX, Pin_ShifterY, Pin_ShifterRev); //SimRacing::LogitechShifter shifter(SHIFTER_SHIELD_V1_PINS); From 42958895b46f85fced75ae1997509f20905e6b16 Mon Sep 17 00:00:00 2001 From: David Madison Date: Tue, 18 Jun 2024 20:55:36 -0400 Subject: [PATCH 15/39] Add DE-9 power pin comments to shifters Mostly because the CS pin pull-up is absolutely necessary for the 'reverse' signal to work on the Driving Force shifters. --- .../LogitechShifter_Joystick/LogitechShifter_Joystick.ino | 3 +++ .../LogitechShifter_Print/LogitechShifter_Print.ino | 3 +++ .../LogitechShifterG25_Joystick.ino | 2 ++ .../LogitechShifterG25_Print/LogitechShifterG25_Print.ino | 2 ++ .../LogitechShifterG27_Joystick.ino | 2 ++ .../LogitechShifterG27_Print/LogitechShifterG27_Print.ino | 2 ++ 6 files changed, 14 insertions(+) diff --git a/examples/Shifter/LogitechShifter/LogitechShifter_Joystick/LogitechShifter_Joystick.ino b/examples/Shifter/LogitechShifter/LogitechShifter_Joystick/LogitechShifter_Joystick.ino index 81ad9e1..3df815e 100644 --- a/examples/Shifter/LogitechShifter/LogitechShifter_Joystick/LogitechShifter_Joystick.ino +++ b/examples/Shifter/LogitechShifter/LogitechShifter_Joystick/LogitechShifter_Joystick.ino @@ -41,6 +41,9 @@ const bool SendAnalogAxis = false; // games, but can be useful for custom controller purposes. const bool SendReverseRaw = false; +// Power (VCC): DE-9 pin 9 +// Ground (GND): DE-9 pin 6 +// Note: DE-9 pin 3 (CS) needs to be pulled-up to VCC! const int Pin_ShifterX = A0; // DE-9 pin 4 const int Pin_ShifterY = A2; // DE-9 pin 8 const int Pin_ShifterRev = 2; // DE-9 pin 2 diff --git a/examples/Shifter/LogitechShifter/LogitechShifter_Print/LogitechShifter_Print.ino b/examples/Shifter/LogitechShifter/LogitechShifter_Print/LogitechShifter_Print.ino index ee91f83..45e5fcb 100644 --- a/examples/Shifter/LogitechShifter/LogitechShifter_Print/LogitechShifter_Print.ino +++ b/examples/Shifter/LogitechShifter/LogitechShifter_Print/LogitechShifter_Print.ino @@ -28,6 +28,9 @@ #include +// Power (VCC): DE-9 pin 9 +// Ground (GND): DE-9 pin 6 +// Note: DE-9 pin 3 (CS) needs to be pulled-up to VCC! const int Pin_ShifterX = A0; // DE-9 pin 4 const int Pin_ShifterY = A2; // DE-9 pin 8 const int Pin_ShifterRev = 2; // DE-9 pin 2 diff --git a/examples/Shifter/LogitechShifterG25/LogitechShifterG25_Joystick/LogitechShifterG25_Joystick.ino b/examples/Shifter/LogitechShifterG25/LogitechShifterG25_Joystick/LogitechShifterG25_Joystick.ino index d8f62e7..38f42ab 100644 --- a/examples/Shifter/LogitechShifterG25/LogitechShifterG25_Joystick/LogitechShifterG25_Joystick.ino +++ b/examples/Shifter/LogitechShifterG25/LogitechShifterG25_Joystick/LogitechShifterG25_Joystick.ino @@ -31,6 +31,8 @@ #include #include +// Power (VCC): DE-9 pin 9 +// Ground (GND): DE-9 pin 6 const int Pin_ShifterX = A0; // DE-9 pin 4 const int Pin_ShifterY = A2; // DE-9 pin 8 diff --git a/examples/Shifter/LogitechShifterG25/LogitechShifterG25_Print/LogitechShifterG25_Print.ino b/examples/Shifter/LogitechShifterG25/LogitechShifterG25_Print/LogitechShifterG25_Print.ino index 537aeeb..87687ca 100644 --- a/examples/Shifter/LogitechShifterG25/LogitechShifterG25_Print/LogitechShifterG25_Print.ino +++ b/examples/Shifter/LogitechShifterG25/LogitechShifterG25_Print/LogitechShifterG25_Print.ino @@ -27,6 +27,8 @@ #include +// Power (VCC): DE-9 pin 9 +// Ground (GND): DE-9 pin 6 const int Pin_ShifterX = A0; // DE-9 pin 4 const int Pin_ShifterY = A2; // DE-9 pin 8 diff --git a/examples/Shifter/LogitechShifterG27/LogitechShifterG27_Joystick/LogitechShifterG27_Joystick.ino b/examples/Shifter/LogitechShifterG27/LogitechShifterG27_Joystick/LogitechShifterG27_Joystick.ino index 25463d2..f80d87a 100644 --- a/examples/Shifter/LogitechShifterG27/LogitechShifterG27_Joystick/LogitechShifterG27_Joystick.ino +++ b/examples/Shifter/LogitechShifterG27/LogitechShifterG27_Joystick/LogitechShifterG27_Joystick.ino @@ -31,6 +31,8 @@ #include #include +// Power (VCC): DE-9 pin 9 +// Ground (GND): DE-9 pin 6 const int Pin_ShifterX = A0; // DE-9 pin 4 const int Pin_ShifterY = A2; // DE-9 pin 8 diff --git a/examples/Shifter/LogitechShifterG27/LogitechShifterG27_Print/LogitechShifterG27_Print.ino b/examples/Shifter/LogitechShifterG27/LogitechShifterG27_Print/LogitechShifterG27_Print.ino index 8cd5312..f22157a 100644 --- a/examples/Shifter/LogitechShifterG27/LogitechShifterG27_Print/LogitechShifterG27_Print.ino +++ b/examples/Shifter/LogitechShifterG27/LogitechShifterG27_Print/LogitechShifterG27_Print.ino @@ -27,6 +27,8 @@ #include +// Power (VCC): DE-9 pin 9 +// Ground (GND): DE-9 pin 6 const int Pin_ShifterX = A0; // DE-9 pin 4 const int Pin_ShifterY = A2; // DE-9 pin 8 From ffe2f47bd48fa8c9c77157b857d7af64cce3d052 Mon Sep 17 00:00:00 2001 From: David Madison Date: Tue, 18 Jun 2024 21:14:36 -0400 Subject: [PATCH 16/39] Update Arduino library keywords --- keywords.txt | 59 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/keywords.txt b/keywords.txt index 9e9c1ac..d37c3cc 100644 --- a/keywords.txt +++ b/keywords.txt @@ -35,6 +35,12 @@ AnalogShifter KEYWORD1 LogitechShifter KEYWORD1 +LogitechShifterG923 KEYWORD1 +LogitechShifterG920 KEYWORD1 +LogitechShifterG29 KEYWORD1 +LogitechShifterG27 KEYWORD1 +LogitechShifterG25 KEYWORD1 + # Handbrake Classes Handbrake KEYWORD1 @@ -46,6 +52,7 @@ begin KEYWORD2 update KEYWORD2 isConnected KEYWORD2 +setStablePeriod KEYWORD2 ####################################### # AnalogInput Class Methods and Functions (KEYWORD2) @@ -99,6 +106,55 @@ getReverseButton KEYWORD2 setCalibration KEYWORD2 serialCalibration KEYWORD2 +####################################### +# LogitechShifterG27 Datatypes (KEYWORD1) +####################################### + +# Button Enum +Button KEYWORD1 + +####################################### +# LogitechShifterG27 Constants (LITERAL1) +####################################### + +# Button Enum Values +BUTTON_UNUSED1 LITERAL1 +BUTTON_REVERSE LITERAL1 +BUTTON_UNUSED2 LITERAL1 +BUTTON_SEQUENTIAL LITERAL1 +BUTTON_3 LITERAL1 +BUTTON_2 LITERAL1 +BUTTON_4 LITERAL1 +BUTTON_1 LITERAL1 +BUTTON_NORTH LITERAL1 +BUTTON_EAST LITERAL1 +BUTTON_WEST LITERAL1 +BUTTON_SOUTH LITERAL1 +DPAD_RIGHT LITERAL1 +DPAD_LEFT LITERAL1 +DPAD_DOWN LITERAL1 +DPAD_UP LITERAL1 + +####################################### +# LogitechShifterG27 Methods and Functions (KEYWORD2) +####################################### + +getButton KEYWORD2 +getButtonChanged KEYWORD2 +getDpadAngle KEYWORD2 +buttonsChanged KEYWORD2 + +####################################### +# LogitechShifterG25 Methods and Functions (KEYWORD2) +####################################### + +inSequentialMode KEYWORD2 +getShiftUp KEYWORD2 +getShiftDown KEYWORD2 + +setCalibrationSequential KEYWORD2 +serialCalibrationSequential KEYWORD2 + ####################################### # Handbrake Methods and Functions (KEYWORD2) ####################################### @@ -118,6 +174,9 @@ serialCalibration KEYWORD2 # Constants (LITERAL1) ####################################### +# Unused Pin Flag +UnusedPin LITERAL1 + # Axis Enum X LITERAL1 Y LITERAL1 From daaac6608d48fe2b96352114e4bd306d8ca26213 Mon Sep 17 00:00:00 2001 From: David Madison Date: Tue, 18 Jun 2024 23:20:43 -0400 Subject: [PATCH 17/39] Add G25/G27 shifters to README --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index f6933ef..50ea736 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,8 @@ Run one of the library examples in the Arduino IDE by going to `File -> Examples ### Commercial Devices * [Logitech Two Pedal Peripheral (Gas, Brake)](http://dmadison.github.io/Sim-Racing-Arduino/docs/logitech_pedals.html) * [Logitech Three Pedal Peripheral (Gas, Brake, Clutch)](http://dmadison.github.io/Sim-Racing-Arduino/docs/logitech_pedals.html) -* [Logitech Driving Force Shifter](http://dmadison.github.io/Sim-Racing-Arduino/docs/logitech_shifter.html) +* [Logitech Driving Force Shifter (G923 / G920 / G29)](http://dmadison.github.io/Sim-Racing-Arduino/docs/logitech_shifter.html) +* [Logitech G25 / G27 Shifter](http://dmadison.github.io/Sim-Racing-Arduino/docs/logitech_shifter_g25.html) ## Adapters From b52b0760775a43df7071b032368fd10907c1a1c3 Mon Sep 17 00:00:00 2001 From: David Madison Date: Tue, 18 Jun 2024 23:25:43 -0400 Subject: [PATCH 18/39] Add G923 / G920 / G29 note to docs page --- docs/pages/devices/logitech_shifter.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/pages/devices/logitech_shifter.md b/docs/pages/devices/logitech_shifter.md index 90ed00f..35eee6c 100644 --- a/docs/pages/devices/logitech_shifter.md +++ b/docs/pages/devices/logitech_shifter.md @@ -1,8 +1,8 @@ -# Logitech Driving Force Shifter {#logitech_shifter} +# Logitech Driving Force Shifter (G923 / G920 / G29) {#logitech_shifter} -The [Logitech Driving Force Shifter](https://www.logitechg.com/en-us/products/driving/driving-force-shifter.html) is implemented using the SimRacing::LogitechShifter class. +The [Logitech Driving Force Shifter](https://www.logitechg.com/en-us/products/driving/driving-force-shifter.html) is implemented using the SimRacing::LogitechShifter class. This shifter is included with the [G923](https://www.logitechg.com/en-us/products/driving/g923-trueforce-sim-racing-wheel.html), [G920](https://en.wikipedia.org/wiki/Logitech_G29), and [G29](https://en.wikipedia.org/wiki/Logitech_G29) wheels. -See the ShiftPrint.ino and ShiftJoystick.ino examples for reference. +See the LogitechShifter_Print.ino and LogitechShifter_Joystick.ino examples for reference. ## Adapters From d50bd665c504a1b7a877ac9d5a10d5184ba29926 Mon Sep 17 00:00:00 2001 From: David Madison Date: Tue, 18 Jun 2024 23:27:49 -0400 Subject: [PATCH 19/39] Add G25 / G27 shifter examples to docs page --- docs/pages/devices/logitech_shifter_g25.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/pages/devices/logitech_shifter_g25.md b/docs/pages/devices/logitech_shifter_g25.md index fdb1d9e..0b03042 100644 --- a/docs/pages/devices/logitech_shifter_g25.md +++ b/docs/pages/devices/logitech_shifter_g25.md @@ -2,6 +2,8 @@ The [Logitech G25](https://en.wikipedia.org/wiki/Logitech_G25) and [Logitech G27](https://en.wikipedia.org/wiki/Logitech_G27) shifters are implemented using the SimRacing::LogitechShifterG25 and SimRacing::LogitechShifterG27 classes, respectively. The G25 and G27 shifters function identically, except for the lack of sequential mode on the G27. +See the LogitechShifterG25_Print.ino, LogitechShifterG27_Print.ino, LogitechShifterG25_Joystick.ino, and LogitechShifterG27_Joystick.ino examples for reference. + These notes are based off of disassembling my own G25 shifter, with the internal PCB marked "202339-0000 REV. A1". ## Connector From a8090b3d3ac5042024af34e493af248e76477f09 Mon Sep 17 00:00:00 2001 From: David Madison Date: Sun, 7 Jul 2024 21:39:18 -0400 Subject: [PATCH 20/39] Const-qualify isConnected() in base This should be const qualified as it was before. The class state should change on 'update()' and the detector state should change on 'poll()'. This function should never change the class state. --- src/SimRacing.cpp | 2 +- src/SimRacing.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/SimRacing.cpp b/src/SimRacing.cpp index 46250dd..5b5b64b 100644 --- a/src/SimRacing.cpp +++ b/src/SimRacing.cpp @@ -366,7 +366,7 @@ bool Peripheral::update() { return this->updateState(connected); } -bool Peripheral::isConnected() { +bool Peripheral::isConnected() const { // if detector exists, return state if (this->detector) { return this->detector->isConnected(); diff --git a/src/SimRacing.h b/src/SimRacing.h index b245996..d35d48c 100644 --- a/src/SimRacing.h +++ b/src/SimRacing.h @@ -281,7 +281,7 @@ namespace SimRacing { * * @returns 'true' if the device is connected, 'false' otherwise */ - bool isConnected(); + bool isConnected() const; /** @copydoc DeviceConnection::setStablePeriod(unsigned long) */ void setStablePeriod(unsigned long t); From 7db12ed32c1ab6d38d83ce84eaa0ae9289070d00 Mon Sep 17 00:00:00 2001 From: David Madison Date: Sun, 7 Jul 2024 22:57:35 -0400 Subject: [PATCH 21/39] Add calibration line to G27 shifter This works fine on my shifter with the DrivingForce calibration, but is more accurate with its own. --- src/SimRacing.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/SimRacing.cpp b/src/SimRacing.cpp index 5b5b64b..285d61f 100644 --- a/src/SimRacing.cpp +++ b/src/SimRacing.cpp @@ -1074,6 +1074,9 @@ LogitechShifterG27::LogitechShifterG27( { this->pinModesSet = false; this->buttonStates = this->previousButtons = 0x0000; // zero all button data + + // using the calibration from my G25 shifter, which is probably closer than the Driving Force + this->setCalibration({ 508, 435 }, { 310, 843 }, { 303, 8 }, { 516, 827 }, { 540, 14 }, { 713, 846 }, { 704, 17 }); } void LogitechShifterG27::cacheButtons(uint16_t newStates) { From b402d613c2a4d4b70ae1bb382035fe767e22c532 Mon Sep 17 00:00:00 2001 From: David Madison Date: Sun, 7 Jul 2024 22:57:45 -0400 Subject: [PATCH 22/39] Fix G27 shifter comment typo --- src/SimRacing.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/SimRacing.cpp b/src/SimRacing.cpp index 285d61f..f960feb 100644 --- a/src/SimRacing.cpp +++ b/src/SimRacing.cpp @@ -1159,7 +1159,7 @@ uint16_t LogitechShifterG27::readShiftRegisters() { digitalWrite(this->pinLatch, HIGH); delayMicroseconds(12); - // clock is pullsed from LOW to HIGH on every bit, + // clock is pulsed from LOW to HIGH on every bit, // and then left to idle low for (int i = 0; i < 16; ++i) { digitalWrite(this->pinClock, LOW); From 9390030c2ceede73a66809ed5a676bd03bf24fd9 Mon Sep 17 00:00:00 2001 From: David Madison Date: Tue, 9 Jul 2024 03:39:12 -0400 Subject: [PATCH 23/39] Add CreateShieldObject template function Convenience function for creating objects that are compatible with the Sim Racing Shields (https://github.com/dmadison/Sim-Racing-Shields) without needing to look up or remember the pinout. --- .../Pedals/PedalsJoystick/PedalsJoystick.ino | 2 +- examples/Pedals/PedalsPrint/PedalsPrint.ino | 2 +- .../LogitechShifter_Joystick.ino | 2 +- .../LogitechShifter_Print.ino | 2 +- .../LogitechShifterG25_Joystick.ino | 1 + .../LogitechShifterG25_Print.ino | 1 + .../LogitechShifterG27_Joystick.ino | 1 + .../LogitechShifterG27_Print.ino | 1 + keywords.txt | 6 ++ src/SimRacing.cpp | 77 ++++++++++++++++++ src/SimRacing.h | 80 +++++++++++++++++++ 11 files changed, 171 insertions(+), 4 deletions(-) diff --git a/examples/Pedals/PedalsJoystick/PedalsJoystick.ino b/examples/Pedals/PedalsJoystick/PedalsJoystick.ino index a50770a..845f1f7 100644 --- a/examples/Pedals/PedalsJoystick/PedalsJoystick.ino +++ b/examples/Pedals/PedalsJoystick/PedalsJoystick.ino @@ -36,7 +36,7 @@ const int Pin_Brake = A1; const int Pin_Clutch = A0; SimRacing::LogitechPedals pedals(Pin_Gas, Pin_Brake, Pin_Clutch); -//SimRacing::LogitechPedals pedals(PEDAL_SHIELD_V1_PINS); +//SimRacing::LogitechPedals pedals = SimRacing::CreateShieldObject(); Joystick_ Joystick( JOYSTICK_DEFAULT_REPORT_ID, // default report (no additional pages) diff --git a/examples/Pedals/PedalsPrint/PedalsPrint.ino b/examples/Pedals/PedalsPrint/PedalsPrint.ino index 395d7a8..474024a 100644 --- a/examples/Pedals/PedalsPrint/PedalsPrint.ino +++ b/examples/Pedals/PedalsPrint/PedalsPrint.ino @@ -32,7 +32,7 @@ const int Pin_Brake = A1; const int Pin_Clutch = A0; SimRacing::LogitechPedals pedals(Pin_Gas, Pin_Brake, Pin_Clutch); -//SimRacing::LogitechPedals pedals(PEDAL_SHIELD_V1_PINS); +//SimRacing::LogitechPedals pedals = SimRacing::CreateShieldObject(); void setup() { diff --git a/examples/Shifter/LogitechShifter/LogitechShifter_Joystick/LogitechShifter_Joystick.ino b/examples/Shifter/LogitechShifter/LogitechShifter_Joystick/LogitechShifter_Joystick.ino index 3df815e..c382312 100644 --- a/examples/Shifter/LogitechShifter/LogitechShifter_Joystick/LogitechShifter_Joystick.ino +++ b/examples/Shifter/LogitechShifter/LogitechShifter_Joystick/LogitechShifter_Joystick.ino @@ -49,7 +49,7 @@ const int Pin_ShifterY = A2; // DE-9 pin 8 const int Pin_ShifterRev = 2; // DE-9 pin 2 SimRacing::LogitechShifter shifter(Pin_ShifterX, Pin_ShifterY, Pin_ShifterRev); -//SimRacing::LogitechShifter shifter(SHIFTER_SHIELD_V1_PINS); +//SimRacing::LogitechShifter shifter = SimRacing::CreateShieldObject(); const int Gears[] = { 1, 2, 3, 4, 5, 6, -1 }; const int NumGears = sizeof(Gears) / sizeof(Gears[0]); diff --git a/examples/Shifter/LogitechShifter/LogitechShifter_Print/LogitechShifter_Print.ino b/examples/Shifter/LogitechShifter/LogitechShifter_Print/LogitechShifter_Print.ino index 45e5fcb..8e09ca0 100644 --- a/examples/Shifter/LogitechShifter/LogitechShifter_Print/LogitechShifter_Print.ino +++ b/examples/Shifter/LogitechShifter/LogitechShifter_Print/LogitechShifter_Print.ino @@ -36,7 +36,7 @@ const int Pin_ShifterY = A2; // DE-9 pin 8 const int Pin_ShifterRev = 2; // DE-9 pin 2 SimRacing::LogitechShifter shifter(Pin_ShifterX, Pin_ShifterY, Pin_ShifterRev); -//SimRacing::LogitechShifter shifter(SHIFTER_SHIELD_V1_PINS); +//SimRacing::LogitechShifter shifter = SimRacing::CreateShieldObject(); const unsigned long PrintSpeed = 1500; // ms unsigned long lastPrint = 0; diff --git a/examples/Shifter/LogitechShifterG25/LogitechShifterG25_Joystick/LogitechShifterG25_Joystick.ino b/examples/Shifter/LogitechShifterG25/LogitechShifterG25_Joystick/LogitechShifterG25_Joystick.ino index 38f42ab..712ebec 100644 --- a/examples/Shifter/LogitechShifterG25/LogitechShifterG25_Joystick/LogitechShifterG25_Joystick.ino +++ b/examples/Shifter/LogitechShifterG25/LogitechShifterG25_Joystick/LogitechShifterG25_Joystick.ino @@ -50,6 +50,7 @@ SimRacing::LogitechShifterG25 shifter( Pin_ShifterLatch, Pin_ShifterClock, Pin_ShifterData, Pin_ShifterDetect, Pin_ShifterLED ); +//SimRacing::LogitechShifterG25 shifter = SimRacing::CreateShieldObject(); // Set this option to 'true' to send the shifter's X/Y position // as a joystick. This is not needed for most games. diff --git a/examples/Shifter/LogitechShifterG25/LogitechShifterG25_Print/LogitechShifterG25_Print.ino b/examples/Shifter/LogitechShifterG25/LogitechShifterG25_Print/LogitechShifterG25_Print.ino index 87687ca..bd0fb0b 100644 --- a/examples/Shifter/LogitechShifterG25/LogitechShifterG25_Print/LogitechShifterG25_Print.ino +++ b/examples/Shifter/LogitechShifterG25/LogitechShifterG25_Print/LogitechShifterG25_Print.ino @@ -46,6 +46,7 @@ SimRacing::LogitechShifterG25 shifter( Pin_ShifterLatch, Pin_ShifterClock, Pin_ShifterData, Pin_ShifterDetect, Pin_ShifterLED ); +//SimRacing::LogitechShifterG25 shifter = SimRacing::CreateShieldObject(); // alias so we don't need to type so much using ShifterButton = SimRacing::LogitechShifterG25::Button; diff --git a/examples/Shifter/LogitechShifterG27/LogitechShifterG27_Joystick/LogitechShifterG27_Joystick.ino b/examples/Shifter/LogitechShifterG27/LogitechShifterG27_Joystick/LogitechShifterG27_Joystick.ino index f80d87a..0b39c57 100644 --- a/examples/Shifter/LogitechShifterG27/LogitechShifterG27_Joystick/LogitechShifterG27_Joystick.ino +++ b/examples/Shifter/LogitechShifterG27/LogitechShifterG27_Joystick/LogitechShifterG27_Joystick.ino @@ -50,6 +50,7 @@ SimRacing::LogitechShifterG27 shifter( Pin_ShifterLatch, Pin_ShifterClock, Pin_ShifterData, Pin_ShifterDetect, Pin_ShifterLED ); +//SimRacing::LogitechShifterG27 shifter = SimRacing::CreateShieldObject(); // Set this option to 'true' to send the shifter's X/Y position // as a joystick. This is not needed for most games. diff --git a/examples/Shifter/LogitechShifterG27/LogitechShifterG27_Print/LogitechShifterG27_Print.ino b/examples/Shifter/LogitechShifterG27/LogitechShifterG27_Print/LogitechShifterG27_Print.ino index f22157a..2e922df 100644 --- a/examples/Shifter/LogitechShifterG27/LogitechShifterG27_Print/LogitechShifterG27_Print.ino +++ b/examples/Shifter/LogitechShifterG27/LogitechShifterG27_Print/LogitechShifterG27_Print.ino @@ -46,6 +46,7 @@ SimRacing::LogitechShifterG27 shifter( Pin_ShifterLatch, Pin_ShifterClock, Pin_ShifterData, Pin_ShifterDetect, Pin_ShifterLED ); +//SimRacing::LogitechShifterG27 shifter = SimRacing::CreateShieldObject(); // alias so we don't need to type so much using ShifterButton = SimRacing::LogitechShifterG27::Button; diff --git a/keywords.txt b/keywords.txt index d37c3cc..139429e 100644 --- a/keywords.txt +++ b/keywords.txt @@ -44,6 +44,12 @@ LogitechShifterG25 KEYWORD1 # Handbrake Classes Handbrake KEYWORD1 +####################################### +# Functions (KEYWORD2) +####################################### + +CreateShieldObject KEYWORD2 + ####################################### # Peripheral Class Methods and Functions (KEYWORD2) ####################################### diff --git a/src/SimRacing.cpp b/src/SimRacing.cpp index f960feb..f615ca8 100644 --- a/src/SimRacing.cpp +++ b/src/SimRacing.cpp @@ -29,6 +29,83 @@ namespace SimRacing { +#if defined(__AVR_ATmega32U4__) || defined(SIM_RACING_DOXYGEN) + +template<> +LogitechPedals CreateShieldObject() { + // Power (VCC): DE-9 pin 9, bridged to DE-9 pin 6 + // Ground (GND): DE-9 pin 1 + + const PinNum Pin_Gas = A2; // DE-9 pin 2 + const PinNum Pin_Brake = A1; // DE-9 pin 3 + const PinNum Pin_Clutch = A0; // DE-9 pin 4 + const PinNum Pin_Detect = 10; // DE-9 pin 6, requires 10k Ohm pull-down + + return LogitechPedals(Pin_Gas, Pin_Brake, Pin_Clutch, Pin_Detect); +} + +template<> +LogitechPedals CreateShieldObject() { + // version 2 of the pedals shield has the same pinout, + // so we can use the v1 function + return CreateShieldObject(); +} + +template<> +LogitechShifter CreateShieldObject() { + // Power (VCC): DE-9 pin 9, bridged to DE-9 pin 7 + // Ground (GND): DE-9 pin 6 + // DE-9 pin 3 (CS) needs to be pulled-up to VCC + + const PinNum Pin_X_Wiper = A1; // DE-9 pin 4 + const PinNum Pin_Y_Wiper = A0; // DE-9 pin 8 + const PinNum Pin_DataOut = 14; // DE-9 pin 2 + const PinNum Pin_Detect = A2; // DE-9 pin 7, requires 10k Ohm pull-down + + return LogitechShifter(Pin_X_Wiper, Pin_Y_Wiper, Pin_DataOut, Pin_Detect); +} + +template<> +LogitechShifter CreateShieldObject() { + // version 2 of the shifter shield has the same data pinout for + // the Driving Force shifter, so we can use the v1 function + return CreateShieldObject(); +} + +/** +* Helper function to create either a LogitechShifterG27 or LogitechShifterG25 +* object using the v2 shifter shield pinout, as both use the same pins. +*/ +template +static T CreateShieldShifter() { + // Power (VCC): DE-9 pin 9, bridged to DE-9 pin 1 + // Ground (GND): DE-9 pin 6 + + const PinNum Pin_X_Wiper = A1; // DE-9 pin 4 + const PinNum Pin_Y_Wiper = A0; // DE-9 pin 8 + const PinNum Pin_DataOut = 14; // DE-9 pin 2 + + const PinNum Pin_Latch = 10; // DE-9 pin 3, aka chip select, requires 10k Ohm pull-up + const PinNum Pin_Clock = A2; // DE-9 pin 7, should have 470 Ohm resistor to prevent shorts + + const PinNum Pin_Detect = 15; // DE-9 pin 1, requires 10k Ohm pull-down + const PinNum Pin_LED = 16; // DE-9 pin 5, requires 100-120 Ohm series resistor + + return T(Pin_X_Wiper, Pin_Y_Wiper, Pin_Latch, Pin_Clock, Pin_DataOut, Pin_Detect, Pin_LED); +} + +template<> +LogitechShifterG27 CreateShieldObject() { + return CreateShieldShifter(); +} + +template<> +LogitechShifterG25 CreateShieldObject() { + return CreateShieldShifter(); +} +#endif // ATmega32U4 for shield functions + + /** * Take a pin number as an input and sanitize it to a known working value * diff --git a/src/SimRacing.h b/src/SimRacing.h index d35d48c..330c86f 100644 --- a/src/SimRacing.h +++ b/src/SimRacing.h @@ -1202,6 +1202,86 @@ namespace SimRacing { #if defined(__AVR_ATmega32U4__) || defined(SIM_RACING_DOXYGEN) + /** + * Create an object for use with one of the Sim Racing Shields, designed + * for the SparkFun Pro Micro (32U4). + * + * This is a convenience function, so that users with a shield don't need to + * look up or remember the pin assignments for their hardware. + * + * @code{.cpp} + * // Generic Usage + * auto myObject = SimRacing::CreateShieldObject(); + * + * // Creating a LogitechShifter object for the v2 shifter shield + * auto myShifter = SimRacing::CreateShieldObject(); + * @endcode + * + * The following classes are supported for the Pedals shield, v1: + * * SimRacing::LogitechPedals + * + * The following classes are supported for the Shifter shield, v1: + * * SimRacing::LogitechShifter (Driving Force) + * * SimRacing::LogitechShifterG923 (alias) + * * SimRacing::LogitechShifterG920 (alias) + * * SimRacing::LogitechShifterG29 (alias) + * + * Version 2 of the shifter shield includes support for all of the classes + * from v1, as well as the following: + * * SimRacing::LogitechShifterG27 + * * SimRacing::LogitechShifterG25 + * + * @note The default version of this template is undefined, so trying to + * create a class that is unsupported by the shield will generate + * a linker error. This is intentional. + * + * @tparam T The class to create + * @tparam Version The major version number of the shield + * + * @returns class instance, using the hardware pins on the shield + * + * @see https://github.com/dmadison/Sim-Racing-Shields + */ + template + T CreateShieldObject(); + + /** + * Create a LogitechPedals object for the Pedals Shield v1 + */ + template<> + LogitechPedals CreateShieldObject(); + + /** + * Create a LogitechPedals object for the Pedals Shield v2 + */ + template<> + LogitechPedals CreateShieldObject(); + + /** + * Create a LogitechShifter object for the Shifter Shield v1 + */ + template<> + LogitechShifter CreateShieldObject(); + + /** + * Create a LogitechShifter object for the Shifter Shield v2 + */ + template<> + LogitechShifter CreateShieldObject(); + + /** + * Create a LogitechShifterG27 object for the Shifter Shield v2 + */ + template<> + LogitechShifterG27 CreateShieldObject(); + + /** + * Create a LogitechShifterG25 object for the Shifter Shield v2 + */ + template<> + LogitechShifterG25 CreateShieldObject(); + + /** * Pin definitions for the Parts Not Included Logitech Shifter Shield, * designed for the SparkFun Pro Micro: From c5414b58884ae25cce4d0b6bfcb1a9474efdb0cc Mon Sep 17 00:00:00 2001 From: David Madison Date: Tue, 9 Jul 2024 03:47:12 -0400 Subject: [PATCH 24/39] Remove shield pin macros These were well-intentioned and easy for the end user, but they aren't flexible. The G25/G27 shifters require different pin arguments and do not work on the v1 shield. These macros have been superseded by the `CreateShieldObject` template function. --- keywords.txt | 4 ---- src/SimRacing.h | 37 ------------------------------------- 2 files changed, 41 deletions(-) diff --git a/keywords.txt b/keywords.txt index 139429e..27fd183 100644 --- a/keywords.txt +++ b/keywords.txt @@ -193,7 +193,3 @@ Accelerator LITERAL1 Throttle LITERAL1 Brake LITERAL1 Clutch LITERAL1 - -# Board Pin Definitions -SHIFTER_SHIELD_V1_PINS LITERAL1 -PEDAL_SHIELD_V1_PINS LITERAL1 diff --git a/src/SimRacing.h b/src/SimRacing.h index 330c86f..0902f2c 100644 --- a/src/SimRacing.h +++ b/src/SimRacing.h @@ -1280,43 +1280,6 @@ namespace SimRacing { */ template<> LogitechShifterG25 CreateShieldObject(); - - - /** - * Pin definitions for the Parts Not Included Logitech Shifter Shield, - * designed for the SparkFun Pro Micro: - * - * * X Wiper: A1 - * * Y Wiper: A0 - * * Reverse Pin: 14 - * * Detect Pin: A2 - * - * This macro can be inserted directly into the constructor in place of the - * normal pin definitions: - * - * @code{.cpp} - * SimRacing::LogitechShifter shifter(SHIFTER_SHIELD_V1_PINS); - * @endcode - */ - #define SHIFTER_SHIELD_V1_PINS A1, A0, 14, A2 - - /** - * Pin definitions for the Parts Not Included Logitech Pedals Shield, - * designed for the SparkFun Pro Micro: - * - * * Gas Wiper: A2 - * * Brake Wiper: A1 - * * Clutch Wiper: A0 - * * Detect Pin: 10 - * - * This macro can be inserted directly into the constructor in place of the - * normal pin definitions: - * - * @code{.cpp} - * SimRacing::LogitechPedals pedals(PEDAL_SHIELD_V1_PINS); - * @endcode - */ - #define PEDAL_SHIELD_V1_PINS A2, A1, A0, 10 #endif } // end SimRacing namespace From eb3069310402c57398a713b3f1a5a88f89b7ecd2 Mon Sep 17 00:00:00 2001 From: David Madison Date: Tue, 9 Jul 2024 04:07:45 -0400 Subject: [PATCH 25/39] Add unused detect pin to shifter examples Matches the layout of the G25 and G27 examples, and shows the user that this feature is available even if it requires extra hardware. --- .../LogitechShifter_Joystick.ino | 10 +++++++++- .../LogitechShifter_Print/LogitechShifter_Print.ino | 10 +++++++++- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/examples/Shifter/LogitechShifter/LogitechShifter_Joystick/LogitechShifter_Joystick.ino b/examples/Shifter/LogitechShifter/LogitechShifter_Joystick/LogitechShifter_Joystick.ino index c382312..b66472d 100644 --- a/examples/Shifter/LogitechShifter/LogitechShifter_Joystick/LogitechShifter_Joystick.ino +++ b/examples/Shifter/LogitechShifter/LogitechShifter_Joystick/LogitechShifter_Joystick.ino @@ -48,7 +48,15 @@ const int Pin_ShifterX = A0; // DE-9 pin 4 const int Pin_ShifterY = A2; // DE-9 pin 8 const int Pin_ShifterRev = 2; // DE-9 pin 2 -SimRacing::LogitechShifter shifter(Pin_ShifterX, Pin_ShifterY, Pin_ShifterRev); +// This pin requires an extra resistor! If you have made the proper +// connections, change the pin number to the one you're using +const int Pin_ShifterDetect = SimRacing::UnusedPin; // DE-9 pin 7, requires pull-down resistor + +SimRacing::LogitechShifter shifter( + Pin_ShifterX, Pin_ShifterY, + Pin_ShifterRev, + Pin_ShifterDetect +); //SimRacing::LogitechShifter shifter = SimRacing::CreateShieldObject(); const int Gears[] = { 1, 2, 3, 4, 5, 6, -1 }; diff --git a/examples/Shifter/LogitechShifter/LogitechShifter_Print/LogitechShifter_Print.ino b/examples/Shifter/LogitechShifter/LogitechShifter_Print/LogitechShifter_Print.ino index 8e09ca0..0434e1b 100644 --- a/examples/Shifter/LogitechShifter/LogitechShifter_Print/LogitechShifter_Print.ino +++ b/examples/Shifter/LogitechShifter/LogitechShifter_Print/LogitechShifter_Print.ino @@ -35,7 +35,15 @@ const int Pin_ShifterX = A0; // DE-9 pin 4 const int Pin_ShifterY = A2; // DE-9 pin 8 const int Pin_ShifterRev = 2; // DE-9 pin 2 -SimRacing::LogitechShifter shifter(Pin_ShifterX, Pin_ShifterY, Pin_ShifterRev); +// This pin requires an extra resistor! If you have made the proper +// connections, change the pin number to the one you're using +const int Pin_ShifterDetect = SimRacing::UnusedPin; // DE-9 pin 7, requires pull-down resistor + +SimRacing::LogitechShifter shifter( + Pin_ShifterX, Pin_ShifterY, + Pin_ShifterRev, + Pin_ShifterDetect +); //SimRacing::LogitechShifter shifter = SimRacing::CreateShieldObject(); const unsigned long PrintSpeed = 1500; // ms From e96cc5486e0f73fd43347f12a26c25c2da484731 Mon Sep 17 00:00:00 2001 From: David Madison Date: Fri, 23 Aug 2024 19:36:14 -0400 Subject: [PATCH 26/39] Clarify G25 shifter pin states Reading back these descriptions and it sounds like the device is holding the pin state, rather than the pin state being driven by the micro (read: library). Rewriting to be clearer. --- docs/pages/devices/logitech_shifter_g25.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/pages/devices/logitech_shifter_g25.md b/docs/pages/devices/logitech_shifter_g25.md index 0b03042..107c33e 100644 --- a/docs/pages/devices/logitech_shifter_g25.md +++ b/docs/pages/devices/logitech_shifter_g25.md @@ -34,11 +34,11 @@ Note that the DE-9 connector is often erroneously referred to as DB-9. These are Pin #2 (Data Output) is connected directly to the output of the EEPROM, and connected to the output of the shift registers through a 1000 Ohm resistor. -Pin #3 (Latch / Chip Select) is shared between the onboard EEPROM and the shift registers. It is normally HIGH. It must be pulsed once (HIGH / LOW / HIGH) to latch the data into the shift registers. Holding it low instructs the EEPROM to listen for commands. +Pin #3 (Latch / Chip Select) is shared between the onboard EEPROM and the shift registers. It is floating but should typically be held HIGH by the microcontroller. It must be pulsed once (HIGH / LOW / HIGH) to latch the data into the shift registers. Holding it LOW instructs the EEPROM to listen for commands. Pin #5 (Data In) is used exclusively by the EEPROM. It is also connected to the "Power" LED through a 330 Ohm resistor. Driving this pin LOW will turn on the LED. As the "Sequential Mode" LED is connected using a 470 Ohm resistor, I would recommend using a 100-120 Ohm resistor in series so that the pair are closer in brightness. -Pin #7 (Clock) is connected to the clock inputs of both the EEPROM and the shift registers. It is normally LOW and must be pulsed HIGH to send or receive data. Be wary of driving this pin to ground without protection, as this pin is also used as a joint power input for the [Logitech Driving Force Shifter](@ref logitech_shifter). +Pin #7 (Clock) is connected to the clock inputs of both the EEPROM and the shift registers. It is floating but should typically be held LOW by the microcontroller. Pulsing the pin HIGH (LOW / HIGH / LOW) will shift one bit of data. Be wary of driving this pin to ground without protection, as this pin is also used as a joint power input for the [Logitech Driving Force Shifter](@ref logitech_shifter). The power pins (#1 / #9) are connected together within the DE-9 connector. Either one can be used, but it is recommended to use #9 for better forwards-compatibility with the [Logitech Driving Force Shifter](@ref logitech_shifter). From 4b9b8eba17a7d2b6a5af26c8c9b7167adb3b1502 Mon Sep 17 00:00:00 2001 From: David Madison Date: Tue, 4 Mar 2025 14:53:37 -0500 Subject: [PATCH 27/39] Fix G27 button output when using DF shifter This lets you read the Driving Force shifter with the G27 object. As the detection pins are the same, the two shifters cannot be simply distinguished by the microcontroller. --- src/SimRacing.cpp | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/SimRacing.cpp b/src/SimRacing.cpp index f615ca8..9597cb4 100644 --- a/src/SimRacing.cpp +++ b/src/SimRacing.cpp @@ -1247,6 +1247,21 @@ uint16_t LogitechShifterG27::readShiftRegisters() { } digitalWrite(this->pinClock, LOW); + // edge case: two of the bits (0x8000 and 0x2000) are connected only to + // pull-down resistors, and should theoretically never be high. If they, + // and all other bits, *are* high, then we are not reading from a shifter + // that has shift registers. The "Driving Force" (G29/G920/G923) shifter + // has its data output connected to the 'reverse' button through a buffer, + // and will report 'high' if the reverse button is pressed no matter how + // many times the clock is pulsed. + // + // QED: we are connected to a "Driving Force" shifter, and not a G27. + // That's okay! If we set the state of the 'reverse' button and clear + // all others, we can still behave like a G27. + if (data == 0xFFFF) { + data = (1 << (uint8_t) Button::BUTTON_REVERSE); + } + return data; } From 5e14a0aa93125e8e93b35efa97da8a2d73b0d034 Mon Sep 17 00:00:00 2001 From: David Madison Date: Tue, 4 Mar 2025 15:08:10 -0500 Subject: [PATCH 28/39] Fix G27 shifter pinout The G27 shifter, although it looks similar to the G25, does *not* have the same pinout (pins 1 and 7 are swapped). This fixes all G27 pinout references throughout the library. --- .../LogitechShifterG27_Joystick.ino | 4 +-- .../LogitechShifterG27_Print.ino | 4 +-- src/SimRacing.cpp | 36 ++++++++++--------- src/SimRacing.h | 25 ++++++++----- 4 files changed, 41 insertions(+), 28 deletions(-) diff --git a/examples/Shifter/LogitechShifterG27/LogitechShifterG27_Joystick/LogitechShifterG27_Joystick.ino b/examples/Shifter/LogitechShifterG27/LogitechShifterG27_Joystick/LogitechShifterG27_Joystick.ino index 0b39c57..cbe738a 100644 --- a/examples/Shifter/LogitechShifterG27/LogitechShifterG27_Joystick/LogitechShifterG27_Joystick.ino +++ b/examples/Shifter/LogitechShifterG27/LogitechShifterG27_Joystick/LogitechShifterG27_Joystick.ino @@ -37,12 +37,12 @@ const int Pin_ShifterX = A0; // DE-9 pin 4 const int Pin_ShifterY = A2; // DE-9 pin 8 const int Pin_ShifterLatch = 5; // DE-9 pin 3 -const int Pin_ShifterClock = 6; // DE-9 pin 7 +const int Pin_ShifterClock = 6; // DE-9 pin 1 const int Pin_ShifterData = 7; // DE-9 pin 2 // These pins require extra resistors! If you have made the proper // connections, change the pin numbers to the ones you're using -const int Pin_ShifterDetect = SimRacing::UnusedPin; // DE-9 pin 1, requires pull-down resistor +const int Pin_ShifterDetect = SimRacing::UnusedPin; // DE-9 pin 7, requires pull-down resistor const int Pin_ShifterLED = SimRacing::UnusedPin; // DE-9 pin 5, requires 100-120 Ohm series resistor SimRacing::LogitechShifterG27 shifter( diff --git a/examples/Shifter/LogitechShifterG27/LogitechShifterG27_Print/LogitechShifterG27_Print.ino b/examples/Shifter/LogitechShifterG27/LogitechShifterG27_Print/LogitechShifterG27_Print.ino index 2e922df..3c89bea 100644 --- a/examples/Shifter/LogitechShifterG27/LogitechShifterG27_Print/LogitechShifterG27_Print.ino +++ b/examples/Shifter/LogitechShifterG27/LogitechShifterG27_Print/LogitechShifterG27_Print.ino @@ -33,12 +33,12 @@ const int Pin_ShifterX = A0; // DE-9 pin 4 const int Pin_ShifterY = A2; // DE-9 pin 8 const int Pin_ShifterLatch = 5; // DE-9 pin 3 -const int Pin_ShifterClock = 6; // DE-9 pin 7 +const int Pin_ShifterClock = 6; // DE-9 pin 1 const int Pin_ShifterData = 7; // DE-9 pin 2 // These pins require extra resistors! If you have made the proper // connections, change the pin numbers to the ones you're using -const int Pin_ShifterDetect = SimRacing::UnusedPin; // DE-9 pin 1, requires pull-down resistor +const int Pin_ShifterDetect = SimRacing::UnusedPin; // DE-9 pin 7, requires pull-down resistor const int Pin_ShifterLED = SimRacing::UnusedPin; // DE-9 pin 5, requires 100-120 Ohm series resistor SimRacing::LogitechShifterG27 shifter( diff --git a/src/SimRacing.cpp b/src/SimRacing.cpp index 9597cb4..865e33c 100644 --- a/src/SimRacing.cpp +++ b/src/SimRacing.cpp @@ -72,13 +72,9 @@ LogitechShifter CreateShieldObject() { return CreateShieldObject(); } -/** -* Helper function to create either a LogitechShifterG27 or LogitechShifterG25 -* object using the v2 shifter shield pinout, as both use the same pins. -*/ -template -static T CreateShieldShifter() { - // Power (VCC): DE-9 pin 9, bridged to DE-9 pin 1 +template<> +LogitechShifterG27 CreateShieldObject() { + // Power (VCC): DE-9 pin 9, bridged to DE-9 pin 7 // Ground (GND): DE-9 pin 6 const PinNum Pin_X_Wiper = A1; // DE-9 pin 4 @@ -86,22 +82,30 @@ static T CreateShieldShifter() { const PinNum Pin_DataOut = 14; // DE-9 pin 2 const PinNum Pin_Latch = 10; // DE-9 pin 3, aka chip select, requires 10k Ohm pull-up - const PinNum Pin_Clock = A2; // DE-9 pin 7, should have 470 Ohm resistor to prevent shorts + const PinNum Pin_Clock = 15; // DE-9 pin 1, should have 470 Ohm resistor to prevent shorts - const PinNum Pin_Detect = 15; // DE-9 pin 1, requires 10k Ohm pull-down + const PinNum Pin_Detect = A2; // DE-9 pin 7, requires 10k Ohm pull-down const PinNum Pin_LED = 16; // DE-9 pin 5, requires 100-120 Ohm series resistor - return T(Pin_X_Wiper, Pin_Y_Wiper, Pin_Latch, Pin_Clock, Pin_DataOut, Pin_Detect, Pin_LED); -} - -template<> -LogitechShifterG27 CreateShieldObject() { - return CreateShieldShifter(); + return LogitechShifterG27(Pin_X_Wiper, Pin_Y_Wiper, Pin_Latch, Pin_Clock, Pin_DataOut, Pin_Detect, Pin_LED); } template<> LogitechShifterG25 CreateShieldObject() { - return CreateShieldShifter(); + // Power (VCC): DE-9 pin 9, bridged to DE-9 pin 1 + // Ground (GND): DE-9 pin 6 + + const PinNum Pin_X_Wiper = A1; // DE-9 pin 4 + const PinNum Pin_Y_Wiper = A0; // DE-9 pin 8 + const PinNum Pin_DataOut = 14; // DE-9 pin 2 + + const PinNum Pin_Latch = 10; // DE-9 pin 3, aka chip select, requires 10k Ohm pull-up + const PinNum Pin_Clock = A2; // DE-9 pin 7, should have 470 Ohm resistor to prevent shorts + + const PinNum Pin_Detect = 15; // DE-9 pin 1, requires 10k Ohm pull-down + const PinNum Pin_LED = 16; // DE-9 pin 5, requires 100-120 Ohm series resistor + + return LogitechShifterG25(Pin_X_Wiper, Pin_Y_Wiper, Pin_Latch, Pin_Clock, Pin_DataOut, Pin_Detect, Pin_LED); } #endif // ATmega32U4 for shield functions diff --git a/src/SimRacing.h b/src/SimRacing.h index 0902f2c..f203dc6 100644 --- a/src/SimRacing.h +++ b/src/SimRacing.h @@ -986,9 +986,9 @@ namespace SimRacing { * @param pinX analog input pin for the X axis, DE-9 pin 4 * @param pinY analog input pin for the Y axis, DE-9 pin 8 * @param pinLatch digital output pin to pulse to latch data, DE-9 pin 3 - * @param pinClock digital output pin to pulse as a clock, DE-9 pin 7 + * @param pinClock digital output pin to pulse as a clock, DE-9 pin 1 * @param pinData digital input pin to use for reading data, DE-9 pin 2 - * @param pinDetect the digital input pin for device detection, DE-9 pin 1. + * @param pinDetect the digital input pin for device detection, DE-9 pin 7. * Requires a pull-down resistor. * @param pinLed digital output pin to light the power LED on connection, * DE-9 pin 5. Requires a 100 Ohm series resistor. @@ -997,7 +997,7 @@ namespace SimRacing { PinNum pinX, PinNum pinY, PinNum pinLatch, PinNum pinClock, PinNum pinData, PinNum pinDetect = UnusedPin, - PinNum pinLed = UnusedPin + PinNum pinLed = UnusedPin ); /** @@ -1086,7 +1086,7 @@ namespace SimRacing { // Pins for the shift register interface PinNum pinLatch; ///< Pin to pulse to latch data, DE-9 pin 3 - PinNum pinClock; ///< Pin to pulse as a clock, DE-9 pin 7 + PinNum pinClock; ///< Pin to pulse as a clock, DE-9 pin 1 PinNum pinData; ///< Pin to use for reading data, DE-9 pin 2 // Generic I/O pins @@ -1114,14 +1114,23 @@ namespace SimRacing { class LogitechShifterG25 : public LogitechShifterG27 { public: /** - * @copydoc LogitechShifterG27::LogitechShifterG27(PinNum, PinNum, - * PinNum, PinNum, PinNum, PinNum, PinNum) - */ + * Class constructor + * + * @param pinX analog input pin for the X axis, DE-9 pin 4 + * @param pinY analog input pin for the Y axis, DE-9 pin 8 + * @param pinLatch digital output pin to pulse to latch data, DE-9 pin 3 + * @param pinClock digital output pin to pulse as a clock, DE-9 pin 7 + * @param pinData digital input pin to use for reading data, DE-9 pin 2 + * @param pinDetect the digital input pin for device detection, DE-9 pin 1. + * Requires a pull-down resistor. + * @param pinLed digital output pin to light the power LED on connection, + * DE-9 pin 5. Requires a 100 Ohm series resistor. + */ LogitechShifterG25( PinNum pinX, PinNum pinY, PinNum pinLatch, PinNum pinClock, PinNum pinData, PinNum pinDetect = UnusedPin, - PinNum pinLed = UnusedPin + PinNum pinLed = UnusedPin ); /** @copydoc LogitechShifterG27::begin() */ From cb57c06efd335f275b90568f6d0d11a5eceb2c82 Mon Sep 17 00:00:00 2001 From: David Madison Date: Tue, 4 Mar 2025 15:24:16 -0500 Subject: [PATCH 29/39] Separate G27 and G25 shifter documentation The pinout difference between the two is significant enough to necessitate separate documentation. The G27 documentation page is based on notes from disassembling my own G27 shifter. --- README.md | 3 +- docs/pages/devices/logitech_shifter_g25.md | 16 ++-- docs/pages/devices/logitech_shifter_g27.md | 90 ++++++++++++++++++++++ docs/pages/supported_devices.md | 1 + 4 files changed, 101 insertions(+), 9 deletions(-) create mode 100644 docs/pages/devices/logitech_shifter_g27.md diff --git a/README.md b/README.md index 50ea736..e618d69 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,8 @@ Run one of the library examples in the Arduino IDE by going to `File -> Examples * [Logitech Two Pedal Peripheral (Gas, Brake)](http://dmadison.github.io/Sim-Racing-Arduino/docs/logitech_pedals.html) * [Logitech Three Pedal Peripheral (Gas, Brake, Clutch)](http://dmadison.github.io/Sim-Racing-Arduino/docs/logitech_pedals.html) * [Logitech Driving Force Shifter (G923 / G920 / G29)](http://dmadison.github.io/Sim-Racing-Arduino/docs/logitech_shifter.html) -* [Logitech G25 / G27 Shifter](http://dmadison.github.io/Sim-Racing-Arduino/docs/logitech_shifter_g25.html) +* [Logitech G27 Shifter](http://dmadison.github.io/Sim-Racing-Arduino/docs/logitech_shifter_g27.html) +* [Logitech G25 Shifter](http://dmadison.github.io/Sim-Racing-Arduino/docs/logitech_shifter_g25.html) ## Adapters diff --git a/docs/pages/devices/logitech_shifter_g25.md b/docs/pages/devices/logitech_shifter_g25.md index 107c33e..c9cc99f 100644 --- a/docs/pages/devices/logitech_shifter_g25.md +++ b/docs/pages/devices/logitech_shifter_g25.md @@ -1,8 +1,8 @@ -# Logitech G25 / G27 Shifter {#logitech_shifter_g25} +# Logitech G25 Shifter {#logitech_shifter_g25} -The [Logitech G25](https://en.wikipedia.org/wiki/Logitech_G25) and [Logitech G27](https://en.wikipedia.org/wiki/Logitech_G27) shifters are implemented using the SimRacing::LogitechShifterG25 and SimRacing::LogitechShifterG27 classes, respectively. The G25 and G27 shifters function identically, except for the lack of sequential mode on the G27. +The [Logitech G25](https://en.wikipedia.org/wiki/Logitech_G25) shifter is implemented using the SimRacing::LogitechShifterG25 class. See the LogitechShifterG25_Print.ino and LogitechShifterG25_Joystick.ino examples for reference. -See the LogitechShifterG25_Print.ino, LogitechShifterG27_Print.ino, LogitechShifterG25_Joystick.ino, and LogitechShifterG27_Joystick.ino examples for reference. +The G25 shifter is near-identical to the [G27 shifter](@ref logitech_shifter_g27). It includes a "sequential" shifting mode, and pins 1 and 7 of the connector are swapped (respectively: power/clock for the G25, clock/power for the G27). These pin swaps are done in the wiring between the DE-9 and internal J11 connector; the circuit board appears to be identical. These notes are based off of disassembling my own G25 shifter, with the internal PCB marked "202339-0000 REV. A1". @@ -14,7 +14,7 @@ These notes are based off of disassembling my own G25 shifter, with the internal DE-9 graphic from [Aeroid](https://commons.wikimedia.org/wiki/User:Aeroid) @ [Wikimedia Commons](https://commons.wikimedia.org/wiki/File:DE9_Diagram.svg#/media/File:DE-9_Female.svg), modified for scale, colors, and creation of a complementary male version. These graphics are licensed under [CC BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/). -Both the G25 and G27 Logitech shifters connect to the wheel base units using [a female DE-9 connector](https://en.wikipedia.org/wiki/D-subminiature). Note that most jumper wires with [DuPont headers](https://en.wikipedia.org/wiki/Jump_wire) will not fit snugly into a DE-9 connector. For reliability and ease of use it's recommended to use a mating male DE-9 connector when interfacing with the shifter. +The Logitech G25 shifter connects to the wheel base unit using [a female DE-9 connector](https://en.wikipedia.org/wiki/D-subminiature). Note that most jumper wires with [DuPont headers](https://en.wikipedia.org/wiki/Jump_wire) will not fit snugly into a DE-9 connector. For reliability and ease of use it's recommended to use a mating male DE-9 connector when interfacing with the shifter. Note that the DE-9 connector is often erroneously referred to as DB-9. These are the same thing. @@ -38,9 +38,9 @@ Pin #3 (Latch / Chip Select) is shared between the onboard EEPROM and the shift Pin #5 (Data In) is used exclusively by the EEPROM. It is also connected to the "Power" LED through a 330 Ohm resistor. Driving this pin LOW will turn on the LED. As the "Sequential Mode" LED is connected using a 470 Ohm resistor, I would recommend using a 100-120 Ohm resistor in series so that the pair are closer in brightness. -Pin #7 (Clock) is connected to the clock inputs of both the EEPROM and the shift registers. It is floating but should typically be held LOW by the microcontroller. Pulsing the pin HIGH (LOW / HIGH / LOW) will shift one bit of data. Be wary of driving this pin to ground without protection, as this pin is also used as a joint power input for the [Logitech Driving Force Shifter](@ref logitech_shifter). +Pin #7 (Clock) is connected to the clock inputs of both the EEPROM and the shift registers. It is floating but should typically be held LOW by the microcontroller. Pulsing the pin HIGH (LOW / HIGH / LOW) will shift one bit of data. Be wary of driving this pin to ground without protection, as this pin is also used as a joint power input for the [Logitech Driving Force Shifter](@ref logitech_shifter) and the [Logitech G27 Shifter](@ref logitech_shifter_g27). -The power pins (#1 / #9) are connected together within the DE-9 connector. Either one can be used, but it is recommended to use #9 for better forwards-compatibility with the [Logitech Driving Force Shifter](@ref logitech_shifter). +The power pins (#1 / #9) are connected together within the DE-9 connector. Either one can be used, but it is recommended to use pin 9 for compatibility with the other shifters. The shifter's electronics are theoretically compatibile with both 3.3V and 5V logic. Be sure to use the appropriate voltage for the logic level of your microcontroller. @@ -52,11 +52,11 @@ The shifter includes 12 user-facing buttons: * One directional pad (D-Pad) * Four red buttons in a straight line -The shifter also contains two internal buttons: a button on the bottom of the shifter to indicate that it's in reverse, and a button on the sequential mode dial to indicate that it's in sequential mode. These buttons are implemented as part of the SimRacing::LogitechShifterButtons class. +The shifter also contains two internal buttons: a button on the bottom of the shift column to indicate that it's in reverse, and a button on the sequential mode dial to indicate that it's in sequential mode. ### Shift Registers -These buttons are connected to the external DE-9 connector through a series of NXP 74HC165D parallel-to-serial shift registers. +These buttons are connected to the external DE-9 connector through a pair of NXP 74HC165D parallel-to-serial shift registers. | Button | Register | Bit | Offset | Enum | |-----------------------|----------|-----|--------|--------------------------------------------------| diff --git a/docs/pages/devices/logitech_shifter_g27.md b/docs/pages/devices/logitech_shifter_g27.md new file mode 100644 index 0000000..9db4fca --- /dev/null +++ b/docs/pages/devices/logitech_shifter_g27.md @@ -0,0 +1,90 @@ +# Logitech G27 Shifter {#logitech_shifter_g27} + +The [Logitech G27](https://en.wikipedia.org/wiki/Logitech_G27) shifter is implemented using the SimRacing::LogitechShifterG27 class. See the LogitechShifterG27_Print.ino and LogitechShifterG27_Joystick.ino examples for reference. + +The G27 shifter is near-identical to the [G25 shifter](@ref logitech_shifter_g25). It is missing the "sequential" mode switch and mechanics, and pins 1 and 7 of the connector are swapped (respectively: power/clock for the G25, clock/power for the G27). These pin swaps are done in the wiring between the DE-9 and internal J11 connector; the circuit board appears to be identical (including unpopulated pads for the sequential mode switch and sequential mode LED). + +These notes are based off of disassembling my own G27 shifter, with the internal PCB marked "210-001096 REV. 001". + +## Connector + +| ![DE-9_Male](DE9_Male.svg) | ![DE-9_Female](DE9_Female.svg) | +| :-----------------------: | :---------------------------: | +| DE-9 Male Connector | DE-9 Female connector | + +DE-9 graphic from [Aeroid](https://commons.wikimedia.org/wiki/User:Aeroid) @ [Wikimedia Commons](https://commons.wikimedia.org/wiki/File:DE9_Diagram.svg#/media/File:DE-9_Female.svg), modified for scale, colors, and creation of a complementary male version. These graphics are licensed under [CC BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/). + +The Logitech G27 shifter connects to the wheel base unit using [a female DE-9 connector](https://en.wikipedia.org/wiki/D-subminiature). Note that most jumper wires with [DuPont headers](https://en.wikipedia.org/wiki/Jump_wire) will not fit snugly into a DE-9 connector. For reliability and ease of use it's recommended to use a mating male DE-9 connector when interfacing with the shifter. + +Note that the DE-9 connector is often erroneously referred to as DB-9. These are the same thing. + +## Pinout + +| Function | DE-9 Pin | Internal J11 Pin | Data Direction | Wire Color | Necessary | Recommended Pin | +|---------------------------|----------|------------------|----------------|------------------|-----------|-----------------| +| Clock (SCLK) | 1 | 6 | In | Purple | X | 6 | +| Data Output (SDO) | 2 | 7 | Out | Gray | X | 7 | +| Latch / Chip Select | 3 | 5 | In | Yellow | X | 5 | +| X Axis Wiper | 4 | 3 | Out | Orange | X | A0 | +| Data In (SDI) / Power LED | 5 | 2 | In | White | | | +| Ground | 6 | 8 | - | Black (Sheathed) | X | GND | +| Power | 7 | 1 | - | Red | | | +| Y Axis Wiper | 8 | 4 | Out | Green | X | A2 | +| Power | 9 | 1 | - | Red | X | VCC | + +Pin #1 (Clock) is connected to the clock inputs of both the EEPROM and the shift registers. It is floating but should typically be held LOW by the microcontroller. Pulsing the pin HIGH (LOW / HIGH / LOW) will shift one bit of data. Be wary of driving this pin to ground without protection, as this pin is also used as a joint power input for the [Logitech G25 Shifter](@ref logitech_shifter_g25). + +Pin #2 (Data Output) is connected directly to the output of the EEPROM, and connected to the output of the shift registers through a 1000 Ohm resistor. + +Pin #3 (Latch / Chip Select) is shared between the onboard EEPROM and the shift registers. It is floating but should typically be held HIGH by the microcontroller. It must be pulsed once (HIGH / LOW / HIGH) to latch the data into the shift registers. Holding it LOW instructs the EEPROM to listen for commands. + +Pin #5 (Data In) is used exclusively by the EEPROM. It is also connected to the "Power" LED through a 330 Ohm resistor. Driving this pin LOW will turn on the LED. + +The power pins (#7 / #9) are connected together within the DE-9 connector. Either one can be used, but it is recommended to use pin 9 for compatibility with the other shifters. + +The shifter's electronics are theoretically compatibile with both 3.3V and 5V logic. Be sure to use the appropriate voltage for the logic level of your microcontroller. + +## Buttons + +The shifter includes 12 user-facing buttons: + +* Four black buttons in a diamond pattern +* One directional pad (D-Pad) +* Four red buttons in a straight line + +The shifter also contains one internal button on the bottom of the shift column to indicate that it's in reverse. + +### Shift Registers + +These buttons are connected to the external DE-9 connector through a pair of NXP 74HC165D parallel-to-serial shift registers. + +| Button | Register | Bit | Offset | Enum | +|-----------------------|----------|-----|--------|--------------------------------------------------| +| (Unused) | Bottom | D7 | 15 | SimRacing::LogitechShifterG27::BUTTON_UNUSED1 | +| Reverse | Bottom | D6 | 14 | SimRacing::LogitechShifterG27::BUTTON_REVERSE | +| (Unused) | Bottom | D5 | 13 | SimRacing::LogitechShifterG27::BUTTON_UNUSED2 | +| Sequential Mode | Bottom | D4 | 12 | SimRacing::LogitechShifterG27::BUTTON_SEQUENTIAL | +| Red #3 | Bottom | D3 | 11 | SimRacing::LogitechShifterG27::BUTTON_3 | +| Red #2 | Bottom | D2 | 10 | SimRacing::LogitechShifterG27::BUTTON_2 | +| Red #4 | Bottom | D1 | 9 | SimRacing::LogitechShifterG27::BUTTON_4 | +| Red #1 | Bottom | D0 | 8 | SimRacing::LogitechShifterG27::BUTTON_1 | +| Black Up | Top | D7 | 7 | SimRacing::LogitechShifterG27::BUTTON_NORTH | +| Black Right | Top | D6 | 6 | SimRacing::LogitechShifterG27::BUTTON_EAST | +| Black Left | Top | D5 | 5 | SimRacing::LogitechShifterG27::BUTTON_WEST | +| Black Down | Top | D4 | 4 | SimRacing::LogitechShifterG27::BUTTON_SOUTH | +| Directional Pad Right | Top | D3 | 3 | SimRacing::LogitechShifterG27::DPAD_RIGHT | +| Directional Pad Left | Top | D2 | 2 | SimRacing::LogitechShifterG27::DPAD_LEFT | +| Directional Pad Down | Top | D1 | 1 | SimRacing::LogitechShifterG27::DPAD_DOWN | +| Directional Pad Up | Top | D0 | 0 | SimRacing::LogitechShifterG27::DPAD_UP | + +Data from the shift registers can be read using the Data Output (DE-9 #2), Latch (DE-9 #3), and Clock (DE-9 #1) pins. The latch must be pulsed LOW (HIGH / LOW / HIGH), then data read out via the data output pin while the clock is pulsed repeatedly from LOW to HIGH. + +All buttons will report a '1' state if they are pressed, and a '0' state if they are unpressed. Internally, all of these buttons are held to ground with 10k pull-downs. + +The red buttons are numbered from left to right, 1-4. The black buttons use cardinal directions. + +## EEPROM Storage + +The Logitech shifter has an internal EEPROM chip, presumably for storing settings and calibration data. In my shifter this is an [ST Microelectronics M95010-W](https://www.st.com/resource/en/datasheet/m95010-w.pdf) in an SO8 package. It has 1 Kbit of memory and can be read and written to via the DE-9 connector. The EERPOM does not need to be used in order to retrieve the control surface data from the shifter. + +This library does not implement EEPROM support, either for reading from the EEPROM or utilizing its data. diff --git a/docs/pages/supported_devices.md b/docs/pages/supported_devices.md index 20bcbbf..f70b18f 100644 --- a/docs/pages/supported_devices.md +++ b/docs/pages/supported_devices.md @@ -11,4 +11,5 @@ - @subpage logitech_pedals - @subpage logitech_shifter +- @subpage logitech_shifter_g27 - @subpage logitech_shifter_g25 From fdb5fb63468bcec95012bbf8bfb8ee4f14f54a6e Mon Sep 17 00:00:00 2001 From: David Madison Date: Tue, 4 Mar 2025 15:50:02 -0500 Subject: [PATCH 30/39] Set G25 shifter sequential calibration defaults These default engagement / release points were already set as static members, but I forgot to add them as defaults to the calibration function. --- src/SimRacing.h | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/SimRacing.h b/src/SimRacing.h index f203dc6..9d9eb2e 100644 --- a/src/SimRacing.h +++ b/src/SimRacing.h @@ -1173,7 +1173,10 @@ namespace SimRacing { * from an engaged gear (as a percentage of distance * from neutral to Y max, 0-1) */ - void setCalibrationSequential(int neutral, int up, int down, float engagePoint, float releasePoint); + void setCalibrationSequential(int neutral, int up, int down, + float engagePoint = LogitechShifterG25::CalEngagementPoint, + float releasePoint = LogitechShifterG25::CalReleasePoint + ); /** @copydoc AnalogShifter::serialCalibration(Stream&) */ void serialCalibrationSequential(Stream& iface = Serial); From 7b3fa7a4ae7e7f1f1df48ef898c2324fef82aca4 Mon Sep 17 00:00:00 2001 From: David Madison Date: Tue, 4 Mar 2025 15:47:22 -0500 Subject: [PATCH 31/39] Separate G27 and G25 shifter calibration In testing the G25 calibration worked fine for my G27 shifter, but doing my due diligence to set the shift points based on the actual hardware. --- src/SimRacing.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/SimRacing.cpp b/src/SimRacing.cpp index 865e33c..e56eb2a 100644 --- a/src/SimRacing.cpp +++ b/src/SimRacing.cpp @@ -1156,8 +1156,8 @@ LogitechShifterG27::LogitechShifterG27( this->pinModesSet = false; this->buttonStates = this->previousButtons = 0x0000; // zero all button data - // using the calibration from my G25 shifter, which is probably closer than the Driving Force - this->setCalibration({ 508, 435 }, { 310, 843 }, { 303, 8 }, { 516, 827 }, { 540, 14 }, { 713, 846 }, { 704, 17 }); + // using the calibration values from my own G27 shifter + this->setCalibration({ 453, 470 }, { 247, 828 }, { 258, 6 }, { 449, 878 }, { 472, 5 }, { 645, 880 }, { 651, 21 }); } void LogitechShifterG27::cacheButtons(uint16_t newStates) { @@ -1407,8 +1407,9 @@ LogitechShifterG25::LogitechShifterG25( sequentialProcess(false), // not in sequential mode sequentialState(0) // no sequential buttons pressed { - // using the values from my own shifter - this->setCalibrationSequential(425, 619, 257, 0.70, 0.50); + // using the calibration values from my own G25 shifter + this->setCalibration({ 508, 435 }, { 310, 843 }, { 303, 8 }, { 516, 827 }, { 540, 14 }, { 713, 846 }, { 704, 17 }); + this->setCalibrationSequential(425, 619, 257); } void LogitechShifterG25::begin() { From 422a07cb78acbc636444aa260a8155452743e817 Mon Sep 17 00:00:00 2001 From: David Madison Date: Tue, 4 Mar 2025 16:50:13 -0500 Subject: [PATCH 32/39] Remove detection from Peripheral constructor Although I think the detector being a part of the Peripheral base class is wise, I'm not a fan of passing the detector pointer through the constructor. If a derived class passes its own detector and a later derived class wants to change that detector for whatever reason, it's blocked. Changing to a protected function makes more sense. This also in effect removes arbitrary detection support for the generic base classes (Shifter, Pedals, Handbrake, etc.). I have no evidence that that feature was ever used by an end-user, and with the way the library is structured that is better implemented as a custom derived class for their specific device. It can always be added back in later if necessary (via a second constructor taking a pin and declaring an object on the heap, most likely). --- src/SimRacing.cpp | 47 +++++++++++++++--------------- src/SimRacing.h | 73 +++++++++++++++++++---------------------------- 2 files changed, 53 insertions(+), 67 deletions(-) diff --git a/src/SimRacing.cpp b/src/SimRacing.cpp index e56eb2a..e63ce1b 100644 --- a/src/SimRacing.cpp +++ b/src/SimRacing.cpp @@ -430,10 +430,6 @@ void AnalogInput::setCalibration(AnalogInput::Calibration newCal) { // Peripheral # //######################################################### -Peripheral::Peripheral(DeviceConnection* detector) - : detector(detector) -{} - bool Peripheral::update() { // if the detector exists, poll for state if (this->detector) { @@ -457,6 +453,10 @@ bool Peripheral::isConnected() const { return true; } +void Peripheral::setDetectPtr(DeviceConnection* d) { + this->detector = d; +} + void Peripheral::setStablePeriod(unsigned long t) { // if detector exists, set the stable period if (this->detector) { @@ -468,9 +468,8 @@ void Peripheral::setStablePeriod(unsigned long t) { // Pedals # //######################################################### -Pedals::Pedals(AnalogInput* dataPtr, uint8_t nPedals, DeviceConnection* detector) +Pedals::Pedals(AnalogInput* dataPtr, uint8_t nPedals) : - Peripheral(detector), pedalData(dataPtr), NumPedals(nPedals), changed(false) @@ -676,8 +675,8 @@ void Pedals::serialCalibration(Stream& iface) { } -TwoPedals::TwoPedals(PinNum gasPin, PinNum brakePin, DeviceConnection* detector) - : Pedals(pedalData, NumPedals, detector), +TwoPedals::TwoPedals(PinNum gasPin, PinNum brakePin) + : Pedals(pedalData, NumPedals), pedalData{ AnalogInput(gasPin), AnalogInput(brakePin) } {} @@ -687,8 +686,8 @@ void TwoPedals::setCalibration(AnalogInput::Calibration gasCal, AnalogInput::Cal } -ThreePedals::ThreePedals(PinNum gasPin, PinNum brakePin, PinNum clutchPin, DeviceConnection* detector) - : Pedals(pedalData, NumPedals, detector), +ThreePedals::ThreePedals(PinNum gasPin, PinNum brakePin, PinNum clutchPin) + : Pedals(pedalData, NumPedals), pedalData{ AnalogInput(gasPin), AnalogInput(brakePin), AnalogInput(clutchPin) } {} @@ -701,9 +700,11 @@ void ThreePedals::setCalibration(AnalogInput::Calibration gasCal, AnalogInput::C LogitechPedals::LogitechPedals(PinNum gasPin, PinNum brakePin, PinNum clutchPin, PinNum detectPin) : - ThreePedals(gasPin, brakePin, clutchPin, &this->detectObj), + ThreePedals(gasPin, brakePin, clutchPin), detectObj(detectPin, false) // active high { + this->setDetectPtr(&this->detectObj); + // taken from calibrating my own pedals. the springs are pretty stiff so while // this covers the whole travel range, users may want to back it down for casual // use (esp. for the brake travel) @@ -712,9 +713,10 @@ LogitechPedals::LogitechPedals(PinNum gasPin, PinNum brakePin, PinNum clutchPin, LogitechDrivingForceGT_Pedals::LogitechDrivingForceGT_Pedals(PinNum gasPin, PinNum brakePin, PinNum detectPin) : - TwoPedals(gasPin, brakePin, &this->detectObj), + TwoPedals(gasPin, brakePin), detectObj(detectPin, false) // active high { + this->setDetectPtr(&this->detectObj); this->setCalibration({ 646, 0 }, { 473, 1023 }); // taken from calibrating my own pedals } @@ -723,9 +725,8 @@ LogitechDrivingForceGT_Pedals::LogitechDrivingForceGT_Pedals(PinNum gasPin, PinN // Shifter # //######################################################### -Shifter::Shifter(Gear min, Gear max, DeviceConnection* detector) +Shifter::Shifter(Gear min, Gear max) : - Peripheral(detector), MinGear(min), MaxGear(max) { this->currentGear = this->previousGear = 0; // neutral @@ -814,10 +815,9 @@ const float AnalogShifter::CalEdgeOffset = 0.60; AnalogShifter::AnalogShifter( Gear gearMin, Gear gearMax, - PinNum pinX, PinNum pinY, PinNum pinRev, - DeviceConnection* detector + PinNum pinX, PinNum pinY, PinNum pinRev ) : - Shifter(gearMin, gearMax, detector), + Shifter(gearMin, gearMax), /* Two axes, X and Y */ analogAxis{ AnalogInput(pinX), AnalogInput(pinY) }, @@ -1132,12 +1132,15 @@ void AnalogShifter::serialCalibration(Stream& iface) { } LogitechShifter::LogitechShifter(PinNum pinX, PinNum pinY, PinNum pinRev, PinNum detectPin) - : AnalogShifter( + : + AnalogShifter( -1, 6, // includes reverse and gears 1-6 - pinX, pinY, pinRev, &this->detectObj), + pinX, pinY, pinRev + ), - detectObj(detectPin, false) // active high + detectObj(detectPin, false) // active high { + this->setDetectPtr(&this->detectObj); this->setCalibration({ 490, 440 }, { 253, 799 }, { 262, 86 }, { 460, 826 }, { 470, 76 }, { 664, 841 }, { 677, 77 }); } @@ -1654,11 +1657,9 @@ void LogitechShifterG25::serialCalibrationSequential(Stream& iface) { // Handbrake # //######################################################### -Handbrake::Handbrake(PinNum pinAx, PinNum detectPin, boolean detectActiveLow) +Handbrake::Handbrake(PinNum pinAx) : - Peripheral(&this->detectObj), analogAxis(pinAx), - detectObj(detectPin, detectActiveLow), changed(false) {} diff --git a/src/SimRacing.h b/src/SimRacing.h index 9d9eb2e..fa913fc 100644 --- a/src/SimRacing.h +++ b/src/SimRacing.h @@ -250,14 +250,6 @@ namespace SimRacing { */ class Peripheral { public: - /** - * Class Constructor - * - * @param detector pointer to a device connection instance, to use for - * determining if the peripheral is connected - */ - Peripheral(DeviceConnection* detector = nullptr); - /** * Class destructor */ @@ -301,6 +293,20 @@ namespace SimRacing { */ virtual bool updateState(bool connected) = 0; + /** + * Sets the pointer to the detector object + * + * The detector object is used to check if the peripheral is connected + * to the microcontroller. The object is polled on every update. + * + * Although the detector instance is accessed via the Peripheral class, + * it is the responsibility of the dervied class to store the + * DeviceConnection object and manage its lifetime. + * + * @param d pointer to the detector object + */ + void setDetectPtr(DeviceConnection* d); + private: DeviceConnection* detector; ///< Pointer to a device connection instance }; @@ -337,12 +343,9 @@ namespace SimRacing { * @param dataPtr pointer to the analog input data managed by the class, * stored elsewhere * @param nPedals the number of pedals stored in said data pointer - * @param detector pointer to a device connection instance, to use for - * determining if the peripheral is connected */ Pedals( - AnalogInput* dataPtr, uint8_t nPedals, - DeviceConnection* detector = nullptr + AnalogInput* dataPtr, uint8_t nPedals ); /** @copydoc Peripheral::begin() */ @@ -438,12 +441,9 @@ namespace SimRacing { * * @param pinGas the analog pin for the gas pedal potentiometer * @param pinBrake the analog pin for the brake pedal potentiometer - * @param detector pointer to a device connection instance, to use for - * determining if the peripheral is connected */ TwoPedals( - PinNum pinGas, PinNum pinBrake, - DeviceConnection* detector = nullptr + PinNum pinGas, PinNum pinBrake ); /** @@ -471,12 +471,9 @@ namespace SimRacing { * @param pinGas the analog pin for the gas pedal potentiometer * @param pinBrake the analog pin for the brake pedal potentiometer * @param pinClutch the analog pin for the clutch pedal potentiometer - * @param detector pointer to a device connection instance, to use for - * determining if the peripheral is connected */ ThreePedals( - PinNum pinGas, PinNum pinBrake, PinNum pinClutch, - DeviceConnection* detector = nullptr + PinNum pinGas, PinNum pinBrake, PinNum pinClutch ); /** @@ -515,12 +512,10 @@ namespace SimRacing { /** * Class constructor * - * @param min the lowest gear possible - * @param max the highest gear possible - * @param detector pointer to a device connection instance, to use for - * determining if the peripheral is connected + * @param min the lowest gear possible + * @param max the highest gear possible */ - Shifter(Gear min, Gear max, DeviceConnection* detector = nullptr); + Shifter(Gear min, Gear max); /** * Returns the currently selected gear. @@ -621,13 +616,11 @@ namespace SimRacing { /** * Class constructor * - * @param gearMin the lowest gear possible - * @param gearMax the highest gear possible - * @param pinX the analog input pin for the X axis - * @param pinY the analog input pin for the Y axis - * @param pinRev the digital input pin for the 'reverse' button - * @param detector pointer to a device connection instance, to use for - * determining if the peripheral is connected + * @param gearMin the lowest gear possible + * @param gearMax the highest gear possible + * @param pinX the analog input pin for the X axis + * @param pinY the analog input pin for the Y axis + * @param pinRev the digital input pin for the 'reverse' button * * @note With the way the class is designed, the lowest possible gear is * -1 (reverse), and the highest possible gear is 6. Setting the @@ -638,8 +631,7 @@ namespace SimRacing { AnalogShifter( Gear gearMin, Gear gearMax, PinNum pinX, PinNum pinY, - PinNum pinRev = UnusedPin, - DeviceConnection* detector = nullptr + PinNum pinRev = UnusedPin ); /** @@ -781,15 +773,9 @@ namespace SimRacing { /** * Class constructor * - * @param pinAx analog pin number for the handbrake axis - * @param pinDetect the digital pin for device detection - * @param detectActiveLow whether the device is detected on a high signal (false, - * default) or a low signal (true) - */ - Handbrake( - PinNum pinAx, - PinNum pinDetect = UnusedPin, boolean detectActiveLow = false - ); + * @param pinAx analog pin number for the handbrake axis + */ + Handbrake(PinNum pinAx); /** * Initializes the pin for reading from the handbrake. @@ -836,7 +822,6 @@ namespace SimRacing { private: AnalogInput analogAxis; ///< axis data for the handbrake's position - DeviceConnection detectObj; ///< detector instance for checking if the handbrake is connected bool changed; ///< whether the handbrake position has changed since the previous update }; From d06390bc4ae51e26190f36e37b6ff9f0f41d90da Mon Sep 17 00:00:00 2001 From: David Madison Date: Tue, 4 Mar 2025 19:37:26 -0500 Subject: [PATCH 33/39] Add a USB adapter FAQ page --- docs/pages/usb_adapter_faq.md | 93 +++++++++++++++++++++++++++++++++++ 1 file changed, 93 insertions(+) create mode 100644 docs/pages/usb_adapter_faq.md diff --git a/docs/pages/usb_adapter_faq.md b/docs/pages/usb_adapter_faq.md new file mode 100644 index 0000000..ad7dedd --- /dev/null +++ b/docs/pages/usb_adapter_faq.md @@ -0,0 +1,93 @@ +# USB Adapter FAQ {#usb_adapter_faq} + +This page constains answers to frequently asked questions (FAQ) about building USB adapters using the [Sim Racing Library for Arduino](https://github.com/dmadison/Sim-Racing-Arduino). + +You can find my tutorial videos for building your own USB adapters [on YouTube](https://www.youtube.com/playlist?list=PLTboGmshZ5EIWQSYEjdrIFqgc2J6sLCSa). + + +### I have a G923 / G920 / G29 shifter. Why do you call it the "Driving Force" shifter? + +[That's what Logitech calls it](https://www.logitechg.com/en-us/products/driving/driving-force-shifter.html). + + +### What shifters are compatible with these DIY USB adapters? + +I have made DIY USB adapter tutorial videos for the [Logitech Driving Force shifter](@ref logitech_shifter), the [Logitech G27 shifter](@ref logitech_shifter_g27), and the [Logitech G25 shifter](@ref logitech_shifter_g25). + +All of these adapters are **only** compatible with the advertised shifter (i.e. if you made an adapter for the G25, it's only compatible with the G25). The exception to this is the G27 adapter, which is forwards-compatible with the Driving Force shifter. Do not connect an incompatible shifter to your adapter, you may damage the shifter or the Arduino! + + +### Can I make an adapter that supports all three shifters? + +Yes, but it requires some additional resistors. The [Sim Racing Shifter Shield](https://github.com/dmadison/Sim-Racing-Shields/) I designed supports all three shifters, as does its included firmware. Take a look at the schematic for reference. + + +### Can I make a USB adapter with both a shifter and pedals? + +Yes! Although you'll need to get your hands a little dirty with the code. + +You'll need to change the wiring to the Arduino board so that you can fit both peripherals. None of the devices are particularly picky about which pins you use, so there's a lot of flexibility there. The only restrictions are that the power pins (5V/GND) need to stay the same, and the analog outputs need to connect to the analog pins (A0-A5). I would recommend changing the pins on the existing examples and testing them out before continuing further. + +You will also need to combine the two example codes together. Believe me, this is easier than it sounds. I'd recommend opening two code windows side-by-side and copying between them. Add the new lines, remove duplicate lines, and if two lines are different do your best to reason out why that is. The most complicated bit is the `Joystick` object definition, which needs to change based on what you're outputting to USB. Hopefully the comments (the `//` bits) are clear enough. + +The library itself does not need to be modified, only the example code. If you run into any trouble there are a number of great learning resources online, including [the Arduino forums](https://forum.arduino.cc/) and [the Arduino subreddit](https://www.reddit.com/r/arduino). Good luck! + + +### Can I use an Arduino Uno / Arduino Nano / Arduino Mega instead of an Arduino Leonardo? + +Short answer: **No**. + +Long answer: *Maybe*. But it requires a specific variant of the board and it's significantly more effort. + +The tutorials suggest using an [Arduino Leonardo](https://docs.arduino.cc/hardware/leonardo/) because the onboard microcontroller (the ATmega32U4) has a hardware USB controller and female headers that don't require soldering. This means that the library code can set the USB descriptors and tell the microcontroller to behave as a USB human interface device (HID), then control the output based on the sim racing peripheral. + +In contrast, the microcontrollers on the [Arduino Uno (ATmega328P)](https://docs.arduino.cc/hardware/uno-rev3/), [Arduino Nano (ATmega328P)](https://docs.arduino.cc/hardware/nano/), and [Arduino Mega (ATmega2560)](https://docs.arduino.cc/hardware/mega-2560/) do **not** have a USB controller. Instead, they use a secondary integrated circuit (IC) to convert the serial data (UART) into USB. This means that the library code *cannot* set the USB descriptors to tell the microcontroller to behave as a USB HID device. + +On the Arduino Nano and on most low cost clones of the Arduino Uno and Arduino Mega, that IC is the [FT232](https://ftdichip.com/products/ft232rl/) or a knockoff like the CH340. These chips are unable to act as a USB adapter. + +On genuine and more expensive Arduino Unos and Arduino Megas, that IC is the ATmega16U2 - another microcontroller, and the brother of the ATmega32U4 onboard the Leonardo. With these boards it *is* technically possible to use them as a USB adapter, although it's not easy. + +To convert the ATmega16U2 into an HID device you can install the [UnoJoy](https://github.com/AlanChatham/UnoJoy) firmware. This is tricky to do over USB (using "Device Firmware Update" / DFU mode), but can be simplified using a hardware ICSP programmer. There are further instructions in that repository's documentation. + +You will also need to modify the library examples to use the UnoJoy library in place of the Joystick library to send serial commands to the ATmega16U2. This should be relatively straightforward, but you will need to understand some basic C/C++ programming. + +To recap, the process is: + + 1. Inspect the board to verify that you have a compatible model (Arduino Uno or Arduino Mega with an ATmega16U2 as the USB to serial interface) + 2. Reset the ATmega16U2 into DFU mode by shorting the reset pins on the ICSP header (or) attach an external hardware programmer + 3. Flash UnoJoy firmware to the ATmega16U2 + 4. Rewrite the library examples to use the UnoJoy library in place of the Joystick library for USB output + 5. Upload the code to the Uno (ATmega328P) + +Fair warning that you may have some difficulty getting the ATmega16U2 into DFU mode, and you may have some difficulty getting it back to functioning as a "regular" Arduino board afterwards. It is also possible to "brick" the board and render it in an unusable state. This is often recoverable but requires a hardware ICSP programmer. + + +### Can I use ______ microcontroller instead? + +Maybe! The library itself should be compatible with most development boards that support the Arduino framework. Try to compile one of the library examples for your microcontroller. If it compiles, it will probably work! + +The [Joystick library](https://github.com/MHeironimus/ArduinoJoystickLibrary/) supports a more limited subset of boards, and you may need to rewrite the Sim Racing Library example code to use a different USB library. + + +### The Arduino Leonardo is really big. Is there a smaller board I can use? + +Yes! The [SparkFun Pro Micro (ATmega32U4)](https://www.sparkfun.com/sparkfun-qwiic-pro-micro-usb-c-atmega32u4.html) has the same microcontroller and the same pinout. It's also the microcontroller that's compatible with the [Sim Racing Shields](https://github.com/dmadison/Sim-Racing-Shields/). But because it doesn't have female headers, you must solder to the pins. + + +### The SparkFun Pro Micro doesn't have a 5V pin. Which pin should I use? + +You should use the VCC pin for power in place of the 5V pin. + + +### I don't know how to solder. How do I connect multiple wires to one pin? + +I would recommend using [Wago 221 Series](https://www.wago.com/us/f/222-series-lever-nuts) or [Wago 222 Series Lever-Nuts](https://www.wago.com/us/f/221-series-levernuts). You can find them at many hardware stores, they're easy to use and make a solid connection. + +You can also use a more traditional solderless breadboard. These are common prototyping tools, but they aren't a great long term solution as the wires can come loose. + + +### Why can't I just use a DE-9 to USB adapter? + +The [DE-9 connector](https://en.wikipedia.org/wiki/D-subminiature) (also commonly called the "DB9" connector) has historically been used for serial devices using the [RS-232 standard](https://en.wikipedia.org/wiki/RS-232). Most common "DE-9 to USB" adapters you will find are, in fact, RS-232 to USB adapters. + +Although they use the DE-9 connector, none of the Logitech sim racing peripherals in this library are RS-232 devices. At best, the adapter will not work. At worst, you may damage the adapter or your sim racing peripheral. From 00db3a57206f947c2df5920c7bbe6ba266af442e Mon Sep 17 00:00:00 2001 From: David Madison Date: Wed, 19 Mar 2025 04:37:50 -0400 Subject: [PATCH 34/39] Change resistor comments for power LED The resistor is not strictly necessary, although it does make a lot of sense on the G25 to match brightness between the two indicator LEDs. --- .../LogitechShifterG25_Joystick.ino | 15 +++++++++++---- .../LogitechShifterG25_Print.ino | 15 +++++++++++---- .../LogitechShifterG27_Joystick.ino | 13 +++++++++---- .../LogitechShifterG27_Print.ino | 13 +++++++++---- src/SimRacing.cpp | 4 ++-- src/SimRacing.h | 4 ++-- 6 files changed, 44 insertions(+), 20 deletions(-) diff --git a/examples/Shifter/LogitechShifterG25/LogitechShifterG25_Joystick/LogitechShifterG25_Joystick.ino b/examples/Shifter/LogitechShifterG25/LogitechShifterG25_Joystick/LogitechShifterG25_Joystick.ino index 712ebec..f4eff78 100644 --- a/examples/Shifter/LogitechShifterG25/LogitechShifterG25_Joystick/LogitechShifterG25_Joystick.ino +++ b/examples/Shifter/LogitechShifterG25/LogitechShifterG25_Joystick/LogitechShifterG25_Joystick.ino @@ -40,10 +40,17 @@ const int Pin_ShifterLatch = 5; // DE-9 pin 3 const int Pin_ShifterClock = 6; // DE-9 pin 7 const int Pin_ShifterData = 7; // DE-9 pin 2 -// These pins require extra resistors! If you have made the proper -// connections, change the pin numbers to the ones you're using -const int Pin_ShifterDetect = SimRacing::UnusedPin; // DE-9 pin 1, requires pull-down resistor -const int Pin_ShifterLED = SimRacing::UnusedPin; // DE-9 pin 5, requires 100-120 Ohm series resistor +// This pin is optional! You do not need to connect it in order +// to read data from the shifter. Connecting it and changing the +// pin number below will light the power LED. On the G25, I +// recommend using a 100 Ohm resistor in series to match the +// brightness of the sequential mode LED. +const int Pin_ShifterLED = SimRacing::UnusedPin; // DE-9 pin 5 + +// This pin requies a pull-down resistor! If you have made the proper +// connections, change the pin number to the one you're using. Setting +// it will zero data when the shifter is disconnected. +const int Pin_ShifterDetect = SimRacing::UnusedPin; // DE-9 pin 1 SimRacing::LogitechShifterG25 shifter( Pin_ShifterX, Pin_ShifterY, diff --git a/examples/Shifter/LogitechShifterG25/LogitechShifterG25_Print/LogitechShifterG25_Print.ino b/examples/Shifter/LogitechShifterG25/LogitechShifterG25_Print/LogitechShifterG25_Print.ino index bd0fb0b..7dc8059 100644 --- a/examples/Shifter/LogitechShifterG25/LogitechShifterG25_Print/LogitechShifterG25_Print.ino +++ b/examples/Shifter/LogitechShifterG25/LogitechShifterG25_Print/LogitechShifterG25_Print.ino @@ -36,10 +36,17 @@ const int Pin_ShifterLatch = 5; // DE-9 pin 3 const int Pin_ShifterClock = 6; // DE-9 pin 7 const int Pin_ShifterData = 7; // DE-9 pin 2 -// These pins require extra resistors! If you have made the proper -// connections, change the pin numbers to the ones you're using -const int Pin_ShifterDetect = SimRacing::UnusedPin; // DE-9 pin 1, requires pull-down resistor -const int Pin_ShifterLED = SimRacing::UnusedPin; // DE-9 pin 5, requires 100-120 Ohm series resistor +// This pin is optional! You do not need to connect it in order +// to read data from the shifter. Connecting it and changing the +// pin number below will light the power LED. On the G25, I +// recommend using a 100 Ohm resistor in series to match the +// brightness of the sequential mode LED. +const int Pin_ShifterLED = SimRacing::UnusedPin; // DE-9 pin 5 + +// This pin requies a pull-down resistor! If you have made the proper +// connections, change the pin number to the one you're using. Setting +// it will zero data when the shifter is disconnected. +const int Pin_ShifterDetect = SimRacing::UnusedPin; // DE-9 pin 1 SimRacing::LogitechShifterG25 shifter( Pin_ShifterX, Pin_ShifterY, diff --git a/examples/Shifter/LogitechShifterG27/LogitechShifterG27_Joystick/LogitechShifterG27_Joystick.ino b/examples/Shifter/LogitechShifterG27/LogitechShifterG27_Joystick/LogitechShifterG27_Joystick.ino index cbe738a..c2a294e 100644 --- a/examples/Shifter/LogitechShifterG27/LogitechShifterG27_Joystick/LogitechShifterG27_Joystick.ino +++ b/examples/Shifter/LogitechShifterG27/LogitechShifterG27_Joystick/LogitechShifterG27_Joystick.ino @@ -40,10 +40,15 @@ const int Pin_ShifterLatch = 5; // DE-9 pin 3 const int Pin_ShifterClock = 6; // DE-9 pin 1 const int Pin_ShifterData = 7; // DE-9 pin 2 -// These pins require extra resistors! If you have made the proper -// connections, change the pin numbers to the ones you're using -const int Pin_ShifterDetect = SimRacing::UnusedPin; // DE-9 pin 7, requires pull-down resistor -const int Pin_ShifterLED = SimRacing::UnusedPin; // DE-9 pin 5, requires 100-120 Ohm series resistor +// This pin is optional! You do not need to connect it in order +// to read data from the shifter. Connecting it and changing the +// pin number below will light the power LED. +const int Pin_ShifterLED = SimRacing::UnusedPin; // DE-9 pin 5 + +// This pin requies a pull-down resistor! If you have made the proper +// connections, change the pin number to the one you're using. Setting +// it will zero data when the shifter is disconnected. +const int Pin_ShifterDetect = SimRacing::UnusedPin; // DE-9 pin 7 SimRacing::LogitechShifterG27 shifter( Pin_ShifterX, Pin_ShifterY, diff --git a/examples/Shifter/LogitechShifterG27/LogitechShifterG27_Print/LogitechShifterG27_Print.ino b/examples/Shifter/LogitechShifterG27/LogitechShifterG27_Print/LogitechShifterG27_Print.ino index 3c89bea..d75f031 100644 --- a/examples/Shifter/LogitechShifterG27/LogitechShifterG27_Print/LogitechShifterG27_Print.ino +++ b/examples/Shifter/LogitechShifterG27/LogitechShifterG27_Print/LogitechShifterG27_Print.ino @@ -36,10 +36,15 @@ const int Pin_ShifterLatch = 5; // DE-9 pin 3 const int Pin_ShifterClock = 6; // DE-9 pin 1 const int Pin_ShifterData = 7; // DE-9 pin 2 -// These pins require extra resistors! If you have made the proper -// connections, change the pin numbers to the ones you're using -const int Pin_ShifterDetect = SimRacing::UnusedPin; // DE-9 pin 7, requires pull-down resistor -const int Pin_ShifterLED = SimRacing::UnusedPin; // DE-9 pin 5, requires 100-120 Ohm series resistor +// This pin is optional! You do not need to connect it in order +// to read data from the shifter. Connecting it and changing the +// pin number below will light the power LED. +const int Pin_ShifterLED = SimRacing::UnusedPin; // DE-9 pin 5 + +// This pin requies a pull-down resistor! If you have made the proper +// connections, change the pin number to the one you're using. Setting +// it will zero data when the shifter is disconnected. +const int Pin_ShifterDetect = SimRacing::UnusedPin; // DE-9 pin 7 SimRacing::LogitechShifterG27 shifter( Pin_ShifterX, Pin_ShifterY, diff --git a/src/SimRacing.cpp b/src/SimRacing.cpp index e63ce1b..e3aec70 100644 --- a/src/SimRacing.cpp +++ b/src/SimRacing.cpp @@ -85,7 +85,7 @@ LogitechShifterG27 CreateShieldObject() { const PinNum Pin_Clock = 15; // DE-9 pin 1, should have 470 Ohm resistor to prevent shorts const PinNum Pin_Detect = A2; // DE-9 pin 7, requires 10k Ohm pull-down - const PinNum Pin_LED = 16; // DE-9 pin 5, requires 100-120 Ohm series resistor + const PinNum Pin_LED = 16; // DE-9 pin 5, has a 100-120 Ohm series resistor return LogitechShifterG27(Pin_X_Wiper, Pin_Y_Wiper, Pin_Latch, Pin_Clock, Pin_DataOut, Pin_Detect, Pin_LED); } @@ -103,7 +103,7 @@ LogitechShifterG25 CreateShieldObject() { const PinNum Pin_Clock = A2; // DE-9 pin 7, should have 470 Ohm resistor to prevent shorts const PinNum Pin_Detect = 15; // DE-9 pin 1, requires 10k Ohm pull-down - const PinNum Pin_LED = 16; // DE-9 pin 5, requires 100-120 Ohm series resistor + const PinNum Pin_LED = 16; // DE-9 pin 5, has a 100-120 Ohm series resistor return LogitechShifterG25(Pin_X_Wiper, Pin_Y_Wiper, Pin_Latch, Pin_Clock, Pin_DataOut, Pin_Detect, Pin_LED); } diff --git a/src/SimRacing.h b/src/SimRacing.h index fa913fc..9571ef8 100644 --- a/src/SimRacing.h +++ b/src/SimRacing.h @@ -976,7 +976,7 @@ namespace SimRacing { * @param pinDetect the digital input pin for device detection, DE-9 pin 7. * Requires a pull-down resistor. * @param pinLed digital output pin to light the power LED on connection, - * DE-9 pin 5. Requires a 100 Ohm series resistor. + * DE-9 pin 5 */ LogitechShifterG27( PinNum pinX, PinNum pinY, @@ -1109,7 +1109,7 @@ namespace SimRacing { * @param pinDetect the digital input pin for device detection, DE-9 pin 1. * Requires a pull-down resistor. * @param pinLed digital output pin to light the power LED on connection, - * DE-9 pin 5. Requires a 100 Ohm series resistor. + * DE-9 pin 5 */ LogitechShifterG25( PinNum pinX, PinNum pinY, From 7c81e7797537ec08a2681cbeaf8bdb0fae921bf2 Mon Sep 17 00:00:00 2001 From: David Madison Date: Wed, 19 Mar 2025 05:06:41 -0400 Subject: [PATCH 35/39] Swap G25/G27 LED and detect pin order This is arbitrary, but it's important to get this change in there before the API is locked for the major release. Originally, I figured that resistors were required for both pins (pull-down for the detect, series for the LED). In which case, it's more likely that users would want the detect feature (disabling inputs on disconnect) over the LED feature (...blinky). After some testing I've determined that even though the LED should probably have a 100 Ohm resistor, it will survive without it. That means it's more likely for users to set it, in which case it should take precedence over the detect pin. In addition, the LED pin is shared by the data input of the EEPROM chip. Although the library does not currently interface with the EEPROM, if it ever does that pin will become required. In which case, again, the detect pin would end up at the end of the list so it can have a default argument. --- .../LogitechShifterG25_Joystick.ino | 2 +- .../LogitechShifterG25_Print.ino | 2 +- .../LogitechShifterG27_Joystick.ino | 2 +- .../LogitechShifterG27_Print.ino | 2 +- src/SimRacing.cpp | 20 +++++++++---------- src/SimRacing.h | 16 +++++++-------- 6 files changed, 22 insertions(+), 22 deletions(-) diff --git a/examples/Shifter/LogitechShifterG25/LogitechShifterG25_Joystick/LogitechShifterG25_Joystick.ino b/examples/Shifter/LogitechShifterG25/LogitechShifterG25_Joystick/LogitechShifterG25_Joystick.ino index f4eff78..3b5602e 100644 --- a/examples/Shifter/LogitechShifterG25/LogitechShifterG25_Joystick/LogitechShifterG25_Joystick.ino +++ b/examples/Shifter/LogitechShifterG25/LogitechShifterG25_Joystick/LogitechShifterG25_Joystick.ino @@ -55,7 +55,7 @@ const int Pin_ShifterDetect = SimRacing::UnusedPin; // DE-9 pin 1 SimRacing::LogitechShifterG25 shifter( Pin_ShifterX, Pin_ShifterY, Pin_ShifterLatch, Pin_ShifterClock, Pin_ShifterData, - Pin_ShifterDetect, Pin_ShifterLED + Pin_ShifterLED, Pin_ShifterDetect ); //SimRacing::LogitechShifterG25 shifter = SimRacing::CreateShieldObject(); diff --git a/examples/Shifter/LogitechShifterG25/LogitechShifterG25_Print/LogitechShifterG25_Print.ino b/examples/Shifter/LogitechShifterG25/LogitechShifterG25_Print/LogitechShifterG25_Print.ino index 7dc8059..c11f445 100644 --- a/examples/Shifter/LogitechShifterG25/LogitechShifterG25_Print/LogitechShifterG25_Print.ino +++ b/examples/Shifter/LogitechShifterG25/LogitechShifterG25_Print/LogitechShifterG25_Print.ino @@ -51,7 +51,7 @@ const int Pin_ShifterDetect = SimRacing::UnusedPin; // DE-9 pin 1 SimRacing::LogitechShifterG25 shifter( Pin_ShifterX, Pin_ShifterY, Pin_ShifterLatch, Pin_ShifterClock, Pin_ShifterData, - Pin_ShifterDetect, Pin_ShifterLED + Pin_ShifterLED, Pin_ShifterDetect ); //SimRacing::LogitechShifterG25 shifter = SimRacing::CreateShieldObject(); diff --git a/examples/Shifter/LogitechShifterG27/LogitechShifterG27_Joystick/LogitechShifterG27_Joystick.ino b/examples/Shifter/LogitechShifterG27/LogitechShifterG27_Joystick/LogitechShifterG27_Joystick.ino index c2a294e..2158e03 100644 --- a/examples/Shifter/LogitechShifterG27/LogitechShifterG27_Joystick/LogitechShifterG27_Joystick.ino +++ b/examples/Shifter/LogitechShifterG27/LogitechShifterG27_Joystick/LogitechShifterG27_Joystick.ino @@ -53,7 +53,7 @@ const int Pin_ShifterDetect = SimRacing::UnusedPin; // DE-9 pin 7 SimRacing::LogitechShifterG27 shifter( Pin_ShifterX, Pin_ShifterY, Pin_ShifterLatch, Pin_ShifterClock, Pin_ShifterData, - Pin_ShifterDetect, Pin_ShifterLED + Pin_ShifterLED, Pin_ShifterDetect ); //SimRacing::LogitechShifterG27 shifter = SimRacing::CreateShieldObject(); diff --git a/examples/Shifter/LogitechShifterG27/LogitechShifterG27_Print/LogitechShifterG27_Print.ino b/examples/Shifter/LogitechShifterG27/LogitechShifterG27_Print/LogitechShifterG27_Print.ino index d75f031..ffd567c 100644 --- a/examples/Shifter/LogitechShifterG27/LogitechShifterG27_Print/LogitechShifterG27_Print.ino +++ b/examples/Shifter/LogitechShifterG27/LogitechShifterG27_Print/LogitechShifterG27_Print.ino @@ -49,7 +49,7 @@ const int Pin_ShifterDetect = SimRacing::UnusedPin; // DE-9 pin 7 SimRacing::LogitechShifterG27 shifter( Pin_ShifterX, Pin_ShifterY, Pin_ShifterLatch, Pin_ShifterClock, Pin_ShifterData, - Pin_ShifterDetect, Pin_ShifterLED + Pin_ShifterLED, Pin_ShifterDetect ); //SimRacing::LogitechShifterG27 shifter = SimRacing::CreateShieldObject(); diff --git a/src/SimRacing.cpp b/src/SimRacing.cpp index e3aec70..5ebf9b7 100644 --- a/src/SimRacing.cpp +++ b/src/SimRacing.cpp @@ -84,10 +84,10 @@ LogitechShifterG27 CreateShieldObject() { const PinNum Pin_Latch = 10; // DE-9 pin 3, aka chip select, requires 10k Ohm pull-up const PinNum Pin_Clock = 15; // DE-9 pin 1, should have 470 Ohm resistor to prevent shorts - const PinNum Pin_Detect = A2; // DE-9 pin 7, requires 10k Ohm pull-down const PinNum Pin_LED = 16; // DE-9 pin 5, has a 100-120 Ohm series resistor + const PinNum Pin_Detect = A2; // DE-9 pin 7, requires 10k Ohm pull-down - return LogitechShifterG27(Pin_X_Wiper, Pin_Y_Wiper, Pin_Latch, Pin_Clock, Pin_DataOut, Pin_Detect, Pin_LED); + return LogitechShifterG27(Pin_X_Wiper, Pin_Y_Wiper, Pin_Latch, Pin_Clock, Pin_DataOut, Pin_LED, Pin_Detect); } template<> @@ -102,10 +102,10 @@ LogitechShifterG25 CreateShieldObject() { const PinNum Pin_Latch = 10; // DE-9 pin 3, aka chip select, requires 10k Ohm pull-up const PinNum Pin_Clock = A2; // DE-9 pin 7, should have 470 Ohm resistor to prevent shorts - const PinNum Pin_Detect = 15; // DE-9 pin 1, requires 10k Ohm pull-down const PinNum Pin_LED = 16; // DE-9 pin 5, has a 100-120 Ohm series resistor + const PinNum Pin_Detect = 15; // DE-9 pin 1, requires 10k Ohm pull-down - return LogitechShifterG25(Pin_X_Wiper, Pin_Y_Wiper, Pin_Latch, Pin_Clock, Pin_DataOut, Pin_Detect, Pin_LED); + return LogitechShifterG25(Pin_X_Wiper, Pin_Y_Wiper, Pin_Latch, Pin_Clock, Pin_DataOut, Pin_LED, Pin_Detect); } #endif // ATmega32U4 for shield functions @@ -1148,8 +1148,8 @@ LogitechShifter::LogitechShifter(PinNum pinX, PinNum pinY, PinNum pinRev, PinNum LogitechShifterG27::LogitechShifterG27( PinNum pinX, PinNum pinY, PinNum pinLatch, PinNum pinClock, PinNum pinData, - PinNum pinDetect, - PinNum pinLed + PinNum pinLed, + PinNum pinDetect ) : LogitechShifter(pinX, pinY, UnusedPin, pinDetect), @@ -1397,14 +1397,14 @@ const float LogitechShifterG25::CalReleasePoint = 0.50; LogitechShifterG25::LogitechShifterG25( PinNum pinX, PinNum pinY, PinNum pinLatch, PinNum pinClock, PinNum pinData, - PinNum pinDetect, - PinNum pinLed + PinNum pinLed, + PinNum pinDetect ) : LogitechShifterG27( pinX, pinY, pinLatch, pinClock, pinData, - pinDetect, - pinLed + pinLed, + pinDetect ), sequentialProcess(false), // not in sequential mode diff --git a/src/SimRacing.h b/src/SimRacing.h index 9571ef8..b5f29f6 100644 --- a/src/SimRacing.h +++ b/src/SimRacing.h @@ -973,16 +973,16 @@ namespace SimRacing { * @param pinLatch digital output pin to pulse to latch data, DE-9 pin 3 * @param pinClock digital output pin to pulse as a clock, DE-9 pin 1 * @param pinData digital input pin to use for reading data, DE-9 pin 2 - * @param pinDetect the digital input pin for device detection, DE-9 pin 7. - * Requires a pull-down resistor. * @param pinLed digital output pin to light the power LED on connection, * DE-9 pin 5 + * @param pinDetect digital input pin for device detection, DE-9 pin 7. + * Requires a pull-down resistor. */ LogitechShifterG27( PinNum pinX, PinNum pinY, PinNum pinLatch, PinNum pinClock, PinNum pinData, - PinNum pinDetect = UnusedPin, - PinNum pinLed = UnusedPin + PinNum pinLed = UnusedPin, + PinNum pinDetect = UnusedPin ); /** @@ -1106,16 +1106,16 @@ namespace SimRacing { * @param pinLatch digital output pin to pulse to latch data, DE-9 pin 3 * @param pinClock digital output pin to pulse as a clock, DE-9 pin 7 * @param pinData digital input pin to use for reading data, DE-9 pin 2 - * @param pinDetect the digital input pin for device detection, DE-9 pin 1. - * Requires a pull-down resistor. * @param pinLed digital output pin to light the power LED on connection, * DE-9 pin 5 + * @param pinDetect digital input pin for device detection, DE-9 pin 1. + * Requires a pull-down resistor. */ LogitechShifterG25( PinNum pinX, PinNum pinY, PinNum pinLatch, PinNum pinClock, PinNum pinData, - PinNum pinDetect = UnusedPin, - PinNum pinLed = UnusedPin + PinNum pinLed = UnusedPin, + PinNum pinDetect = UnusedPin ); /** @copydoc LogitechShifterG27::begin() */ From df384597024f6148dffd520b8a838a7353193a81 Mon Sep 17 00:00:00 2001 From: David Madison Date: Wed, 19 Mar 2025 06:59:43 -0400 Subject: [PATCH 36/39] Add function to control G25/G27 power LED --- keywords.txt | 2 ++ src/SimRacing.cpp | 15 ++++++++++++--- src/SimRacing.h | 24 ++++++++++++++++++++++++ 3 files changed, 38 insertions(+), 3 deletions(-) diff --git a/keywords.txt b/keywords.txt index 27fd183..ca843ef 100644 --- a/keywords.txt +++ b/keywords.txt @@ -149,6 +149,8 @@ getButton KEYWORD2 getButtonChanged KEYWORD2 getDpadAngle KEYWORD2 buttonsChanged KEYWORD2 +setPowerLED KEYWORD2 +getPowerLED KEYWORD2 ####################################### # LogitechShifterG25 Methods and Functions (KEYWORD2) diff --git a/src/SimRacing.cpp b/src/SimRacing.cpp index 5ebf9b7..0129017 100644 --- a/src/SimRacing.cpp +++ b/src/SimRacing.cpp @@ -1157,6 +1157,7 @@ LogitechShifterG27::LogitechShifterG27( pinLed(sanitizePin(pinLed)) { this->pinModesSet = false; + this->setPowerLED(1); // power LED on by default this->buttonStates = this->previousButtons = 0x0000; // zero all button data // using the calibration values from my own G27 shifter @@ -1197,10 +1198,10 @@ void LogitechShifterG27::setPinModes(bool enabled) { digitalWrite(this->pinClock, LOW); pinMode(this->pinClock, OUTPUT); - // if we have an LED pin, set it to output and turn - // the LED on (active low) + // if we have an LED pin, set it to output and write the + // commanded state (inverted, as the LED is active-low) if (this->pinLed != UnusedPin) { - digitalWrite(this->pinLed, LOW); + digitalWrite(this->pinLed, !(this->ledState)); pinMode(this->pinLed, OUTPUT); } } @@ -1229,6 +1230,10 @@ void LogitechShifterG27::setPinModes(bool enabled) { this->pinModesSet = enabled; } +void LogitechShifterG27::setPowerLED(bool state) { + this->ledState = state; +} + uint16_t LogitechShifterG27::readShiftRegisters() { // if the pin outputs are not set, quit (none pressed) if (!this->pinModesSet) return 0x0000; @@ -1293,6 +1298,10 @@ bool LogitechShifterG27::updateState(bool connected) { this->setPinModes(1); } + if (this->pinLed != UnusedPin) { + digitalWrite(this->pinLed, !(this->ledState)); // active low + } + const uint16_t data = this->readShiftRegisters(); this->cacheButtons(data); changed |= this->buttonsChanged(); diff --git a/src/SimRacing.h b/src/SimRacing.h index b5f29f6..fd5a780 100644 --- a/src/SimRacing.h +++ b/src/SimRacing.h @@ -1024,6 +1024,29 @@ namespace SimRacing { */ bool buttonsChanged() const; + /** + * Sets the state of the shifter's power LED + * + * If the shifter is currently connected, this function will turn the + * power LED on and off. If the shifter is not connected, this will + * buffer the commanded state and set the LED when the shifter is next + * connected. + * + * @note The update() function must be called in order to push the + * commanded state to the shifter. + * + * @param state the state to set: 1 = on, 0 = off + */ + void setPowerLED(bool state); + + /** + * Gets the commanded state of the shifter's power LED + * + * @returns 'true' if the power LED is commanded to be on, 'false' + * if it's commanded to be off. + */ + bool getPowerLED() const { return this->ledState; } + protected: /** @copydoc Peripheral::updateState(bool) */ virtual bool updateState(bool connected); @@ -1079,6 +1102,7 @@ namespace SimRacing { // I/O state bool pinModesSet; ///< Flag for whether the output pins are enabled / driven + bool ledState; ///< Commanded state of the power LED output, DE-9 pin 5 // Button states uint16_t buttonStates; ///< the state of the buttons, as a packed word (where 0 = unpressed and 1 = pressed) From 793de2c5b2b8f020b6c0f882180a9207e2f88630 Mon Sep 17 00:00:00 2001 From: David Madison Date: Wed, 19 Mar 2025 07:15:33 -0400 Subject: [PATCH 37/39] Add G25/G27 power LED info to USB FAQ --- docs/pages/usb_adapter_faq.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/docs/pages/usb_adapter_faq.md b/docs/pages/usb_adapter_faq.md index ad7dedd..8e9c490 100644 --- a/docs/pages/usb_adapter_faq.md +++ b/docs/pages/usb_adapter_faq.md @@ -22,6 +22,15 @@ All of these adapters are **only** compatible with the advertised shifter (i.e. Yes, but it requires some additional resistors. The [Sim Racing Shifter Shield](https://github.com/dmadison/Sim-Racing-Shields/) I designed supports all three shifters, as does its included firmware. Take a look at the schematic for reference. +### Why is the power LED off for my G25 / G27 adapter? How do I turn it on? + +The power LED is controlled separately, and isn't required for the adapter to function (which is why it's not included in the tutorial video). + +The control pin for the power LED is DE-9 pin 5. If you connect that pin to ground (GND) on the Arduino, the LED will always be on. If you connect that pin to one of the Arduino's I/O pins and pass the pin number to the shifter object's constructor, you can control its state using the `SimRacing::LogitechShifterG27::setPowerLED(bool)` function. + +On the G25, I would recommend adding a 100 Ohm resistor in series so that the power and sequential mode LEDs are similar in brightness. You can add a series resistor on either shifter to reduce the brightness of the LED. + + ### Can I make a USB adapter with both a shifter and pedals? Yes! Although you'll need to get your hands a little dirty with the code. From 9bd5b1fa5ed1ce7a69ddc3cff23a0da5c887e1f8 Mon Sep 17 00:00:00 2001 From: David Madison Date: Sun, 23 Mar 2025 13:14:06 -0400 Subject: [PATCH 38/39] Add new shifters to shields README section --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e618d69..87657da 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@ Run one of the library examples in the Arduino IDE by going to `File -> Examples ## Adapters -Open source shields are available to connect the [Logitech Three Pedal Peripheral](http://dmadison.github.io/Sim-Racing-Arduino/docs/logitech_pedals.html) and the [Logitech Driving Force Shifter](http://dmadison.github.io/Sim-Racing-Arduino/docs/logitech_shifter.html) to a [SparkFun Pro Micro](https://github.com/sparkfun/Pro_Micro). The design comes with a 3D printable case and custom board files so that the adapter appears with a custom identity and "Sim Racing" name over USB. You can use these shields to build an inexpensive USB HID adapter. +Open source shields are available to connect the [Logitech Three Pedal Peripheral](http://dmadison.github.io/Sim-Racing-Arduino/docs/logitech_pedals.html) and the Logitech shifters ([Driving Force](http://dmadison.github.io/Sim-Racing-Arduino/docs/logitech_shifter.html) / [G27](http://dmadison.github.io/Sim-Racing-Arduino/docs/logitech_shifter_g27.html) / [G25](http://dmadison.github.io/Sim-Racing-Arduino/docs/logitech_shifter_g25.html)) to a [SparkFun Pro Micro](https://github.com/sparkfun/Pro_Micro). The design comes with a 3D printable case and custom board files so that the adapter appears with a custom identity and "Sim Racing" name over USB. You can use these shields to build an inexpensive USB HID adapter. You can find all of the necessary files in [the project repository](https://github.com/dmadison/Sim-Racing-Shields). From 4197222fd7b7541039953cd71fae3918a23eba9a Mon Sep 17 00:00:00 2001 From: David Madison Date: Mon, 31 Mar 2025 07:16:06 -0400 Subject: [PATCH 39/39] Add shifter tutorial videos to docs --- docs/pages/devices/logitech_shifter.md | 4 ++-- docs/pages/devices/logitech_shifter_g25.md | 10 ++++++++++ docs/pages/devices/logitech_shifter_g27.md | 10 ++++++++++ 3 files changed, 22 insertions(+), 2 deletions(-) diff --git a/docs/pages/devices/logitech_shifter.md b/docs/pages/devices/logitech_shifter.md index 35eee6c..7509da5 100644 --- a/docs/pages/devices/logitech_shifter.md +++ b/docs/pages/devices/logitech_shifter.md @@ -6,9 +6,9 @@ See the LogitechShifter_Print.ino and LogitechShifter_Joystick.ino examples for ## Adapters -@youtube_embed{https://www.youtube.com/embed/ngXsOidoWhI} +@youtube_embed{https://www.youtube.com/embed/yLL9XBgx8bs} -The best way to connect to the shifter is to build your own DIY adapter using a male DE-9 connector. This is simple to make and does not require any modifications to the shifter. The above video walks you through the process of wiring to an Arduino Leonardo. +You can build your own DIY USB adapter using a male DE-9 connector. This is simple to make and does not require any modifications to the shifter. The above video walks you through the process of wiring to an Arduino Leonardo. If you want something more robust, an open source shield is available to connect the shifter to a [SparkFun Pro Micro](https://github.com/sparkfun/Pro_Micro). The design comes with a 3D printable case and custom board files so that the device appears as a "Sim Racing Shifter" over USB. You can use this shield to build an inexpensive USB HID adapter. diff --git a/docs/pages/devices/logitech_shifter_g25.md b/docs/pages/devices/logitech_shifter_g25.md index c9cc99f..53dce68 100644 --- a/docs/pages/devices/logitech_shifter_g25.md +++ b/docs/pages/devices/logitech_shifter_g25.md @@ -6,6 +6,16 @@ The G25 shifter is near-identical to the [G27 shifter](@ref logitech_shifter_g27 These notes are based off of disassembling my own G25 shifter, with the internal PCB marked "202339-0000 REV. A1". +## Adapters + +@youtube_embed{https://www.youtube.com/embed/BVbpuYmPmm0} + +You can build your own DIY USB adapter using a male DE-9 connector. This is simple to make and does not require any modifications to the shifter. The above video walks you through the process of wiring to an Arduino Leonardo. + +If you want something more robust, an open source shield is available to connect the shifter to a [SparkFun Pro Micro](https://github.com/sparkfun/Pro_Micro). The design comes with a 3D printable case and custom board files so that the device appears as a "Sim Racing Shifter" over USB. You can use this shield to build an inexpensive USB HID adapter. + +You can find all of the necessary files in [the project repository](https://github.com/dmadison/Sim-Racing-Shields). + ## Connector | ![DE-9_Male](DE9_Male.svg) | ![DE-9_Female](DE9_Female.svg) | diff --git a/docs/pages/devices/logitech_shifter_g27.md b/docs/pages/devices/logitech_shifter_g27.md index 9db4fca..c63a81b 100644 --- a/docs/pages/devices/logitech_shifter_g27.md +++ b/docs/pages/devices/logitech_shifter_g27.md @@ -6,6 +6,16 @@ The G27 shifter is near-identical to the [G25 shifter](@ref logitech_shifter_g25 These notes are based off of disassembling my own G27 shifter, with the internal PCB marked "210-001096 REV. 001". +## Adapters + +@youtube_embed{https://www.youtube.com/embed/1yXbaHrBhXQ} + +You can build your own DIY USB adapter using a male DE-9 connector. This is simple to make and does not require any modifications to the shifter. The above video walks you through the process of wiring to an Arduino Leonardo. + +If you want something more robust, an open source shield is available to connect the shifter to a [SparkFun Pro Micro](https://github.com/sparkfun/Pro_Micro). The design comes with a 3D printable case and custom board files so that the device appears as a "Sim Racing Shifter" over USB. You can use this shield to build an inexpensive USB HID adapter. + +You can find all of the necessary files in [the project repository](https://github.com/dmadison/Sim-Racing-Shields). + ## Connector | ![DE-9_Male](DE9_Male.svg) | ![DE-9_Female](DE9_Female.svg) |