reworked Pedal Input
This commit is contained in:
@@ -13,6 +13,12 @@ platform = atmelavr
|
||||
board = nanoatmega328new
|
||||
framework = arduino
|
||||
|
||||
[env:megaatmega2560]
|
||||
platform = atmelavr
|
||||
board = megaatmega2560
|
||||
framework = arduino
|
||||
|
||||
|
||||
;upload_protocol = custom
|
||||
;upload_flags =
|
||||
; -C
|
||||
|
||||
@@ -1,151 +1,266 @@
|
||||
// PedalController.cpp
|
||||
#include "PedalController.h"
|
||||
|
||||
const int minMin = 50; // Minimum allowed raw value for calibration
|
||||
const int maxMin = 200; // Maximum allowed raw value for calibration
|
||||
const int minMax = 800; // Minimum allowed maximum value for calibration
|
||||
const int maxMax = 1000; // Maximum allowed raw value for calibration
|
||||
|
||||
const PedalConfig defaultConfig = {
|
||||
// Basiskonfiguration
|
||||
static const PedalConfig defaultConfig = {
|
||||
.minRaw = 0,
|
||||
.maxRaw = 1023,
|
||||
.calibrated = false
|
||||
};
|
||||
.calibrated = false,
|
||||
.filterStrength = 8 // 8er Moving Average als Standard
|
||||
};
|
||||
|
||||
PedalController::PedalController(int pin)
|
||||
: pedalPin(pin)
|
||||
// Tuning-Konstanten für die Auto-Cal
|
||||
static const int STABLE_THRESHOLD = 3; // zulässige ADC-Differenz für "stabil"
|
||||
static const unsigned long STABLE_TIME = 1000; // ms, die der Wert stabil sein muss
|
||||
static const unsigned long SWEEP_TIME = 3000; // ms für die Sweep-Phase
|
||||
static const int MIN_RANGE = 50; // minimale Spannweite min/max
|
||||
static const int SAFETY_MARGIN_LOW = 5; // Puffer unten
|
||||
static const int SAFETY_MARGIN_HIGH = 5; // Puffer oben
|
||||
static const uint8_t MIN_FS = 2;
|
||||
static const uint8_t MAX_FS = 32;
|
||||
static const uint8_t DEFAULT_FS = 8;
|
||||
|
||||
PedalController::PedalController(int analogPin,
|
||||
int digitalPin,
|
||||
uint8_t digitalNotPressedLevel)
|
||||
: analogPin(analogPin),
|
||||
digitalPin(digitalPin),
|
||||
useDigital(digitalPin >= 0),
|
||||
digitalNotPressedLevel(digitalNotPressedLevel ? 1 : 0),
|
||||
filteredValue(-1),
|
||||
calState(IDLE),
|
||||
calStep(0),
|
||||
stepStartTime(0),
|
||||
lastChangeTime(0),
|
||||
lastSample(0),
|
||||
minSeen(1023),
|
||||
maxSeen(0)
|
||||
{
|
||||
if (useDigital)
|
||||
{
|
||||
pinMode(digitalPin, INPUT); // Hall-Module haben normalerweise eigenen Pullup
|
||||
}
|
||||
|
||||
loadDefaults();
|
||||
pinMode(pedalPin, INPUT);
|
||||
}
|
||||
|
||||
void PedalController::loadDefaults()
|
||||
// Rohwert lesen und glätten
|
||||
int PedalController::readRaw()
|
||||
{
|
||||
config = defaultConfig;
|
||||
int raw = analogRead(analogPin);
|
||||
return applySmoothing(raw);
|
||||
}
|
||||
|
||||
int PedalController::applySmoothing(int rawValue)
|
||||
int PedalController::applySmoothing(int raw)
|
||||
{
|
||||
filteredValue = (filteredValue * (config.filterStrength - 1) + rawValue) / config.filterStrength;
|
||||
return filteredValue;
|
||||
uint8_t fs = config.filterStrength;
|
||||
|
||||
// Filter deaktiviert? Direkt durchreichen.
|
||||
if (fs <= 1)
|
||||
{
|
||||
filteredValue = raw;
|
||||
return raw;
|
||||
}
|
||||
|
||||
// Erster Wert: direkt übernehmen
|
||||
if (filteredValue < 0)
|
||||
{
|
||||
filteredValue = raw;
|
||||
return raw;
|
||||
}
|
||||
|
||||
// 32-Bit Akkumulator, um Überlauf zu vermeiden
|
||||
int32_t acc = filteredValue * (int32_t)(fs - 1) + raw;
|
||||
filteredValue = acc / fs;
|
||||
|
||||
return (int)filteredValue;
|
||||
}
|
||||
|
||||
CalibrationState PedalController::autoCalibrate(bool reset)
|
||||
{
|
||||
static int step = 0;
|
||||
static unsigned long lastChangeTime = 0;
|
||||
static int previousValue = 0;
|
||||
static CalibrationState errorState = IDLE;
|
||||
unsigned long now = millis();
|
||||
|
||||
if (reset)
|
||||
{
|
||||
step = 0;
|
||||
config.minRaw = 1023;
|
||||
config.maxRaw = 0;
|
||||
lastChangeTime = millis();
|
||||
// Konfiguration auf Defaults zurücksetzen
|
||||
config = defaultConfig;
|
||||
config.calibrated = false;
|
||||
errorState = IDLE;
|
||||
Serial.println("Calibration reset.");
|
||||
return IDLE;
|
||||
|
||||
// interne Zustände für die Kalibrierung
|
||||
calState = RUNNING;
|
||||
calStep = 1; // Schritt 1: Pedal losgelassen
|
||||
stepStartTime = now;
|
||||
lastChangeTime = now;
|
||||
|
||||
filteredValue = -1;
|
||||
int raw = readRaw();
|
||||
lastSample = raw;
|
||||
minSeen = 1023;
|
||||
maxSeen = 0;
|
||||
|
||||
return calState;
|
||||
}
|
||||
|
||||
unsigned long currentTime = millis();
|
||||
int rawValue = applySmoothing(analogRead(pedalPin));
|
||||
|
||||
switch (step)
|
||||
// Kein Reset angefordert, aber auch keine aktive Kalibrierung
|
||||
if (calStep == 0)
|
||||
{
|
||||
case 0: // Initial state: wait for pedal release
|
||||
if (rawValue == previousValue && currentTime - lastChangeTime > 2000)
|
||||
calState = IDLE;
|
||||
return calState;
|
||||
}
|
||||
|
||||
int raw = readRaw();
|
||||
int diff = abs(raw - lastSample);
|
||||
|
||||
printStatus(raw);
|
||||
|
||||
switch (calStep)
|
||||
{
|
||||
case 1: // Schritt 1: Pedal losgelassen halten
|
||||
if (diff > STABLE_THRESHOLD)
|
||||
{
|
||||
if (rawValue >= minMin && rawValue <= maxMin)
|
||||
{
|
||||
config.minRaw = rawValue;
|
||||
step++;
|
||||
lastChangeTime = currentTime;
|
||||
errorState = RUNNING;
|
||||
}
|
||||
else
|
||||
{
|
||||
errorState = ERROR;
|
||||
}
|
||||
lastSample = raw;
|
||||
lastChangeTime = now;
|
||||
}
|
||||
else if (rawValue != previousValue)
|
||||
|
||||
// Wert war lange genug stabil -> als unteres Ende merken
|
||||
if (now - lastChangeTime > STABLE_TIME)
|
||||
{
|
||||
lastChangeTime = currentTime;
|
||||
minSeen = raw;
|
||||
config.minRaw = raw;
|
||||
calStep = 2;
|
||||
lastSample = raw;
|
||||
lastChangeTime = now;
|
||||
}
|
||||
|
||||
calState = RUNNING;
|
||||
break;
|
||||
|
||||
case 1: // Wait for pedal full press
|
||||
if (rawValue == previousValue && currentTime - lastChangeTime > 2000)
|
||||
case 2: // Schritt 2: Pedal voll durchtreten
|
||||
if (diff > STABLE_THRESHOLD)
|
||||
{
|
||||
if (rawValue >= minMax && rawValue <= maxMax)
|
||||
{
|
||||
config.maxRaw = rawValue;
|
||||
step++;
|
||||
lastChangeTime = currentTime;
|
||||
errorState = RUNNING;
|
||||
}
|
||||
else
|
||||
{
|
||||
errorState = ERROR;
|
||||
}
|
||||
lastSample = raw;
|
||||
lastChangeTime = now;
|
||||
}
|
||||
else if (rawValue != previousValue)
|
||||
|
||||
// Wert war lange genug stabil -> als erstes oberes Ende merken
|
||||
if (now - lastChangeTime > STABLE_TIME)
|
||||
{
|
||||
lastChangeTime = currentTime;
|
||||
maxSeen = raw;
|
||||
config.maxRaw = raw;
|
||||
calStep = 3;
|
||||
stepStartTime = now;
|
||||
}
|
||||
|
||||
calState = RUNNING;
|
||||
break;
|
||||
|
||||
case 2: // Validate calibration
|
||||
if (config.maxRaw > config.minRaw + 100)
|
||||
{ // Ensure valid range
|
||||
step++;
|
||||
case 3: // Schritt 3: Sweep, um echten min/max Bereich zu erfassen
|
||||
if (raw < minSeen)
|
||||
minSeen = raw;
|
||||
if (raw > maxSeen)
|
||||
maxSeen = raw;
|
||||
|
||||
if (now - stepStartTime > SWEEP_TIME)
|
||||
{
|
||||
int low = minSeen + SAFETY_MARGIN_LOW;
|
||||
int high = maxSeen - SAFETY_MARGIN_HIGH;
|
||||
|
||||
// Falls Richtung "invertiert" ist, drehen
|
||||
if (high < low)
|
||||
{
|
||||
int tmp = low;
|
||||
low = high;
|
||||
high = tmp;
|
||||
}
|
||||
|
||||
// Bereich zu klein, Kalibrierung fehlgeschlagen
|
||||
if (high - low < MIN_RANGE)
|
||||
{
|
||||
config.calibrated = false;
|
||||
calState = ERROR;
|
||||
calStep = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
config.minRaw = low;
|
||||
config.maxRaw = high;
|
||||
config.calibrated = true;
|
||||
errorState = SUCCESS;
|
||||
|
||||
calState = SUCCESS;
|
||||
calStep = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
errorState = ERROR;
|
||||
calState = RUNNING;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
errorState = IDLE;
|
||||
step = 0;
|
||||
// Fallback, falls irgendetwas entgleist
|
||||
calState = ERROR;
|
||||
calStep = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
previousValue = rawValue;
|
||||
return errorState;
|
||||
return calState;
|
||||
}
|
||||
|
||||
CalibrationState PedalController::getStatus() const
|
||||
{
|
||||
return calState;
|
||||
}
|
||||
|
||||
int PedalController::getPedal()
|
||||
{
|
||||
if (!config.calibrated || autoCalibrate(false) != IDLE)
|
||||
// 1) Digital-Sanity: Wenn DO "nicht gedrückt" meldet → 0
|
||||
if (useDigital)
|
||||
{
|
||||
int d = digitalRead(digitalPin);
|
||||
|
||||
if ((uint8_t)d == digitalNotPressedLevel)
|
||||
{
|
||||
// Optional: Filter sanft Richtung "unten" ziehen
|
||||
// aber sicherheitshalber nichts fahren
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
// 2) Ohne gültige Kalibrierung: kein Pedal
|
||||
if (!config.calibrated)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
int rawValue = applySmoothing(analogRead(pedalPin));
|
||||
if (rawValue < config.minRaw || rawValue > config.maxRaw)
|
||||
int raw = readRaw();
|
||||
|
||||
int minR = config.minRaw;
|
||||
int maxR = config.maxRaw;
|
||||
|
||||
// Defekter Config-Bereich, zur Sicherheit 0
|
||||
if (maxR <= minR + 1)
|
||||
{
|
||||
return 0; // Out of bounds
|
||||
return 0;
|
||||
}
|
||||
|
||||
return map(rawValue, config.minRaw, config.maxRaw, 0, 100);
|
||||
// Clamping
|
||||
if (raw < minR)
|
||||
raw = minR;
|
||||
if (raw > maxR)
|
||||
raw = maxR;
|
||||
|
||||
long span = (long)maxR - (long)minR;
|
||||
long scaled = (long)(raw - minR) * 100L / span;
|
||||
|
||||
// kleine Deadzone unten
|
||||
if (scaled < 5)
|
||||
scaled = 0;
|
||||
if (scaled > 100)
|
||||
scaled = 100;
|
||||
|
||||
return (int)scaled;
|
||||
}
|
||||
|
||||
CalibrationState PedalController::getStatus()
|
||||
{
|
||||
return autoCalibrate(false);
|
||||
}
|
||||
|
||||
int PedalController::getRawValue()
|
||||
{
|
||||
return analogRead(pedalPin);
|
||||
}
|
||||
|
||||
PedalConfig PedalController::getConfig()
|
||||
PedalConfig PedalController::getConfig() const
|
||||
{
|
||||
return config;
|
||||
}
|
||||
@@ -153,4 +268,84 @@ PedalConfig PedalController::getConfig()
|
||||
void PedalController::setConfig(const PedalConfig &newConfig)
|
||||
{
|
||||
config = newConfig;
|
||||
|
||||
// FilterStrength sanitizen
|
||||
if (config.filterStrength < MIN_FS || config.filterStrength > MAX_FS)
|
||||
{
|
||||
config.filterStrength = DEFAULT_FS;
|
||||
}
|
||||
|
||||
if (config.maxRaw < config.minRaw)
|
||||
{
|
||||
int16_t tmp = config.minRaw;
|
||||
config.minRaw = config.maxRaw;
|
||||
config.maxRaw = tmp;
|
||||
}
|
||||
|
||||
filteredValue = -1;
|
||||
}
|
||||
|
||||
void PedalController::loadDefaults()
|
||||
{
|
||||
config = defaultConfig;
|
||||
|
||||
if (config.filterStrength < MIN_FS || config.filterStrength > MAX_FS)
|
||||
{
|
||||
config.filterStrength = DEFAULT_FS;
|
||||
}
|
||||
|
||||
calState = IDLE;
|
||||
calStep = 0;
|
||||
filteredValue = -1;
|
||||
stepStartTime = 0;
|
||||
lastChangeTime = 0;
|
||||
lastSample = 0;
|
||||
minSeen = 1023;
|
||||
maxSeen = 0;
|
||||
}
|
||||
|
||||
void PedalController::printStatus(int raw)
|
||||
{
|
||||
unsigned long now = millis();
|
||||
if (now - lastStatusPrint < 1000)
|
||||
return; // nur jede Sekunde
|
||||
lastStatusPrint = now;
|
||||
|
||||
int digitalState = -1;
|
||||
if (useDigital)
|
||||
{
|
||||
digitalState = digitalRead(digitalPin);
|
||||
}
|
||||
|
||||
Serial.print("[CAL] Step=");
|
||||
Serial.print(calStep);
|
||||
Serial.print(" RAW=");
|
||||
Serial.print(raw);
|
||||
Serial.print(" Filtered=");
|
||||
Serial.print(filteredValue);
|
||||
|
||||
Serial.print(" DO-State=");
|
||||
if (useDigital)
|
||||
Serial.print(digitalState);
|
||||
else
|
||||
Serial.print("N/A");
|
||||
|
||||
Serial.print(" minSeen=");
|
||||
Serial.print(minSeen);
|
||||
Serial.print(" maxSeen=");
|
||||
Serial.print(maxSeen);
|
||||
|
||||
Serial.println();
|
||||
}
|
||||
|
||||
int PedalController::debugGetRaw()
|
||||
{
|
||||
return readRaw();
|
||||
}
|
||||
|
||||
int PedalController::debugGetDO()
|
||||
{
|
||||
if (!useDigital)
|
||||
return -1;
|
||||
return digitalRead(digitalPin);
|
||||
}
|
||||
|
||||
@@ -14,33 +14,56 @@ enum CalibrationState {
|
||||
|
||||
// Pedal configuration structure
|
||||
struct PedalConfig {
|
||||
int minRaw;
|
||||
int maxRaw;
|
||||
bool calibrated;
|
||||
int filterStrength;
|
||||
int16_t minRaw;
|
||||
int16_t maxRaw;
|
||||
bool calibrated;
|
||||
uint8_t filterStrength; // 1 = kein Filter, >1 = gleitender Mittelwert
|
||||
};
|
||||
|
||||
class PedalController {
|
||||
private:
|
||||
int pedalPin;
|
||||
int filteredValue;
|
||||
PedalConfig config;
|
||||
|
||||
int applySmoothing(int rawValue);
|
||||
int getRawValue();
|
||||
|
||||
public:
|
||||
PedalController(int pin);
|
||||
|
||||
CalibrationState autoCalibrate(bool reset);
|
||||
CalibrationState getStatus();
|
||||
// analogPin = ADC-Eingang
|
||||
// digitalPin = optionaler Digital-Eingang des Hall-Boards (DO), -1 wenn keiner
|
||||
// digitalNotPressedLevel = 0 (LOW) oder 1 (HIGH), welches Level "nicht gedrückt" bedeutet
|
||||
explicit PedalController(int analogPin,
|
||||
int digitalPin = -1,
|
||||
uint8_t digitalNotPressedLevel = LOW);
|
||||
|
||||
// Startet / läuft Auto-Kalibrierung
|
||||
CalibrationState autoCalibrate(bool reset);
|
||||
CalibrationState getStatus() const;
|
||||
|
||||
// 0–100 %, bei nicht kalibriert → 0
|
||||
int getPedal();
|
||||
|
||||
PedalConfig getConfig();
|
||||
PedalConfig getConfig() const;
|
||||
void setConfig(const PedalConfig &newConfig);
|
||||
void loadDefaults();
|
||||
int debugGetRaw();
|
||||
int debugGetDO();
|
||||
|
||||
private:
|
||||
int analogPin;
|
||||
int digitalPin;
|
||||
bool useDigital;
|
||||
uint8_t digitalNotPressedLevel; // 0 oder 1
|
||||
|
||||
PedalConfig config;
|
||||
int32_t filteredValue;
|
||||
|
||||
// Auto-Cal intern
|
||||
CalibrationState calState;
|
||||
uint8_t calStep;
|
||||
unsigned long stepStartTime;
|
||||
unsigned long lastChangeTime;
|
||||
int lastSample;
|
||||
int minSeen;
|
||||
int maxSeen;
|
||||
unsigned long lastStatusPrint;
|
||||
|
||||
int readRaw(); // roher ADC-Wert (mit Glättung)
|
||||
int applySmoothing(int raw);
|
||||
void printStatus(int raw);
|
||||
};
|
||||
|
||||
#endif // PEDALCONTROLLER_H
|
||||
|
||||
55
src/main.cpp
55
src/main.cpp
@@ -12,16 +12,17 @@
|
||||
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
|
||||
|
||||
// Pin definitions
|
||||
const int hallSensor = 2; // Hall sensor input
|
||||
const int encoderPinA = 3; // Encoder pin A
|
||||
const int encoderPinB = 4; // Encoder pin B
|
||||
const int encoderButton = 5; // Encoder button
|
||||
const int motorPin = 9; // Motor PWM pin
|
||||
const int pedalPin = A0; // Pedal input pin
|
||||
const int hallSensor = 2; // Hall sensor input
|
||||
const int encoderPinA = 3; // Encoder pin A
|
||||
const int encoderPinB = 4; // Encoder pin B
|
||||
const int encoderButton = 5; // Encoder button
|
||||
const int motorPin = 9; // Motor PWM pin
|
||||
const int pedalPinAnalog = A1; // Pedal analog Input pin
|
||||
const int pedalPinDigital = 8; // Pedal digital Input pin
|
||||
|
||||
// Global objects
|
||||
MotorController motor(motorPin); // Motor control object
|
||||
PedalController pedal(pedalPin); // Pedal input object
|
||||
MotorController motor(motorPin); // Motor control object
|
||||
PedalController pedal(pedalPinAnalog, pedalPinDigital, HIGH); // Pedal input object
|
||||
|
||||
// Combined settings structure
|
||||
struct Settings
|
||||
@@ -36,6 +37,9 @@ bool autoCalibrating = false;
|
||||
bool serialControlEnabled = false;
|
||||
CalibrationState lastCalibrationState = IDLE;
|
||||
|
||||
bool liveSensorOutput = false;
|
||||
unsigned long lastSensorPrint = 0;
|
||||
|
||||
// Function prototypes
|
||||
void loadSettings();
|
||||
void saveSettings();
|
||||
@@ -133,6 +137,26 @@ void loop()
|
||||
motor.setTargetSpeed(pedalValue);
|
||||
}
|
||||
|
||||
if (liveSensorOutput)
|
||||
{
|
||||
unsigned long now = millis();
|
||||
if (now - lastSensorPrint > 200)
|
||||
{
|
||||
lastSensorPrint = now;
|
||||
|
||||
int raw = pedal.debugGetRaw();
|
||||
int pedalValue = pedal.getPedal();
|
||||
int doState = pedal.debugGetDO();
|
||||
|
||||
Serial.print("[SENSOR] RAW=");
|
||||
Serial.print(raw);
|
||||
Serial.print(" Pedal%=");
|
||||
Serial.print(pedalValue);
|
||||
Serial.print(" DO=");
|
||||
Serial.println(doState);
|
||||
}
|
||||
}
|
||||
|
||||
// Update display
|
||||
// updateDisplay();
|
||||
|
||||
@@ -290,6 +314,18 @@ void handleSerialInput()
|
||||
Serial.println(motor.getSpeed());
|
||||
}
|
||||
break;
|
||||
|
||||
case 'S':
|
||||
liveSensorOutput = !liveSensorOutput; // toggeln
|
||||
if (liveSensorOutput)
|
||||
{
|
||||
Serial.println("Sensor-Live-Ausgabe: ON");
|
||||
}
|
||||
else
|
||||
{
|
||||
Serial.println("Sensor-Live-Ausgabe: OFF");
|
||||
}
|
||||
break;
|
||||
}
|
||||
Serial.println(input);
|
||||
}
|
||||
@@ -306,10 +342,11 @@ void printHelp()
|
||||
Serial.println("h - Show this help");
|
||||
Serial.println("+ - Increment PWM");
|
||||
Serial.println("- - Decrement PWM");
|
||||
Serial.println("S - Toggle debug Pedal-Input");
|
||||
}
|
||||
|
||||
void configurePWMFrequency()
|
||||
{
|
||||
// Configure Timer1 for higher PWM frequency (~4 kHz)
|
||||
TCCR1B = (1<<CS11); // Set prescaler to 8
|
||||
TCCR1B = (1 << CS11); // Set prescaler to 8
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user