massive Rework

This commit is contained in:
2025-12-04 11:10:15 +01:00
parent 23c7acd386
commit 71cd463b04
7 changed files with 758 additions and 387 deletions

View File

@@ -1,68 +1,62 @@
// MotorController.cpp
#include "MotorController.h"
// Default-Konfiguration
const MotorConfig defaultConfig = {
.minPWM = 0,
.maxPWM = 255,
.inverted = true,
.responseSpeed = 50,
.lookupTable = {0, 10, 20, 30, 40, 50, 60, 70, 80, 100} // Percent values for 0% to 100%
.responseSpeed = 50, // ca. 50% pro Sekunde
.lookupTable = {0, 10, 20, 30, 40, 50, 60, 70, 80, 100} // 0..100% Kennlinie
};
MotorController::MotorController(int motorPin, bool *debugFlag)
: motorPWMPin(motorPin), currentSpeed(0), targetSpeed(0), debugFlag(debugFlag)
MotorController::MotorController(const MotorSignals &signals)
: signals_(signals)
{
loadDefaults();
pinMode(motorPWMPin, OUTPUT);
stopMotor();
// Pin initialisieren, falls sinnvoll
if (signals_.pwmPin >= 0)
{
pinMode(signals_.pwmPin, OUTPUT);
analogWrite(signals_.pwmPin, 0);
}
smoothedPromille = 0;
lastUpdateTime = millis();
lastPWM = 0;
lastStatusPrint = 0;
lastTargetPercent = 0;
lastSpeedPercent = 0;
}
void MotorController::setSignals(const MotorSignals &signals)
{
signals_ = signals;
// Bei neuer Verdrahtung ggf. PinMode setzen
if (signals_.pwmPin >= 0)
{
pinMode(signals_.pwmPin, OUTPUT);
}
}
void MotorController::loadDefaults()
{
config = defaultConfig;
}
void MotorController::setTargetSpeed(int targetSpeedPercent)
{
targetSpeed = constrain(targetSpeedPercent, 0, 100);
}
void MotorController::maintain()
{
targetSpeed = constrain(targetSpeed, 0, 100); // Ensure targetSpeed is valid
int adjustedSpeed = applyDynamicResponse(targetSpeed); // Dynamic adjustment in percentage
// Interpolate between lookup table values
int lowerIndex = map(adjustedSpeed, 0, 100, 0, MotorConfig::lookupTableSize - 1);
int upperIndex = min(lowerIndex + 1, MotorConfig::lookupTableSize - 1);
int lowerPercent = config.lookupTable[lowerIndex];
int upperPercent = config.lookupTable[upperIndex];
float interpolationFactor = (adjustedSpeed - lowerIndex * (100 / (MotorConfig::lookupTableSize - 1))) / (100.0 / (MotorConfig::lookupTableSize - 1));
int interpolatedPWM = map(lowerPercent + interpolationFactor * (upperPercent - lowerPercent), 0, 100, config.minPWM, config.maxPWM);
setPWM(interpolatedPWM); // Apply final interpolated PWM value
currentSpeed = adjustedSpeed; // Update currentSpeed in percentage
if (debugFlag && *debugFlag)
// responseSpeed absichern
if (config.responseSpeed <= 0)
{
Serial.print("[MOTOR] current=");
Serial.print(currentSpeed);
Serial.print(" target=");
Serial.println(targetSpeed);
config.responseSpeed = 50; // Fallback
}
}
int MotorController::getSpeed() const
{
return currentSpeed;
}
void MotorController::stopMotor()
{
setTargetSpeed(0);
maintain();
// min/max sanity
if (config.maxPWM < config.minPWM)
{
uint8_t tmp = config.minPWM;
config.minPWM = config.maxPWM;
config.maxPWM = tmp;
}
}
MotorConfig MotorController::getConfig() const
@@ -73,46 +67,173 @@ MotorConfig MotorController::getConfig() const
void MotorController::setConfig(const MotorConfig &newConfig)
{
config = newConfig;
// min/max sanity
if (config.maxPWM < config.minPWM)
{
uint8_t tmp = config.minPWM;
config.minPWM = config.maxPWM;
config.maxPWM = tmp;
}
if (config.responseSpeed <= 0)
{
config.responseSpeed = 50;
}
}
void MotorController::setPWM(uint8_t pwmValue)
void MotorController::maintain()
{
int constrainedPWM = constrain(pwmValue, config.minPWM, config.maxPWM);
analogWrite(motorPWMPin, config.inverted ? 255 - constrainedPWM : constrainedPWM);
// 1) Target bestimmen
int16_t targetPercent = 0;
if (signals_.targetSpeedPercent)
{
targetPercent = constrain(*signals_.targetSpeedPercent, 0, 100);
}
// Enable beachten: wenn vorhanden und false -> Ziel = 0
if (signals_.enabled && !*signals_.enabled)
{
targetPercent = 0;
}
lastTargetPercent = targetPercent;
// 2) Dynamische Rampenbildung
int16_t adjustedSpeed = applyDynamicResponse(targetPercent); // 0..100%
lastSpeedPercent = adjustedSpeed;
// 3) Lookup-Interpolation von Prozent -> LUT-Prozent
const int16_t n = MotorConfig::lookupTableSize;
float pos = (adjustedSpeed / 100.0f) * (n - 1); // 0..(n-1) als float
int16_t lowerIndex = (int16_t)pos;
if (lowerIndex < 0)
lowerIndex = 0;
if (lowerIndex > n - 1)
lowerIndex = n - 1;
int16_t upperIndex = lowerIndex + 1;
if (upperIndex > n - 1)
upperIndex = n - 1;
float frac = pos - lowerIndex; // 0..1 zwischen den beiden LUT-Punkten
int16_t lowerPercent = config.lookupTable[lowerIndex];
int16_t upperPercent = config.lookupTable[upperIndex];
float lutPercent = lowerPercent + frac * (upperPercent - lowerPercent);
// LUT liefert 0..100% -> auf PWM-Bereich skalieren
int16_t pwmLogical = map((int16_t)lutPercent, 0, 100, config.minPWM, config.maxPWM);
pwmLogical = constrain(pwmLogical, config.minPWM, config.maxPWM);
lastPWM = (uint8_t)pwmLogical;
// 4) PWM ausgeben
setPWM(lastPWM);
// 5) Ausgänge in Signals spiegeln
if (signals_.currentSpeedPercent)
*signals_.currentSpeedPercent = adjustedSpeed;
if (signals_.currentPWM)
*signals_.currentPWM = lastPWM;
// 6) Debug-Ausgabe
printStatus();
}
int MotorController::applyDynamicResponse(int targetPercent)
void MotorController::stopMotor()
{
// Intern in Promille arbeiten für höhere Genauigkeit
static int smoothedPromille = 0;
static unsigned long lastUpdateTime = millis();
// Hard Stop: Rampe und PWM sofort auf 0 ziehen
smoothedPromille = 0;
lastSpeedPercent = 0;
lastTargetPercent = 0;
lastPWM = 0;
unsigned long currentTime = millis();
unsigned long elapsedTime = currentTime - lastUpdateTime;
setPWM(0);
if (signals_.currentSpeedPercent)
*signals_.currentSpeedPercent = 0;
if (signals_.currentPWM)
*signals_.currentPWM = 0;
}
int16_t MotorController::applyDynamicResponse(int16_t targetPercent)
{
uint32_t currentTime = millis();
uint32_t elapsedTime = currentTime - lastUpdateTime;
if (elapsedTime == 0)
return smoothedPromille / 10; // Rückgabe in Prozent
{
return (smoothedPromille + 5) / 10; // Rückgabe in %
}
// Zielwert in Promille berechnen
int targetPromille = targetPercent * 10;
targetPercent = constrain(targetPercent, 0, 100);
int16_t targetPromille = targetPercent * 10;
// Dynamische Reaktionsgeschwindigkeit in Promille pro Sekunde
int maxChangePerMs = config.responseSpeed * 10; // Konfigurierbarer Faktor
// responseSpeed = Prozent pro Sekunde → Promille pro Sekunde
int16_t maxChangePerSec = config.responseSpeed * 10;
// Berechne maximale Änderung basierend auf vergangener Zeit
int maxDelta = (maxChangePerMs * elapsedTime) / 1000;
// maximaler Delta in diesem Zeitintervall
int16_t maxDelta = (int16_t)((maxChangePerSec * (int32_t)elapsedTime) / 1000L);
// Tatsächliche Änderung begrenzen
int delta = targetPromille - smoothedPromille;
int16_t delta = targetPromille - smoothedPromille;
if (delta > maxDelta)
delta = maxDelta;
else if (delta < -maxDelta)
delta = -maxDelta;
// Smoothed Promille aktualisieren
smoothedPromille += delta;
lastUpdateTime = currentTime;
// Rückgabe in Prozent (gerundet)
return (smoothedPromille + 5) / 10; // Rundung durch Addition von 5 vor Division
return (smoothedPromille + 5) / 10; // runden
}
void MotorController::setPWM(uint8_t pwmLogical)
{
if (signals_.pwmPin < 0)
return;
uint8_t pwmOut = pwmLogical;
if (config.inverted)
{
// Logischer Wert im Bereich [min,max] wird gespiegelt
int16_t span = (int16_t)config.maxPWM - (int16_t)config.minPWM;
int16_t offset = (int16_t)pwmLogical - (int16_t)config.minPWM;
int16_t invOffset = span - offset;
int16_t inv = (int16_t)config.minPWM + invOffset;
if (inv < 0)
inv = 0;
if (inv > 255)
inv = 255;
pwmOut = (uint8_t)inv;
}
analogWrite(signals_.pwmPin, pwmOut);
}
void MotorController::printStatus()
{
if (!(signals_.debugFlag && *signals_.debugFlag))
return;
uint32_t now = millis();
if (now - lastStatusPrint < 1000)
return; // nur jede Sekunde
lastStatusPrint = now;
Serial.print("[MOTOR] target=");
Serial.print(lastTargetPercent);
Serial.print("% current=");
Serial.print(lastSpeedPercent);
Serial.print("% pwm=");
Serial.print(lastPWM);
Serial.print(" resp=");
Serial.print(config.responseSpeed);
Serial.print(" inv=");
Serial.println(config.inverted ? "Y" : "N");
}

View File

@@ -1,43 +1,67 @@
// MotorController.h
#ifndef MOTORCONTROLLER_H
#define MOTORCONTROLLER_H
#include <Arduino.h>
// Motor configuration structure
struct MotorConfig
{
uint8_t minPWM;
uint8_t maxPWM;
bool inverted;
int responseSpeed;
static const int lookupTableSize = 10;
int16_t responseSpeed; // Prozent pro Sekunde
static const int16_t lookupTableSize = 10;
uint8_t lookupTable[lookupTableSize];
};
typedef struct MotorSignals
{
// Pin (by value)
int16_t pwmPin; // PWM-Ausgangspin, <0 = deaktiviert
// Eingänge
int16_t *targetSpeedPercent; // Sollwert 0..100%
bool *enabled; // optionaler Enable (true = aktiv)
// Ausgänge
int16_t *currentSpeedPercent; // Istwert 0..100%
uint8_t *currentPWM; // tatsächlicher PWM-Wert (logisch)
// Debug
bool *debugFlag; // globales Debug-Flag
} MotorSignals;
class MotorController
{
private:
int motorPWMPin;
int currentSpeed;
int targetSpeed;
bool *debugFlag;
MotorConfig config;
int applyDynamicResponse(int targetValue);
void setPWM(uint8_t pwmValue);
public:
MotorController(int motorPin, bool *debugFlag = nullptr);
explicit MotorController(const MotorSignals &signals);
void setSignals(const MotorSignals &signals);
// zyklisch in loop() aufrufen
void maintain();
// Hard Stop
void stopMotor();
void setTargetSpeed(int targetSpeedPercent);
int getSpeed() const;
MotorConfig getConfig() const;
void setConfig(const MotorConfig &newConfig);
void loadDefaults();
private:
MotorSignals signals_;
MotorConfig config;
// interner Dynamik-State
int16_t smoothedPromille = 0; // 0..1000
uint32_t lastUpdateTime = 0;
uint8_t lastPWM = 0;
uint32_t lastStatusPrint = 0;
int16_t lastTargetPercent = 0;
int16_t lastSpeedPercent = 0;
int16_t applyDynamicResponse(int16_t targetPercent);
void setPWM(uint8_t pwmLogical);
void printStatus();
};
#endif // MOTORCONTROLLER_H

View File

@@ -11,28 +11,25 @@ static const PedalConfig defaultConfig = {
};
// Tuning-Konstanten für die Auto-Cal
static const int STABLE_THRESHOLD = 3; // ADC-Delta, ab dem Stabilität neu bewertet wird
static const unsigned long STABLE_TIME = 1000; // ms, die der Wert stabil sein muss
static const int NOISE_TOLERANCE = 5; // max. erlaubtes Rauschen im Stabilitätsfenster
static const int MIN_RANGE = 50; // minimale ADC-Spannweite für gültigen Pedalweg
static const int SAFETY_MARGIN_LOW = 3; // Untere Grenze etwas anheben
static const int SAFETY_MARGIN_HIGH = 3; // Obere Grenze etwas absenken
static const int MOVE_DELTA = 15; // min. Abstand zur Referenz für "echte Bewegung"
static const int NEAR_TOLERANCE = 10; // max. Differenz zw. Wiederholmessungen
static const uint8_t MIN_FS = 1; // Filterstärke min.
static const uint8_t MAX_FS = 32; // Filterstärke max.
static const uint8_t DEFAULT_FS = 3; // Standard-Filterstärke
static const int16_t STABLE_THRESHOLD = 3; // ADC-Delta, ab dem Stabilität neu bewertet wird
static const uint32_t STABLE_TIME = 1000; // ms, die der Wert stabil sein muss
static const int16_t NOISE_TOLERANCE = 5; // max. erlaubtes Rauschen im Stabilitätsfenster
static const int16_t MIN_RANGE = 50; // minimale ADC-Spannweite für gültigen Pedalweg
static const int16_t SAFETY_MARGIN_LOW = 3; // Untere Grenze etwas anheben
static const int16_t SAFETY_MARGIN_HIGH = 3; // Obere Grenze etwas absenken
static const int16_t MOVE_DELTA = 15; // min. Abstand zur Referenz für "echte Bewegung"
static const int16_t NEAR_TOLERANCE = 10; // max. Differenz zw. Wiederholmessungen
static const uint8_t MIN_FS = 1; // Filterstärke min.
static const uint8_t MAX_FS = 32; // Filterstärke max.
static const uint8_t DEFAULT_FS = 3; // Standard-Filterstärke
PedalController::PedalController(int analogPin,
int digitalPin,
uint8_t digitalNotPressedLevel,
bool *debugFlag)
: analogPin(analogPin),
digitalPin(digitalPin),
useDigital(digitalPin >= 0),
digitalNotPressedLevel(digitalNotPressedLevel ? 1 : 0),
debugFlag(debugFlag),
PedalController::PedalController(const PedalSignals &signals,
uint8_t digitalNotPressedLevel_)
: signals_(signals),
digitalNotPressedLevel(digitalNotPressedLevel_ ? 1 : 0),
config(),
filteredValue(-1),
lastPedalPercent(0),
calState(IDLE),
calStep(0),
stepStartTime(0),
@@ -49,21 +46,25 @@ PedalController::PedalController(int analogPin,
step3NearReleased(false),
step4NearPressed(false)
{
if (useDigital)
// Digital-Pin als INPUT konfigurieren, falls vorhanden
if (signals_.digitalPin >= 0)
{
pinMode(digitalPin, INPUT); // nur noch Debug
pinMode(signals_.digitalPin, INPUT);
}
loadDefaults();
}
// Rohwert lesen (unglättet)
int PedalController::readRaw()
int16_t PedalController::readRaw()
{
return analogRead(analogPin);
if (signals_.analogPin < 0)
return 0;
return analogRead(signals_.analogPin);
}
int PedalController::applySmoothing(int raw)
int16_t PedalController::applySmoothing(int16_t raw)
{
uint8_t fs = config.filterStrength;
@@ -74,6 +75,7 @@ int PedalController::applySmoothing(int raw)
return raw;
}
// Wenn Wert steigt: sofort übernehmen (reagiert schneller nach oben)
if (raw >= filteredValue)
{
filteredValue = raw;
@@ -90,12 +92,46 @@ int PedalController::applySmoothing(int raw)
int32_t acc = filteredValue * (int32_t)(fs - 1) + raw;
filteredValue = (acc + (fs / 2)) / fs;
return (int)filteredValue;
return (int16_t)filteredValue;
}
int16_t PedalController::mapRawToPercent(int16_t raw)
{
// Ohne gültige Kalibrierung: 0 %
if (!config.calibrated)
{
return 0;
}
int16_t minR = config.minRaw; // gedrückt → niedriger
int16_t maxR = config.maxRaw; // losgelassen → höher
if (maxR <= minR + 1)
{
return 0;
}
if (raw < minR)
raw = minR;
if (raw > maxR)
raw = maxR;
int32_t span = (int32_t)maxR - (int32_t)minR;
// invertiertes Mapping, weil "hoch = losgelassen"
int32_t scaled = (int32_t)(maxR - raw) * 100L / span;
// kleine Deadzone unten (Pedal "nicht gedrückt")
if (scaled < 3)
scaled = 0;
if (scaled > 100)
scaled = 100;
return (int16_t)scaled;
}
CalibrationState PedalController::autoCalibrate(bool reset)
{
unsigned long now = millis();
uint32_t now = millis();
if (reset)
{
@@ -133,8 +169,8 @@ CalibrationState PedalController::autoCalibrate(bool reset)
return calState;
}
int raw = applySmoothing(readRaw());
int diff = abs(raw - lastSample);
int16_t raw = applySmoothing(readRaw());
int16_t diff = abs(raw - lastSample);
switch (calStep)
{
@@ -155,7 +191,7 @@ CalibrationState PedalController::autoCalibrate(bool reset)
if (now - lastChangeTime > STABLE_TIME)
{
int noise = maxSeen - minSeen;
int16_t noise = maxSeen - minSeen;
if (noise > NOISE_TOLERANCE)
{
Serial.print("CAL: FEHLER - zu viel Rauschen im losgelassenen Zustand (noise=");
@@ -327,7 +363,7 @@ CalibrationState PedalController::autoCalibrate(bool reset)
case 5:
{ // 5. Final loslassen & stabil -> finalisieren
// Ziel-Release-Punkt aus den beiden Messungen mitteln
int relTarget = (firstReleased + secondReleased) / 2;
int16_t relTarget = (firstReleased + secondReleased) / 2;
// Sind wir schon nah genug an der Release-Position?
if (abs(raw - relTarget) > MOVE_DELTA)
@@ -348,15 +384,15 @@ CalibrationState PedalController::autoCalibrate(bool reset)
if (now - lastChangeTime > STABLE_TIME)
{
int relAvg = relTarget;
int pressAvg = (firstPressed + secondPressed) / 2;
int16_t relAvg = relTarget;
int16_t pressAvg = (firstPressed + secondPressed) / 2;
int low = pressAvg;
int high = relAvg;
int16_t low = pressAvg;
int16_t high = relAvg;
if (high < low)
{
int tmp = low;
int16_t tmp = low;
low = high;
high = tmp;
}
@@ -414,40 +450,39 @@ CalibrationState PedalController::getStatus() const
return calState;
}
int PedalController::getPedal()
int16_t PedalController::getPedal()
{
// Ohne gültige Kalibrierung: kein Pedal
if (!config.calibrated)
int16_t raw = applySmoothing(readRaw());
int16_t percent = mapRawToPercent(raw);
lastPedalPercent = percent;
return percent;
}
void PedalController::maintain()
{
int16_t raw = applySmoothing(readRaw());
// Outputs befüllen
if (signals_.rawValue)
*signals_.rawValue = raw;
// digitaler Eingang (falls vorhanden)
if (signals_.digitalState)
{
return 0;
if (signals_.digitalPin >= 0)
*signals_.digitalState = digitalRead(signals_.digitalPin);
else
*signals_.digitalState = -1;
}
int raw = applySmoothing(readRaw());
int16_t percent = mapRawToPercent(raw);
lastPedalPercent = percent;
int minR = config.minRaw; // gedrückt → niedriger
int maxR = config.maxRaw; // losgelassen → höher
if (signals_.pedalPercent)
*signals_.pedalPercent = percent;
if (maxR <= minR + 1)
{
return 0;
}
if (raw < minR)
raw = minR;
if (raw > maxR)
raw = maxR;
long span = (long)maxR - (long)minR;
// WICHTIG: invertiertes Mapping, weil "hoch = losgelassen"
long scaled = (long)(maxR - raw) * 100L / span;
// kleine Deadzone unten (Pedal "nicht gedrückt")
if (scaled < 3)
scaled = 0;
if (scaled > 100)
scaled = 100;
return (int)scaled;
if (signals_.calibrated)
*signals_.calibrated = config.calibrated;
}
PedalConfig PedalController::getConfig() const
@@ -503,14 +538,24 @@ void PedalController::loadDefaults()
step4NearPressed = false;
}
void PedalController::printStatus(int raw)
void PedalController::setSignals(const PedalSignals &signals)
{
if (!(debugFlag && *debugFlag))
signals_ = signals;
if (signals_.digitalPin >= 0)
{
pinMode(signals_.digitalPin, INPUT);
}
}
void PedalController::printStatus(int16_t raw)
{
if (!(signals_.debugFlag && *signals_.debugFlag))
{
return;
}
unsigned long now = millis();
uint32_t now = millis();
if (now - lastStatusPrint < 1000)
return; // nur jede Sekunde
lastStatusPrint = now;
@@ -523,9 +568,9 @@ void PedalController::printStatus(int raw)
Serial.print(filteredValue);
Serial.print(" DO-State=");
if (useDigital)
if (signals_.digitalPin >= 0)
{
Serial.print(digitalRead(digitalPin));
Serial.print(digitalRead(signals_.digitalPin));
}
else
{
@@ -543,21 +588,3 @@ void PedalController::printStatus(int raw)
Serial.println();
}
// Debug-Methoden
int PedalController::debugGetRaw()
{
return readRaw();
}
int PedalController::debugGetSmooth()
{
return applySmoothing(readRaw());
}
int PedalController::debugGetDO()
{
if (!useDigital)
return -1;
return digitalRead(digitalPin);
}

View File

@@ -4,7 +4,6 @@
#include <Arduino.h>
// Calibration state enumeration
enum CalibrationState
{
IDLE,
@@ -13,72 +12,80 @@ enum CalibrationState
ERROR
};
// Pedal configuration structure
struct PedalConfig
{
int16_t minRaw;
int16_t maxRaw;
bool calibrated;
uint8_t filterStrength; // 1 = kein Filter, >1 = gleitender Mittelwert
bool calibrated;
uint8_t filterStrength;
};
typedef struct PedalSignals
{
// Pins (by value)
int16_t analogPin; // ADC-Pin
int16_t digitalPin; // -1 falls unbenutzt
// Eingänge
bool *debugFlag; // globales Debug-Flag
// Ausgänge
int16_t *pedalPercent; // 0..100%
int16_t *rawValue; // gefilterter Rawwert
bool *calibrated; // Status
int16_t *digitalState; // DO-Status oder -1
} PedalSignals;
class PedalController
{
public:
explicit PedalController(int analogPin,
int digitalPin = -1,
uint8_t digitalNotPressedLevel = LOW,
bool *debugFlag = nullptr);
PedalController(const PedalSignals &signals,
uint8_t digitalNotPressedLevel = LOW);
void setSignals(const PedalSignals &signals);
// Startet / läuft Auto-Kalibrierung
CalibrationState autoCalibrate(bool reset);
CalibrationState getStatus() const;
// 0100 %, bei nicht kalibriert → 0
int getPedal();
int16_t getPedal(); // alte API (blocking)
void maintain(); // neue Non-Blocking-API
PedalConfig getConfig() const;
void setConfig(const PedalConfig &newConfig);
void setConfig(const PedalConfig &cfg);
void loadDefaults();
// Debug-Helfer
int debugGetRaw();
int debugGetSmooth();
int debugGetDO();
private:
int analogPin;
int digitalPin;
bool useDigital;
uint8_t digitalNotPressedLevel; // nur Debug
bool *debugFlag;
PedalSignals signals_;
uint8_t digitalNotPressedLevel; // nur Debug / Interpretation
PedalConfig config;
int32_t filteredValue;
int16_t lastPedalPercent;
// Auto-Cal intern
CalibrationState calState;
uint8_t calStep;
unsigned long stepStartTime;
unsigned long lastChangeTime;
int lastSample;
int minSeen;
int maxSeen;
unsigned long lastStatusPrint;
uint8_t calStep;
uint32_t stepStartTime;
uint32_t lastChangeTime;
int16_t lastSample;
int16_t minSeen;
int16_t maxSeen;
uint32_t lastStatusPrint;
// Vier Referenzpunkte aus dem Ablauf
int firstReleased;
int firstPressed;
int secondReleased;
int secondPressed;
int16_t firstReleased;
int16_t firstPressed;
int16_t secondReleased;
int16_t secondPressed;
bool step2MovedEnough;
bool step3NearReleased;
bool step4NearPressed;
int readRaw();
int applySmoothing(int raw);
void printStatus(int raw);
int16_t readRaw();
int16_t applySmoothing(int16_t raw);
int16_t mapRawToPercent(int16_t raw);
void printStatus(int16_t raw);
};
#endif // PEDALCONTROLLER_H

155
src/SerialController.cpp Normal file
View File

@@ -0,0 +1,155 @@
#include <Arduino.h>
#include "SerialController.h"
SerialController::SerialController(const SerialSignals &signals)
: signals_(signals)
{
}
void SerialController::maintain()
{
while (Serial.available() > 0)
{
char c = Serial.read();
handleCommand(c);
}
}
void SerialController::handleCommand(char c)
{
switch (c)
{
case 'h':
printHelp();
break;
case 'd': // Debug toggeln
if (signals_.debugFlag)
{
*signals_.debugFlag = !*signals_.debugFlag;
Serial.print("Debug: ");
Serial.println(*signals_.debugFlag ? "ON" : "OFF");
}
break;
case '+': // Geschwindigkeit +5%
if (signals_.targetSpeedPercent)
{
if (signals_.serialControlEnabled)
*signals_.serialControlEnabled = true;
int16_t v = *signals_.targetSpeedPercent;
v = constrain(v + 5, (int16_t)0, (int16_t)100);
*signals_.targetSpeedPercent = v;
Serial.print("Target +: ");
Serial.print(v);
Serial.println("%");
}
break;
case '-': // Geschwindigkeit -5%
if (signals_.targetSpeedPercent)
{
if (signals_.serialControlEnabled)
*signals_.serialControlEnabled = true;
int16_t v = *signals_.targetSpeedPercent;
v = constrain(v - 5, (int16_t)0, (int16_t)100);
*signals_.targetSpeedPercent = v;
Serial.print("Target -: ");
Serial.print(v);
Serial.println("%");
}
break;
// direkte 0100%-Vorgabe in 10%-Schritten
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
if (signals_.targetSpeedPercent)
{
if (signals_.serialControlEnabled)
*signals_.serialControlEnabled = true;
int16_t v = (c - '0') * 10;
v = constrain(v, (int16_t)0, (int16_t)100);
*signals_.targetSpeedPercent = v;
Serial.print("Target set: ");
Serial.print(v);
Serial.println("%");
}
break;
case 'x': // Serielle Steuerung stoppen & Motor freigeben
if (signals_.serialControlEnabled)
*signals_.serialControlEnabled = false;
if (signals_.targetSpeedPercent)
*signals_.targetSpeedPercent = 0;
Serial.println("Serial control disabled, target=0%.");
break;
case 'c': // Auto-Cal anfordern
if (signals_.requestAutoCal)
{
*signals_.requestAutoCal = true;
Serial.println("Request: Auto-Calibration.");
}
break;
case 's': // Save EEPROM
if (signals_.requestSaveEEPROM)
{
*signals_.requestSaveEEPROM = true;
Serial.println("Request: Save settings to EEPROM.");
}
break;
case 'l': // Load EEPROM
if (signals_.requestLoadEEPROM)
{
*signals_.requestLoadEEPROM = true;
Serial.println("Request: Load settings from EEPROM.");
}
break;
case 'r': // Defaults laden
if (signals_.requestLoadDefaults)
{
*signals_.requestLoadDefaults = true;
Serial.println("Request: Load defaults.");
}
break;
default:
// Optional: unerkanntes Kommando anzeigen
// Serial.print("Unknown command: ");
// Serial.println(c);
break;
}
}
void SerialController::printHelp()
{
Serial.println(F("=== Serial Control Help ==="));
Serial.println(F("h - Show this help"));
Serial.println(F("d - Toggle debug output"));
Serial.println(F("c - Request auto-calibration"));
Serial.println(F("s - Request save settings to EEPROM"));
Serial.println(F("l - Request load settings from EEPROM"));
Serial.println(F("r - Request load defaults"));
Serial.println(F("x - Disable serial control, set target=0%"));
Serial.println(F("+ - Increase target speed by 5%"));
Serial.println(F("- - Decrease target speed by 5%"));
Serial.println(F("0-9- Set target speed in 10% steps (0..100%)"));
}

43
src/SerialController.h Normal file
View File

@@ -0,0 +1,43 @@
#ifndef SERIALCONTROLLER_H
#define SERIALCONTROLLER_H
#include <Arduino.h>
#include "MotorController.h"
#include "PedalController.h"
typedef struct SerialSignals
{
int16_t *targetSpeedPercent; // Motor-Sollwert in %
bool *debugFlag; // globales Debug-Flag
// Steuerflags (werden in main ausgewertet)
bool *requestAutoCal;
bool *requestSaveEEPROM;
bool *requestLoadEEPROM;
bool *requestLoadDefaults;
bool *serialControlEnabled; // schaltet „Serielle Steuerung“ an/aus
// Optional: Zeiger auf Configs für Dumps
MotorConfig *motorConfig;
PedalConfig *pedalConfig;
} SerialSignals;
class SerialController
{
public:
SerialController(const SerialSignals &signals);
// im loop() aufrufen
void maintain();
// einmalig bei Start oder auf Kommando
void printHelp();
private:
SerialSignals signals_;
void handleCommand(char c);
};
#endif

View File

@@ -2,8 +2,10 @@
#include <EEPROM.h>
#include <Wire.h>
#include <Adafruit_SSD1306.h>
#include "MotorController.h"
#include "PedalController.h"
#include "SerialController.h"
// OLED display setup
#define OLED_RESET -1
@@ -11,20 +13,36 @@
#define SCREEN_HEIGHT 64
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 pedalPinAnalog = A0; // Pedal analog Input pin
const int pedalPinDigital = 8; // Pedal digital Input pin
// Pin definitions (Werte, keine Pointer)
const int16_t hallSensorPin = 2; // Hall sensor input
const int16_t encoderPinA = 3; // Encoder pin A
const int16_t encoderPinB = 4; // Encoder pin B
const int16_t encoderButtonPin = 5; // Encoder button
const int16_t motorPwmPin = 13; // Motor PWM pin
const int16_t pedalPinAnalog = A0; // Pedal analog Input pin
const int16_t pedalPinDigital = 8; // Pedal digital Input pin
// Debug-Flag (gemeinsam für alle Controller)
bool debug = false;
// Global objects
MotorController motor(motorPin, &debug);
PedalController pedal(pedalPinAnalog, pedalPinDigital, LOW, &debug);
// Globale Signale für Motor
int16_t gTargetSpeedPercent = 0; // Sollwert 0..100%
int16_t gCurrentSpeedPercent = 0; // Istwert 0..100%
uint8_t gCurrentPwm = 0; // aktueller PWM-Wert (logisch)
bool gMotorEnabled = true;
// Globale Signale für Pedal
int16_t gPedalPercent = 0;
int16_t gPedalRaw = 0;
bool gPedalCalibrated = false;
int16_t gPedalDigitalState = 0;
// Serial-Steuerflags
bool gReqAutoCal = false;
bool gReqSaveEEPROM = false;
bool gReqLoadEEPROM = false;
bool gReqDefaults = false;
bool gSerialControl = false;
// Combined settings structure
struct Settings
@@ -35,12 +53,42 @@ struct Settings
};
Settings settings;
bool autoCalibrating = false;
bool serialControlEnabled = false;
CalibrationState lastCalibrationState = IDLE;
bool autoCalibrating = false;
bool liveSensorOutput = false;
unsigned long lastSensorPrint = 0;
// Signals-Instanzen
MotorSignals motorSignals{
.pwmPin = motorPwmPin,
.targetSpeedPercent = &gTargetSpeedPercent,
.enabled = &gMotorEnabled,
.currentSpeedPercent = &gCurrentSpeedPercent,
.currentPWM = &gCurrentPwm,
.debugFlag = &debug};
PedalSignals pedalSignals{
.analogPin = pedalPinAnalog,
.digitalPin = pedalPinDigital,
.debugFlag = &debug,
.pedalPercent = &gPedalPercent,
.rawValue = &gPedalRaw,
.calibrated = &gPedalCalibrated,
.digitalState = &gPedalDigitalState};
SerialSignals serialSignals{
.targetSpeedPercent = &gTargetSpeedPercent,
.debugFlag = &debug,
.requestAutoCal = &gReqAutoCal,
.requestSaveEEPROM = &gReqSaveEEPROM,
.requestLoadEEPROM = &gReqLoadEEPROM,
.requestLoadDefaults = &gReqDefaults,
.serialControlEnabled = &gSerialControl,
.motorConfig = &settings.motorConfig,
.pedalConfig = &settings.pedalConfig};
// Global objects
MotorController motor(motorSignals);
PedalController pedal(pedalSignals, LOW);
SerialController serialCtrl(serialSignals);
// Function prototypes
void loadSettings();
@@ -49,48 +97,82 @@ uint8_t calculateChecksum(const Settings &s);
void handleEncoder();
void handleEncoderButton();
void updateDisplay();
void handleSerialInput();
void configurePWMFrequency();
void stopMotorAtNeedleUp();
void printHelp();
void printStatusLine();
// ----------------- SETUP -----------------
void setup()
{
// Initialize pins
pinMode(hallSensor, INPUT);
// Pins
pinMode(hallSensorPin, INPUT);
pinMode(encoderPinA, INPUT_PULLUP);
pinMode(encoderPinB, INPUT_PULLUP);
pinMode(encoderButton, INPUT_PULLUP);
pinMode(encoderButtonPin, INPUT_PULLUP);
pinMode(LED_BUILTIN, OUTPUT);
// Configure PWM frequency
configurePWMFrequency();
// Attach interrupts for encoder
attachInterrupt(digitalPinToInterrupt(encoderPinA), handleEncoder, CHANGE);
attachInterrupt(digitalPinToInterrupt(encoderPinB), handleEncoder, CHANGE);
attachInterrupt(digitalPinToInterrupt(encoderButton), handleEncoderButton, FALLING);
attachInterrupt(digitalPinToInterrupt(encoderButtonPin), handleEncoderButton, FALLING);
// Initialize serial and load settings
Serial.begin(9600);
loadSettings();
// Initialize OLED display
// if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
// Serial.println(F("SSD1306 allocation failed"));
// }
// display.clearDisplay();
// display.display();
// OLED optional
// if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
// Serial.println(F("SSD1306 allocation failed"));
// }
// display.clearDisplay();
// display.display();
printHelp();
serialCtrl.printHelp();
}
// ----------------- LOOP -----------------
void loop()
{
// Handle serial input for debugging and manual control
handleSerialInput();
motor.maintain();
// Alle seriellen Befehle in Signale schreiben lassen
serialCtrl.maintain();
// Serial-Requests auswerten
if (gReqDefaults)
{
gReqDefaults = false;
Serial.println("Loading defaults.");
motor.loadDefaults();
pedal.loadDefaults();
}
if (gReqLoadEEPROM)
{
gReqLoadEEPROM = false;
Serial.println("Loading settings from EEPROM.");
loadSettings();
}
if (gReqSaveEEPROM)
{
gReqSaveEEPROM = false;
Serial.println("Saving settings to EEPROM.");
saveSettings();
}
if (gReqAutoCal)
{
gReqAutoCal = false;
Serial.println("Starting auto-calibration...");
pedal.autoCalibrate(true);
autoCalibrating = true;
lastCalibrationState = IDLE;
gTargetSpeedPercent = 0;
gSerialControl = false;
}
if (autoCalibrating)
{
@@ -105,85 +187,71 @@ void loop()
break;
case SUCCESS:
Serial.println("Auto-calibration completed.");
autoCalibrating = false;
break;
case ERROR:
Serial.println("Auto-calibration failed.");
autoCalibrating = false;
break;
default:
break;
}
}
motor.setTargetSpeed(0); // Ensure motor stays off during calibration
}
else if (serialControlEnabled)
{
static int lastSpeed = 0;
int currSpeed = motor.getSpeed();
if (lastSpeed > currSpeed)
{
Serial.print("Decreased Speed to: ");
Serial.println(currSpeed);
lastSpeed = currSpeed;
}
else if (lastSpeed < currSpeed)
{
Serial.print("Increased Speed to: ");
Serial.println(currSpeed);
lastSpeed = currSpeed;
}
gTargetSpeedPercent = 0; // während Cal immer 0
}
else
{
int pedalValue = pedal.getPedal();
motor.setTargetSpeed(pedalValue);
}
// Nur im Normalbetrieb: Pedal einlesen
pedal.maintain();
if (liveSensorOutput)
{
unsigned long now = millis();
if (now - lastSensorPrint > 200)
if (!gSerialControl)
{
lastSensorPrint = now;
int raw = pedal.debugGetRaw();
int smooth = pedal.debugGetSmooth();
int pedalValue = pedal.getPedal();
int doState = pedal.debugGetDO();
Serial.print("[SENSOR] RAW=");
Serial.print(raw);
Serial.print(" Smooth=");
Serial.print(smooth);
Serial.print(" Pedal%=");
Serial.print(pedalValue);
Serial.print(" DO=");
Serial.println(doState);
// Standard: Pedal → TargetSpeed
gTargetSpeedPercent = gPedalPercent;
}
else
{
// Serielle Steuerung ist aktiv, Target wird von SerialController manipuliert
static int16_t lastSpeed = -1;
if (lastSpeed != gCurrentSpeedPercent)
{
Serial.print("Current Speed: ");
Serial.println(gCurrentSpeedPercent);
lastSpeed = gCurrentSpeedPercent;
}
}
}
// Update display
// updateDisplay();
// Motor nachführen
motor.maintain();
delay(10); // Short delay for stability
// Optional: Display
// updateDisplay();
// Optional: eine Debug-Zeile mit Kerninfos
// printStatusLine();
delay(10);
}
// ----------------- HILFSFUNKTIONEN -----------------
void stopMotorAtNeedleUp()
{
motor.stopMotor();
delay(100); // Stabilize
delay(100);
}
void loadSettings()
{
EEPROM.get(0, settings);
// Validate checksum
if (calculateChecksum(settings) != settings.checksum)
{
Serial.println("Invalid settings checksum, loading defaults.");
motor.loadDefaults();
pedal.autoCalibrate(true);
pedal.loadDefaults();
settings.motorConfig = motor.getConfig();
settings.pedalConfig = pedal.getConfig();
@@ -243,7 +311,7 @@ void handleEncoder()
void handleEncoderButton()
{
// Handle encoder button press
// Encoder-Button-Logik
}
void updateDisplay()
@@ -254,110 +322,36 @@ void updateDisplay()
display.setCursor(0, 0);
display.print("Pedal: ");
display.println(pedal.getPedal());
display.println(gPedalPercent);
display.setCursor(0, 10);
display.print("Target: ");
display.println(gTargetSpeedPercent);
display.setCursor(0, 20);
display.print("Speed: ");
display.println(gCurrentSpeedPercent);
display.display();
}
void handleSerialInput()
{
if (Serial.available())
{
char input = Serial.read();
switch (input)
{
case 'd':
debug = !debug;
Serial.print("Debug: ");
Serial.println(debug ? "ON" : "OFF");
break;
case 'c':
Serial.println("Starting auto-calibration...");
pedal.autoCalibrate(true);
autoCalibrating = true;
lastCalibrationState = IDLE; // Reset state tracking
break;
case 'x':
Serial.println("Stopping auto-calibration or serial control.");
autoCalibrating = false;
serialControlEnabled = false;
motor.stopMotor();
break;
case 'r':
Serial.println("Loading defaults.");
motor.loadDefaults();
pedal.loadDefaults();
break;
case 's':
Serial.println("Saving settings to EEPROM.");
saveSettings();
break;
case 'l':
Serial.println("Loading settings from EEPROM.");
loadSettings();
break;
case 'h':
printHelp();
break;
case '+':
serialControlEnabled = true;
motor.setTargetSpeed(motor.getSpeed() + 1);
break;
case '-':
serialControlEnabled = true;
motor.setTargetSpeed(motor.getSpeed() - 1);
break;
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
if (serialControlEnabled)
{
motor.setTargetSpeed((input - 0x30) * 10);
Serial.print("Set Speed to: ");
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);
}
}
void printHelp()
{
Serial.println("Available commands:");
Serial.println("d - Toggle Calibration debug output");
Serial.println("c - Start auto-calibration");
Serial.println("x - Stop auto-calibration or serial control");
Serial.println("r - Load defaults");
Serial.println("s - Save settings to EEPROM");
Serial.println("l - Load settings from EEPROM");
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
}
void printStatusLine()
{
if (!debug)
return;
Serial.print("[STAT] Pedal=");
Serial.print(gPedalPercent);
Serial.print("% Target=");
Serial.print(gTargetSpeedPercent);
Serial.print("% Speed=");
Serial.print(gCurrentSpeedPercent);
Serial.print("% PWM=");
Serial.println(gCurrentPwm);
}