- Remove idle/dot printing logic from MotorController::update() - Only print current readings when direction != 0 (motor running) - Simplifies logging by completely silencing output when stopped
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.
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%)
- 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
Components
| Component | Model/Specification |
|---|---|
| Microcontroller | ESP32 LOLIN32 Rev1 |
| Motor Drivers | 2× BTS7960 Dual H-Bridge Module |
| Power Supply | 12V DC |
| Sense Resistors | 4× 1kΩ (for current sensing) |
BTS7960 Module Reference
Wiring
Pin Assignments
Both drivers share enable pins (they enable/disable together).
| Function | Motor 1 | Motor 2 | Notes |
|---|---|---|---|
| R_EN | GPIO 27 | GPIO 27 (shared) | Both drivers enable together |
| L_EN | GPIO 14 | GPIO 14 (shared) | Both drivers enable together |
| RPWM | GPIO 25 | GPIO 32 | Forward PWM |
| LPWM | GPIO 26 | GPIO 33 | Reverse PWM |
| R_IS | GPIO 34 | GPIO 36 (VP) | Current sense - input only |
| L_IS | GPIO 35 | GPIO 39 (VN) | Current sense - input only |
Wiring Diagram
ESP32 LOLIN32 BTS7960 #1 BTS7960 #2
-------------- ---------- ----------
GPIO 27 (R_EN) ------> R_EN -----------> R_EN
GPIO 14 (L_EN) ------> L_EN -----------> L_EN
GPIO 25 (RPWM) ------> RPWM
GPIO 26 (LPWM) ------> LPWM
GPIO 34 (R_IS) <------ R_IS
GPIO 35 (L_IS) <------ L_IS
GPIO 32 (RPWM) --------------------> RPWM
GPIO 33 (LPWM) --------------------> LPWM
GPIO 36 (R_IS) <-------------------- R_IS
GPIO 39 (L_IS) <-------------------- L_IS
Current Sensing Circuit
The BTS7960 has IS (Current Sense) pins that output current proportional to motor load. A resistor converts this to voltage for ESP32 ADC.
BTS7960 Module ESP32
┌─────────────────┐ ┌──────┐
│ R_IS (pin 7) ─┼──────┬───────┤GPIOxx│ (34 for M1, 36 for M2)
│ │ [R] │ │
│ │ 1kΩ │ │
│ GND ─┼──────┴───────┤GND │
│ │ │ │
│ L_IS (pin 8) ─┼──────┬───────┤GPIOxx│ (35 for M1, 39 for M2)
│ │ [R] │ │
│ │ 1kΩ │ │
│ GND ─┼──────┴───────┤GND │
└─────────────────┘ └──────┘
Note: GPIO34, 35, 36, 39 are input-only pins on ESP32, ideal for ADC readings.
Current Sensing Math
| Parameter | Value | Formula |
|---|---|---|
| Sense Ratio | 8500:1 | I_sense = I_motor / 8500 |
| Sense Resistor | 1kΩ | V_sense = I_sense × R |
| At 4A motor current | 0.47V | (4 / 8500) × 1000 |
| Max readable current | 28A | (3.3V × 8500) / 1000 |
Configuration
Key settings in 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 |
CURRENT_SENSING_ENABLED |
true | Enable/disable in src/motor.cpp |
Network
| Setting | Value |
|---|---|
| WiFi SSID | tami |
| Static IP | 10.81.2.185 |
| HTTP Port | 80 |
Build & Upload
pio run # Build
pio run -t upload # Build and upload
pio device monitor # Serial monitor (115200 baud)
Web Interface
Access the control panel at http://10.81.2.185 (or the IP shown on serial monitor).
Features
- Dual Motor Panels: Side-by-side controls for Motor 1 (Left) and Motor 2 (Right)
- 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
- Emergency Stop: Global button to stop both motors immediately
API Endpoints
| Endpoint | Method | Parameters | Description |
|---|---|---|---|
/ |
GET | - | Control panel HTML page |
/status |
GET | - | JSON with both motors' status |
/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/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 |
/stop |
GET | - | Emergency stop (both motors) |
/speed |
GET | value |
Legacy: maps to motor1 |
/direction |
GET | value |
Legacy: maps to motor1 |
Status JSON Format
{
"motor1": {
"speed": 50,
"direction": 1,
"currentR": 2.35,
"currentL": 0.00,
"stalled": false
},
"motor2": {
"speed": 50,
"direction": -1,
"currentR": 0.00,
"currentL": 1.87,
"stalled": false
}
}
Stall Protection
The stall detection uses a sample-based approach for reliability (independent per motor):
- Threshold: Current above 8.0A indicates potential stall (based on observed ~2A running vs ~17A stalled)
- Debounce: 3 consecutive samples above threshold confirms stall (~300ms at 100ms intervals)
- Stabilization: Ignores current spikes for 500ms after direction changes
When stall is confirmed on a motor:
- That motor stops immediately
- Serial log:
MotorX STALL DETECTED! Current=X.XXA (threshold=8.0A) - Web interface shows red "STALL!" warning on that motor's panel
To resume operation, send a new direction command via the web interface.
Description
Languages
C++
90.2%
C
9.8%