@@ -1060,6 +1060,231 @@ LogitechShifter::LogitechShifter(PinNum pinX, PinNum pinY, PinNum pinRev, PinNum
10601060 this ->setCalibration ({ 490 , 440 }, { 253 , 799 }, { 262 , 86 }, { 460 , 826 }, { 470 , 76 }, { 664 , 841 }, { 677 , 77 });
10611061}
10621062
1063+
1064+ LogitechShifterG27::LogitechShifterG27 (
1065+ PinNum pinX, PinNum pinY,
1066+ PinNum pinLatch, PinNum pinClock, PinNum pinData,
1067+ PinNum pinDetect,
1068+ PinNum pinLed
1069+ ) :
1070+ LogitechShifter(pinX, pinY, UnusedPin, pinDetect),
1071+
1072+ pinLatch(sanitizePin(pinLatch)), pinClock(sanitizePin(pinClock)), pinData(sanitizePin(pinData)),
1073+ pinLed(sanitizePin(pinLed))
1074+ {
1075+ this ->pinModesSet = false ;
1076+ this ->buttonStates = this ->previousButtons = 0x0000 ; // zero all button data
1077+ }
1078+
1079+ void LogitechShifterG27::cacheButtons (uint16_t newStates) {
1080+ this ->previousButtons = this ->buttonStates ; // save current to previous
1081+ this ->buttonStates = newStates; // replace current with new value
1082+ }
1083+
1084+ void LogitechShifterG27::setPinModes (bool enabled) {
1085+ // check if pins are valid. if one or more pins is unused,
1086+ // this isn't going to work and we shouldn't bother setting
1087+ // any of the pin states
1088+ if (
1089+ this ->pinData == UnusedPin ||
1090+ this ->pinLatch == UnusedPin ||
1091+ this ->pinClock == UnusedPin)
1092+ {
1093+ return ;
1094+ }
1095+
1096+ // set up data pin to read from regardless
1097+ pinMode (this ->pinData , INPUT);
1098+
1099+ // enabled = drive the output pins
1100+ if (enabled) {
1101+ // note: writing the output before setting the
1102+ // pin mode so that we don't accidentally drive
1103+ // the wrong direction momentarily
1104+
1105+ // set latch pin as output, HIGH on idle
1106+ digitalWrite (this ->pinLatch , HIGH);
1107+ pinMode (this ->pinLatch , OUTPUT);
1108+
1109+ // set clock pin as output, LOW on idle
1110+ digitalWrite (this ->pinClock , LOW);
1111+ pinMode (this ->pinClock , OUTPUT);
1112+
1113+ // if we have an LED pin, set it to output and turn
1114+ // the LED on (active low)
1115+ if (this ->pinLed != UnusedPin) {
1116+ digitalWrite (this ->pinLed , LOW);
1117+ pinMode (this ->pinLed , OUTPUT);
1118+ }
1119+ }
1120+
1121+ // disabled = leave output pins as high-z
1122+ else {
1123+ // note: setting the mode before writing the
1124+ // output for the same reason; changing in
1125+ // high-z mode is safer
1126+
1127+ // set latch pin as high impedance, with pull-up
1128+ pinMode (this ->pinLatch , INPUT);
1129+ digitalWrite (this ->pinLatch , HIGH);
1130+
1131+ // set clock pin as high impedance, no pull-up
1132+ pinMode (this ->pinClock , INPUT);
1133+ digitalWrite (this ->pinClock , LOW);
1134+
1135+ // if we have an LED pin, set it to input, LOW on idle
1136+ if (this ->pinLed != UnusedPin) {
1137+ pinMode (this ->pinLed , INPUT);
1138+ digitalWrite (this ->pinLed , LOW);
1139+ }
1140+ }
1141+
1142+ this ->pinModesSet = enabled;
1143+ }
1144+
1145+ uint16_t LogitechShifterG27::readShiftRegisters () {
1146+ // if the pin outputs are not set, quit (none pressed)
1147+ if (!this ->pinModesSet ) return 0x0000 ;
1148+
1149+ uint16_t data = 0x0000 ;
1150+
1151+ // pulse shift register latch from high to low to high, 12 us
1152+ // (this timing is *completely* arbitrary, but it's nice to have
1153+ // *some* delay so that much faster MCUs don't blow through it)
1154+ digitalWrite (this ->pinLatch , LOW);
1155+ delayMicroseconds (12 );
1156+ digitalWrite (this ->pinLatch , HIGH);
1157+ delayMicroseconds (12 );
1158+
1159+ // clock is pullsed from LOW to HIGH on every bit,
1160+ // and then left to idle low
1161+ for (int i = 0 ; i < 16 ; ++i) {
1162+ digitalWrite (this ->pinClock , LOW);
1163+ const bool state = digitalRead (this ->pinData );
1164+ if (state) data |= 1 << (15 - i); // store data in word, MSB-first
1165+ digitalWrite (this ->pinClock , HIGH);
1166+ delayMicroseconds (6 );
1167+ }
1168+ digitalWrite (this ->pinClock , LOW);
1169+
1170+ return data;
1171+ }
1172+
1173+ void LogitechShifterG27::begin () {
1174+ // disable pin outputs. this sets the initial
1175+ // 'safe' state. the outputs will be enabled
1176+ // by the 'updateState(bool)' function when needed.
1177+ this ->setPinModes (0 );
1178+
1179+ // call the begin() class of the base, which will also
1180+ // poll 'update()' on our behalf
1181+ this ->AnalogShifter ::begin ();
1182+ }
1183+
1184+ bool LogitechShifterG27::updateState (bool connected) {
1185+ bool changed = false ;
1186+
1187+ // if we're connected, set the pin modes, read the
1188+ // shift registers, and cache the data
1189+ if (connected) {
1190+ if (!this ->pinModesSet ) {
1191+ this ->setPinModes (1 );
1192+ }
1193+
1194+ const uint16_t data = this ->readShiftRegisters ();
1195+ this ->cacheButtons (data);
1196+ changed |= this ->buttonsChanged ();
1197+ }
1198+
1199+ // if we're *not* connected, reset the pin modes and
1200+ // set no buttons pressed
1201+ else {
1202+ if (this ->pinModesSet ) {
1203+ this ->setPinModes (0 );
1204+ }
1205+
1206+ this ->cacheButtons (0x0000 );
1207+ changed |= this ->buttonsChanged ();
1208+ }
1209+
1210+ // we also need to update the data for the analog shifter
1211+ changed |= AnalogShifter::updateState (connected);
1212+
1213+ return changed;
1214+ }
1215+
1216+ bool LogitechShifterG27::buttonsChanged () const {
1217+ return this ->buttonStates != this ->previousButtons ;
1218+ }
1219+
1220+ bool LogitechShifterG27::getButton (Button button) const {
1221+ return this ->extractButton (button, this ->buttonStates );
1222+ }
1223+
1224+ bool LogitechShifterG27::getButtonChanged (Button button) const {
1225+ return this ->getButton (button) != this ->extractButton (button, this ->previousButtons );
1226+ }
1227+
1228+ int LogitechShifterG27::getDpadAngle () const {
1229+ const Button pads[4 ] = {
1230+ DPAD_UP,
1231+ DPAD_RIGHT,
1232+ DPAD_DOWN,
1233+ DPAD_LEFT,
1234+ };
1235+
1236+ // combine pads to a bitfield (nybble)
1237+ uint8_t dpad = 0x00 ;
1238+ for (uint8_t i = 0 ; i < 4 ; ++i) {
1239+ dpad |= (this ->getButton (pads[i]) << i);
1240+ }
1241+
1242+ // The hatswitch value is from 0-7 proceeding clockwise
1243+ // from top (0 is 'up', 1 is 'up + right', etc.). I don't
1244+ // know of a great way to do this, so have this naive
1245+ // lookup table with a built-in SOCD cleaner
1246+
1247+ // For this, simultaneous opposing cardinal directions
1248+ // are neutral (because this is presumably used for
1249+ // navigation only, and not fighting games. Probably).
1250+
1251+ // bitfield to hatswitch lookup table
1252+ const uint8_t hat_table[16 ] = {
1253+ 8 , // 0b0000, Unpressed
1254+ 0 , // 0b0001, Up
1255+ 2 , // 0b0010, Right
1256+ 1 , // 0b0011, Right + Up
1257+ 4 , // 0b0100, Down
1258+ 8 , // 0b0101, Down + Up (SOCD None)
1259+ 3 , // 0b0110, Down + Right
1260+ 2 , // 0b0111, Down + Right + Up (SOCD Right)
1261+ 6 , // 0b1000, Left
1262+ 7 , // 0b1001, Left + Up
1263+ 8 , // 0b1010, Left + Right (SOCD None)
1264+ 0 , // 0b1011, Left + Right + Up (SOCD Up)
1265+ 5 , // 0b1100, Left + Down
1266+ 6 , // 0b1101, Left + Down + Up (SOCD Left)
1267+ 4 , // 0b1110, Left + Down + Right (SOCD Down)
1268+ 8 , // 0b1111, Left + Down + Right + Up (SOCD None)
1269+ };
1270+
1271+ // multiply the 0-8 value by 45 to get it in degrees
1272+ int16_t angle = hat_table[dpad & 0x0F ] * 45 ;
1273+
1274+ // edge case: if no buttons are pressed, the angle is '-1'
1275+ if (angle == 360 ) angle = -1 ;
1276+
1277+ return angle;
1278+ }
1279+
1280+ bool LogitechShifterG27::readReverseButton () {
1281+ // this virtual function is provided for the sake of the AnalogShifter base
1282+ // class, which can use this to get the button state from the shift register
1283+ // without needing to interface with the shift registers themselves
1284+ return this ->getButton (BUTTON_REVERSE);
1285+ }
1286+
1287+
10631288// #########################################################
10641289// Handbrake #
10651290// #########################################################
0 commit comments