Replace stall/homing flow with signed slider drive control
This streamlines manual dual-motor operation by using spring-centered signed sliders and synchronized drive modes while keeping live current monitoring in the UI and API.
This commit is contained in:
@@ -69,12 +69,6 @@ const MotorPins MOTOR2_PINS = {
|
||||
// Current logging interval for data collection
|
||||
#define CURRENT_LOG_INTERVAL_MS 100 // Log current readings every 100ms
|
||||
|
||||
// Simple Stall Detection
|
||||
// Based on clean data: Running ~2A, Stall ~17A (8.5x difference)
|
||||
#define STALL_THRESHOLD 8.0f // Amps - midpoint between 2A run and 17A stall
|
||||
#define STALL_CONFIRM_SAMPLES 3 // Debounce: 3 samples = 300ms at 100ms interval
|
||||
#define STALL_STABILIZE_MS 500 // Ignore current spikes for 500ms after direction change
|
||||
|
||||
// Web Server
|
||||
#define HTTP_PORT 80
|
||||
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
#ifndef HOMING_H
|
||||
#define HOMING_H
|
||||
|
||||
// Coordinated movement functions (defined in main.cpp)
|
||||
int toggleCoordinatedMovement(int speed);
|
||||
void stopCoordinatedMovement();
|
||||
int getCoordinatedState();
|
||||
bool isCoordinatedMovementDone();
|
||||
|
||||
#endif
|
||||
@@ -4,9 +4,6 @@
|
||||
#include <Arduino.h>
|
||||
#include "config.h"
|
||||
|
||||
// Stall callback function type
|
||||
typedef void (*StallCallback)();
|
||||
|
||||
class MotorController {
|
||||
public:
|
||||
// Constructor with pin configuration and motor name
|
||||
@@ -25,11 +22,6 @@ public:
|
||||
float getCurrentActive(); // Current from active direction
|
||||
const char* getName(); // Get motor name for logging
|
||||
|
||||
// Stall detection
|
||||
bool isStalled();
|
||||
void setStallCallback(StallCallback callback);
|
||||
void resetStallDetection();
|
||||
|
||||
private:
|
||||
MotorPins _pins;
|
||||
const char* _name;
|
||||
@@ -43,19 +35,12 @@ private:
|
||||
int _adcOffsetRight = 0;
|
||||
int _adcOffsetLeft = 0;
|
||||
|
||||
// Stall detection state
|
||||
bool _stalled = false;
|
||||
int _stallConfirmCount = 0;
|
||||
StallCallback _stallCallback = nullptr;
|
||||
unsigned long _lastDirectionChangeTime = 0;
|
||||
|
||||
// Static flag to track if enable pins are already configured
|
||||
static bool _enablePinsConfigured;
|
||||
|
||||
void applyMotorState();
|
||||
float readCurrentSense(int pin);
|
||||
void calibrateCurrentOffset();
|
||||
void checkStall();
|
||||
};
|
||||
|
||||
// Two motor controller instances
|
||||
|
||||
41
readme.md
41
readme.md
@@ -1,15 +1,13 @@
|
||||
# Dual BTS7960 Motor Controller
|
||||
|
||||
ESP32-based differential drive robot controller with web interface, using two BTS7960 dual H-bridge drivers with current sensing and stall protection.
|
||||
ESP32-based differential drive robot controller with web interface, using two BTS7960 dual H-bridge drivers with live current sensing.
|
||||
|
||||
## Features
|
||||
|
||||
- Web-based dual motor control panel with real-time current monitoring
|
||||
- Independent Forward/Reverse/Stop control for each motor with speed sliders (20-100%)
|
||||
- Two independent spring-to-center sliders (one per motor), each supporting forward and reverse
|
||||
- Current sensing on both H-bridge sides for each motor
|
||||
- Sample-based stall detection with automatic motor shutoff (per motor)
|
||||
- ADC offset calibration at startup for accurate current readings
|
||||
- Stall warning displayed on web interface per motor
|
||||
- Emergency stop for both motors
|
||||
|
||||
## Hardware
|
||||
@@ -98,9 +96,6 @@ Key settings in [`include/config.h`](include/config.h):
|
||||
|
||||
| Setting | Default | Description |
|
||||
|---------|---------|-------------|
|
||||
| `STALL_THRESHOLD` | 8.0A | Current threshold for stall detection |
|
||||
| `STALL_CONFIRM_SAMPLES` | 3 | Number of consecutive samples to confirm stall (~300ms) |
|
||||
| `STALL_STABILIZE_MS` | 500ms | Ignore current spikes after direction change |
|
||||
| `PWM_FREQ` | 20kHz | PWM frequency (reduces motor noise) |
|
||||
| `MIN_PWM_PERCENT` | 20% | Minimum PWM when motor is running |
|
||||
| `CURRENT_LOG_INTERVAL_MS` | 100ms | Current sampling/logging interval |
|
||||
@@ -128,11 +123,10 @@ Access the control panel at `http://10.81.2.185` (or the IP shown on serial moni
|
||||
|
||||
### Features
|
||||
|
||||
- **Dual Motor Panels**: Side-by-side controls for Motor 1 (Left) and Motor 2 (Right)
|
||||
- **Dual Motor Panels**: Side-by-side controls for Motor 1 and Motor 2
|
||||
- **Current Display**: Real-time forward (FWD) and reverse (REV) current readings per motor
|
||||
- **Direction Status**: Shows FORWARD, REVERSE, or STOPPED per motor
|
||||
- **Speed Sliders**: Adjustable from 20% to 100% per motor
|
||||
- **Stall Warning**: Red banner per motor when stall is detected
|
||||
- **Spring-to-Center Sliders**: Signed sliders (`-100..100`) per motor
|
||||
- **Bidirectional Drive**: Positive values = forward, negative values = reverse, `0` = stop
|
||||
- **Emergency Stop**: Global button to stop both motors immediately
|
||||
|
||||
### API Endpoints
|
||||
@@ -141,9 +135,11 @@ Access the control panel at `http://10.81.2.185` (or the IP shown on serial moni
|
||||
|----------|--------|------------|-------------|
|
||||
| `/` | GET | - | Control panel HTML page |
|
||||
| `/status` | GET | - | JSON with both motors' status |
|
||||
| `/motor1/drive` | GET | `value` (-100..100) | Set motor 1 speed + direction in one command |
|
||||
| `/motor1/speed` | GET | `value` (0-100) | Set motor 1 speed percentage |
|
||||
| `/motor1/direction` | GET | `value` (-1, 0, 1) | Set motor 1 direction |
|
||||
| `/motor1/stop` | GET | - | Stop motor 1 |
|
||||
| `/motor2/drive` | GET | `value` (-100..100) | Set motor 2 speed + direction in one command |
|
||||
| `/motor2/speed` | GET | `value` (0-100) | Set motor 2 speed percentage |
|
||||
| `/motor2/direction` | GET | `value` (-1, 0, 1) | Set motor 2 direction |
|
||||
| `/motor2/stop` | GET | - | Stop motor 2 |
|
||||
@@ -159,30 +155,19 @@ Access the control panel at `http://10.81.2.185` (or the IP shown on serial moni
|
||||
"speed": 50,
|
||||
"direction": 1,
|
||||
"currentR": 2.35,
|
||||
"currentL": 0.00,
|
||||
"stalled": false
|
||||
"currentL": 0.00
|
||||
},
|
||||
"motor2": {
|
||||
"speed": 50,
|
||||
"direction": -1,
|
||||
"currentR": 0.00,
|
||||
"currentL": 1.87,
|
||||
"stalled": false
|
||||
"currentL": 1.87
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Stall Protection
|
||||
## Control Behavior
|
||||
|
||||
The stall detection uses a sample-based approach for reliability (independent per motor):
|
||||
|
||||
1. **Threshold**: Current above 8.0A indicates potential stall (based on observed ~2A running vs ~17A stalled)
|
||||
2. **Debounce**: 3 consecutive samples above threshold confirms stall (~300ms at 100ms intervals)
|
||||
3. **Stabilization**: Ignores current spikes for 500ms after direction changes
|
||||
|
||||
When stall is confirmed on a motor:
|
||||
1. That motor stops immediately
|
||||
2. Serial log: `MotorX STALL DETECTED! Current=X.XXA (threshold=8.0A)`
|
||||
3. Web interface shows red "STALL!" warning on that motor's panel
|
||||
|
||||
To resume operation, send a new direction command via the web interface.
|
||||
- Each motor is controlled by its own signed slider in the web UI.
|
||||
- Releasing a slider returns it to `0` and immediately stops that motor.
|
||||
- Current sensing remains active for both directions on both motors and is shown live in the UI.
|
||||
|
||||
115
src/main.cpp
115
src/main.cpp
@@ -4,118 +4,6 @@
|
||||
#include "motor.h"
|
||||
#include "webserver.h"
|
||||
|
||||
// Coordinated movement state
|
||||
enum CoordinatedState {
|
||||
COORD_IDLE = 0,
|
||||
COORD_MOVING_REV = 1, // Homing - moving to reverse end
|
||||
COORD_MOVING_FWD = 2 // Moving to forward end
|
||||
};
|
||||
|
||||
// Global coordinated movement state
|
||||
static CoordinatedState coordState = COORD_IDLE;
|
||||
static bool motor1Done = false;
|
||||
static bool motor2Done = false;
|
||||
static int coordSpeed = 50; // Default coordinated movement speed
|
||||
|
||||
// Get current coordinated movement state
|
||||
int getCoordinatedState() {
|
||||
return (int)coordState;
|
||||
}
|
||||
|
||||
// Check if coordinated movement is complete
|
||||
bool isCoordinatedMovementDone() {
|
||||
return motor1Done && motor2Done;
|
||||
}
|
||||
|
||||
// Called when motor 1 stall is detected
|
||||
void onMotor1Stall() {
|
||||
Serial.println("Motor1 stall callback triggered - stopping motor!");
|
||||
motor1.stop();
|
||||
|
||||
// Mark motor1 as done if in coordinated movement
|
||||
if (coordState != COORD_IDLE) {
|
||||
motor1Done = true;
|
||||
Serial.println("Motor1 reached end position (stall)");
|
||||
|
||||
if (isCoordinatedMovementDone()) {
|
||||
Serial.println("Coordinated movement complete - both motors at end position");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Called when motor 2 stall is detected
|
||||
void onMotor2Stall() {
|
||||
Serial.println("Motor2 stall callback triggered - stopping motor!");
|
||||
motor2.stop();
|
||||
|
||||
// Mark motor2 as done if in coordinated movement
|
||||
if (coordState != COORD_IDLE) {
|
||||
motor2Done = true;
|
||||
Serial.println("Motor2 reached end position (stall)");
|
||||
|
||||
if (isCoordinatedMovementDone()) {
|
||||
Serial.println("Coordinated movement complete - both motors at end position");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Start coordinated movement in specified direction
|
||||
// dir: -1 = reverse (homing), 1 = forward
|
||||
void startCoordinatedMovement(int dir, int speed) {
|
||||
coordSpeed = speed;
|
||||
motor1Done = false;
|
||||
motor2Done = false;
|
||||
|
||||
if (dir < 0) {
|
||||
coordState = COORD_MOVING_REV;
|
||||
Serial.printf("Starting coordinated REVERSE movement at %d%% speed\n", speed);
|
||||
} else {
|
||||
coordState = COORD_MOVING_FWD;
|
||||
Serial.printf("Starting coordinated FORWARD movement at %d%% speed\n", speed);
|
||||
}
|
||||
|
||||
// Start both motors
|
||||
motor1.setSpeed(speed);
|
||||
motor1.setDirection(dir);
|
||||
|
||||
motor2.setSpeed(speed);
|
||||
motor2.setDirection(dir);
|
||||
}
|
||||
|
||||
// Toggle coordinated movement (for button press)
|
||||
// Returns: new state (0=idle, 1=moving_rev, 2=moving_fwd)
|
||||
int toggleCoordinatedMovement(int speed) {
|
||||
if (coordState == COORD_IDLE) {
|
||||
// First press: start moving reverse (homing)
|
||||
startCoordinatedMovement(-1, speed);
|
||||
return COORD_MOVING_REV;
|
||||
} else if (coordState == COORD_MOVING_REV) {
|
||||
// If currently moving rev or completed rev, switch to forward
|
||||
// Stop any still-moving motors first
|
||||
if (!motor1Done) motor1.stop();
|
||||
if (!motor2Done) motor2.stop();
|
||||
startCoordinatedMovement(1, speed);
|
||||
return COORD_MOVING_FWD;
|
||||
} else {
|
||||
// Currently moving fwd or completed fwd, switch to reverse
|
||||
// Stop any still-moving motors first
|
||||
if (!motor1Done) motor1.stop();
|
||||
if (!motor2Done) motor2.stop();
|
||||
startCoordinatedMovement(-1, speed);
|
||||
return COORD_MOVING_REV;
|
||||
}
|
||||
}
|
||||
|
||||
// Stop coordinated movement
|
||||
void stopCoordinatedMovement() {
|
||||
coordState = COORD_IDLE;
|
||||
motor1Done = false;
|
||||
motor2Done = false;
|
||||
motor1.stop();
|
||||
motor2.stop();
|
||||
Serial.println("Coordinated movement stopped");
|
||||
}
|
||||
|
||||
void setupWiFi() {
|
||||
Serial.println("Connecting to WiFi...");
|
||||
|
||||
@@ -156,10 +44,7 @@ void setup() {
|
||||
|
||||
// Initialize motor controllers
|
||||
motor1.begin();
|
||||
motor1.setStallCallback(onMotor1Stall);
|
||||
|
||||
motor2.begin();
|
||||
motor2.setStallCallback(onMotor2Stall);
|
||||
|
||||
// Connect to WiFi
|
||||
setupWiFi();
|
||||
|
||||
@@ -65,12 +65,7 @@ void MotorController::setSpeed(int speed) {
|
||||
}
|
||||
|
||||
void MotorController::setDirection(int dir) {
|
||||
int newDir = constrain(dir, -1, 1);
|
||||
if (newDir != _direction) {
|
||||
_lastDirectionChangeTime = millis();
|
||||
resetStallDetection();
|
||||
}
|
||||
_direction = newDir;
|
||||
_direction = constrain(dir, -1, 1);
|
||||
applyMotorState();
|
||||
}
|
||||
|
||||
@@ -79,7 +74,6 @@ void MotorController::stop() {
|
||||
_direction = 0;
|
||||
ledcWrite(_pins.pwm_channel_r, 0);
|
||||
ledcWrite(_pins.pwm_channel_l, 0);
|
||||
resetStallDetection();
|
||||
Serial.printf("%s: STOPPED (manual)\n", _name);
|
||||
}
|
||||
|
||||
@@ -99,8 +93,6 @@ void MotorController::update() {
|
||||
_name, _currentRight, _currentLeft, _direction, _speed);
|
||||
}
|
||||
|
||||
// Check for stall condition
|
||||
checkStall();
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -133,61 +125,6 @@ const char* MotorController::getName() {
|
||||
return _name;
|
||||
}
|
||||
|
||||
// Stall detection
|
||||
bool MotorController::isStalled() {
|
||||
return _stalled;
|
||||
}
|
||||
|
||||
void MotorController::setStallCallback(StallCallback callback) {
|
||||
_stallCallback = callback;
|
||||
}
|
||||
|
||||
void MotorController::resetStallDetection() {
|
||||
_stalled = false;
|
||||
_stallConfirmCount = 0;
|
||||
}
|
||||
|
||||
void MotorController::checkStall() {
|
||||
// Only check when motor is running
|
||||
if (_direction == 0 || _speed == 0) {
|
||||
resetStallDetection();
|
||||
return;
|
||||
}
|
||||
|
||||
// Skip stall check during stabilization period after direction change
|
||||
// (prevents false positives from inrush current spikes)
|
||||
if ((millis() - _lastDirectionChangeTime) < STALL_STABILIZE_MS) {
|
||||
return;
|
||||
}
|
||||
|
||||
float activeCurrent = getCurrentActive();
|
||||
|
||||
// Simple threshold-based stall detection with debounce
|
||||
if (activeCurrent > STALL_THRESHOLD) {
|
||||
_stallConfirmCount++;
|
||||
|
||||
if (_stallConfirmCount >= STALL_CONFIRM_SAMPLES && !_stalled) {
|
||||
_stalled = true;
|
||||
Serial.printf("%s STALL DETECTED! Current=%.2fA (threshold=%.1fA)\n",
|
||||
_name, activeCurrent, STALL_THRESHOLD);
|
||||
|
||||
// Call stall callback if registered
|
||||
if (_stallCallback != nullptr) {
|
||||
_stallCallback();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Reset counter if current drops below threshold
|
||||
_stallConfirmCount = 0;
|
||||
|
||||
// Clear stall flag if current returns to normal
|
||||
if (_stalled) {
|
||||
_stalled = false;
|
||||
Serial.printf("%s: Stall condition cleared\n", _name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MotorController::applyMotorState() {
|
||||
// Apply minimum PWM when motor is running
|
||||
int effectiveSpeed = _speed;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user