Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- #include <Wire.h>
- #include <Adafruit_ADS1X15.h>
- #include <Adafruit_SSD1306.h>
- #include <BleGamepad.h>
- #include <Adafruit_TinyUSB.h>
- #include "tusb.h"
- #include <EEPROM.h>
- // OLED Config
- #define SCREEN_WIDTH 128
- #define SCREEN_HEIGHT 64
- #define OLED_RESET -1
- Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
- // ADS1115 Instance
- Adafruit_ADS1115 ads;
- // BLE Gamepad Instance
- BleGamepad bleGamepad("Generic Sim Pedals BLE", "xAI", 100);
- // USB HID Instance
- Adafruit_USBD_HID usb_hid;
- // HID Report Descriptor (3-axis joystick)
- 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};
- // Pins
- #define SDA_PIN 9
- #define SCL_PIN 10
- #define GAS_BUTTON_PIN 2
- #define BRAKE_BUTTON_PIN 3
- #define CLUTCH_BUTTON_PIN 4
- // Constants
- const uint16_t DEFAULT_MIN_VOLT = 500; // mV
- const uint16_t DEFAULT_MAX_VOLT = 4500; // mV
- const uint8_t DEAD_ZONE_PCT = 5; // 5%
- const uint16_t CALIBRATION_TICKS = 1000;// 10s @ 10ms/tick
- const uint16_t RESET_TICKS = 500; // 5s @ 10ms/tick
- const uint16_t DEBOUNCE_TICKS = 50; // 0.5s @ 10ms/tick
- enum PedalType { PEDAL_GAS = 0, PEDAL_BRAKE = 1, PEDAL_CLUTCH = 2, NUM_PEDALS = 3 };
- enum State { NORMAL, CALIBRATING, RESETTING };
- // Calibration Values (in mV)
- uint16_t minVolts[NUM_PEDALS] = {DEFAULT_MIN_VOLT, DEFAULT_MIN_VOLT, DEFAULT_MIN_VOLT};
- uint16_t maxVolts[NUM_PEDALS] = {DEFAULT_MAX_VOLT, DEFAULT_MAX_VOLT, DEFAULT_MAX_VOLT};
- // Axis Values
- int16_t axisValues[NUM_PEDALS];
- // EEPROM Structure (packed to save space)
- struct CalibrationData {
- uint16_t minVolts[NUM_PEDALS];
- uint16_t maxVolts[NUM_PEDALS];
- uint16_t checksum;
- } __attribute__((packed));
- // State Variables
- volatile uint8_t buttonFlags = 0;
- State currentState = NORMAL;
- PedalType activePedal = PEDAL_GAS;
- uint16_t stateTicks = 0;
- uint16_t minV = 5000, maxV = 0;
- void IRAM_ATTR handleButton() {
- static uint32_t lastInterrupt = 0;
- uint32_t now = millis();
- if (now - lastInterrupt < 50) return; // Debounce 50ms
- lastInterrupt = now;
- if (!digitalRead(GAS_BUTTON_PIN)) buttonFlags |= (1 << PEDAL_GAS);
- if (!digitalRead(BRAKE_BUTTON_PIN)) buttonFlags |= (1 << PEDAL_BRAKE);
- if (!digitalRead(CLUTCH_BUTTON_PIN)) buttonFlags |= (1 << PEDAL_CLUTCH);
- }
- void displayText(const char* line1, const char* line2 = "") {
- display.clearDisplay();
- display.setTextSize(1);
- display.setCursor(0, 0);
- display.println(line1);
- if (line2[0]) display.println(line2);
- display.display();
- }
- void updateCalibration(PedalType pedal) {
- uint16_t voltage = ads.computeVolts(ads.readADC_SingleEnded(pedal)) * 1000; // mV
- minV = min(minV, voltage);
- maxV = max(maxV, voltage);
- }
- void saveCalibration() {
- CalibrationData data;
- memcpy(data.minVolts, minVolts, sizeof(minVolts));
- memcpy(data.maxVolts, maxVolts, sizeof(maxVolts));
- data.checksum = 0;
- const uint8_t* bytes = (const uint8_t*)&data;
- for (int i = 0; i < sizeof(CalibrationData) - sizeof(uint16_t); i++) {
- data.checksum += bytes[i];
- }
- EEPROM.put(0, data);
- }
- bool loadCalibration() {
- CalibrationData data;
- EEPROM.get(0, data);
- uint16_t checksum = 0;
- const uint8_t* bytes = (const uint8_t*)&data;
- for (int i = 0; i < sizeof(CalibrationData) - sizeof(uint16_t); i++) {
- checksum += bytes[i];
- }
- if (checksum == data.checksum) {
- memcpy(minVolts, data.minVolts, sizeof(minVolts));
- memcpy(maxVolts, data.maxVolts, sizeof(maxVolts));
- return true;
- }
- return false;
- }
- int16_t applyDeadZone(uint16_t value, uint16_t minV, uint16_t maxV) {
- uint32_t range = maxV - minV;
- uint32_t deadZone = (range * DEAD_ZONE_PCT) / 100;
- uint32_t activeRange = range - (2 * deadZone);
- if (value <= minV + deadZone) return 0;
- if (value >= maxV - deadZone) return 32767;
- return ((value - (minV + deadZone)) * 32767UL) / activeRange;
- }
- void setup() {
- Wire.begin(SDA_PIN, SCL_PIN);
- pinMode(GAS_BUTTON_PIN, INPUT_PULLUP);
- pinMode(BRAKE_BUTTON_PIN, INPUT_PULLUP);
- pinMode(CLUTCH_BUTTON_PIN, INPUT_PULLUP);
- attachInterrupt(digitalPinToInterrupt(GAS_BUTTON_PIN), handleButton, FALLING);
- attachInterrupt(digitalPinToInterrupt(BRAKE_BUTTON_PIN), handleButton, FALLING);
- attachInterrupt(digitalPinToInterrupt(CLUTCH_BUTTON_PIN), handleButton, FALLING);
- if (!ads.begin(0x48)) {
- display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
- displayText("ADC Error");
- while (1);
- }
- ads.setGain(GAIN_TWOTHIRDS);
- if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
- while (1);
- }
- displayText("Sim Pedals");
- usb_hid.setReportDescriptor(hid_report_descriptor, sizeof(hid_report_descriptor));
- usb_hid.begin();
- bleGamepad.begin();
- if (!loadCalibration()) {
- for (int i = 0; i < NUM_PEDALS; i++) {
- minVolts[i] = DEFAULT_MIN_VOLT;
- maxVolts[i] = DEFAULT_MAX_VOLT;
- }
- saveCalibration();
- }
- }
- void loop() {
- // Handle state machine
- if (buttonFlags) {
- for (int i = 0; i < NUM_PEDALS; i++) {
- if (buttonFlags & (1 << i)) {
- activePedal = (PedalType)i;
- currentState = CALIBRATING;
- stateTicks = 0;
- minV = 5000;
- maxV = 0;
- buttonFlags &= ~(1 << i);
- const char* names[] = {"Gas", "Brake", "Clutch"};
- displayText((String(names[i]) + " Calibration").c_str(), "Press pedal fully");
- break;
- }
- }
- }
- if (currentState != NORMAL) {
- stateTicks++;
- if (currentState == CALIBRATING) {
- updateCalibration(activePedal);
- if (stateTicks >= CALIBRATION_TICKS) {
- minVolts[activePedal] = minV;
- maxVolts[activePedal] = maxV;
- saveCalibration();
- currentState = NORMAL;
- } else if (stateTicks == 300) { // 3s delay before sampling
- displayText("Release pedal");
- }
- } else if (currentState == RESETTING) {
- if (stateTicks >= RESET_TICKS) {
- minVolts[activePedal] = DEFAULT_MIN_VOLT;
- maxVolts[activePedal] = DEFAULT_MAX_VOLT;
- saveCalibration();
- currentState = NORMAL;
- }
- }
- static const uint8_t buttonPins[] = {GAS_BUTTON_PIN, BRAKE_BUTTON_PIN, CLUTCH_BUTTON_PIN};
- if (stateTicks >= DEBOUNCE_TICKS && digitalRead(buttonPins[activePedal]) == LOW) {
- currentState = RESETTING;
- stateTicks = 0;
- const char* names[] = {"Gas", "Brake", "Clutch"};
- displayText((String(names[activePedal]) + " Reset").c_str(), "to Defaults");
- }
- delay(10);
- return;
- }
- // Normal operation
- int16_t rawValues[NUM_PEDALS];
- bool adcError = false;
- for (int i = 0; i < NUM_PEDALS; i++) {
- rawValues[i] = ads.readADC_SingleEnded(i);
- if (rawValues[i] < 0) adcError = true;
- }
- if (adcError) {
- displayText("ADC Read", "Error");
- delay(1000);
- return;
- }
- uint16_t voltages[NUM_PEDALS];
- for (int i = 0; i < NUM_PEDALS; i++) {
- voltages[i] = ads.computeVolts(rawValues[i]) * 1000; // mV
- axisValues[i] = applyDeadZone(voltages[i], minVolts[i], maxVolts[i]);
- }
- if (usb_hid.ready()) {
- uint8_t report[6];
- for (int i = 0; i < NUM_PEDALS; i++) {
- report[i * 2] = axisValues[i] & 0xFF;
- report[i * 2 + 1] = axisValues[i] >> 8;
- }
- usb_hid.sendReport(1, report, 6);
- }
- if (bleGamepad.isConnected()) {
- bleGamepad.setAxes(axisValues[PEDAL_GAS], axisValues[PEDAL_BRAKE], axisValues[PEDAL_CLUTCH], 0, 0, 0, 0, DPAD_CENTERED);
- }
- float brakeVolts = voltages[PEDAL_BRAKE] / 1000.0f; // Convert back to volts
- 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);
- display.clearDisplay();
- display.setTextSize(1);
- display.setCursor(0, 0);
- display.print(usb_hid.ready() ? "USB" : (bleGamepad.isConnected() ? "BT" : ""));
- display.setTextSize(2);
- display.setCursor(40, 16);
- display.println("Brake");
- display.setCursor(28, 40);
- display.print(brakePSI, 2); // 2 decimal places
- display.print(" PSI");
- display.display();
- delay(5);
- }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement