43static constexpr long invertAxis(
long value,
long min,
long max) {
44 return max - value + min;
65static long remap(
long value,
long inMin,
long inMax,
long outMin,
long outMax) {
68 const long temp = inMin;
72 value = invertAxis(value, inMin, inMax);
75 if (value <= inMin)
return outMin;
76 if (value >= inMax)
return outMax;
77 return map(value, inMin, inMax, outMin, outMax);
87static float floatPercent(
float pct) {
88 if (pct < 0.0) pct = 0.0;
89 else if (pct > 1.0) pct = 1.0;
101static void flushClient(Stream& client) {
102 while (client.read() != -1) { delay(2); }
110static void waitClient(Stream& client) {
112 while (client.peek() == -1) { delay(1); }
127static void readFloat(
float& value, Stream& client) {
128 client.print(
"(to skip this step and go with the default value of '");
130 client.print(
"', send 'n')");
134 if (client.peek() ==
'n')
return;
139 client.setTimeout(200);
140 input = client.parseFloat();
142 if (input >= 0.0 && input <= 1.0) {
143 client.print(F(
"Set the new value to '"));
148 client.print(F(
"Input '"));
150 client.print(F(
"' not within acceptable range (0.0 - 1.0). Please try again."));
166 Pin(pin), Inverted(invert), stablePeriod(detectTime),
186 lastChange(millis() - detectTime)
193 const bool newState = readPin();
198 if (pinState != newState) {
200 lastChange = millis();
203 if (pinState == HIGH) {
215 if (pinState == HIGH) {
216 const unsigned long now = millis();
217 if (now - lastChange >= stablePeriod) {
240 const unsigned long now = millis();
244 if (now - lastChange < stablePeriod) {
245 lastChange = now - stablePeriod;
250bool DeviceConnection::readPin()
const {
251 if (Pin == NOT_A_PIN)
return HIGH;
252 const bool state = digitalRead(Pin);
253 return Inverted ? !state : state;
264 if (Pin != NOT_A_PIN) {
270 bool changed =
false;
272 if (Pin != NOT_A_PIN) {
273 const int previous = this->position;
274 this->position = analogRead(Pin);
277 if (previous != this->position) {
285 !(previous < rMin && this->position < rMin) &&
289 !(previous > rMax && this->position > rMax)
308 return this->position;
312 return (this->cal.
min > this->cal.max);
316 this->position = newPos;
355 changed |= pedalData[i].
read();
361 const int min = pedalData[i].
getMin();
387 pedalData[pedal].
setPosition(pedalData[pedal].getMin());
397 case(PedalID::Brake):
400 case(PedalID::Clutch):
411 const char* separator =
"------------------------------------";
414 iface.println(F(
"Sim Racing Library Pedal Calibration"));
415 iface.println(separator);
419 iface.println(F(
"Take your feet off of the pedals so they move to their resting position."));
420 iface.println(F(
"Send any character to continue."));
423 const int MaxPedals = 3;
428 for (
int i = 0; (i <
getNumPedals()) && (i < MaxPedals); i++) {
432 iface.println(F(
"\nMinimum values for all pedals successfully recorded!\n"));
433 iface.println(separator);
436 iface.println(F(
"\nOne at a time, let's measure the maximum range of each pedal.\n"));
437 for (
int i = 0; (i <
getNumPedals()) && (i < MaxPedals); i++) {
439 iface.print(F(
"Push the "));
443 iface.print(F(
" pedal to the floor. "));
445 iface.println(F(
"Send any character to continue."));
453 iface.println(separator);
456 float DeadzoneMin = 0.01;
457 float DeadzoneMax = 0.025;
459 iface.println(F(
"These settings are optional. Send 'y' to customize. Send any other character to continue with the default values."));
461 iface.print(F(
" * Pedal Travel Deadzone, Start: \t"));
462 iface.print(DeadzoneMin);
463 iface.println(F(
" (Used to avoid the pedal always being slightly pressed)"));
465 iface.print(F(
" * Pedal Travel Deadzone, End: \t"));
466 iface.print(DeadzoneMax);
467 iface.println(F(
" (Used to guarantee that the pedal can be fully pressed)"));
473 if (iface.read() ==
'y') {
474 iface.println(F(
"Set the pedal travel starting deadzone as a floating point percentage."));
475 readFloat(DeadzoneMin, iface);
478 iface.println(F(
"Set the pedal travel ending deadzone as a floating point percentage."));
479 readFloat(DeadzoneMax, iface);
486 for (
int i = 0; (i <
getNumPedals()) && (i < MaxPedals); i++) {
487 auto &cMin = pedalCal[i].
min;
488 auto &cMax = pedalCal[i].
max;
490 const int range = abs(cMax - cMin);
491 const int dzMin = DeadzoneMin * (float)range;
492 const int dzMax = DeadzoneMax * (float)range;
507 iface.println(F(
"Here is your calibration:"));
508 iface.println(separator);
511 iface.print(F(
"pedals.setCalibration("));
513 for (
int i = 0; (i <
getNumPedals()) && (i < MaxPedals); i++) {
514 if(i > 0) iface.print(F(
", "));
517 iface.print(pedalCal[i].min);
518 iface.print(F(
", "));
519 iface.print(pedalCal[i].max);
529 iface.println(separator);
532 iface.print(F(
"Paste this line into the setup() function. The "));
533 iface.print(F(
"pedals"));
534 iface.print(F(
" will be calibrated with these values on startup."));
535 iface.println(F(
"\nCalibration complete! :)\n\n"));
542 :
Pedals(pedalData, NumPedals, detectPin),
553 :
Pedals(pedalData, NumPedals, detectPin),
566 :
ThreePedals(gasPin, brakePin, clutchPin, detectPin)
586 : MinGear(min), MaxGear(max)
600 if (gear > 0 && gear <= 9)
622 if (gear < 0 || gear > 9) {
656const float AnalogShifter::CalEngagementPoint = 0.70;
657const float AnalogShifter::CalReleasePoint = 0.50;
658const float AnalogShifter::CalEdgeOffset = 0.60;
665 Shifter(pinRev != NOT_A_PIN ? -1 : 0, 6),
671 detector(detectPin, false)
675 pinMode(PinReverse, INPUT);
686 analogAxis[Axis::X].
read();
687 analogAxis[Axis::Y].
read();
694 const int8_t previousGear = this->
getGear();
696 analogAxis[Axis::X].
setPosition(calibration.neutralX);
697 analogAxis[Axis::Y].
setPosition(calibration.neutralY);
699 if (previousGear != 0)
changed =
true;
714 const int8_t previousGear = this->
getGear();
715 const bool prevOdd = ((previousGear != -1) && (previousGear & 1));
716 const bool prevEven = (!prevOdd && previousGear != 0);
723 if ((prevOdd && y > calibration.oddRelease) || (prevEven && y < calibration.evenRelease)) {
724 newGear = previousGear;
730 if (y > calibration.oddTrigger) {
733 else if (y < calibration.evenTrigger) {
739 if (x > calibration.rightEdge) newGear += 4;
740 else if (x >= calibration.leftEdge) newGear += 2;
748 if (reverse && newGear == 5) {
755 else if ((reverse || previousGear == -1) && newGear == 6) {
761 changed = (newGear != previousGear) ? 1 : 0;
768 if (ax != Axis::X && ax != Axis::Y)
return min;
783 return digitalRead(PinReverse);
789 float engagePoint,
float releasePoint,
float edgeOffset) {
792 engagePoint = floatPercent(engagePoint);
793 releasePoint = floatPercent(releasePoint);
794 edgeOffset = floatPercent(edgeOffset);
796 const int xLeft = (g1.
x + g2.
x) / 2;
797 const int xRight = (g5.
x + g6.
x) / 2;
799 const int yOdd = (g1.
y + g3.
y + g5.
y) / 3;
800 const int yEven = (g2.
y + g4.
y + g6.
y) / 3;
807 calibration.neutralX = neutral.
x;
808 calibration.neutralY = neutral.
y;
814 const Axis axes[2] = { Axis::X, Axis::Y };
815 int*
const neutralAxis[2] = { &neutral.
x, &neutral.
y };
816 for (
int i = 0; i < 2; i++) {
819 *neutralAxis[i] = analogAxis[axes[i]].
getPosition();
831 calibration.oddTrigger = neutral.
y + ((float)yOddDiff * engagePoint);
832 calibration.oddRelease = neutral.
y + ((float)yOddDiff * releasePoint);
834 calibration.evenTrigger = neutral.
y - ((float)yEvenDiff * engagePoint);
835 calibration.evenRelease = neutral.
y - ((float)yEvenDiff * releasePoint);
837 calibration.leftEdge = neutral.
x - ((float)leftDiff * edgeOffset);
838 calibration.rightEdge = neutral.
x + ((float)rightDiff * edgeOffset);
841 Serial.print(
"Odd Trigger: ");
842 Serial.println(calibration.oddTrigger);
843 Serial.print(
"Odd Release: ");
844 Serial.println(calibration.oddRelease);
845 Serial.print(
"Even Trigger: ");
846 Serial.println(calibration.evenTrigger);
847 Serial.print(
"Even Release: ");
848 Serial.println(calibration.evenRelease);
849 Serial.print(
"Left Edge: ");
850 Serial.println(calibration.leftEdge);
851 Serial.print(
"Right Edge: ");
852 Serial.println(calibration.rightEdge);
855 Serial.print(
"X Min: ");
856 Serial.println(analogAxis[Axis::X].getMin());
857 Serial.print(
"X Max: ");
858 Serial.println(analogAxis[Axis::X].getMax());
860 Serial.print(
"Y Min: ");
861 Serial.println(analogAxis[Axis::Y].getMin());
862 Serial.print(
"Y Max: ");
863 Serial.println(analogAxis[Axis::Y].getMax());
869 iface.print(F(
"Error! Cannot perform calibration, "));
870 iface.print(F(
"shifter"));
871 iface.println(F(
" is not connected."));
875 const char* separator =
"------------------------------------";
878 iface.println(F(
"Sim Racing Library Shifter Calibration"));
879 iface.println(separator);
883 float engagementPoint = CalEngagementPoint;
884 float releasePoint = CalReleasePoint;
885 float edgeOffset = CalEdgeOffset;
887 for (
int i = 0; i <= 6; i++) {
890 iface.print(F(
"Please move the gear shifter into "));
891 iface.print(gearName);
892 iface.println(F(
". Send any character to continue."));
902 iface.print(
"Gear '");
903 iface.print(gearName);
904 iface.print(
"' position recorded as { ");
905 iface.print(gears[i].x);
907 iface.print(gears[i].y);
912 iface.println(separator);
915 iface.println(F(
"These settings are optional. Send 'y' to customize. Send any other character to continue with the default values."));
917 iface.print(F(
" * Gear Engagement Point: \t"));
918 iface.println(engagementPoint);
920 iface.print(F(
" * Gear Release Point: \t"));
921 iface.println(releasePoint);
923 iface.print(F(
" * Horizontal Gate Offset:\t"));
924 iface.println(edgeOffset);
930 if (iface.read() ==
'y') {
931 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 engaging gears."));
932 readFloat(engagementPoint, iface);
935 iface.println(F(
"Set the release point as a floating point percentage. This is the percentage away from the neutral axis on Y to go back into neutral. It must be less than the engagement point."));
936 readFloat(releasePoint, iface);
939 iface.println(F(
"Set the gate offset as a floating point percentage. This is the percentage away from the neutral axis on X to select the side gears."));
940 readFloat(edgeOffset, iface);
946 this->
setCalibration(gears[0], gears[1], gears[2], gears[3], gears[4], gears[5], gears[6], engagementPoint, releasePoint, edgeOffset);
948 iface.println(F(
"Here is your calibration:"));
949 iface.println(separator);
952 iface.print(F(
"shifter.setCalibration( "));
954 for (
int i = 0; i < 7; i++) {
957 iface.print(gears[i].x);
959 iface.print(gears[i].y);
964 iface.print(engagementPoint);
966 iface.print(releasePoint);
968 iface.print(edgeOffset);
973 iface.println(separator);
976 iface.println(F(
"Paste this line into the setup() function to calibrate on startup."));
977 iface.println(F(
"\n\nCalibration complete! :)\n"));
983 this->
setCalibration({ 490, 440 }, { 253, 799 }, { 262, 86 }, { 460, 826 }, { 470, 76 }, { 664, 841 }, { 677, 77 });
1006 changed = analogAxis.
read();
1031 iface.print(F(
"Error! Cannot perform calibration, "));
1032 iface.print(F(
"handbrake"));
1033 iface.println(F(
" is not connected."));
1037 const char* separator =
"------------------------------------";
1040 iface.println(F(
"Sim Racing Library Handbrake Calibration"));
1041 iface.println(separator);
1047 iface.println(F(
"Keep your hand off of the handbrake to record its resting position"));
1048 iface.println(F(
"Send any character to continue."));
1056 iface.println(F(
"Now pull on the handbrake and hold it at the end of its range"));
1057 iface.println(F(
"Send any character to continue."));
1068 iface.println(F(
"Here is your calibration:"));
1069 iface.println(separator);
1072 iface.print(F(
"handbrake.setCalibration("));
1074 iface.print(newCal.
min);
1075 iface.print(F(
", "));
1076 iface.print(newCal.
max);
1081 iface.println(separator);
1084 iface.print(F(
"Paste this line into the setup() function. The "));
1085 iface.print(F(
"handbrake"));
1086 iface.print(F(
" will be calibrated with these values on startup."));
1087 iface.println(F(
"\nCalibration complete! :)\n\n"));
Header file for the Sim Racing Library.
Axis
Enumeration for analog axis names, mapped to integers.
Interface with shifters using two potentiometers for gear position.
void setCalibration(GearPosition neutral, GearPosition g1, GearPosition g2, GearPosition g3, GearPosition g4, GearPosition g5, GearPosition g6, float engagePoint=CalEngagementPoint, float releasePoint=CalReleasePoint, float edgeOffset=CalEdgeOffset)
Calibrate the gear shifter for more accurate shifting.
void serialCalibration(Stream &iface=Serial)
Runs an interactive calibration tool using the serial interface.
bool getReverseButton() const
Checks the current state of the "reverse" button at the bottom of the shift column.
AnalogShifter(uint8_t pinX, uint8_t pinY, uint8_t pinRev=NOT_A_PIN, uint8_t detectPin=NOT_A_PIN)
Class constructor.
int getPositionRaw(Axis ax) const
Retrieves the buffered position for the analog axis.
virtual bool update()
Polls the hardware to update the current gear state.
bool isConnected() const
Check if the device is physically connected to the board.
virtual void begin()
Initializes the hardware pins for reading the gear states.
long getPosition(Axis ax, long rMin=AnalogInput::Min, long rMax=AnalogInput::Max) const
Retrieves the buffered position for the analog axis, rescaled to a nominal range using the calibratio...
void poll()
Checks if the pin detects a connection.
void setStablePeriod(unsigned long t)
Allows the user to change the stable period of the detector.
DeviceConnection(uint8_t pin, bool invert=false, unsigned long detectTime=250)
Class constructor.
ConnectionState getState() const
Retrieves the current ConnectionState from the instance.
ConnectionState
The state of the connection, whether it is connected, disconnected, and everywhere in-between.
@ Unplug
Device was just removed (connection ends)
@ PlugIn
Device was just plugged in (connection starts), unstable.
@ Disconnected
No connection present.
@ Connected
Connection present and stable.
bool isConnected() const
Check if the device is physically connected to the board.
virtual void begin()
Initializes the pin for reading from the handbrake.
long getPosition(long rMin=0, long rMax=100) const
Retrieves the buffered position for the handbrake axis, rescaled to a nominal range using the calibra...
void setCalibration(AnalogInput::Calibration newCal)
Calibrate the axis' min/max values for rescaling.
Handbrake(uint8_t pinAx, uint8_t detectPin=NOT_A_PIN)
Class constructor.
int getPositionRaw() const
Retrieves the buffered position for the handbrake, ignoring the calibration data.
virtual bool update()
Polls the handbrake to update its position.
bool isConnected() const
Check if the device is physically connected to the board.
void serialCalibration(Stream &iface=Serial)
Runs an interactive calibration tool using the serial interface.
LogitechDrivingForceGT_Pedals(uint8_t gasPin, uint8_t brakePin, uint8_t detectPin=NOT_A_PIN)
Class constructor.
LogitechPedals(uint8_t gasPin, uint8_t brakePin, uint8_t clutchPin, uint8_t detectPin=NOT_A_PIN)
Class constructor.
LogitechShifter(uint8_t pinX, uint8_t pinY, uint8_t pinRev=NOT_A_PIN, uint8_t detectPin=NOT_A_PIN)
Class constructor.
Base class for all pedals instances.
void serialCalibration(Stream &iface=Serial)
Runs an interactive calibration tool using the serial interface.
void setCalibration(PedalID pedal, AnalogInput::Calibration cal)
Calibrate a pedal's min/max values for rescaling.
Pedals(AnalogInput *dataPtr, uint8_t nPedals, uint8_t detectPin)
Class constructor.
static String getPedalName(PedalID pedal)
Utility function to get the string name for each pedal.
bool hasPedal(PedalID pedal) const
Checks if a given pedal is present in the class.
int getPositionRaw(PedalID pedal) const
Retrieves the buffered position for the pedal, ignoring the calibration data.
virtual bool update()
Perform a poll of the hardware to refresh the class state.
virtual void begin()
Initialize the hardware (if necessary)
long getPosition(PedalID pedal, long rMin=0, long rMax=100) const
Retrieves the buffered position for the pedal, rescaled to a nominal range using the calibration valu...
int getNumPedals() const
Retrieves the number of pedals handled by the class.
Base class for all shifter instances.
String getGearString() const
Returns a String that represents the current gear.
int8_t getGear() const
Returns the currently selected gear.
Shifter(int8_t min, int8_t max)
Class constructor.
char getGearChar() const
Returns a character that represents the current gear.
bool changed
whether the gear has changed since the previous update
int8_t currentGear
index of the current gear
Pedal implementation for devices with gas, brake, and clutch.
ThreePedals(uint8_t gasPin, uint8_t brakePin, uint8_t clutchPin, uint8_t detectPin=NOT_A_PIN)
Class constructor.
void setCalibration(AnalogInput::Calibration gasCal, AnalogInput::Calibration brakeCal, AnalogInput::Calibration clutchCal)
Sets the calibration data (min/max) for the pedals.
Pedal implementation for devices with only gas and brake.
void setCalibration(AnalogInput::Calibration gasCal, AnalogInput::Calibration brakeCal)
Sets the calibration data (min/max) for the pedals.
TwoPedals(uint8_t gasPin, uint8_t brakePin, uint8_t detectPin=NOT_A_PIN)
Class constructor.
Simple struct to store X/Y coordinates for the calibration function.
int y
Y coordinate of the gear position from the ADC.
int x
X coordinate of the gear position from the ADC.