Advertisement
LandoRo

V2 W/Calibration

Mar 29th, 2025
392
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
C++ 9.54 KB | Gaming | 0 0
  1. #include <Wire.h>
  2. #include <Adafruit_ADS1X15.h>
  3. #include <Adafruit_SSD1306.h>
  4. #include <BleGamepad.h>
  5. #include <Adafruit_TinyUSB.h>
  6. #include "tusb.h"
  7. #include <EEPROM.h>
  8.  
  9. // PROGMEM strings
  10. const char gasStr[] PROGMEM = "Gas";
  11. const char brakeStr[] PROGMEM = "Brake";
  12. const char clutchStr[] PROGMEM = "Clutch";
  13. const char* const pedalNames[] PROGMEM = {gasStr, brakeStr, clutchStr};
  14. const char simPedalsStr[] PROGMEM = "Sim Pedals";
  15. const char adcErrorStr[] PROGMEM = "ADC Error";
  16. const char pressPedalStr[] PROGMEM = "Press pedal fully";
  17. const char releasePedalStr[] PROGMEM = "Release pedal";
  18. const char resetToDefaultsStr[] PROGMEM = "to Defaults";
  19.  
  20. // OLED Config
  21. #define SCREEN_WIDTH 128
  22. #define SCREEN_HEIGHT 64
  23. #define OLED_RESET -1
  24. Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
  25.  
  26. // ADS1115 Instance
  27. Adafruit_ADS1115 ads;
  28.  
  29. // BLE Gamepad Instance
  30. BleGamepad bleGamepad("Generic Sim Pedals BLE", "xAI", 100);
  31.  
  32. // USB HID Instance
  33. Adafruit_USBD_HID usb_hid;
  34.  
  35. // HID Report Descriptor (3-axis joystick)
  36. static const uint8_t hid_report_descriptor[] = {0x05, 0x01, 0x09, 0x05, 0xA1, 0x01, 0x85, 0x01, 0x09, 0x30, 0x09, 0x31, 0x09, 0x32, 0x15, 0x00, 0x26, 0xFF, 0x7F, 0x75, 0x10, 0x95, 0x03, 0x81, 0x02, 0xC0};
  37.  
  38. // Pins
  39. #define SDA_PIN 9
  40. #define SCL_PIN 10
  41. #define GAS_BUTTON_PIN 2
  42. #define BRAKE_BUTTON_PIN 3
  43. #define CLUTCH_BUTTON_PIN 4
  44.  
  45. // Constants
  46. const uint16_t DEFAULT_MIN_VOLT = 500;  // mV
  47. const uint16_t DEFAULT_MAX_VOLT = 4500; // mV
  48. const uint8_t DEAD_ZONE_PCT = 5;        // 5%
  49. const uint16_t CALIBRATION_TICKS = 1000;// 10s @ 10ms/tick
  50. const uint16_t RESET_TICKS = 500;       // 5s @ 10ms/tick
  51. const uint16_t DEBOUNCE_TICKS = 50;     // 0.5s @ 10ms/tick
  52.  
  53. enum PedalType { PEDAL_GAS = 0, PEDAL_BRAKE = 1, PEDAL_CLUTCH = 2, NUM_PEDALS = 3 };
  54. enum State { NORMAL, CALIBRATING, RESETTING };
  55.  
  56. // Calibration Values (in mV)
  57. uint16_t minVolts[NUM_PEDALS] = {DEFAULT_MIN_VOLT, DEFAULT_MIN_VOLT, DEFAULT_MIN_VOLT};
  58. uint16_t maxVolts[NUM_PEDALS] = {DEFAULT_MAX_VOLT, DEFAULT_MAX_VOLT, DEFAULT_MAX_VOLT};
  59.  
  60. // Axis Values
  61. int16_t axisValues[NUM_PEDALS];
  62.  
  63. // EEPROM Structure (packed to save space)
  64. struct CalibrationData {
  65.   uint16_t minVolts[NUM_PEDALS];
  66.   uint16_t maxVolts[NUM_PEDALS];
  67.   uint16_t checksum;
  68. } __attribute__((packed));
  69.  
  70. // State Variables
  71. volatile uint8_t buttonFlags = 0;
  72. State currentState = NORMAL;
  73. PedalType activePedal = PEDAL_GAS;
  74. uint16_t stateTicks = 0;
  75. uint16_t minV = 5000, maxV = 0;
  76.  
  77. void IRAM_ATTR handleButton() {
  78.   static uint32_t lastInterrupt[NUM_PEDALS] = {0};
  79.   uint32_t now = millis();
  80.   uint8_t pins[] = {GAS_BUTTON_PIN, BRAKE_BUTTON_PIN, CLUTCH_BUTTON_PIN};
  81.  
  82.   for(int i = 0; i < NUM_PEDALS; i++) {
  83.     if(!digitalRead(pins[i]) && (now - lastInterrupt[i] > 50)) {
  84.       buttonFlags |= (1 << i);
  85.       lastInterrupt[i] = now;
  86.     }
  87.   }
  88. }
  89.  
  90. void displayText(const char* line1, const char* line2 = "") {
  91.   display.clearDisplay();
  92.   display.setTextSize(1);
  93.   display.setCursor(0, 0);
  94.   display.println(line1);
  95.   if (line2[0]) display.println(line2);
  96.   display.display();
  97. }
  98.  
  99. void updateCalibration(PedalType pedal) {
  100.   uint16_t voltage = ads.computeVolts(ads.readADC_SingleEnded(pedal)) * 1000;  // mV
  101.   minV = min(minV, voltage);
  102.   maxV = max(maxV, voltage);
  103. }
  104.  
  105. void saveCalibration() {
  106.   CalibrationData data;
  107.   memcpy(data.minVolts, minVolts, sizeof(minVolts));
  108.   memcpy(data.maxVolts, maxVolts, sizeof(maxVolts));
  109.   data.checksum = 0;
  110.   const uint8_t* bytes = (const uint8_t*)&data;
  111.   for (int i = 0; i < sizeof(CalibrationData) - sizeof(uint16_t); i++) {
  112.     data.checksum += bytes[i];
  113.   }
  114.   EEPROM.put(0, data);
  115. }
  116.  
  117. bool loadCalibration() {
  118.   CalibrationData data;
  119.   EEPROM.get(0, data);
  120.   uint16_t checksum = 0;
  121.   const uint8_t* bytes = (const uint8_t*)&data;
  122.   for (int i = 0; i < sizeof(CalibrationData) - sizeof(uint16_t); i++) {
  123.     checksum += bytes[i];
  124.   }
  125.   if (checksum == data.checksum) {
  126.     memcpy(minVolts, data.minVolts, sizeof(minVolts));
  127.     memcpy(maxVolts, data.maxVolts, sizeof(maxVolts));
  128.     return true;
  129.   }
  130.   return false;
  131. }
  132.  
  133. int16_t applyDeadZone(uint16_t value, uint16_t minV, uint16_t maxV) {
  134.   uint32_t range = maxV - minV;
  135.   uint32_t deadZone = (range * DEAD_ZONE_PCT) / 100;
  136.   uint32_t activeRange = range - (2 * deadZone);
  137.  
  138.   if (value <= minV + deadZone) return 0;
  139.   if (value >= maxV - deadZone) return 32767;
  140.  
  141.   return ((value - (minV + deadZone)) * 32767UL) / activeRange;
  142. }
  143.  
  144. void setup() {
  145.   Wire.begin(SDA_PIN, SCL_PIN);
  146.   pinMode(GAS_BUTTON_PIN, INPUT_PULLUP);
  147.   pinMode(BRAKE_BUTTON_PIN, INPUT_PULLUP);
  148.   pinMode(CLUTCH_BUTTON_PIN, INPUT_PULLUP);
  149.   attachInterrupt(digitalPinToInterrupt(GAS_BUTTON_PIN), handleButton, FALLING);
  150.   attachInterrupt(digitalPinToInterrupt(BRAKE_BUTTON_PIN), handleButton, FALLING);
  151.   attachInterrupt(digitalPinToInterrupt(CLUTCH_BUTTON_PIN), handleButton, FALLING);
  152.  
  153.   // Initialize display with timeout
  154.   unsigned long start = millis();
  155.   bool displaySuccess = false;
  156.   while (millis() - start < 1000 && !displaySuccess) {
  157.       displaySuccess = display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
  158.       if (!displaySuccess) {
  159.           delay(10);
  160.       }
  161.   }
  162.   if (!displaySuccess) {
  163.       while (1); // Halt
  164.   }
  165.  
  166.   // Initialize ADC with timeout
  167.   start = millis();
  168.   bool adcSuccess = false;
  169.   while (millis() - start < 1000 && !adcSuccess) {
  170.       adcSuccess = ads.begin(0x48);
  171.       if (!adcSuccess) {
  172.           delay(10);
  173.       }
  174.   }
  175.   if (!adcSuccess) {
  176.       char buffer[20];
  177.       strcpy_P(buffer, adcErrorStr);
  178.       displayText(buffer);
  179.       while (1);
  180.   }
  181.   ads.setGain(GAIN_TWOTHIRDS);
  182.  
  183.   char buffer[20];
  184.   strcpy_P(buffer, simPedalsStr);
  185.   displayText(buffer);
  186.   usb_hid.setReportDescriptor(hid_report_descriptor, sizeof(hid_report_descriptor));
  187.   usb_hid.begin();
  188.   bleGamepad.begin();
  189.  
  190.   if (!loadCalibration()) {
  191.     for (int i = 0; i < NUM_PEDALS; i++) {
  192.       minVolts[i] = DEFAULT_MIN_VOLT;
  193.       maxVolts[i] = DEFAULT_MAX_VOLT;
  194.     }
  195.     saveCalibration();
  196.   }
  197. }
  198.  
  199. void loop() {
  200.   // Handle state machine
  201.   if (buttonFlags) {
  202.     for (int i = 0; i < NUM_PEDALS; i++) {
  203.       if (buttonFlags & (1 << i)) {
  204.         activePedal = (PedalType)i;
  205.         currentState = CALIBRATING;
  206.         stateTicks = 0;
  207.         minV = 5000;
  208.         maxV = 0;
  209.         buttonFlags &= ~(1 << i);
  210.         char pedalName[10];
  211.         strcpy_P(pedalName, (PGM_P)pgm_read_word(&(pedalNames[i])));
  212.         char buffer[20];
  213.         snprintf(buffer, sizeof(buffer), "%s Calibration", pedalName);
  214.         char buffer2[20];
  215.         strcpy_P(buffer2, pressPedalStr);
  216.         displayText(buffer, buffer2);
  217.         break;
  218.       }
  219.     }
  220.   }
  221.  
  222.   if (currentState != NORMAL) {
  223.     stateTicks++;
  224.     if (currentState == CALIBRATING) {
  225.       updateCalibration(activePedal);
  226.       if (stateTicks >= CALIBRATION_TICKS) {
  227.         minVolts[activePedal] = minV;
  228.         maxVolts[activePedal] = maxV;
  229.         saveCalibration();
  230.         currentState = NORMAL;
  231.       } else if (stateTicks == 300) {  // 3s delay before sampling
  232.         char buffer[20];
  233.         strcpy_P(buffer, releasePedalStr);
  234.         displayText(buffer);
  235.       }
  236.     } else if (currentState == RESETTING) {
  237.       if (stateTicks >= RESET_TICKS) {
  238.         minVolts[activePedal] = DEFAULT_MIN_VOLT;
  239.         maxVolts[activePedal] = DEFAULT_MAX_VOLT;
  240.         saveCalibration();
  241.         currentState = NORMAL;
  242.       }
  243.     }
  244.     static const uint8_t buttonPins[] = {GAS_BUTTON_PIN, BRAKE_BUTTON_PIN, CLUTCH_BUTTON_PIN};
  245.     if (stateTicks >= DEBOUNCE_TICKS && digitalRead(buttonPins[activePedal]) == LOW) {
  246.       currentState = RESETTING;
  247.       stateTicks = 0;
  248.       char pedalName[10];
  249.       strcpy_P(pedalName, (PGM_P)pgm_read_word(&(pedalNames[activePedal])));
  250.       char buffer[20];
  251.       snprintf(buffer, sizeof(buffer), "%s Reset", pedalName);
  252.       char buffer2[20];
  253.       strcpy_P(buffer2, resetToDefaultsStr);
  254.       displayText(buffer, buffer2);
  255.     }
  256.     delay(10);
  257.     return;
  258.   }
  259.  
  260.   // Normal operation
  261.   int16_t rawValues[NUM_PEDALS];
  262.   bool adcError = false;
  263.   for (int i = 0; i < NUM_PEDALS; i++) {
  264.     rawValues[i] = ads.readADC_SingleEnded(i);
  265.     if (rawValues[i] < 0) adcError = true;
  266.   }
  267.  
  268.   if (adcError) {
  269.     char buffer[20];
  270.     strcpy_P(buffer, adcErrorStr);
  271.     displayText(buffer, F("Read Error"));
  272.     delay(1000);
  273.     return;
  274.   }
  275.  
  276.   uint16_t voltages[NUM_PEDALS];
  277.   for (int i = 0; i < NUM_PEDALS; i++) {
  278.     voltages[i] = ads.computeVolts(rawValues[i]) * 1000;  // mV
  279.     axisValues[i] = applyDeadZone(voltages[i], minVolts[i], maxVolts[i]);
  280.   }
  281.  
  282.   if (usb_hid.ready()) {
  283.     uint8_t report[6];
  284.     for (int i = 0; i < NUM_PEDALS; i++) {
  285.       report[i * 2] = axisValues[i] & 0xFF;
  286.       report[i * 2 + 1] = axisValues[i] >> 8;
  287.     }
  288.     usb_hid.sendReport(1, report, 6);
  289.   }
  290.   if (bleGamepad.isConnected()) {
  291.     bleGamepad.setAxes(axisValues[PEDAL_GAS], axisValues[PEDAL_BRAKE], axisValues[PEDAL_CLUTCH], 0, 0, 0, 0, DPAD_CENTERED);
  292.   }
  293.  
  294.   float brakeVolts = voltages[PEDAL_BRAKE] / 1000.0f;  // Convert back to volts
  295.   float brakePSI = constrain(((brakeVolts - (minVolts[PEDAL_BRAKE] / 1000.0f)) / ((maxVolts[PEDAL_BRAKE] / 1000.0f) - (minVolts[PEDAL_BRAKE] / 1000.0f))) * 100.0f, 0.0f, 100.0f);
  296.   display.clearDisplay();
  297.   display.setTextSize(1);
  298.   display.setCursor(0, 0);
  299.   display.print(usb_hid.ready() ? "USB" : (bleGamepad.isConnected() ? "BT" : ""));
  300.   display.setTextSize(2);
  301.   display.setCursor(40, 16);
  302.   display.println("Brake");
  303.   display.setCursor(28, 40);
  304.   display.print(brakePSI, 2);
  305.   display.print(F(" PSI"));
  306.   display.display();
  307.  
  308.   delay(5);
  309. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement