Arduino Sim Racing Library v1.1.3
SimRacing.cpp
Go to the documentation of this file.
1/*
2 * Project Sim Racing Library for Arduino
3 * @author David Madison
4 * @link github.com/dmadison/Sim-Racing-Arduino
5 * @license LGPLv3 - Copyright (c) 2022 David Madison
6 *
7 * This file is part of the Sim Racing Library for Arduino.
8 *
9 * This program is free software: you can redistribute it and/or modify
10 * it under the terms of the GNU Lesser General Public License as published by
11 * the Free Software Foundation, either version 3 of the License, or
12 * (at your option) any later version.
13 *
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU Lesser General Public License for more details.
18 *
19 * You should have received a copy of the GNU Lesser General Public License
20 * along with this program. If not, see <http://www.gnu.org/licenses/>.
21 */
22
23#include "SimRacing.h"
24
30namespace SimRacing {
31
32
43static constexpr long invertAxis(long value, long min, long max) {
44 return max - value + min; // flip to other side of the scale
45}
46
47
65static long remap(long value, long inMin, long inMax, long outMin, long outMax) {
66 // if inverted, swap min/max and adjust position of value
67 if (inMin > inMax) {
68 const long temp = inMin;
69 inMin = inMax;
70 inMax = temp;
71
72 value = invertAxis(value, inMin, inMax);
73 }
74
75 if (value <= inMin) return outMin;
76 if (value >= inMax) return outMax;
77 return map(value, inMin, inMax, outMin, outMax);
78}
79
80
87static float floatPercent(float pct) {
88 if (pct < 0.0) pct = 0.0;
89 else if (pct > 1.0) pct = 1.0;
90 return pct;
91}
92
101static void flushClient(Stream& client) {
102 while (client.read() != -1) { delay(2); } // 9600 baud = ~1 ms per byte
103}
104
110static void waitClient(Stream& client) {
111 flushClient(client);
112 while (client.peek() == -1) { delay(1); } // wait for a new byte (using delay to avoid watchdog)
113}
114
127static void readFloat(float& value, Stream& client) {
128 client.print("(to skip this step and go with the default value of '");
129 client.print(value);
130 client.print("', send 'n')");
131 client.println();
132
133 waitClient(client);
134 if (client.peek() == 'n') return; // skip this step
135
136 float input;
137
138 while (true) {
139 client.setTimeout(200);
140 input = client.parseFloat();
141
142 if (input >= 0.0 && input <= 1.0) {
143 client.print(F("Set the new value to '"));
144 client.print(input);
145 client.println("'");
146 break;
147 }
148 client.print(F("Input '"));
149 client.print(input);
150 client.print(F("' not within acceptable range (0.0 - 1.0). Please try again."));
151 client.println();
152
153 waitClient(client);
154 }
155
156 value = input;
157}
158
159
160//#########################################################
161// DeviceConnection #
162//#########################################################
163
164DeviceConnection::DeviceConnection(uint8_t pin, bool invert, unsigned long detectTime)
165 :
166 Pin(pin), Inverted(invert), stablePeriod(detectTime), // constants(ish)
167
168 /* Assume we're connected on first call
169 */
170 state(ConnectionState::Connected),
171
172 /* Init state to "not inverted", which is the connected state. For example
173 * if we're looking for 'HIGH' then inverted is false, which means the
174 * initial state is 'true' and thus connected.
175 *
176 * We're assuming we're connected on first call here because it allows
177 * the device to be read as connected as soon as the board turns on, without
178 * having to wait an arbitrary amount.
179 */
180 pinState(!Inverted),
181
182 /* Set the last pin change to right now minus the stable period so it's
183 * read as being already stable. Again, this will make the class return
184 * 'present' as soon as the board starts up
185 */
186 lastChange(millis() - detectTime)
187
188{
189 pinMode(Pin, INPUT); // set pin as input, *no* pull-up
190}
191
193 const bool newState = readPin();
194
195 if (newState == HIGH && state == ConnectionState::Connected) return; // short circuit, already connected
196
197 // check if the pin changed. if it did, record the time
198 if (pinState != newState) {
199 pinState = newState;
200 lastChange = millis();
201
202 // rising, we just connected
203 if (pinState == HIGH) {
204 state = ConnectionState::PlugIn;
205 }
206 // falling, we just disconnected
207 else {
208 state = ConnectionState::Unplug;
209 }
210 }
211
212 // if pin hasn't changed, compare stable times
213 else {
214 // check stable connection (over time)
215 if (pinState == HIGH) {
216 const unsigned long now = millis();
217 if (now - lastChange >= stablePeriod) {
218 state = ConnectionState::Connected;
219 }
220 }
221 // if we were previously unplugged and are still low, now we're disconnected
222 else if (state == ConnectionState::Unplug) {
223 state = ConnectionState::Disconnected;
224 }
225 }
226}
227
229 return state;
230}
231
233 return this->getState() == ConnectionState::Connected;
234}
235
237 stablePeriod = t;
238
239 if (state == ConnectionState::Connected) {
240 const unsigned long now = millis();
241
242 // if we were previously considered connected, adjust the timestamps
243 // accordingly so that we still are
244 if (now - lastChange < stablePeriod) {
245 lastChange = now - stablePeriod;
246 }
247 }
248}
249
250bool DeviceConnection::readPin() const {
251 if (Pin == NOT_A_PIN) return HIGH; // if no pin is set, we're always connected
252 const bool state = digitalRead(Pin);
253 return Inverted ? !state : state;
254}
255
256//#########################################################
257// AnalogInput #
258//#########################################################
259
260
262 : Pin(p), position(AnalogInput::Min), cal({AnalogInput::Min, AnalogInput::Max})
263{
264 if (Pin != NOT_A_PIN) {
265 pinMode(Pin, INPUT);
266 }
267}
268
270 bool changed = false;
271
272 if (Pin != NOT_A_PIN) {
273 const int previous = this->position;
274 this->position = analogRead(Pin);
275
276 // check if value is different for 'changed' flag
277 if (previous != this->position) {
278
279 const int rMin = isInverted() ? getMax() : getMin();
280 const int rMax = isInverted() ? getMin() : getMax();
281
282 if (
283 // if the previous value was under the minimum range
284 // and the current value is as well, no change
285 !(previous < rMin && this->position < rMin) &&
286
287 // if the previous value was over the maximum range
288 // and the current value is as well, no change
289 !(previous > rMax && this->position > rMax)
290 )
291 {
292 // otherwise, the current value is either within the
293 // range limits *or* it has changed from one extreme
294 // to the other. Either way, mark it changed!
295 changed = true;
296 }
297 }
298 }
299 return changed;
300}
301
302long AnalogInput::getPosition(long rMin, long rMax) const {
303 // inversion is handled within the remap function
304 return remap(getPositionRaw(), getMin(), getMax(), rMin, rMax);
305}
306
308 return this->position;
309}
310
312 return (this->cal.min > this->cal.max); // inverted if min is greater than max
313}
314
315void AnalogInput::setPosition(int newPos) {
316 this->position = newPos;
317}
318
319void AnalogInput::setInverted(bool invert) {
320 if (isInverted() == invert) return; // inversion already set
321
322 // to change inversion, swap max and min of the current calibration
323 AnalogInput::Calibration inverted = { this->cal.max, this->cal.min };
324 setCalibration(inverted);
325}
326
328 this->cal = newCal;
329}
330
331
332//#########################################################
333// Pedals #
334//#########################################################
335
336Pedals::Pedals(AnalogInput* dataPtr, uint8_t nPedals, uint8_t detectPin)
337 :
338 pedalData(dataPtr),
339 NumPedals(nPedals),
340 detector(detectPin),
341 changed(false)
342{}
343
345 update(); // set initial pedal position
346}
347
349 changed = false;
350
351 detector.poll();
352 if (detector.getState() == DeviceConnection::Connected) {
353 // if connected, read all pedal positions
354 for (int i = 0; i < getNumPedals(); ++i) {
355 changed |= pedalData[i].read();
356 }
357 }
358 else if (detector.getState() == DeviceConnection::Unplug) {
359 // on unplug event, zero all pedals
360 for (int i = 0; i < getNumPedals(); ++i) {
361 const int min = pedalData[i].getMin();
362 pedalData[i].setPosition(min);
363 }
364 changed = true; // set flag so we know everything moved to 0
365 }
366
367 return changed;
368}
369
370long Pedals::getPosition(PedalID pedal, long rMin, long rMax) const {
371 if (!hasPedal(pedal)) return rMin; // not a pedal
372 return pedalData[pedal].getPosition(rMin, rMax);
373}
374
376 if (!hasPedal(pedal)) return AnalogInput::Min; // not a pedal
377 return pedalData[pedal].getPositionRaw();
378}
379
380bool Pedals::hasPedal(PedalID pedal) const {
381 return (pedal < getNumPedals());
382}
383
385 if (!hasPedal(pedal)) return;
386 pedalData[pedal].setCalibration(cal);
387 pedalData[pedal].setPosition(pedalData[pedal].getMin()); // reset to min position
388}
389
391 String name;
392
393 switch (pedal) {
394 case(PedalID::Gas):
395 name = F("gas");
396 break;
397 case(PedalID::Brake):
398 name = F("brake");
399 break;
400 case(PedalID::Clutch):
401 name = F("clutch");
402 break;
403 default:
404 name = F("???");
405 break;
406 }
407 return name;
408}
409
410void Pedals::serialCalibration(Stream& iface) {
411 const char* separator = "------------------------------------";
412
413 iface.println();
414 iface.println(F("Sim Racing Library Pedal Calibration"));
415 iface.println(separator);
416 iface.println();
417
418 // read minimums
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."));
421 waitClient(iface);
422
423 const int MaxPedals = 3; // hard-coded at 3 pedals
424
425 AnalogInput::Calibration pedalCal[MaxPedals];
426
427 // read minimums
428 for (int i = 0; (i < getNumPedals()) && (i < MaxPedals); i++) {
429 pedalData[i].read(); // read position
430 pedalCal[i].min = pedalData[i].getPositionRaw(); // set min to the recorded position
431 }
432 iface.println(F("\nMinimum values for all pedals successfully recorded!\n"));
433 iface.println(separator);
434
435 // read maximums
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++) {
438
439 iface.print(F("Push the "));
440 String name = getPedalName(static_cast<PedalID>(i));
441 name.toLowerCase();
442 iface.print(name);
443 iface.print(F(" pedal to the floor. "));
444
445 iface.println(F("Send any character to continue."));
446 waitClient(iface);
447
448 pedalData[i].read(); // read position
449 pedalCal[i].max = pedalData[i].getPositionRaw(); // set max to the recorded position
450 }
451
452 // deadzone options
453 iface.println(separator);
454 iface.println();
455
456 float DeadzoneMin = 0.01; // by default, 1% (trying to keep things responsive)
457 float DeadzoneMax = 0.025; // by default, 2.5%
458
459 iface.println(F("These settings are optional. Send 'y' to customize. Send any other character to continue with the default values."));
460
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)"));
464
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)"));
468
469 iface.println();
470
471 waitClient(iface);
472
473 if (iface.read() == 'y') {
474 iface.println(F("Set the pedal travel starting deadzone as a floating point percentage."));
475 readFloat(DeadzoneMin, iface);
476 iface.println();
477
478 iface.println(F("Set the pedal travel ending deadzone as a floating point percentage."));
479 readFloat(DeadzoneMax, iface);
480 iface.println();
481 }
482
483 flushClient(iface);
484
485 // calculate deadzone offsets
486 for (int i = 0; (i < getNumPedals()) && (i < MaxPedals); i++) {
487 auto &cMin = pedalCal[i].min;
488 auto &cMax = pedalCal[i].max;
489
490 const int range = abs(cMax - cMin);
491 const int dzMin = DeadzoneMin * (float)range;
492 const int dzMax = DeadzoneMax * (float)range;
493
494 // non-inverted
495 if (cMax >= cMin) {
496 cMax -= dzMax; // 'cut' into the range so it limits sooner
497 cMin += dzMin;
498 }
499 // inverted
500 else {
501 cMax += dzMax;
502 cMin -= dzMin;
503 }
504 }
505
506 // print finished calibration
507 iface.println(F("Here is your calibration:"));
508 iface.println(separator);
509 iface.println();
510
511 iface.print(F("pedals.setCalibration("));
512
513 for (int i = 0; (i < getNumPedals()) && (i < MaxPedals); i++) {
514 if(i > 0) iface.print(F(", "));
515 iface.print('{');
516
517 iface.print(pedalCal[i].min);
518 iface.print(F(", "));
519 iface.print(pedalCal[i].max);
520
521 iface.print('}');
522
523 this->setCalibration(static_cast<PedalID>(i), pedalCal[i]); // and set it ourselves, too
524 }
525 iface.print(");");
526 iface.println();
527
528 iface.println();
529 iface.println(separator);
530 iface.println();
531
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"));
536
537 flushClient(iface);
538}
539
540
541TwoPedals::TwoPedals(uint8_t gasPin, uint8_t brakePin, uint8_t detectPin)
542 : Pedals(pedalData, NumPedals, detectPin),
543 pedalData{ AnalogInput(gasPin), AnalogInput(brakePin) }
544{}
545
547 this->Pedals::setCalibration(PedalID::Gas, gasCal);
548 this->Pedals::setCalibration(PedalID::Brake, brakeCal);
549}
550
551
552ThreePedals::ThreePedals(uint8_t gasPin, uint8_t brakePin, uint8_t clutchPin, uint8_t detectPin)
553 : Pedals(pedalData, NumPedals, detectPin),
554 pedalData{ AnalogInput(gasPin), AnalogInput(brakePin), AnalogInput(clutchPin) }
555{}
556
558 this->Pedals::setCalibration(PedalID::Gas, gasCal);
559 this->Pedals::setCalibration(PedalID::Brake, brakeCal);
560 this->Pedals::setCalibration(PedalID::Clutch, clutchCal);
561}
562
563
564
565LogitechPedals::LogitechPedals(uint8_t gasPin, uint8_t brakePin, uint8_t clutchPin, uint8_t detectPin)
566 : ThreePedals(gasPin, brakePin, clutchPin, detectPin)
567{
568 // taken from calibrating my own pedals. the springs are pretty stiff so while
569 // this covers the whole travel range, users may want to back it down for casual
570 // use (esp. for the brake travel)
571 this->setCalibration({ 904, 48 }, { 944, 286 }, { 881, 59 });
572}
573
574LogitechDrivingForceGT_Pedals::LogitechDrivingForceGT_Pedals(uint8_t gasPin, uint8_t brakePin, uint8_t detectPin)
575 : TwoPedals(gasPin, brakePin, detectPin)
576{
577 this->setCalibration({ 646, 0 }, { 473, 1023 }); // taken from calibrating my own pedals
578}
579
580
581//#########################################################
582// Shifter #
583//#########################################################
584
585Shifter::Shifter(int8_t min, int8_t max)
586 : MinGear(min), MaxGear(max)
587{}
588
589char Shifter::getGearChar(int gear) {
590 char c = '?';
591
592 switch (gear) {
593 case(-1):
594 c = 'r';
595 break;
596 case(0):
597 c = 'n';
598 break;
599 default:
600 if (gear > 0 && gear <= 9)
601 c = '0' + gear;
602 break;
603 }
604 return c;
605}
606
608 return getGearChar(getGear());
609}
610
611String Shifter::getGearString(int gear) {
612 String name;
613
614 switch (gear) {
615 case(-1):
616 name = F("reverse");
617 break;
618 case(0):
619 name = F("neutral");
620 break;
621 default: {
622 if (gear < 0 || gear > 9) {
623 name = F("???");
624 break; // out of range
625 }
626 name = gear; // set string to current gear
627
628 switch (gear) {
629 case(1):
630 name += F("st");
631 break;
632 case(2):
633 name += F("nd");
634 break;
635 case(3):
636 name += F("rd");
637 break;
638 default:
639 name += F("th");
640 break;
641 }
642 break;
643 }
644 }
645 return name;
646}
647
649 return getGearString(getGear());
650}
651
652
653/* Static calibration constants
654* These values are arbitrary - just what worked well with my own shifter.
655*/
656const float AnalogShifter::CalEngagementPoint = 0.70;
657const float AnalogShifter::CalReleasePoint = 0.50;
658const float AnalogShifter::CalEdgeOffset = 0.70;
659
660AnalogShifter::AnalogShifter(uint8_t pinX, uint8_t pinY, uint8_t pinRev, uint8_t detectPin)
661 :
662 /* In initializing the Shifter, the lowest gear is going to be '-1' if a pin
663 * exists for reverse, otherwise it's going to be '0' (neutral).
664 */
665 Shifter(pinRev != NOT_A_PIN ? -1 : 0, 6),
666
667 /* Two axes, X and Y */
668 analogAxis{ AnalogInput(pinX), AnalogInput(pinY) },
669
670 PinReverse(pinRev),
671 detector(detectPin, false) // not inverted
672{}
673
675 pinMode(PinReverse, INPUT);
676 update(); // set initial gear position
677}
678
680 detector.poll();
681
682 switch (detector.getState()) {
683
684 // connected! poll the ADC for new analog axis data
686 analogAxis[Axis::X].read();
687 analogAxis[Axis::Y].read();
688 break;
689
690 // on an unplug event, we want to reset our position back to
691 // neutral and then immediately return
693 {
694 const int8_t previousGear = this->getGear();
695
696 analogAxis[Axis::X].setPosition(calibration.neutralX);
697 analogAxis[Axis::Y].setPosition(calibration.neutralY);
698
699 if (previousGear != 0) changed = true;
700 currentGear = 0;
701 return changed;
702 break;
703 }
704
705 // if the device is either disconnected or just plugged in and unstable, set gear
706 // 'changed' to false and then immediately return false to save on processing
709 changed = false;
710 return changed;
711 break;
712 }
713
714 const int8_t previousGear = this->getGear();
715 const bool prevOdd = ((previousGear != -1) && (previousGear & 1)); // were we previously in an odd gear
716 const bool prevEven = (!prevOdd && previousGear != 0); // were we previously in an even gear
717
718 const int x = analogAxis[Axis::X].getPosition();
719 const int y = analogAxis[Axis::Y].getPosition();
720 int8_t newGear = 0;
721
722 // If we're below the 'release' thresholds, we must still be in the previous gear
723 if ((prevOdd && y > calibration.oddRelease) || (prevEven && y < calibration.evenRelease)) {
724 newGear = previousGear;
725 }
726
727 // If we're *not* below the release thresholds, we may be in a different gear
728 else {
729 // Check if we're in even or odd gears (Y axis)
730 if (y > calibration.oddTrigger) {
731 newGear = 1; // we're in an odd gear
732 }
733 else if (y < calibration.evenTrigger) {
734 newGear = 2; // we're in an even gear
735 }
736
737 if (newGear != 0) {
738 // Now check *which* gear we're in, if we're in one (X axis)
739 if (x > calibration.rightEdge) newGear += 4; // 1-2 + 4 = 5-6
740 else if (x >= calibration.leftEdge) newGear += 2; // 1-2 + 2 = 3-4
741 // (note the '>=', because it would normally be a '<' check for the lower range)
742 // else gear = 1-2 (as set above)
743
744 const bool reverse = getReverseButton();
745
746 // If the reverse button is pressed and we're in 5th gear
747 // something is wrong. Revert that and go back to neutral.
748 if (reverse && newGear == 5) {
749 newGear = 0;
750 }
751
752 // If the reverse button is pressed or we were previously
753 // in reverse *and* we are currently in 6th gear, then we
754 // should be in reverse.
755 else if ((reverse || previousGear == -1) && newGear == 6) {
756 newGear = -1;
757 }
758 }
759 }
760
761 changed = (newGear != previousGear) ? 1 : 0;
762 currentGear = newGear;
763
764 return changed;
765}
766
767long AnalogShifter::getPosition(Axis ax, long min, long max) const {
768 if (ax != Axis::X && ax != Axis::Y) return min; // not an axis
769 return analogAxis[ax].getPosition(min, max);
770}
771
773 if (ax != Axis::X && ax != Axis::Y) return AnalogInput::Min; // not an axis
774 return analogAxis[ax].getPositionRaw();
775}
776
778 // if the reverse pin is not set *or* if the device is not currently
779 // connected, avoid reading the floating input and just return 'false'
780 if (PinReverse == NOT_A_PIN || detector.getState() != DeviceConnection::Connected) {
781 return false;
782 }
783 return digitalRead(PinReverse);
784}
785
787 GearPosition neutral,
789 float engagePoint, float releasePoint, float edgeOffset) {
790
791 // limit percentage thresholds
792 engagePoint = floatPercent(engagePoint);
793 releasePoint = floatPercent(releasePoint);
794 edgeOffset = floatPercent(edgeOffset);
795
796 const int xLeft = (g1.x + g2.x) / 2; // find the minimum X position average
797 const int xRight = (g5.x + g6.x) / 2; // find the maximum X position average
798
799 const int yOdd = (g1.y + g3.y + g5.y) / 3; // find the maximum Y position average
800 const int yEven = (g2.y + g4.y + g6.y) / 3; // find the minimum Y position average
801
802 // set X/Y calibration and inversion
803 analogAxis[Axis::X].setCalibration({ xLeft, xRight });
804 analogAxis[Axis::Y].setCalibration({ yEven, yOdd });
805
806 // save neutral values (raw)
807 calibration.neutralX = neutral.x;
808 calibration.neutralY = neutral.y;
809
810 // get normalized and inverted neutral values
811 // this lets us take advantage of the AnalogInput normalization function
812 // that handles inverted axes and automatic range rescaling, so the rest of
813 // the calibration options can be in the normalized range
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++) {
817 const int previous = analogAxis[axes[i]].getPositionRaw(); // save current value
818 analogAxis[axes[i]].setPosition(*neutralAxis[i]); // set new value to neutral calibration
819 *neutralAxis[i] = analogAxis[axes[i]].getPosition(); // get normalized neutral value
820 analogAxis[axes[i]].setPosition(previous); // reset axis position to previous
821 }
822
823 // calculate the distances between each neutral and the limits of each axis
824 const int yOddDiff = AnalogInput::Max - neutral.y;
825 const int yEvenDiff = neutral.y - AnalogInput::Min;
826
827 const int leftDiff = neutral.x - AnalogInput::Min;
828 const int rightDiff = AnalogInput::Max - neutral.x;
829
830 // calculate and save the trigger and release points for each level
831 calibration.oddTrigger = neutral.y + ((float)yOddDiff * engagePoint);
832 calibration.oddRelease = neutral.y + ((float)yOddDiff * releasePoint);
833
834 calibration.evenTrigger = neutral.y - ((float)yEvenDiff * engagePoint);
835 calibration.evenRelease = neutral.y - ((float)yEvenDiff * releasePoint);
836
837 calibration.leftEdge = neutral.x - ((float)leftDiff * edgeOffset);
838 calibration.rightEdge = neutral.x + ((float)rightDiff * edgeOffset);
839
840#if 0
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);
853 Serial.println();
854
855 Serial.print("X Min: ");
856 Serial.println(analogAxis[Axis::X].getMin());
857 Serial.print("X Max: ");
858 Serial.println(analogAxis[Axis::X].getMax());
859
860 Serial.print("Y Min: ");
861 Serial.println(analogAxis[Axis::Y].getMin());
862 Serial.print("Y Max: ");
863 Serial.println(analogAxis[Axis::Y].getMax());
864#endif
865}
866
868 if (isConnected() == false) {
869 iface.print(F("Error! Cannot perform calibration, "));
870 iface.print(F("shifter"));
871 iface.println(F(" is not connected."));
872 return;
873 }
874
875 const char* separator = "------------------------------------";
876
877 iface.println();
878 iface.println(F("Sim Racing Library Shifter Calibration"));
879 iface.println(separator);
880 iface.println();
881
882 AnalogShifter::GearPosition gears[7]; // neutral, then 1-6
883 float engagementPoint = CalEngagementPoint;
884 float releasePoint = CalReleasePoint;
885 float edgeOffset = CalEdgeOffset;
886
887 for (int i = 0; i <= 6; i++) {
888 const String gearName = this->getGearString(i);
889
890 iface.print(F("Please move the gear shifter into "));
891 iface.print(gearName);
892 iface.println(F(". Send any character to continue."));
893
894 waitClient(iface);
895
896 this->update();
897 gears[i] = {
898 this->analogAxis[Axis::X].getPositionRaw(),
899 this->analogAxis[Axis::Y].getPositionRaw()
900 };
901
902 iface.print("Gear '");
903 iface.print(gearName);
904 iface.print("' position recorded as { ");
905 iface.print(gears[i].x);
906 iface.print(", ");
907 iface.print(gears[i].y);
908 iface.println(" }");
909 iface.println();
910 }
911
912 iface.println(separator);
913 iface.println();
914
915 iface.println(F("These settings are optional. Send 'y' to customize. Send any other character to continue with the default values."));
916
917 iface.print(F(" * Gear Engagement Point: \t"));
918 iface.println(engagementPoint);
919
920 iface.print(F(" * Gear Release Point: \t"));
921 iface.println(releasePoint);
922
923 iface.print(F(" * Horizontal Gate Offset:\t"));
924 iface.println(edgeOffset);
925
926 iface.println();
927
928 waitClient(iface);
929
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);
933 iface.println();
934
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);
937 iface.println();
938
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);
941 iface.println();
942 }
943
944 flushClient(iface);
945
946 this->setCalibration(gears[0], gears[1], gears[2], gears[3], gears[4], gears[5], gears[6], engagementPoint, releasePoint, edgeOffset);
947
948 iface.println(F("Here is your calibration:"));
949 iface.println(separator);
950 iface.println();
951
952 iface.print(F("shifter.setCalibration( "));
953
954 for (int i = 0; i < 7; i++) {
955 iface.print('{');
956
957 iface.print(gears[i].x);
958 iface.print(", ");
959 iface.print(gears[i].y);
960
961 iface.print('}');
962 iface.print(", ");
963 }
964 iface.print(engagementPoint);
965 iface.print(", ");
966 iface.print(releasePoint);
967 iface.print(", ");
968 iface.print(edgeOffset);
969 iface.print(");");
970 iface.println();
971
972 iface.println();
973 iface.println(separator);
974 iface.println();
975
976 iface.println(F("Paste this line into the setup() function to calibrate on startup."));
977 iface.println(F("\n\nCalibration complete! :)\n"));
978}
979
980LogitechShifter::LogitechShifter(uint8_t pinX, uint8_t pinY, uint8_t pinRev, uint8_t detectPin)
981 : AnalogShifter(pinX, pinY, pinRev, detectPin)
982{
983 this->setCalibration({ 490, 440 }, { 253, 799 }, { 262, 86 }, { 460, 826 }, { 470, 76 }, { 664, 841 }, { 677, 77 }, 0.70, 0.50, 0.70);
984}
985
986//#########################################################
987// Handbrake #
988//#########################################################
989
990Handbrake::Handbrake(uint8_t pinAx, uint8_t detectPin)
991 :
992 analogAxis(pinAx),
993 detector(detectPin),
994 changed(false)
995{}
996
998 update(); // set initial handbrake position
999}
1000
1002 changed = false;
1003
1004 detector.poll();
1005 if (detector.getState() == DeviceConnection::Connected) {
1006 changed = analogAxis.read();
1007 }
1008 else if (detector.getState() == DeviceConnection::Unplug) {
1009 analogAxis.setPosition(analogAxis.getMin());
1010 changed = true;
1011 }
1012
1013 return changed;
1014}
1015
1016long Handbrake::getPosition(long rMin, long rMax) const {
1017 return analogAxis.getPosition(rMin, rMax);
1018}
1019
1021 return analogAxis.getPositionRaw();
1022}
1023
1025 analogAxis.setCalibration(newCal);
1026 analogAxis.setPosition(analogAxis.getMin()); // reset to min
1027}
1028
1029void Handbrake::serialCalibration(Stream& iface) {
1030 if (isConnected() == false) {
1031 iface.print(F("Error! Cannot perform calibration, "));
1032 iface.print(F("handbrake"));
1033 iface.println(F(" is not connected."));
1034 return;
1035 }
1036
1037 const char* separator = "------------------------------------";
1038
1039 iface.println();
1040 iface.println(F("Sim Racing Library Handbrake Calibration"));
1041 iface.println(separator);
1042 iface.println();
1043
1045
1046 // read minimum
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."));
1049 waitClient(iface);
1050
1051 analogAxis.read();
1052 newCal.min = analogAxis.getPositionRaw();
1053 iface.println();
1054
1055 // read maximum
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."));
1058 waitClient(iface);
1059
1060 analogAxis.read();
1061 newCal.max = analogAxis.getPositionRaw();
1062 iface.println();
1063
1064 // set new calibration
1065 this->setCalibration(newCal);
1066
1067 // print finished calibration
1068 iface.println(F("Here is your calibration:"));
1069 iface.println(separator);
1070 iface.println();
1071
1072 iface.print(F("handbrake.setCalibration("));
1073 iface.print('{');
1074 iface.print(newCal.min);
1075 iface.print(F(", "));
1076 iface.print(newCal.max);
1077 iface.print("});");
1078 iface.println();
1079
1080 iface.println();
1081 iface.println(separator);
1082 iface.println();
1083
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"));
1088
1089 flushClient(iface);
1090}
1091
1092}; // end SimRacing namespace
Header file for the Sim Racing Library.
Handle I/O for analog (ADC) inputs.
Definition: SimRacing.h:124
void setInverted(bool invert=true)
Definition: SimRacing.cpp:319
bool isInverted() const
Definition: SimRacing.cpp:311
long getPosition(long rMin=Min, long rMax=Max) const
Definition: SimRacing.cpp:302
int getPositionRaw() const
Definition: SimRacing.cpp:307
static const int Max
Maximum value of the analog to digital (ADC) converter. 10-bit by default.
Definition: SimRacing.h:127
int getMax() const
Definition: SimRacing.h:176
void setCalibration(Calibration newCal)
Definition: SimRacing.cpp:327
virtual bool read()
Definition: SimRacing.cpp:269
int getMin() const
Definition: SimRacing.h:169
void setPosition(int newPos)
Definition: SimRacing.cpp:315
static const int Min
Minimum value of the analog to digital (ADC) converter.
Definition: SimRacing.h:126
Interface with shifters using two potentiometers for gear position.
Definition: SimRacing.h:535
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)
Definition: SimRacing.cpp:786
void serialCalibration(Stream &iface=Serial)
Definition: SimRacing.cpp:867
bool getReverseButton() const
Definition: SimRacing.cpp:777
AnalogShifter(uint8_t pinX, uint8_t pinY, uint8_t pinRev=NOT_A_PIN, uint8_t detectPin=NOT_A_PIN)
Definition: SimRacing.cpp:660
int getPositionRaw(Axis ax) const
Definition: SimRacing.cpp:772
virtual bool update()
Definition: SimRacing.cpp:679
bool isConnected() const
Definition: SimRacing.h:629
virtual void begin()
Definition: SimRacing.cpp:674
long getPosition(Axis ax, long rMin=AnalogInput::Min, long rMax=AnalogInput::Max) const
Definition: SimRacing.cpp:767
void setStablePeriod(unsigned long t)
Definition: SimRacing.cpp:236
DeviceConnection(uint8_t pin, bool invert=false, unsigned long detectTime=250)
Definition: SimRacing.cpp:164
ConnectionState getState() const
Definition: SimRacing.cpp:228
@ Unplug
Device was just removed (connection ends)
Definition: SimRacing.h:60
@ PlugIn
Device was just plugged in (connection starts), unstable.
Definition: SimRacing.h:58
@ Disconnected
No connection present.
Definition: SimRacing.h:57
@ Connected
Connection present and stable.
Definition: SimRacing.h:59
virtual void begin()
Definition: SimRacing.cpp:997
long getPosition(long rMin=0, long rMax=100) const
Definition: SimRacing.cpp:1016
void setCalibration(AnalogInput::Calibration newCal)
Definition: SimRacing.cpp:1024
Handbrake(uint8_t pinAx, uint8_t detectPin=NOT_A_PIN)
Definition: SimRacing.cpp:990
int getPositionRaw() const
Definition: SimRacing.cpp:1020
virtual bool update()
Definition: SimRacing.cpp:1001
bool isConnected() const
Definition: SimRacing.h:733
void serialCalibration(Stream &iface=Serial)
Definition: SimRacing.cpp:1029
LogitechDrivingForceGT_Pedals(uint8_t gasPin, uint8_t brakePin, uint8_t detectPin=NOT_A_PIN)
Definition: SimRacing.cpp:574
LogitechPedals(uint8_t gasPin, uint8_t brakePin, uint8_t clutchPin, uint8_t detectPin=NOT_A_PIN)
Definition: SimRacing.cpp:565
LogitechShifter(uint8_t pinX, uint8_t pinY, uint8_t pinRev=NOT_A_PIN, uint8_t detectPin=NOT_A_PIN)
Definition: SimRacing.cpp:980
Base class for all pedals instances.
Definition: SimRacing.h:276
void serialCalibration(Stream &iface=Serial)
Definition: SimRacing.cpp:410
void setCalibration(PedalID pedal, AnalogInput::Calibration cal)
Definition: SimRacing.cpp:384
Pedals(AnalogInput *dataPtr, uint8_t nPedals, uint8_t detectPin)
Definition: SimRacing.cpp:336
static String getPedalName(PedalID pedal)
Definition: SimRacing.cpp:390
bool hasPedal(PedalID pedal) const
Definition: SimRacing.cpp:380
int getPositionRaw(PedalID pedal) const
Definition: SimRacing.cpp:375
virtual bool update()
Definition: SimRacing.cpp:348
virtual void begin()
Definition: SimRacing.cpp:344
long getPosition(PedalID pedal, long rMin=0, long rMax=100) const
Definition: SimRacing.cpp:370
int getNumPedals() const
Definition: SimRacing.h:332
Base class for all shifter instances.
Definition: SimRacing.h:445
String getGearString() const
Definition: SimRacing.cpp:648
int8_t getGear() const
Definition: SimRacing.h:463
Shifter(int8_t min, int8_t max)
Definition: SimRacing.cpp:585
char getGearChar() const
Definition: SimRacing.cpp:607
bool changed
whether the gear has changed since the previous update
Definition: SimRacing.h:528
int8_t currentGear
index of the current gear
Definition: SimRacing.h:527
Pedal implementation for devices with gas, brake, and clutch.
Definition: SimRacing.h:407
ThreePedals(uint8_t gasPin, uint8_t brakePin, uint8_t clutchPin, uint8_t detectPin=NOT_A_PIN)
Definition: SimRacing.cpp:552
void setCalibration(AnalogInput::Calibration gasCal, AnalogInput::Calibration brakeCal, AnalogInput::Calibration clutchCal)
Definition: SimRacing.cpp:557
Pedal implementation for devices with only gas and brake.
Definition: SimRacing.h:379
void setCalibration(AnalogInput::Calibration gasCal, AnalogInput::Calibration brakeCal)
Definition: SimRacing.cpp:546
TwoPedals(uint8_t gasPin, uint8_t brakePin, uint8_t detectPin=NOT_A_PIN)
Definition: SimRacing.cpp:541
Pedal
Pedal ID names.
Definition: SimRacing.h:265
Simple struct containing min/max values for axis calibration.
Definition: SimRacing.h:215
int max
Maximum value of the analog axis.
Definition: SimRacing.h:217
int min
Minimum value of the analog axis.
Definition: SimRacing.h:216
Simple struct to store X/Y coordinates for the calibration function.
Definition: SimRacing.h:583
int y
Y coordinate of the gear position from the ADC.
Definition: SimRacing.h:585
int x
X coordinate of the gear position from the ADC.
Definition: SimRacing.h:584