43static constexpr PinNum sanitizePin(PinNum pin) {
58static constexpr long invertAxis(
long value,
long min,
long max) {
59 return max - value + min;
80static long remap(
long value,
long inMin,
long inMax,
long outMin,
long outMax) {
83 const long temp = inMin;
87 value = invertAxis(value, inMin, inMax);
90 if (value <= inMin)
return outMin;
91 if (value >= inMax)
return outMax;
92 return map(value, inMin, inMax, outMin, outMax);
102static float floatPercent(
float pct) {
103 if (pct < 0.0) pct = 0.0;
104 else if (pct > 1.0) pct = 1.0;
116static void flushClient(Stream& client) {
117 while (client.read() != -1) { delay(2); }
125static void waitClient(Stream& client) {
127 while (client.peek() == -1) { delay(1); }
142static void readFloat(
float& value, Stream& client) {
143 client.print(
"(to skip this step and go with the default value of '");
145 client.print(
"', send 'n')");
149 if (client.peek() ==
'n')
return;
154 client.setTimeout(200);
155 input = client.parseFloat();
157 if (input >= 0.0 && input <= 1.0) {
158 client.print(F(
"Set the new value to '"));
163 client.print(F(
"Input '"));
165 client.print(F(
"' not within acceptable range (0.0 - 1.0). Please try again."));
181 pin(sanitizePin(pin)), inverted(invert), stablePeriod(detectTime),
201 lastChange(millis() - detectTime)
210 const bool newState = readPin();
215 if (pinState != newState) {
217 lastChange = millis();
220 if (pinState == HIGH) {
232 if (pinState == HIGH) {
233 const unsigned long now = millis();
234 if (now - lastChange >= stablePeriod) {
257 const unsigned long now = millis();
261 if (now - lastChange < stablePeriod) {
262 lastChange = now - stablePeriod;
267bool DeviceConnection::readPin()
const {
268 if (pin == UnusedPin)
return HIGH;
269 const bool state = digitalRead(pin);
270 return inverted ? !state : state;
281 if (pin != UnusedPin) {
287 bool changed =
false;
290 const int previous = this->position;
291 this->position = analogRead(pin);
294 if (previous != this->position) {
302 !(previous < rMin && this->position < rMin) &&
306 !(previous > rMax && this->position > rMax)
325 return this->position;
329 return (this->cal.
min > this->cal.max);
333 this->position = newPos;
372 changed |= pedalData[i].
read();
378 const int min = pedalData[i].
getMin();
404 pedalData[pedal].
setPosition(pedalData[pedal].getMin());
414 case(PedalID::Brake):
417 case(PedalID::Clutch):
428 const char* separator =
"------------------------------------";
431 iface.println(F(
"Sim Racing Library Pedal Calibration"));
432 iface.println(separator);
436 iface.println(F(
"Take your feet off of the pedals so they move to their resting position."));
437 iface.println(F(
"Send any character to continue."));
440 const int MaxPedals = 3;
445 for (
int i = 0; (i <
getNumPedals()) && (i < MaxPedals); i++) {
449 iface.println(F(
"\nMinimum values for all pedals successfully recorded!\n"));
450 iface.println(separator);
453 iface.println(F(
"\nOne at a time, let's measure the maximum range of each pedal.\n"));
454 for (
int i = 0; (i <
getNumPedals()) && (i < MaxPedals); i++) {
456 iface.print(F(
"Push the "));
460 iface.print(F(
" pedal to the floor. "));
462 iface.println(F(
"Send any character to continue."));
470 iface.println(separator);
473 float DeadzoneMin = 0.01;
474 float DeadzoneMax = 0.025;
476 iface.println(F(
"These settings are optional. Send 'y' to customize. Send any other character to continue with the default values."));
478 iface.print(F(
" * Pedal Travel Deadzone, Start: \t"));
479 iface.print(DeadzoneMin);
480 iface.println(F(
" (Used to avoid the pedal always being slightly pressed)"));
482 iface.print(F(
" * Pedal Travel Deadzone, End: \t"));
483 iface.print(DeadzoneMax);
484 iface.println(F(
" (Used to guarantee that the pedal can be fully pressed)"));
490 if (iface.read() ==
'y') {
491 iface.println(F(
"Set the pedal travel starting deadzone as a floating point percentage."));
492 readFloat(DeadzoneMin, iface);
495 iface.println(F(
"Set the pedal travel ending deadzone as a floating point percentage."));
496 readFloat(DeadzoneMax, iface);
503 for (
int i = 0; (i <
getNumPedals()) && (i < MaxPedals); i++) {
504 auto &cMin = pedalCal[i].
min;
505 auto &cMax = pedalCal[i].
max;
507 const int range = abs(cMax - cMin);
508 const int dzMin = DeadzoneMin * (float)range;
509 const int dzMax = DeadzoneMax * (float)range;
524 iface.println(F(
"Here is your calibration:"));
525 iface.println(separator);
528 iface.print(F(
"pedals.setCalibration("));
530 for (
int i = 0; (i <
getNumPedals()) && (i < MaxPedals); i++) {
531 if(i > 0) iface.print(F(
", "));
534 iface.print(pedalCal[i].min);
535 iface.print(F(
", "));
536 iface.print(pedalCal[i].max);
546 iface.println(separator);
549 iface.print(F(
"Paste this line into the setup() function. The "));
550 iface.print(F(
"pedals"));
551 iface.print(F(
" will be calibrated with these values on startup."));
552 iface.println(F(
"\nCalibration complete! :)\n\n"));
559 :
Pedals(pedalData, NumPedals, detectPin),
570 :
Pedals(pedalData, NumPedals, detectPin),
583 :
ThreePedals(gasPin, brakePin, clutchPin, detectPin)
603 : MinGear(min), MaxGear(max)
617 if (gear > 0 && gear <= 9)
639 if (gear < 0 || gear > 9) {
673const float AnalogShifter::CalEngagementPoint = 0.70;
674const float AnalogShifter::CalReleasePoint = 0.50;
675const float AnalogShifter::CalEdgeOffset = 0.60;
687 pinReverse(sanitizePin(pinRev)),
688 detector(detectPin, false)
693 pinMode(pinReverse, INPUT);
705 analogAxis[Axis::X].
read();
706 analogAxis[Axis::Y].
read();
713 const int8_t previousGear = this->
getGear();
715 analogAxis[Axis::X].
setPosition(calibration.neutralX);
716 analogAxis[Axis::Y].
setPosition(calibration.neutralY);
718 if (previousGear != 0)
changed =
true;
733 const int8_t previousGear = this->
getGear();
734 const bool prevOdd = ((previousGear != -1) && (previousGear & 1));
735 const bool prevEven = (!prevOdd && previousGear != 0);
742 if ((prevOdd && y > calibration.oddRelease) || (prevEven && y < calibration.evenRelease)) {
743 newGear = previousGear;
749 if (y > calibration.oddTrigger) {
752 else if (y < calibration.evenTrigger) {
758 if (x > calibration.rightEdge) newGear += 4;
759 else if (x >= calibration.leftEdge) newGear += 2;
767 if (reverse && newGear == 5) {
774 else if ((reverse || previousGear == -1) && newGear == 6) {
780 changed = (newGear != previousGear) ? 1 : 0;
787 if (ax != Axis::X && ax != Axis::Y)
return min;
802 return digitalRead(pinReverse);
808 float engagePoint,
float releasePoint,
float edgeOffset) {
811 engagePoint = floatPercent(engagePoint);
812 releasePoint = floatPercent(releasePoint);
813 edgeOffset = floatPercent(edgeOffset);
815 const int xLeft = (g1.
x + g2.
x) / 2;
816 const int xRight = (g5.
x + g6.
x) / 2;
818 const int yOdd = (g1.
y + g3.
y + g5.
y) / 3;
819 const int yEven = (g2.
y + g4.
y + g6.
y) / 3;
826 calibration.neutralX = neutral.
x;
827 calibration.neutralY = neutral.
y;
833 const Axis axes[2] = { Axis::X, Axis::Y };
834 int*
const neutralAxis[2] = { &neutral.
x, &neutral.
y };
835 for (
int i = 0; i < 2; i++) {
838 *neutralAxis[i] = analogAxis[axes[i]].
getPosition();
850 calibration.oddTrigger = neutral.
y + ((float)yOddDiff * engagePoint);
851 calibration.oddRelease = neutral.
y + ((float)yOddDiff * releasePoint);
853 calibration.evenTrigger = neutral.
y - ((float)yEvenDiff * engagePoint);
854 calibration.evenRelease = neutral.
y - ((float)yEvenDiff * releasePoint);
856 calibration.leftEdge = neutral.
x - ((float)leftDiff * edgeOffset);
857 calibration.rightEdge = neutral.
x + ((float)rightDiff * edgeOffset);
860 Serial.print(
"Odd Trigger: ");
861 Serial.println(calibration.oddTrigger);
862 Serial.print(
"Odd Release: ");
863 Serial.println(calibration.oddRelease);
864 Serial.print(
"Even Trigger: ");
865 Serial.println(calibration.evenTrigger);
866 Serial.print(
"Even Release: ");
867 Serial.println(calibration.evenRelease);
868 Serial.print(
"Left Edge: ");
869 Serial.println(calibration.leftEdge);
870 Serial.print(
"Right Edge: ");
871 Serial.println(calibration.rightEdge);
874 Serial.print(
"X Min: ");
875 Serial.println(analogAxis[Axis::X].getMin());
876 Serial.print(
"X Max: ");
877 Serial.println(analogAxis[Axis::X].getMax());
879 Serial.print(
"Y Min: ");
880 Serial.println(analogAxis[Axis::Y].getMin());
881 Serial.print(
"Y Max: ");
882 Serial.println(analogAxis[Axis::Y].getMax());
888 iface.print(F(
"Error! Cannot perform calibration, "));
889 iface.print(F(
"shifter"));
890 iface.println(F(
" is not connected."));
894 const char* separator =
"------------------------------------";
897 iface.println(F(
"Sim Racing Library Shifter Calibration"));
898 iface.println(separator);
902 float engagementPoint = CalEngagementPoint;
903 float releasePoint = CalReleasePoint;
904 float edgeOffset = CalEdgeOffset;
906 for (
int i = 0; i <= 6; i++) {
909 iface.print(F(
"Please move the gear shifter into "));
910 iface.print(gearName);
911 iface.println(F(
". Send any character to continue."));
921 iface.print(
"Gear '");
922 iface.print(gearName);
923 iface.print(
"' position recorded as { ");
924 iface.print(gears[i].x);
926 iface.print(gears[i].y);
931 iface.println(separator);
934 iface.println(F(
"These settings are optional. Send 'y' to customize. Send any other character to continue with the default values."));
936 iface.print(F(
" * Gear Engagement Point: \t"));
937 iface.println(engagementPoint);
939 iface.print(F(
" * Gear Release Point: \t"));
940 iface.println(releasePoint);
942 iface.print(F(
" * Horizontal Gate Offset:\t"));
943 iface.println(edgeOffset);
949 if (iface.read() ==
'y') {
950 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."));
951 readFloat(engagementPoint, iface);
954 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."));
955 readFloat(releasePoint, iface);
958 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."));
959 readFloat(edgeOffset, iface);
965 this->
setCalibration(gears[0], gears[1], gears[2], gears[3], gears[4], gears[5], gears[6], engagementPoint, releasePoint, edgeOffset);
967 iface.println(F(
"Here is your calibration:"));
968 iface.println(separator);
971 iface.print(F(
"shifter.setCalibration( "));
973 for (
int i = 0; i < 7; i++) {
976 iface.print(gears[i].x);
978 iface.print(gears[i].y);
983 iface.print(engagementPoint);
985 iface.print(releasePoint);
987 iface.print(edgeOffset);
992 iface.println(separator);
995 iface.println(F(
"Paste this line into the setup() function to calibrate on startup."));
996 iface.println(F(
"\n\nCalibration complete! :)\n"));
1002 this->
setCalibration({ 490, 440 }, { 253, 799 }, { 262, 86 }, { 460, 826 }, { 470, 76 }, { 664, 841 }, { 677, 77 });
1012 detector(detectPin),
1025 changed = analogAxis.
read();
1050 iface.print(F(
"Error! Cannot perform calibration, "));
1051 iface.print(F(
"handbrake"));
1052 iface.println(F(
" is not connected."));
1056 const char* separator =
"------------------------------------";
1059 iface.println(F(
"Sim Racing Library Handbrake Calibration"));
1060 iface.println(separator);
1066 iface.println(F(
"Keep your hand off of the handbrake to record its resting position"));
1067 iface.println(F(
"Send any character to continue."));
1075 iface.println(F(
"Now pull on the handbrake and hold it at the end of its range"));
1076 iface.println(F(
"Send any character to continue."));
1087 iface.println(F(
"Here is your calibration:"));
1088 iface.println(separator);
1091 iface.print(F(
"handbrake.setCalibration("));
1093 iface.print(newCal.
min);
1094 iface.print(F(
", "));
1095 iface.print(newCal.
max);
1100 iface.println(separator);
1103 iface.print(F(
"Paste this line into the setup() function. The "));
1104 iface.print(F(
"handbrake"));
1105 iface.print(F(
" will be calibrated with these values on startup."));
1106 iface.println(F(
"\nCalibration complete! :)\n\n"));
Header file for the Sim Racing Library.
Axis
Enumeration for analog axis names, mapped to integers.
const PinNum UnusedPin
Dummy pin number signaling that a pin is unused and can be safely ignored.
int16_t PinNum
Type alias for pin numbers, using Arduino numbering.
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.
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.
AnalogShifter(PinNum pinX, PinNum pinY, PinNum pinRev=UnusedPin, PinNum pinDetect=UnusedPin)
Class constructor.
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.
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.
DeviceConnection(PinNum pin, bool invert=false, unsigned long detectTime=250)
Class constructor.
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.
int getPositionRaw() const
Retrieves the buffered position for the handbrake, ignoring the calibration data.
virtual bool update()
Polls the handbrake to update its position.
Handbrake(PinNum pinAx, PinNum pinDetect=UnusedPin)
Class constructor.
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(PinNum pinGas, PinNum pinBrake, PinNum pinDetect=UnusedPin)
Class constructor.
LogitechPedals(PinNum pinGas, PinNum pinBrake, PinNum pinClutch, PinNum pinDetect=UnusedPin)
Class constructor.
LogitechShifter(PinNum pinX, PinNum pinY, PinNum pinRev=UnusedPin, PinNum pinDetect=UnusedPin)
Class constructor.
Base class for all pedals instances.
Pedals(AnalogInput *dataPtr, uint8_t nPedals, PinNum detectPin)
Class constructor.
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.
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.
void setCalibration(AnalogInput::Calibration gasCal, AnalogInput::Calibration brakeCal, AnalogInput::Calibration clutchCal)
Sets the calibration data (min/max) for the pedals.
ThreePedals(PinNum pinGas, PinNum pinBrake, PinNum pinClutch, PinNum pinDetect=UnusedPin)
Class constructor.
Pedal implementation for devices with only gas and brake.
TwoPedals(PinNum pinGas, PinNum pinBrake, PinNum pinDetect=UnusedPin)
Class constructor.
void setCalibration(AnalogInput::Calibration gasCal, AnalogInput::Calibration brakeCal)
Sets the calibration data (min/max) for the pedals.
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.