diff --git a/include/config.h b/include/config.h index 845c534..f166813 100644 --- a/include/config.h +++ b/include/config.h @@ -37,22 +37,8 @@ #define ADC_VREF 3.3f // ADC reference voltage #define CURRENT_CALIBRATION 1.0f // Calibration factor (measured current / reported current) -// Stall Detection Configuration (delta-based) -// Detects sudden current spikes above the rolling average -#define STALL_DELTA_THRESHOLD 1.2f // Current spike above average that indicates stall (amps) -#define STALL_EMA_ALPHA 0.1f // EMA smoothing factor (0.1 = slow, 0.5 = fast response) -#define STALL_EMA_BASELINE 2.0f // Expected running current baseline for EMA seeding (amps) -#define STALL_STABILIZE_MS 300 // Ignore stalls for this long after motor starts/changes -#define STALL_CONFIRM_MS 50 // Current must exceed threshold for this long to confirm stall -#define DISABLE_STALL_DETECT false // Set to true to disable stall detection - -// Under-current stall detection: motor commanded but drawing no current -#define STALL_UNDERCURRENT_THRESHOLD 0.5f // If current below this for too long = stall (amps) -#define STALL_UNDERCURRENT_MS 1000 // Under-current must persist this long to confirm stall - -// Repeated spike stall detection: catches oscillating stall (driver protection cycling) -#define STALL_CANDIDATE_COUNT 5 // Number of stall candidates in window to confirm stall -#define STALL_CANDIDATE_WINDOW_MS 2000 // Time window for counting stall candidates +// Current logging interval for data collection +#define CURRENT_LOG_INTERVAL_MS 100 // Log current readings every 100ms // Web Server #define HTTP_PORT 80 diff --git a/include/motor.h b/include/motor.h index 4b4ae58..891e9f3 100644 --- a/include/motor.h +++ b/include/motor.h @@ -10,53 +10,33 @@ public: void setSpeed(int speed); // 0-100 percentage void setDirection(int dir); // -1=reverse, 0=stop, 1=forward void stop(); - void update(); // Call in loop() for stall detection + void update(); // Call in loop() for current monitoring int getSpeed(); int getDirection(); float getCurrentRight(); // Current in amps (forward direction) float getCurrentLeft(); // Current in amps (reverse direction) float getCurrentActive(); // Current from active direction - bool isStalled(); // True if stall detected - // Callback for stall events - void setStallCallback(void (*callback)(float current)); - - // Pingpong mode - void startPingpong(int speed, int timeMs, int speedRandomPercent, int timeRandomPercent, bool useStallReturn); + // Pingpong mode (time-based only) + void startPingpong(int speed, int timeMs, int speedRandomPercent, int timeRandomPercent); void stopPingpong(); bool isPingpongActive(); int getPingpongSpeed(); int getPingpongTime(); int getPingpongSpeedRandom(); int getPingpongTimeRandom(); - bool getPingpongStallReturn(); private: int _speed = 0; int _direction = 0; float _currentRight = 0; float _currentLeft = 0; - bool _stalled = false; - unsigned long _stallStartTime = 0; - void (*_stallCallback)(float current) = nullptr; // ADC zero-current offset (calibrated at startup) int _adcOffsetRight = 0; int _adcOffsetLeft = 0; - // Delta-based stall detection (rolling average tracking) - float _currentEMA = 0; // Exponential moving average of current - unsigned long _motorStartTime = 0; // When motor started (for stabilization period) - bool _emaInitialized = false; // EMA needs seeding on first reading - - // Under-current stall detection - unsigned long _undercurrentStartTime = 0; // When under-current condition started - - // Repeated spike detection (oscillating stall pattern) - int _stallCandidateCount = 0; // Number of stall candidates in current window - unsigned long _stallCandidateWindowStart = 0; // When current counting window started - // Pingpong state bool _pingpongActive = false; int _pingpongBaseSpeed = 50; @@ -67,13 +47,10 @@ private: int _pingpongCurrentTime = 2000; unsigned long _pingpongLastSwitch = 0; int _pingpongDirection = 1; - bool _pingpongUseStallReturn = false; // Return only after stall detection void applyMotorState(); float readCurrentSense(int pin); void calibrateCurrentOffset(); - void checkStall(); - void resetStallDetection(); void updatePingpong(); int applyRandomness(int baseValue, int randomPercent); }; diff --git a/src/main.cpp b/src/main.cpp index 8b1f95b..b6d6d40 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -33,32 +33,18 @@ void setupWiFi() { } } -// Stall protection callback - stops motor immediately when stall detected -// (unless pingpong with stallReturn is active - it handles stall by switching direction) -void onMotorStall(float current) { - if (motor.isPingpongActive() && motor.getPingpongStallReturn()) { - // Pingpong will handle stall by switching direction - Serial.printf("STALL DETECTED: Pingpong will switch direction (current: %.2fA)\n", current); - return; - } - Serial.printf("STALL PROTECTION: Stopping motor (current: %.2fA)\n", current); - motor.stop(); -} - void setup() { Serial.begin(115200); delay(1000); Serial.println("\n============================="); Serial.println(" BTS7960 Motor Controller"); + Serial.println(" (Stall Detection Removed)"); Serial.println("=============================\n"); // Initialize motor controller motor.begin(); - // Register stall protection callback - motor.setStallCallback(onMotorStall); - // Connect to WiFi setupWiFi(); @@ -72,6 +58,6 @@ void setup() { void loop() { handleWebServer(); - motor.update(); // Update current sensing and stall detection + motor.update(); // Update current sensing and logging delay(1); } diff --git a/src/motor.cpp b/src/motor.cpp index 921f4f0..d6fc3a9 100644 --- a/src/motor.cpp +++ b/src/motor.cpp @@ -45,34 +45,21 @@ void MotorController::begin() { void MotorController::setSpeed(int speed) { _speed = constrain(speed, 0, 100); - resetStallDetection(); applyMotorState(); } void MotorController::setDirection(int dir) { _direction = constrain(dir, -1, 1); - resetStallDetection(); applyMotorState(); } void MotorController::stop() { // Don't reset _speed - keep last speed setting _direction = 0; - resetStallDetection(); ledcWrite(PWM_CHANNEL_R, 0); ledcWrite(PWM_CHANNEL_L, 0); } -void MotorController::resetStallDetection() { - _stalled = false; - _stallStartTime = 0; - _undercurrentStartTime = 0; - _stallCandidateCount = 0; - _stallCandidateWindowStart = 0; - _emaInitialized = false; // Re-seed EMA on motor state change - _motorStartTime = millis(); -} - void MotorController::update() { #if CURRENT_SENSING_ENABLED static unsigned long lastPrintTime = 0; @@ -81,17 +68,13 @@ void MotorController::update() { _currentRight = readCurrentSense(R_IS_PIN); _currentLeft = readCurrentSense(L_IS_PIN); - // Log current readings every 500ms when motor is running - if ((_direction != 0 || _speed != 0) && (millis() - lastPrintTime > 500)) { - lastPrintTime = millis(); - float activeCurrent = getCurrentActive(); - float delta = activeCurrent - _currentEMA; - Serial.printf("Current: R=%.2fA L=%.2fA Active=%.2fA (avg=%.2fA, delta=%.2fA, thresh=%.1fA)\n", - _currentRight, _currentLeft, activeCurrent, _currentEMA, delta, STALL_DELTA_THRESHOLD); + // Log current readings frequently for data collection + unsigned long now = millis(); + if (now - lastPrintTime >= CURRENT_LOG_INTERVAL_MS) { + lastPrintTime = now; + Serial.printf("CURRENT: R=%.2fA L=%.2fA dir=%d spd=%d\n", + _currentRight, _currentLeft, _direction, _speed); } - - // Check for stall condition - checkStall(); #endif // Update pingpong mode @@ -123,14 +106,6 @@ float MotorController::getCurrentActive() { return 0.0f; } -bool MotorController::isStalled() { - return _stalled; -} - -void MotorController::setStallCallback(void (*callback)(float current)) { - _stallCallback = callback; -} - void MotorController::applyMotorState() { // Apply minimum PWM when motor is running int effectiveSpeed = _speed; @@ -206,32 +181,27 @@ void MotorController::calibrateCurrentOffset() { #endif } -// Pingpong mode implementation -void MotorController::startPingpong(int speed, int timeMs, int speedRandomPercent, int timeRandomPercent, bool useStallReturn) { +// Pingpong mode implementation (time-based only) +void MotorController::startPingpong(int speed, int timeMs, int speedRandomPercent, int timeRandomPercent) { _pingpongBaseSpeed = constrain(speed, 0, 100); _pingpongBaseTime = constrain(timeMs, 100, 30000); _pingpongSpeedRandomPercent = constrain(speedRandomPercent, 0, 100); _pingpongTimeRandomPercent = constrain(timeRandomPercent, 0, 100); - _pingpongUseStallReturn = useStallReturn; _pingpongDirection = 1; _pingpongCurrentSpeed = applyRandomness(_pingpongBaseSpeed, _pingpongSpeedRandomPercent); - // Time randomness disabled when using stall return - _pingpongCurrentTime = _pingpongUseStallReturn ? _pingpongBaseTime : applyRandomness(_pingpongBaseTime, _pingpongTimeRandomPercent); + _pingpongCurrentTime = applyRandomness(_pingpongBaseTime, _pingpongTimeRandomPercent); _pingpongLastSwitch = millis(); _pingpongActive = true; - _stalled = false; // Reset stall state when starting pingpong - _stallStartTime = 0; // Apply initial state _speed = _pingpongCurrentSpeed; _direction = _pingpongDirection; applyMotorState(); - Serial.printf("Pingpong started: speed=%d%% (base=%d, rand=%d%%), time=%dms (base=%d, rand=%d%%), stallReturn=%s\n", + Serial.printf("Pingpong started: speed=%d%% (base=%d, rand=%d%%), time=%dms (base=%d, rand=%d%%)\n", _pingpongCurrentSpeed, _pingpongBaseSpeed, _pingpongSpeedRandomPercent, - _pingpongCurrentTime, _pingpongBaseTime, _pingpongTimeRandomPercent, - _pingpongUseStallReturn ? "true" : "false"); + _pingpongCurrentTime, _pingpongBaseTime, _pingpongTimeRandomPercent); } void MotorController::stopPingpong() { @@ -260,41 +230,19 @@ int MotorController::getPingpongTimeRandom() { return _pingpongTimeRandomPercent; } -bool MotorController::getPingpongStallReturn() { - return _pingpongUseStallReturn; -} - void MotorController::updatePingpong() { if (!_pingpongActive) return; - bool shouldSwitch = false; unsigned long now = millis(); - if (_pingpongUseStallReturn) { - // Switch direction only when stall is detected - if (_stalled) { - shouldSwitch = true; - Serial.println("Pingpong: stall detected, switching direction"); - } - } else { - // Time-based switching - if ((now - _pingpongLastSwitch) >= (unsigned long)_pingpongCurrentTime) { - shouldSwitch = true; - } - } - - if (shouldSwitch) { + // Time-based switching + if ((now - _pingpongLastSwitch) >= (unsigned long)_pingpongCurrentTime) { // Switch direction _pingpongDirection = -_pingpongDirection; - // Full stall detection reset for new direction - // This triggers STALL_STABILIZE_MS grace period to ignore motor inrush current - resetStallDetection(); - // Apply randomness for next cycle _pingpongCurrentSpeed = applyRandomness(_pingpongBaseSpeed, _pingpongSpeedRandomPercent); - // Time randomness disabled when using stall return - _pingpongCurrentTime = _pingpongUseStallReturn ? _pingpongBaseTime : applyRandomness(_pingpongBaseTime, _pingpongTimeRandomPercent); + _pingpongCurrentTime = applyRandomness(_pingpongBaseTime, _pingpongTimeRandomPercent); _pingpongLastSwitch = now; // Apply new state @@ -302,9 +250,8 @@ void MotorController::updatePingpong() { _direction = _pingpongDirection; applyMotorState(); - Serial.printf("Pingpong switch: dir=%d, speed=%d%%, next_time=%dms, stallReturn=%s\n", - _pingpongDirection, _pingpongCurrentSpeed, _pingpongCurrentTime, - _pingpongUseStallReturn ? "true" : "false"); + Serial.printf("Pingpong switch: dir=%d, speed=%d%%, next_time=%dms\n", + _pingpongDirection, _pingpongCurrentSpeed, _pingpongCurrentTime); } } @@ -318,120 +265,3 @@ int MotorController::applyRandomness(int baseValue, int randomPercent) { // Ensure result stays positive and reasonable return max(1, result); } - -void MotorController::checkStall() { -#if CURRENT_SENSING_ENABLED - if (DISABLE_STALL_DETECT) return; - - // Only check stall when motor should be running - if (_direction == 0 || _speed == 0) { - _stalled = false; - _stallStartTime = 0; - _undercurrentStartTime = 0; - _emaInitialized = false; - return; - } - - unsigned long now = millis(); - float activeCurrent = getCurrentActive(); - - // Initialize EMA at expected baseline (prevents inrush from looking like stall) - if (!_emaInitialized) { - _currentEMA = STALL_EMA_BASELINE; - _emaInitialized = true; - } - - // During stabilization: DON'T update EMA - keep baseline to avoid drift toward 0 - // This lets stall detection work correctly even if starting from stalled position - if ((now - _motorStartTime) < STALL_STABILIZE_MS) { - return; - } - - // Calculate delta from average - float delta = activeCurrent - _currentEMA; - - // Check if current spike exceeds delta threshold - bool spikeDetected = (delta > STALL_DELTA_THRESHOLD); - - // Update EMA only when no spike is being investigated - // This prevents EMA from "chasing" up to the spike during detection window - if (!spikeDetected && !_stalled) { - _currentEMA = (STALL_EMA_ALPHA * activeCurrent) + ((1.0f - STALL_EMA_ALPHA) * _currentEMA); - } - - if (spikeDetected) { - if (_stallStartTime == 0) { - // Start timing potential stall - _stallStartTime = now; - - // Count this as a stall candidate for repeated-spike detection - if (_stallCandidateWindowStart == 0) { - _stallCandidateWindowStart = now; - _stallCandidateCount = 1; - } else if ((now - _stallCandidateWindowStart) > STALL_CANDIDATE_WINDOW_MS) { - // Window expired, start new window - _stallCandidateWindowStart = now; - _stallCandidateCount = 1; - } else { - _stallCandidateCount++; - } - - Serial.printf("Stall candidate #%d: current=%.2fA, avg=%.2fA, delta=%.2fA\n", - _stallCandidateCount, activeCurrent, _currentEMA, delta); - - // Check if enough candidates to confirm stall (oscillating pattern) - if (_stallCandidateCount >= STALL_CANDIDATE_COUNT && !_stalled) { - _stalled = true; - Serial.printf("STALL DETECTED (repeated spikes)! %d candidates in %lums\n", - _stallCandidateCount, now - _stallCandidateWindowStart); - - if (_stallCallback != nullptr) { - _stallCallback(activeCurrent); - } - } - } else if ((now - _stallStartTime) > STALL_CONFIRM_MS) { - // Sustained spike - stall confirmed - if (!_stalled) { - _stalled = true; - Serial.printf("STALL DETECTED! Current: %.2fA (avg: %.2fA, delta: %.2fA)\n", - activeCurrent, _currentEMA, delta); - - // Call callback if registered - if (_stallCallback != nullptr) { - _stallCallback(activeCurrent); - } - } - } - } else { - // Current normal, reset spike stall timing (but keep EMA updating) - if (_stallStartTime != 0) { - Serial.printf("Stall candidate cleared: current=%.2fA, avg=%.2fA\n", - activeCurrent, _currentEMA); - } - _stallStartTime = 0; - // Don't clear _stalled here - under-current/repeated-spike detection may have set it - } - - // Under-current stall detection: motor commanded but not drawing current - // This catches stalls where the motor can't even start (e.g., already at end stop) - if (!_stalled && activeCurrent < STALL_UNDERCURRENT_THRESHOLD) { - if (_undercurrentStartTime == 0) { - _undercurrentStartTime = now; - } else if ((now - _undercurrentStartTime) > STALL_UNDERCURRENT_MS) { - _stalled = true; - Serial.printf("STALL DETECTED (under-current)! Current: %.2fA (threshold: %.2fA)\n", - activeCurrent, STALL_UNDERCURRENT_THRESHOLD); - - if (_stallCallback != nullptr) { - _stallCallback(activeCurrent); - } - } - } else if (activeCurrent >= STALL_UNDERCURRENT_THRESHOLD) { - // Current is normal, reset under-current timer and clear stall - _undercurrentStartTime = 0; - if (!spikeDetected) { - _stalled = false; - } - } -#endif -} diff --git a/src/webserver.cpp b/src/webserver.cpp index 9370251..d195aab 100644 --- a/src/webserver.cpp +++ b/src/webserver.cpp @@ -58,15 +58,6 @@ const char index_html[] PROGMEM = R"rawliteral( font-size: 12px; color: #888; } - .stall-warning { - background: #ff5252; - color: white; - padding: 10px; - border-radius: 5px; - margin-bottom: 20px; - display: none; - } - .stall-warning.active { display: block; } .slider-container { background: #16213e; padding: 20px; @@ -178,18 +169,14 @@ const char index_html[] PROGMEM = R"rawliteral(