Advertisement
LandoRo

Esp32s3 Ssd1306 ADS1115 W/ Calibration

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