- Refactor MotorController to parameterized class with MotorPins struct - Add motor1 and motor2 instances with shared enable pins (GPIO 14, 27) - Motor 2 uses GPIO 32/33 for PWM, GPIO 36/39 for current sense - Update web UI with side-by-side dual motor control panels - Add per-motor API endpoints (/motor1/*, /motor2/*) - Add emergency stop button for both motors - Legacy endpoints map to motor1 for backwards compatibility - Update readme and AGENTS.md documentation
189 lines
6.7 KiB
Markdown
189 lines
6.7 KiB
Markdown
# 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
|
||
|
||
- [DeepBlue Embedded Guide](https://deepbluembedded.com/arduino-bts7960-dc-motor-driver/)
|
||
- [BTN7960 Datasheet](https://www.infineon.com/dgdl/Infineon-BTN7960-DS-v01_01-EN.pdf)
|
||
|
||
## 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`](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`](src/motor.cpp) |
|
||
|
||
## Network
|
||
|
||
| Setting | Value |
|
||
|---------|-------|
|
||
| WiFi SSID | tami |
|
||
| Static IP | 10.81.2.185 |
|
||
| HTTP Port | 80 |
|
||
|
||
## Build & Upload
|
||
|
||
```bash
|
||
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
|
||
|
||
```json
|
||
{
|
||
"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):
|
||
|
||
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.
|