-
Notifications
You must be signed in to change notification settings - Fork 11
Expand file tree
/
Copy pathSimRacing.h
More file actions
1314 lines (1156 loc) · 40.3 KB
/
SimRacing.h
File metadata and controls
1314 lines (1156 loc) · 40.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/*
* Project Sim Racing Library for Arduino
* @author David Madison
* @link github.com/dmadison/Sim-Racing-Arduino
* @license LGPLv3 - Copyright (c) 2022 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 <http://www.gnu.org/licenses/>.
*/
#ifndef SIM_RACING_LIB_H
#define SIM_RACING_LIB_H
#include <Arduino.h>
/**
* @file SimRacing.h
* @brief Header file for the Sim Racing Library
*/
namespace SimRacing {
/**
* Type alias for pin numbers, using Arduino numbering
*/
using PinNum = int16_t;
/**
* Dummy pin number signaling that a pin is unused
* and can be safely ignored
*/
const PinNum UnusedPin = -1;
/**
* Enumeration for analog axis names, mapped to integers
*/
enum Axis : uint8_t {
X = 0, ///< Cartesian X axis
Y = 1, ///< Cartesian Y axis
};
/**
* @brief Used for tracking whether a device is connected to
* a specific pin and stable.
*/
class DeviceConnection {
public:
/**
* The state of the connection, whether it is connected, disconnected, and
* everywhere in-between. This is the type returned by the main 'getState()'
* method.
*
* @see getState()
*/
enum ConnectionState {
Disconnected, ///< No connection present
PlugIn, ///< Device was just plugged in (connection starts), unstable
Connected, ///< Connection present and stable
Unplug, ///< Device was just removed (connection ends)
};
/**
* Class constructor
*
* @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'
*/
DeviceConnection(PinNum pin, bool activeLow = false, unsigned long detectTime = 250);
/**
* Checks if the pin detects a connection. This polls the input and checks
* for whether it has been connected sufficiently long enough to be
* deemed "stable".
*/
void poll();
/**
* Retrieves the current ConnectionState from the instance. This
* allows functions to check the same connection state multiple times
* without having to re-poll.
*/
ConnectionState getState() const;
/**
* Check if the device is physically connected to the board. That means
* it is both present and detected long enough to be considered 'stable'.
*
* @return 'true' if the device is connected, 'false' otherwise
*/
bool isConnected() const;
/**
* 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'
*/
void setStablePeriod(unsigned long t);
private:
/**
* Reads the state of the pin, compensating for inversion
*
* @return 'true' if the pin is detected, 'false' otherwise
*/
bool readPin() const;
PinNum pin; ///< The pin number being read from. Can be 'UnusedPin' to disable
bool inverted; ///< Whether the input is inverted, so 'LOW' is detected instead of 'HIGH'
unsigned long stablePeriod; ///< The amount of time the input must be stable for (ms)
ConnectionState state; ///< The current state of the connection
bool pinState; ///< Buffered state of the input pin, accounting for inversion
unsigned long lastChange; ///< Timestamp of the last pin change, in ms (using millis())
};
/**
* @brief Handle I/O for analog (ADC) inputs
*/
class AnalogInput {
public:
static const int Min = 0; ///< Minimum value of the analog to digital (ADC) converter
static const int Max = 1023; ///< Maximum value of the analog to digital (ADC) converter. 10-bit by default.
/**
* Class constructor
*
* @param pin the I/O pin for this input (Arduino numbering)
*/
AnalogInput(PinNum pin);
/**
* Updates the current value of the axis by polling the ADC
*
* @return 'true' if the value changed, 'false' otherwise
*/
virtual bool read();
/**
* Retrieves the buffered position for the analog axis, rescaled to a
* nominal range using the calibration values.
*
* By default this is rescaled to a 10-bit value, matching the range
* used by the AVR analog to digital converter (ADC).
*
* @param rMin the minimum output value for the rescaling function
* @param rMax the maximum output value for the rescaling function
*
* @return the axis position, buffered and rescaled
*/
long getPosition(long rMin = Min, long rMax = Max) const;
/**
* Retrieves the buffered position for the analog axis.
*
* @return the axis position, buffered
*/
int getPositionRaw() const;
/**
* Retrieves the calibrated minimum position.
*
* @return the minimum position for the axis, per the calibration
*/
int getMin() const { return this->cal.min; }
/**
* Retrieves the calibrated maximum position.
*
* @return the maximum position for the axis, per the calibration
*/
int getMax() const { return this->cal.max; }
/**
* Check whether the axis is inverted or not.
*
* @return 'true' if the axis is inverted, 'false' otherwise
*/
bool isInverted() const;
/**
* Override the current position with a custom value.
*
* This is useful for whenever a device has disconnected and we want to
* set a new 'default' value for position requests.
*
* @param newPos the new position value to set
*/
void setPosition(int newPos);
/**
* Set the 'inverted' state of the axis. This will return a flipped
* number when getPosition() is called (e.g. the axis at its maximum
* will return a minimum value).
*
* @param invert whether the axis is inverted
*/
void setInverted(bool invert = true);
/**
* @brief Simple struct containing min/max values for axis calibration
*
* Note that this does not set default values for these members, even
* though it would make sense to set them to the ADC min and max,
* because doing so inhibits aggregate initialization under C++11. That
* means users could not call the calibration functions with a simple
* brace-enclosed initializer list without creating a variable first.
*
* @see https://stackoverflow.com/a/18184210
*/
struct Calibration {
int min; ///< Minimum value of the analog axis
int max; ///< Maximum value of the analog axis
};
/**
* Calibrate the axis' min/max values for rescaling.
*
* @param newCal the calibration data struct to pass
*/
void setCalibration(Calibration newCal);
private:
PinNum pin; ///< the digital pin number for this input
int position; ///< the axis' position in its range, buffered
Calibration cal; ///< the calibration values for the axis
};
/**
* @brief Abstract class for all peripherals
*/
class Peripheral {
public:
/**
* Class destructor
*/
virtual ~Peripheral() {}
/**
* Initialize the hardware (if necessary)
*/
virtual void begin() {};
/**
* Perform a poll of the hardware to refresh the class state
*
* @returns 'true' if device state changed, 'false' otherwise
*/
bool update();
/**
* 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() const;
/** @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;
/**
* 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
};
/**
* @defgroup Pedals Pedals
* @brief Classes that interface with pedal devices.
* @{
*/
/**
* @brief Pedal ID names
*/
enum Pedal {
Gas = 0,
Accelerator = Gas,
Throttle = Gas,
Brake = 1,
Clutch = 2,
};
/**
* @brief Base class for all pedals instances
*/
class Pedals : public Peripheral {
public:
/** Scoped alias for SimRacing::Pedal */
using PedalID = SimRacing::Pedal;
/**
* 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
*/
Pedals(
AnalogInput* dataPtr, uint8_t nPedals
);
/** @copydoc Peripheral::begin() */
virtual void begin();
/**
* Retrieves the buffered position for the pedal, rescaled to a
* nominal range using the calibration values.
*
* By default this is rescaled to an integer percentage.
*
* @param pedal the pedal to retrieve position for
* @param rMin the minimum output value for the rescaling function
* @param rMax the maximum output value for the rescaling function
*
* @return the pedal position, buffered and rescaled
*/
long getPosition(PedalID pedal, long rMin = 0, long rMax = 100) const;
/**
* Retrieves the buffered position for the pedal, ignoring the
* calibration data.
*
* @param pedal the pedal to retrieve position for
* @return the axis position, buffered
*/
int getPositionRaw(PedalID pedal) const;
/**
* Checks if a given pedal is present in the class.
*
* @param pedal the pedal to check
* @return 'true' if there is data for the pedal, 'false' otherwise
*/
bool hasPedal(PedalID pedal) const;
/**
* Retrieves the number of pedals handled by the class.
*
* @return the number of pedals handled by the class
*/
int getNumPedals() const { return this->NumPedals; }
/**
* Checks whether the current pedal positions have changed since the last update.
*
* @return 'true' if position has changed, 'false' otherwise
*/
bool positionChanged() const { return changed; }
/**
* Calibrate a pedal's min/max values for rescaling.
*
* @param pedal the pedal to set the calibration of
* @param cal the calibration data to set
*/
void setCalibration(PedalID pedal, AnalogInput::Calibration cal);
/**
* Runs an interactive calibration tool using the serial interface.
*
* @param iface the serial interface to send and receive prompts.
* Defaults to Serial (CDC USB on most boards).
*/
void serialCalibration(Stream& iface = Serial);
/**
* Utility function to get the string name for each pedal.
*
* @param pedal the pedal to get the name of
* @return the name of the pedal, as a String
*/
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
bool changed; ///< whether the pedal position has changed since the previous update
};
/**
* @brief Pedal implementation for devices with only gas and brake
*/
class TwoPedals : public Pedals {
public:
/**
* Class constructor
*
* @param pinGas the analog pin for the gas pedal potentiometer
* @param pinBrake the analog pin for the brake pedal potentiometer
*/
TwoPedals(
PinNum pinGas, PinNum pinBrake
);
/**
* Sets the calibration data (min/max) for the pedals
*
* @param gasCal the calibration data for the gas pedal
* @param brakeCal the calibration data for the brake pedal
*/
void setCalibration(AnalogInput::Calibration gasCal, AnalogInput::Calibration brakeCal);
private:
static const uint8_t NumPedals = 2; ///< number of pedals handled by this class
AnalogInput pedalData[NumPedals]; ///< pedal data storage struct, passed to AxisManager
};
/**
* @brief Pedal implementation for devices with gas, brake, and clutch
*/
class ThreePedals : public Pedals {
public:
/**
* 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
*/
ThreePedals(
PinNum pinGas, PinNum pinBrake, PinNum pinClutch
);
/**
* Sets the calibration data (min/max) for the pedals
*
* @param gasCal the calibration data for the gas pedal
* @param brakeCal the calibration data for the brake pedal
* @param clutchCal the calibration data for the clutch pedal
*/
void setCalibration(AnalogInput::Calibration gasCal, AnalogInput::Calibration brakeCal, AnalogInput::Calibration clutchCal);
private:
static const uint8_t NumPedals = 3; ///< number of pedals handled by this class
AnalogInput pedalData[NumPedals]; ///< pedal data storage struct, passed to AxisManager
};
/// @} Pedals
/**
* @defgroup Shifters Shifters
* @brief Classes that interface with shifting devices.
* @{
*/
/**
* @brief Base class for all shifter instances
*/
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(Gear min, Gear max);
/**
* Returns the currently selected gear.
*
* Will either be reverse (-1), neutral (0), or the current gear
* indexed at 1 (1st gear is 1, 2nd gear is 2, etc.).
*
* @return current gear index
*/
Gear getGear() const { return currentGear; }
/**
* Returns a character that represents the given gear.
*
* 'r' for reverse, 'n' for neutral, or the number of the current gear.
*
* @param gear the gear index to get the representation for
* @return letter representing the current gear
*/
static char getGearChar(int gear);
/**
* Returns a character that represents the current gear.
*
* @return letter representing the current gear
* @see getGearChar(int)
*/
char getGearChar() const;
/**
* Returns a String that represents the given gear.
*
* "reverse" for reverse, "neutral" for neutral, and then "1st", "2nd",
* "3rd", and so on.
*
* @param gear the gear index to get the representation for
* @return String representing the current gear
*/
static String getGearString(int gear);
/**
* Returns a String that represents the current gear.
*
* @return String representing the current gear
* @see getGearString(int)
*/
String getGearString() const;
/**
* Checks whether the current gear has changed since the last update.
*
* @return 'true' if gear has changed, 'false' otherwise
*/
bool gearChanged() const {
return this->currentGear != this->previousGear;
}
/**
* Retrieves the minimum possible gear index.
*
* @return the lowest gear index
*/
Gear getGearMin() { return MinGear; }
/**
* Retrieves the maximum possible gear index.
*
* @return the highest gear index
*/
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
Gear previousGear; ///< index of the last selected gear
};
/**
* @brief Interface with shifters using two potentiometers for gear position
*/
class AnalogShifter : public Shifter {
public:
/**
* 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
*
* @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
);
/**
* Initializes the hardware pins for reading the gear states.
*
* Should be called in `setup()` before reading from the shifter.
*/
virtual void begin();
/** @copydoc AnalogInput::getPosition()
* @param ax the axis to get the position of
*/
long getPosition(Axis ax, long rMin = AnalogInput::Min, long rMax = AnalogInput::Max) const;
/** @copydoc AnalogInput::getPositionRaw()
* @param ax the axis to get the position of
*/
int getPositionRaw(Axis ax) const;
/**
* Checks the current state of the "reverse" button at the bottom
* of the shift column. This button is pressed (HIGH) when the shifter
* is in reverse gear, LOW otherwise.
*
* @return current state of the "reverse" button
*/
bool getReverseButton() const;
/**
* @brief Simple struct to store X/Y coordinates for the calibration function
*/
struct GearPosition {
int x; ///< X coordinate of the gear position from the ADC
int y; ///< Y coordinate of the gear position from the ADC
};
/**
* Calibrate the gear shifter for more accurate shifting.
*
* Note that this uses a large number of GearPosition arguments rather
* than an array because it allows for the use of aggregate initialization.
*
* This way users can copy a single line to set calibration, rather than
* declaring an array of GearPosition elements and then passing that by
* pointer to this function.
*
* @param neutral the X/Y position of the shifter in neutral
* @param g1 the X/Y position of the shifter in 1st gear
* @param g2 the X/Y position of the shifter in 2nd gear
* @param g3 the X/Y position of the shifter in 3rd gear
* @param g4 the X/Y position of the shifter in 4th gear
* @param g5 the X/Y position of the shifter in 5th gear
* @param g6 the X/Y position of the shifter in 6th gear
* @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)
* @param edgeOffset distance from neutral on X to select the side gears
* rather than the center gears (as a percentage of
* distance from neutral to X max, 0-1)
*/
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);
/**
* Runs an interactive calibration tool using the serial interface.
*
* @param iface the serial interface to send and receive prompts.
* Defaults to Serial (CDC USB on most boards).
*/
void serialCalibration(Stream& iface = Serial);
protected:
/** @copydoc Peripheral::updateState(bool) */
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
* 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;
/**
* Distance from neutral on X to select the side gears
* rather than the center gears (as a percentage of
* distance from neutral to X max, 0-1). Used for calibration.
*/
static const float CalEdgeOffset;
/*** Internal calibration struct */
struct Calibration {
int neutralX; ///< X-axis neutral position, for reset on disconnect
int neutralY; ///< Y-axis neutral position, for reset on disconnect
int oddTrigger; ///< Odd gear threshold to set the input 'on' if disengaged
int oddRelease; ///< Odd gear threshold to set the input 'off' if engaged
int evenTrigger; ///< Even gear threshold to set the input 'on' if disengaged
int evenRelease; ///< Even gear threshold to set the input 'off' if engaged
int leftEdge; ///< Threshold for the lower (left) gears, 1 + 2
int rightEdge; ///< Threshold for the higher (right) gears, 5 + 6
} calibration;
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
/**
* @brief Interface with analog handbrakes that use hall effect sensors
*/
class Handbrake : public Peripheral {
public:
/**
* Class constructor
*
* @param pinAx analog pin number for the handbrake axis
*/
Handbrake(PinNum pinAx);
/**
* Initializes the pin for reading from the handbrake.
*/
virtual void begin();
/**
* Retrieves the buffered position for the handbrake axis, rescaled to a
* nominal range using the calibration values.
*
* By default this is rescaled to an integer percentage (0 - 100)
*
* @param rMin the minimum output value
* @param rMax the maximum output value
*
* @return the handbrake position, buffered and rescaled
*/
long getPosition(long rMin = 0, long rMax = 100) const;
/**
* Retrieves the buffered position for the handbrake, ignoring the
* calibration data.
*
* @return the handbrake position, buffered
*/
int getPositionRaw() const;
/**
* Checks whether the handbrake's position has changed since the last update.
*
* @return 'true' if the position has changed, 'false' otherwise
*/
bool positionChanged() const { return this->changed; }
/// @copydoc AnalogInput::setCalibration()
void setCalibration(AnalogInput::Calibration newCal);
/// @copydoc AnalogShifter::serialCalibration()
void serialCalibration(Stream& iface = Serial);
protected:
/** @copydoc Peripheral::updateState(bool) */
virtual bool updateState(bool connected);
private:
AnalogInput analogAxis; ///< axis data for the handbrake's position
bool changed; ///< whether the handbrake position has changed since the previous update
};
/**
* @brief Interface with the Logitech pedals (Gas, Brake, and Clutch)
* @ingroup Pedals
*
* @see https://www.logitechg.com/en-us/products/driving/driving-force-racing-wheel.html
*/
class LogitechPedals : public ThreePedals {
public:
/**
* 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);
private:
DeviceConnection detectObj; ///< detector instance for checking if the pedals are connected
};
/**
* @brief Interface with the Logitech Driving Force GT pedals (Gas + Brake)
* @ingroup Pedals
*
* Note that this is the older wheel made for the PS3. It is not the modern
* "Driving Force" wheel.
*
* @see https://en.wikipedia.org/wiki/Logitech_Driving_Force_GT
*/
class LogitechDrivingForceGT_Pedals : public TwoPedals {
public:
/**
* 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);
private:
DeviceConnection detectObj; ///< detector instance for checking if the pedals are connected
};
/**
* @brief Interface with the Logitech Driving Force shifter
* @ingroup Shifters
*
* @see https://www.logitechg.com/en-us/products/driving/driving-force-shifter.941-000119.html
*/
class LogitechShifter : public AnalogShifter {
public:
/**
* 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);
private:
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
*
* 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 1
* @param pinData digital input pin to use for reading data, DE-9 pin 2
* @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 pinLed = UnusedPin,
PinNum pinDetect = 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;