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" #include "MotorController.h"
// Default-Konfiguration
const MotorConfig defaultConfig = { const MotorConfig defaultConfig = {
.minPWM = 0, .minPWM = 0,
.maxPWM = 255, .maxPWM = 255,
.inverted = true, .inverted = true,
.responseSpeed = 50, .responseSpeed = 50, // ca. 50% pro Sekunde
.lookupTable = {0, 10, 20, 30, 40, 50, 60, 70, 80, 100} // Percent values for 0% to 100% .lookupTable = {0, 10, 20, 30, 40, 50, 60, 70, 80, 100} // 0..100% Kennlinie
}; };
MotorController::MotorController(int motorPin, bool *debugFlag) MotorController::MotorController(const MotorSignals &signals)
: motorPWMPin(motorPin), currentSpeed(0), targetSpeed(0), debugFlag(debugFlag) : signals_(signals)
{ {
loadDefaults(); 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() void MotorController::loadDefaults()
{ {
config = defaultConfig; config = defaultConfig;
}
void MotorController::setTargetSpeed(int targetSpeedPercent) // responseSpeed absichern
{ if (config.responseSpeed <= 0)
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)
{ {
Serial.print("[MOTOR] current="); config.responseSpeed = 50; // Fallback
Serial.print(currentSpeed);
Serial.print(" target=");
Serial.println(targetSpeed);
} }
}
int MotorController::getSpeed() const // min/max sanity
{ if (config.maxPWM < config.minPWM)
return currentSpeed; {
} uint8_t tmp = config.minPWM;
config.minPWM = config.maxPWM;
void MotorController::stopMotor() config.maxPWM = tmp;
{ }
setTargetSpeed(0);
maintain();
} }
MotorConfig MotorController::getConfig() const MotorConfig MotorController::getConfig() const
@@ -73,46 +67,173 @@ MotorConfig MotorController::getConfig() const
void MotorController::setConfig(const MotorConfig &newConfig) void MotorController::setConfig(const MotorConfig &newConfig)
{ {
config = 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); // 1) Target bestimmen
analogWrite(motorPWMPin, config.inverted ? 255 - constrainedPWM : constrainedPWM); 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 // Hard Stop: Rampe und PWM sofort auf 0 ziehen
static int smoothedPromille = 0; smoothedPromille = 0;
static unsigned long lastUpdateTime = millis(); lastSpeedPercent = 0;
lastTargetPercent = 0;
lastPWM = 0;
unsigned long currentTime = millis(); setPWM(0);
unsigned long elapsedTime = currentTime - lastUpdateTime;
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) if (elapsedTime == 0)
return smoothedPromille / 10; // Rückgabe in Prozent {
return (smoothedPromille + 5) / 10; // Rückgabe in %
}
// Zielwert in Promille berechnen targetPercent = constrain(targetPercent, 0, 100);
int targetPromille = targetPercent * 10; int16_t targetPromille = targetPercent * 10;
// Dynamische Reaktionsgeschwindigkeit in Promille pro Sekunde // responseSpeed = Prozent pro Sekunde → Promille pro Sekunde
int maxChangePerMs = config.responseSpeed * 10; // Konfigurierbarer Faktor int16_t maxChangePerSec = config.responseSpeed * 10;
// Berechne maximale Änderung basierend auf vergangener Zeit // maximaler Delta in diesem Zeitintervall
int maxDelta = (maxChangePerMs * elapsedTime) / 1000; int16_t maxDelta = (int16_t)((maxChangePerSec * (int32_t)elapsedTime) / 1000L);
// Tatsächliche Änderung begrenzen int16_t delta = targetPromille - smoothedPromille;
int delta = targetPromille - smoothedPromille;
if (delta > maxDelta) if (delta > maxDelta)
delta = maxDelta; delta = maxDelta;
else if (delta < -maxDelta) else if (delta < -maxDelta)
delta = -maxDelta; delta = -maxDelta;
// Smoothed Promille aktualisieren
smoothedPromille += delta; smoothedPromille += delta;
lastUpdateTime = currentTime; lastUpdateTime = currentTime;
// Rückgabe in Prozent (gerundet) return (smoothedPromille + 5) / 10; // runden
return (smoothedPromille + 5) / 10; // Rundung durch Addition von 5 vor Division }
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 #ifndef MOTORCONTROLLER_H
#define MOTORCONTROLLER_H #define MOTORCONTROLLER_H
#include <Arduino.h> #include <Arduino.h>
// Motor configuration structure
struct MotorConfig struct MotorConfig
{ {
uint8_t minPWM; uint8_t minPWM;
uint8_t maxPWM; uint8_t maxPWM;
bool inverted; bool inverted;
int responseSpeed; int16_t responseSpeed; // Prozent pro Sekunde
static const int lookupTableSize = 10; static const int16_t lookupTableSize = 10;
uint8_t lookupTable[lookupTableSize]; 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 class MotorController
{ {
private:
int motorPWMPin;
int currentSpeed;
int targetSpeed;
bool *debugFlag;
MotorConfig config;
int applyDynamicResponse(int targetValue);
void setPWM(uint8_t pwmValue);
public: public:
MotorController(int motorPin, bool *debugFlag = nullptr); explicit MotorController(const MotorSignals &signals);
void setSignals(const MotorSignals &signals);
// zyklisch in loop() aufrufen
void maintain(); void maintain();
// Hard Stop
void stopMotor(); void stopMotor();
void setTargetSpeed(int targetSpeedPercent);
int getSpeed() const;
MotorConfig getConfig() const; MotorConfig getConfig() const;
void setConfig(const MotorConfig &newConfig); void setConfig(const MotorConfig &newConfig);
void loadDefaults(); 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 #endif // MOTORCONTROLLER_H

View File

@@ -11,28 +11,25 @@ static const PedalConfig defaultConfig = {
}; };
// Tuning-Konstanten für die Auto-Cal // Tuning-Konstanten für die Auto-Cal
static const int STABLE_THRESHOLD = 3; // ADC-Delta, ab dem Stabilität neu bewertet wird static const int16_t 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 uint32_t STABLE_TIME = 1000; // ms, die der Wert stabil sein muss
static const int NOISE_TOLERANCE = 5; // max. erlaubtes Rauschen im Stabilitätsfenster static const int16_t 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 int16_t MIN_RANGE = 50; // minimale ADC-Spannweite für gültigen Pedalweg
static const int SAFETY_MARGIN_LOW = 3; // Untere Grenze etwas anheben static const int16_t SAFETY_MARGIN_LOW = 3; // Untere Grenze etwas anheben
static const int SAFETY_MARGIN_HIGH = 3; // Obere Grenze etwas absenken static const int16_t SAFETY_MARGIN_HIGH = 3; // Obere Grenze etwas absenken
static const int MOVE_DELTA = 15; // min. Abstand zur Referenz für "echte Bewegung" static const int16_t MOVE_DELTA = 15; // min. Abstand zur Referenz für "echte Bewegung"
static const int NEAR_TOLERANCE = 10; // max. Differenz zw. Wiederholmessungen 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 MIN_FS = 1; // Filterstärke min.
static const uint8_t MAX_FS = 32; // Filterstärke max. static const uint8_t MAX_FS = 32; // Filterstärke max.
static const uint8_t DEFAULT_FS = 3; // Standard-Filterstärke static const uint8_t DEFAULT_FS = 3; // Standard-Filterstärke
PedalController::PedalController(int analogPin, PedalController::PedalController(const PedalSignals &signals,
int digitalPin, uint8_t digitalNotPressedLevel_)
uint8_t digitalNotPressedLevel, : signals_(signals),
bool *debugFlag) digitalNotPressedLevel(digitalNotPressedLevel_ ? 1 : 0),
: analogPin(analogPin), config(),
digitalPin(digitalPin),
useDigital(digitalPin >= 0),
digitalNotPressedLevel(digitalNotPressedLevel ? 1 : 0),
debugFlag(debugFlag),
filteredValue(-1), filteredValue(-1),
lastPedalPercent(0),
calState(IDLE), calState(IDLE),
calStep(0), calStep(0),
stepStartTime(0), stepStartTime(0),
@@ -49,21 +46,25 @@ PedalController::PedalController(int analogPin,
step3NearReleased(false), step3NearReleased(false),
step4NearPressed(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(); loadDefaults();
} }
// Rohwert lesen (unglättet) // 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; uint8_t fs = config.filterStrength;
@@ -74,6 +75,7 @@ int PedalController::applySmoothing(int raw)
return raw; return raw;
} }
// Wenn Wert steigt: sofort übernehmen (reagiert schneller nach oben)
if (raw >= filteredValue) if (raw >= filteredValue)
{ {
filteredValue = raw; filteredValue = raw;
@@ -90,12 +92,46 @@ int PedalController::applySmoothing(int raw)
int32_t acc = filteredValue * (int32_t)(fs - 1) + raw; int32_t acc = filteredValue * (int32_t)(fs - 1) + raw;
filteredValue = (acc + (fs / 2)) / fs; 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) CalibrationState PedalController::autoCalibrate(bool reset)
{ {
unsigned long now = millis(); uint32_t now = millis();
if (reset) if (reset)
{ {
@@ -133,8 +169,8 @@ CalibrationState PedalController::autoCalibrate(bool reset)
return calState; return calState;
} }
int raw = applySmoothing(readRaw()); int16_t raw = applySmoothing(readRaw());
int diff = abs(raw - lastSample); int16_t diff = abs(raw - lastSample);
switch (calStep) switch (calStep)
{ {
@@ -155,7 +191,7 @@ CalibrationState PedalController::autoCalibrate(bool reset)
if (now - lastChangeTime > STABLE_TIME) if (now - lastChangeTime > STABLE_TIME)
{ {
int noise = maxSeen - minSeen; int16_t noise = maxSeen - minSeen;
if (noise > NOISE_TOLERANCE) if (noise > NOISE_TOLERANCE)
{ {
Serial.print("CAL: FEHLER - zu viel Rauschen im losgelassenen Zustand (noise="); Serial.print("CAL: FEHLER - zu viel Rauschen im losgelassenen Zustand (noise=");
@@ -327,7 +363,7 @@ CalibrationState PedalController::autoCalibrate(bool reset)
case 5: case 5:
{ // 5. Final loslassen & stabil -> finalisieren { // 5. Final loslassen & stabil -> finalisieren
// Ziel-Release-Punkt aus den beiden Messungen mitteln // 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? // Sind wir schon nah genug an der Release-Position?
if (abs(raw - relTarget) > MOVE_DELTA) if (abs(raw - relTarget) > MOVE_DELTA)
@@ -348,15 +384,15 @@ CalibrationState PedalController::autoCalibrate(bool reset)
if (now - lastChangeTime > STABLE_TIME) if (now - lastChangeTime > STABLE_TIME)
{ {
int relAvg = relTarget; int16_t relAvg = relTarget;
int pressAvg = (firstPressed + secondPressed) / 2; int16_t pressAvg = (firstPressed + secondPressed) / 2;
int low = pressAvg; int16_t low = pressAvg;
int high = relAvg; int16_t high = relAvg;
if (high < low) if (high < low)
{ {
int tmp = low; int16_t tmp = low;
low = high; low = high;
high = tmp; high = tmp;
} }
@@ -414,40 +450,39 @@ CalibrationState PedalController::getStatus() const
return calState; return calState;
} }
int PedalController::getPedal() int16_t PedalController::getPedal()
{ {
// Ohne gültige Kalibrierung: kein Pedal int16_t raw = applySmoothing(readRaw());
if (!config.calibrated) 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 if (signals_.pedalPercent)
int maxR = config.maxRaw; // losgelassen → höher *signals_.pedalPercent = percent;
if (maxR <= minR + 1) if (signals_.calibrated)
{ *signals_.calibrated = config.calibrated;
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;
} }
PedalConfig PedalController::getConfig() const PedalConfig PedalController::getConfig() const
@@ -503,14 +538,24 @@ void PedalController::loadDefaults()
step4NearPressed = false; 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; return;
} }
unsigned long now = millis(); uint32_t now = millis();
if (now - lastStatusPrint < 1000) if (now - lastStatusPrint < 1000)
return; // nur jede Sekunde return; // nur jede Sekunde
lastStatusPrint = now; lastStatusPrint = now;
@@ -523,9 +568,9 @@ void PedalController::printStatus(int raw)
Serial.print(filteredValue); Serial.print(filteredValue);
Serial.print(" DO-State="); Serial.print(" DO-State=");
if (useDigital) if (signals_.digitalPin >= 0)
{ {
Serial.print(digitalRead(digitalPin)); Serial.print(digitalRead(signals_.digitalPin));
} }
else else
{ {
@@ -543,21 +588,3 @@ void PedalController::printStatus(int raw)
Serial.println(); 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> #include <Arduino.h>
// Calibration state enumeration
enum CalibrationState enum CalibrationState
{ {
IDLE, IDLE,
@@ -13,72 +12,80 @@ enum CalibrationState
ERROR ERROR
}; };
// Pedal configuration structure
struct PedalConfig struct PedalConfig
{ {
int16_t minRaw; int16_t minRaw;
int16_t maxRaw; int16_t maxRaw;
bool calibrated; bool calibrated;
uint8_t filterStrength; // 1 = kein Filter, >1 = gleitender Mittelwert 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 class PedalController
{ {
public: public:
explicit PedalController(int analogPin, PedalController(const PedalSignals &signals,
int digitalPin = -1, uint8_t digitalNotPressedLevel = LOW);
uint8_t digitalNotPressedLevel = LOW,
bool *debugFlag = nullptr); void setSignals(const PedalSignals &signals);
// Startet / läuft Auto-Kalibrierung
CalibrationState autoCalibrate(bool reset); CalibrationState autoCalibrate(bool reset);
CalibrationState getStatus() const; CalibrationState getStatus() const;
// 0100 %, bei nicht kalibriert → 0 int16_t getPedal(); // alte API (blocking)
int getPedal(); void maintain(); // neue Non-Blocking-API
PedalConfig getConfig() const; PedalConfig getConfig() const;
void setConfig(const PedalConfig &newConfig); void setConfig(const PedalConfig &cfg);
void loadDefaults(); void loadDefaults();
// Debug-Helfer
int debugGetRaw();
int debugGetSmooth();
int debugGetDO();
private: private:
int analogPin; PedalSignals signals_;
int digitalPin; uint8_t digitalNotPressedLevel; // nur Debug / Interpretation
bool useDigital;
uint8_t digitalNotPressedLevel; // nur Debug
bool *debugFlag;
PedalConfig config; PedalConfig config;
int32_t filteredValue; int32_t filteredValue;
int16_t lastPedalPercent;
// Auto-Cal intern // Auto-Cal intern
CalibrationState calState; CalibrationState calState;
uint8_t calStep; uint8_t calStep;
unsigned long stepStartTime; uint32_t stepStartTime;
unsigned long lastChangeTime; uint32_t lastChangeTime;
int lastSample; int16_t lastSample;
int minSeen; int16_t minSeen;
int maxSeen; int16_t maxSeen;
unsigned long lastStatusPrint; uint32_t lastStatusPrint;
// Vier Referenzpunkte aus dem Ablauf // Vier Referenzpunkte aus dem Ablauf
int firstReleased; int16_t firstReleased;
int firstPressed; int16_t firstPressed;
int secondReleased; int16_t secondReleased;
int secondPressed; int16_t secondPressed;
bool step2MovedEnough; bool step2MovedEnough;
bool step3NearReleased; bool step3NearReleased;
bool step4NearPressed; bool step4NearPressed;
int readRaw(); int16_t readRaw();
int applySmoothing(int raw); int16_t applySmoothing(int16_t raw);
void printStatus(int raw); int16_t mapRawToPercent(int16_t raw);
void printStatus(int16_t raw);
}; };
#endif // PEDALCONTROLLER_H #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 <EEPROM.h>
#include <Wire.h> #include <Wire.h>
#include <Adafruit_SSD1306.h> #include <Adafruit_SSD1306.h>
#include "MotorController.h" #include "MotorController.h"
#include "PedalController.h" #include "PedalController.h"
#include "SerialController.h"
// OLED display setup // OLED display setup
#define OLED_RESET -1 #define OLED_RESET -1
@@ -11,20 +13,36 @@
#define SCREEN_HEIGHT 64 #define SCREEN_HEIGHT 64
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET); Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
// Pin definitions // Pin definitions (Werte, keine Pointer)
const int hallSensor = 2; // Hall sensor input const int16_t hallSensorPin = 2; // Hall sensor input
const int encoderPinA = 3; // Encoder pin A const int16_t encoderPinA = 3; // Encoder pin A
const int encoderPinB = 4; // Encoder pin B const int16_t encoderPinB = 4; // Encoder pin B
const int encoderButton = 5; // Encoder button const int16_t encoderButtonPin = 5; // Encoder button
const int motorPin = 9; // Motor PWM pin const int16_t motorPwmPin = 13; // Motor PWM pin
const int pedalPinAnalog = A0; // Pedal analog Input pin const int16_t pedalPinAnalog = A0; // Pedal analog Input pin
const int pedalPinDigital = 8; // Pedal digital Input pin const int16_t pedalPinDigital = 8; // Pedal digital Input pin
// Debug-Flag (gemeinsam für alle Controller)
bool debug = false; bool debug = false;
// Global objects // Globale Signale für Motor
MotorController motor(motorPin, &debug); int16_t gTargetSpeedPercent = 0; // Sollwert 0..100%
PedalController pedal(pedalPinAnalog, pedalPinDigital, LOW, &debug); 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 // Combined settings structure
struct Settings struct Settings
@@ -35,12 +53,42 @@ struct Settings
}; };
Settings settings; Settings settings;
bool autoCalibrating = false;
bool serialControlEnabled = false;
CalibrationState lastCalibrationState = IDLE; CalibrationState lastCalibrationState = IDLE;
bool autoCalibrating = false;
bool liveSensorOutput = false; // Signals-Instanzen
unsigned long lastSensorPrint = 0; 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 // Function prototypes
void loadSettings(); void loadSettings();
@@ -49,48 +97,82 @@ uint8_t calculateChecksum(const Settings &s);
void handleEncoder(); void handleEncoder();
void handleEncoderButton(); void handleEncoderButton();
void updateDisplay(); void updateDisplay();
void handleSerialInput();
void configurePWMFrequency(); void configurePWMFrequency();
void stopMotorAtNeedleUp(); void stopMotorAtNeedleUp();
void printHelp();
void printStatusLine();
// ----------------- SETUP -----------------
void setup() void setup()
{ {
// Initialize pins // Pins
pinMode(hallSensor, INPUT); pinMode(hallSensorPin, INPUT);
pinMode(encoderPinA, INPUT_PULLUP); pinMode(encoderPinA, INPUT_PULLUP);
pinMode(encoderPinB, INPUT_PULLUP); pinMode(encoderPinB, INPUT_PULLUP);
pinMode(encoderButton, INPUT_PULLUP); pinMode(encoderButtonPin, INPUT_PULLUP);
pinMode(LED_BUILTIN, OUTPUT); pinMode(LED_BUILTIN, OUTPUT);
// Configure PWM frequency
configurePWMFrequency(); configurePWMFrequency();
// Attach interrupts for encoder
attachInterrupt(digitalPinToInterrupt(encoderPinA), handleEncoder, CHANGE); attachInterrupt(digitalPinToInterrupt(encoderPinA), handleEncoder, CHANGE);
attachInterrupt(digitalPinToInterrupt(encoderPinB), 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); Serial.begin(9600);
loadSettings(); loadSettings();
// Initialize OLED display // OLED optional
// if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { // if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
// Serial.println(F("SSD1306 allocation failed")); // Serial.println(F("SSD1306 allocation failed"));
// } // }
// display.clearDisplay(); // display.clearDisplay();
// display.display(); // display.display();
printHelp(); serialCtrl.printHelp();
} }
// ----------------- LOOP -----------------
void loop() void loop()
{ {
// Handle serial input for debugging and manual control // Alle seriellen Befehle in Signale schreiben lassen
handleSerialInput(); serialCtrl.maintain();
motor.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) if (autoCalibrating)
{ {
@@ -105,85 +187,71 @@ void loop()
break; break;
case SUCCESS: case SUCCESS:
Serial.println("Auto-calibration completed."); Serial.println("Auto-calibration completed.");
autoCalibrating = false;
break; break;
case ERROR: case ERROR:
Serial.println("Auto-calibration failed."); Serial.println("Auto-calibration failed.");
autoCalibrating = false;
break; break;
default: default:
break; break;
} }
} }
motor.setTargetSpeed(0); // Ensure motor stays off during calibration
}
else if (serialControlEnabled)
{
static int lastSpeed = 0;
int currSpeed = motor.getSpeed();
if (lastSpeed > currSpeed) gTargetSpeedPercent = 0; // während Cal immer 0
{
Serial.print("Decreased Speed to: ");
Serial.println(currSpeed);
lastSpeed = currSpeed;
}
else if (lastSpeed < currSpeed)
{
Serial.print("Increased Speed to: ");
Serial.println(currSpeed);
lastSpeed = currSpeed;
}
} }
else else
{ {
int pedalValue = pedal.getPedal(); // Nur im Normalbetrieb: Pedal einlesen
motor.setTargetSpeed(pedalValue); pedal.maintain();
}
if (liveSensorOutput) if (!gSerialControl)
{ {
unsigned long now = millis(); // Standard: Pedal → TargetSpeed
if (now - lastSensorPrint > 200) gTargetSpeedPercent = gPedalPercent;
}
else
{ {
lastSensorPrint = now; // Serielle Steuerung ist aktiv, Target wird von SerialController manipuliert
static int16_t lastSpeed = -1;
int raw = pedal.debugGetRaw(); if (lastSpeed != gCurrentSpeedPercent)
int smooth = pedal.debugGetSmooth(); {
int pedalValue = pedal.getPedal(); Serial.print("Current Speed: ");
int doState = pedal.debugGetDO(); Serial.println(gCurrentSpeedPercent);
lastSpeed = gCurrentSpeedPercent;
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);
} }
} }
// Update display // Motor nachführen
motor.maintain();
// Optional: Display
// updateDisplay(); // updateDisplay();
delay(10); // Short delay for stability // Optional: eine Debug-Zeile mit Kerninfos
// printStatusLine();
delay(10);
} }
// ----------------- HILFSFUNKTIONEN -----------------
void stopMotorAtNeedleUp() void stopMotorAtNeedleUp()
{ {
motor.stopMotor(); motor.stopMotor();
delay(100); // Stabilize delay(100);
} }
void loadSettings() void loadSettings()
{ {
EEPROM.get(0, settings); EEPROM.get(0, settings);
// Validate checksum
if (calculateChecksum(settings) != settings.checksum) if (calculateChecksum(settings) != settings.checksum)
{ {
Serial.println("Invalid settings checksum, loading defaults."); Serial.println("Invalid settings checksum, loading defaults.");
motor.loadDefaults(); motor.loadDefaults();
pedal.autoCalibrate(true); pedal.loadDefaults();
settings.motorConfig = motor.getConfig(); settings.motorConfig = motor.getConfig();
settings.pedalConfig = pedal.getConfig(); settings.pedalConfig = pedal.getConfig();
@@ -243,7 +311,7 @@ void handleEncoder()
void handleEncoderButton() void handleEncoderButton()
{ {
// Handle encoder button press // Encoder-Button-Logik
} }
void updateDisplay() void updateDisplay()
@@ -254,110 +322,36 @@ void updateDisplay()
display.setCursor(0, 0); display.setCursor(0, 0);
display.print("Pedal: "); 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(); 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() void configurePWMFrequency()
{ {
// Configure Timer1 for higher PWM frequency (~4 kHz) // Configure Timer1 for higher PWM frequency (~4 kHz)
TCCR1B = (1 << CS11); // Set prescaler to 8 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);
}