diff --git a/AGENTS.md b/AGENTS.md index 8f44d09..8d7c45b 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -4,7 +4,7 @@ This file provides guidance to agents when working with code in this repository. ## Project Overview -PlatformIO project for ESP32 LOLIN32 Rev1 controlling BTS7960 dual H-bridge motor driver (12V DC). +PlatformIO project for ESP32 LOLIN32 Rev1 controlling **two BTS7960 dual H-bridge motor drivers** for a differential drive robot (12V DC). ## Build Commands @@ -17,8 +17,11 @@ pio device monitor # Serial monitor (115200 baud) ## Hardware-Specific Notes - **Board**: ESP32 LOLIN32 Rev1 (use `lolin32` in platformio.ini) -- **Motor Driver**: BTS7960 dual H-bridge - requires 4 GPIO pins (RPWM=25, LPWM=26, R_EN=27, L_EN=14) -- **Power**: 12V DC input to motor driver - logic level is 3.3V compatible with ESP32 +- **Motor Drivers**: 2× BTS7960 dual H-bridge - shared enable pins, independent PWM and current sense +- **Motor 1**: RPWM=25, LPWM=26, R_IS=34, L_IS=35 (PWM channels 0,1) +- **Motor 2**: RPWM=32, LPWM=33, R_IS=36, L_IS=39 (PWM channels 2,3) +- **Shared enables**: R_EN=27, L_EN=14 (both drivers enable together) +- **Power**: 12V DC input to motor drivers - logic level is 3.3V compatible with ESP32 - **WiFi**: Connects to SSID 'tami' with static IP 10.81.2.185 - **Reference**: https://deepbluembedded.com/arduino-bts7960-dc-motor-driver/ @@ -27,5 +30,7 @@ pio device monitor # Serial monitor (115200 baud) - Use built-in `WebServer` library (NOT ESPAsyncWebServer - has enum compatibility issues with ESP32 Arduino 3.x) - WebServer requires `handleClient()` call in loop() - unlike async version - LEDC PWM at 20kHz reduces motor noise -- Enable pins (R_EN, L_EN) must be HIGH before PWM works +- Enable pins (R_EN, L_EN) must be HIGH before PWM works - configured once by first motor init - **BTS7960 is 3.3V compatible**: Inputs are TTL/CMOS compatible (VIL<0.8V, VIH>2.0V) - ESP32 GPIOs work directly without level shifters (per BTN7960 datasheet section 5.4.1) +- **MotorController is parameterized**: Instances `motor1` and `motor2` are created with `MotorPins` struct +- **Legacy compatibility**: `motor` macro aliases to `motor1`, legacy `/speed` and `/direction` endpoints map to motor1 diff --git a/include/config.h b/include/config.h index 6ae6458..3231931 100644 --- a/include/config.h +++ b/include/config.h @@ -11,22 +11,51 @@ #define SUBNET IPAddress(255, 255, 255, 0) #define DNS IPAddress(10, 81, 2, 1) -// BTS7960 Pin Definitions -#define L_EN_PIN 14 // Left Enable -#define LPWM_PIN 26 // Left PWM (Reverse) -#define L_IS_PIN 35 // Left Current Sense (ADC input only) +// Shared Enable Pins (both drivers share these) +#define R_EN_PIN 27 // Right Enable (shared) +#define L_EN_PIN 14 // Left Enable (shared) -#define R_EN_PIN 27 // Right Enable -#define RPWM_PIN 25 // Right PWM (Forward) -#define R_IS_PIN 34 // Right Current Sense (ADC input only) +// Motor 1 BTS7960 Pin Definitions +#define RPWM1_PIN 25 // Right PWM Motor 1 (Forward) +#define LPWM1_PIN 26 // Left PWM Motor 1 (Reverse) +#define R_IS1_PIN 34 // Right Current Sense Motor 1 (ADC input only) +#define L_IS1_PIN 35 // Left Current Sense Motor 1 (ADC input only) + +// Motor 2 BTS7960 Pin Definitions +#define RPWM2_PIN 32 // Right PWM Motor 2 (Forward) +#define LPWM2_PIN 33 // Left PWM Motor 2 (Reverse) +#define R_IS2_PIN 36 // Right Current Sense Motor 2 (ADC input only - VP) +#define L_IS2_PIN 39 // Left Current Sense Motor 2 (ADC input only - VN) // PWM Configuration #define PWM_FREQ 20000 // 20kHz - reduces motor noise #define PWM_RESOLUTION 8 // 8-bit resolution (0-255) -#define PWM_CHANNEL_R 0 // LEDC channel for RPWM -#define PWM_CHANNEL_L 1 // LEDC channel for LPWM +#define PWM_CHANNEL_R1 0 // LEDC channel for Motor 1 RPWM +#define PWM_CHANNEL_L1 1 // LEDC channel for Motor 1 LPWM +#define PWM_CHANNEL_R2 2 // LEDC channel for Motor 2 RPWM +#define PWM_CHANNEL_L2 3 // LEDC channel for Motor 2 LPWM #define MIN_PWM_PERCENT 20 // Minimum PWM when motor is running (%) +// Motor pin configuration structure +struct MotorPins { + uint8_t rpwm; // Right PWM pin (forward) + uint8_t lpwm; // Left PWM pin (reverse) + uint8_t r_is; // Right current sense pin + uint8_t l_is; // Left current sense pin + uint8_t pwm_channel_r; // LEDC PWM channel for right + uint8_t pwm_channel_l; // LEDC PWM channel for left +}; + +// Motor 1 pin configuration +const MotorPins MOTOR1_PINS = { + RPWM1_PIN, LPWM1_PIN, R_IS1_PIN, L_IS1_PIN, PWM_CHANNEL_R1, PWM_CHANNEL_L1 +}; + +// Motor 2 pin configuration +const MotorPins MOTOR2_PINS = { + RPWM2_PIN, LPWM2_PIN, R_IS2_PIN, L_IS2_PIN, PWM_CHANNEL_R2, PWM_CHANNEL_L2 +}; + // Current Sense Configuration // BTS7960 current sense ratio: 8500:1 (kilo-amps) // With 1kΩ resistor on IS pin: V = I_motor / 8500 diff --git a/include/motor.h b/include/motor.h index 5932bb7..c371021 100644 --- a/include/motor.h +++ b/include/motor.h @@ -9,6 +9,9 @@ typedef void (*StallCallback)(); class MotorController { public: + // Constructor with pin configuration and motor name + MotorController(const MotorPins& pins, const char* name); + void begin(); void setSpeed(int speed); // 0-100 percentage void setDirection(int dir); // -1=reverse, 0=stop, 1=forward @@ -20,6 +23,7 @@ public: float getCurrentRight(); // Current in amps (forward direction) float getCurrentLeft(); // Current in amps (reverse direction) float getCurrentActive(); // Current from active direction + const char* getName(); // Get motor name for logging // Stall detection bool isStalled(); @@ -27,6 +31,9 @@ public: void resetStallDetection(); private: + MotorPins _pins; + const char* _name; + int _speed = 0; int _direction = 0; float _currentRight = 0; @@ -42,12 +49,20 @@ private: 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(); }; -extern MotorController motor; +// Two motor controller instances +extern MotorController motor1; +extern MotorController motor2; + +// Legacy alias for backwards compatibility +#define motor motor1 #endif diff --git a/plans/dual-motor-driver.md b/plans/dual-motor-driver.md new file mode 100644 index 0000000..f421ff7 --- /dev/null +++ b/plans/dual-motor-driver.md @@ -0,0 +1,118 @@ +# Dual BTS7960 Motor Driver Integration Plan + +## Overview +Add a second BTS7960 motor driver for differential drive robot, sharing enable pins between both drivers. + +## Hardware Configuration + +### Pin Assignments + +| 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 (RPWM2) --------------------> RPWM +GPIO 33 (LPWM2) --------------------> LPWM +GPIO 36 (R_IS2) <-------------------- R_IS +GPIO 39 (L_IS2) <-------------------- L_IS +``` + +## Code Changes + +### 1. config.h - Add Motor 2 pin definitions + +```cpp +// Motor 2 BTS7960 Pin Definitions +#define RPWM2_PIN 32 // Right PWM Motor 2 +#define LPWM2_PIN 33 // Left PWM Motor 2 +#define R_IS2_PIN 36 // Right Current Sense Motor 2 +#define L_IS2_PIN 39 // Left Current Sense Motor 2 + +// Motor 2 PWM Channels +#define PWM_CHANNEL_R2 2 +#define PWM_CHANNEL_L2 3 +``` + +### 2. motor.h - Parameterized MotorController + +```cpp +struct MotorPins { + uint8_t rpwm; + uint8_t lpwm; + uint8_t r_is; + uint8_t l_is; + uint8_t pwm_channel_r; + uint8_t pwm_channel_l; +}; + +class MotorController { +public: + MotorController(const MotorPins& pins, const char* name); + void begin(); + // ... existing methods unchanged +private: + MotorPins _pins; + const char* _name; + // ... existing members +}; + +extern MotorController motor1; +extern MotorController motor2; +``` + +### 3. motor.cpp - Use parameterized pins + +- Constructor stores pin config +- `begin()` uses `_pins.rpwm` instead of `RPWM_PIN`, etc. +- Serial output includes motor name for debugging + +### 4. webserver.cpp - Dual motor API + +New endpoints: +- `/motor1/speed`, `/motor1/direction`, `/motor1/stop` +- `/motor2/speed`, `/motor2/direction`, `/motor2/stop` +- `/status` - returns both motors data + +Keep legacy endpoints mapping to motor1 for backwards compatibility. + +### 5. main.cpp - Initialize both controllers + +```cpp +void setup() { + motor1.begin(); + motor1.setStallCallback(onMotor1Stall); + motor2.begin(); + motor2.setStallCallback(onMotor2Stall); + // ... +} + +void loop() { + handleWebServer(); + motor1.update(); + motor2.update(); + delay(1); +} +``` + +## Notes + +- Enable pins are set HIGH once in motor1.begin - both drivers share them +- GPIO 36 and 39 are valid for ADC input - they are input-only pins +- GPIO 32 and 33 support both PWM and ADC, using them for PWM only +- Web UI will need expansion to show both motors - consider tabbed interface or side-by-side layout diff --git a/readme.md b/readme.md index ddee4e7..e360346 100644 --- a/readme.md +++ b/readme.md @@ -1,15 +1,16 @@ -# BTS7960 Motor Controller +# Dual BTS7960 Motor Controller -ESP32-based DC motor controller with web interface, using BTS7960 dual H-bridge driver with current sensing and stall protection. +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 control panel with real-time current monitoring -- Forward/Reverse/Stop motor control with speed slider (20-100%) -- Current sensing on both H-bridge sides (R_IS and L_IS) -- Sample-based stall detection with automatic motor shutoff +- 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 +- Stall warning displayed on web interface per motor +- Emergency stop for both motors ## Hardware @@ -18,9 +19,9 @@ ESP32-based DC motor controller with web interface, using BTS7960 dual H-bridge | Component | Model/Specification | |-----------|---------------------| | Microcontroller | ESP32 LOLIN32 Rev1 | -| Motor Driver | BTS7960 Dual H-Bridge Module | +| Motor Drivers | 2× BTS7960 Dual H-Bridge Module | | Power Supply | 12V DC | -| Sense Resistors | 2× 1kΩ (for current sensing) | +| Sense Resistors | 4× 1kΩ (for current sensing) | ### BTS7960 Module Reference @@ -29,16 +30,37 @@ ESP32-based DC motor controller with web interface, using BTS7960 dual H-bridge ## Wiring -### Motor Control Pins +### Pin Assignments -| BTS7960 Pin | ESP32 GPIO | Function | -|-------------|------------|----------| -| RPWM | GPIO25 | Forward PWM | -| LPWM | GPIO26 | Reverse PWM | -| R_EN | GPIO27 | Right Enable | -| L_EN | GPIO14 | Left Enable | -| VCC | 3.3V | Logic Power | -| GND | GND | Ground | +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 @@ -47,29 +69,19 @@ The BTS7960 has IS (Current Sense) pins that output current proportional to moto ``` BTS7960 Module ESP32 ┌─────────────────┐ ┌──────┐ -│ │ │ │ -│ R_IS (pin 7) ─┼──────┬───────┤GPIO34│ -│ │ │ │ │ -│ │ [R1] │ │ +│ R_IS (pin 7) ─┼──────┬───────┤GPIOxx│ (34 for M1, 36 for M2) +│ │ [R] │ │ │ │ 1kΩ │ │ -│ │ │ │ │ │ GND ─┼──────┴───────┤GND │ │ │ │ │ -│ L_IS (pin 8) ─┼──────┬───────┤GPIO35│ -│ │ │ │ │ -│ │ [R2] │ │ +│ L_IS (pin 8) ─┼──────┬───────┤GPIOxx│ (35 for M1, 39 for M2) +│ │ [R] │ │ │ │ 1kΩ │ │ -│ │ │ │ │ │ GND ─┼──────┴───────┤GND │ └─────────────────┘ └──────┘ ``` -| Connection | Details | -|------------|---------| -| R_IS → GPIO34 | Through 1kΩ resistor to GND | -| L_IS → GPIO35 | Through 1kΩ resistor to GND | - -**Note:** GPIO34 and GPIO35 are input-only pins on ESP32, ideal for ADC readings. +**Note:** GPIO34, 35, 36, 39 are input-only pins on ESP32, ideal for ADC readings. ### Current Sensing Math @@ -116,32 +128,61 @@ Access the control panel at `http://10.81.2.185` (or the IP shown on serial moni ### Features -- **Current Display**: Real-time left (L) and right (R) current readings in amps -- **Direction Status**: Shows FORWARD, REVERSE, or STOPPED -- **Speed Slider**: Adjustable from 20% to 100% (minimum PWM prevents motor stalling at low speeds) -- **Stall Warning**: Red banner appears when stall is detected +- **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: `{speed, direction, currentR, currentL, stalled}` | -| `/speed` | POST | `value` (0-100) | Set motor speed percentage | -| `/direction` | POST | `value` (-1, 0, 1) | Set direction (reverse/stop/forward) | -| `/stop` | POST | - | Emergency stop | +| `/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: +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: -1. Motor stops immediately -2. Serial log: `STALL PROTECTION: Stopping motor (current: X.XXA)` -3. Web interface shows red "STALL DETECTED" warning +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. diff --git a/src/main.cpp b/src/main.cpp index 640e767..bd6ad8d 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -4,10 +4,16 @@ #include "motor.h" #include "webserver.h" -// Called when motor stall is detected -void onMotorStall() { - Serial.println("Stall callback triggered - stopping motor!"); - motor.stop(); +// Called when motor 1 stall is detected +void onMotor1Stall() { + Serial.println("Motor1 stall callback triggered - stopping motor!"); + motor1.stop(); +} + +// Called when motor 2 stall is detected +void onMotor2Stall() { + Serial.println("Motor2 stall callback triggered - stopping motor!"); + motor2.stop(); } void setupWiFi() { @@ -44,13 +50,16 @@ void setup() { delay(1000); Serial.println("\n============================="); - Serial.println(" BTS7960 Motor Controller"); - Serial.println(" Simple Stall Detection"); + Serial.println(" Dual BTS7960 Motor Controller"); + Serial.println(" Differential Drive Robot"); Serial.println("=============================\n"); - // Initialize motor controller - motor.begin(); - motor.setStallCallback(onMotorStall); + // Initialize motor controllers + motor1.begin(); + motor1.setStallCallback(onMotor1Stall); + + motor2.begin(); + motor2.setStallCallback(onMotor2Stall); // Connect to WiFi setupWiFi(); @@ -65,6 +74,7 @@ void setup() { void loop() { handleWebServer(); - motor.update(); // Update current sensing and logging + motor1.update(); // Update current sensing and logging for motor 1 + motor2.update(); // Update current sensing and logging for motor 2 delay(1); } diff --git a/src/motor.cpp b/src/motor.cpp index 41ff8d1..1fffff3 100644 --- a/src/motor.cpp +++ b/src/motor.cpp @@ -6,41 +6,57 @@ // Number of samples for offset calibration #define OFFSET_CALIBRATION_SAMPLES 16 -MotorController motor; +// Static member initialization +bool MotorController::_enablePinsConfigured = false; + +// Create two motor controller instances +MotorController motor1(MOTOR1_PINS, "Motor1"); +MotorController motor2(MOTOR2_PINS, "Motor2"); + +// Constructor +MotorController::MotorController(const MotorPins& pins, const char* name) + : _pins(pins), _name(name) { +} void MotorController::begin() { - // Configure enable pins as outputs - pinMode(R_EN_PIN, OUTPUT); - pinMode(L_EN_PIN, OUTPUT); + // Configure shared enable pins only once (first motor to initialize does this) + if (!_enablePinsConfigured) { + pinMode(R_EN_PIN, OUTPUT); + pinMode(L_EN_PIN, OUTPUT); + + // Enable the H-bridge (shared by both drivers) + digitalWrite(R_EN_PIN, HIGH); + digitalWrite(L_EN_PIN, HIGH); + + _enablePinsConfigured = true; + Serial.println("Shared enable pins configured (HIGH)"); + } - // Enable the H-bridge - digitalWrite(R_EN_PIN, HIGH); - digitalWrite(L_EN_PIN, HIGH); - - // Configure LEDC PWM channels for ESP32 - ledcSetup(PWM_CHANNEL_R, PWM_FREQ, PWM_RESOLUTION); - ledcSetup(PWM_CHANNEL_L, PWM_FREQ, PWM_RESOLUTION); + // Configure LEDC PWM channels for this motor + ledcSetup(_pins.pwm_channel_r, PWM_FREQ, PWM_RESOLUTION); + ledcSetup(_pins.pwm_channel_l, PWM_FREQ, PWM_RESOLUTION); // Attach PWM channels to GPIO pins - ledcAttachPin(RPWM_PIN, PWM_CHANNEL_R); - ledcAttachPin(LPWM_PIN, PWM_CHANNEL_L); + ledcAttachPin(_pins.rpwm, _pins.pwm_channel_r); + ledcAttachPin(_pins.lpwm, _pins.pwm_channel_l); #if CURRENT_SENSING_ENABLED // Configure current sense pins as analog inputs - // GPIO34 and GPIO35 are input-only pins, perfect for ADC + // GPIO34, 35, 36, 39 are input-only pins, perfect for ADC analogSetAttenuation(ADC_11db); // Full range 0-3.3V - pinMode(R_IS_PIN, INPUT); - pinMode(L_IS_PIN, INPUT); + pinMode(_pins.r_is, INPUT); + pinMode(_pins.l_is, INPUT); // Calibrate zero-current offset (ESP32 ADC has inherent offset) calibrateCurrentOffset(); - Serial.println("Current sensing enabled"); + Serial.printf("%s: Current sensing enabled\n", _name); #endif // Start stopped stop(); - Serial.println("Motor controller initialized"); + Serial.printf("%s: Controller initialized (RPWM=%d, LPWM=%d)\n", + _name, _pins.rpwm, _pins.lpwm); } void MotorController::setSpeed(int speed) { @@ -61,10 +77,10 @@ void MotorController::setDirection(int dir) { void MotorController::stop() { // Don't reset _speed - keep last speed setting _direction = 0; - ledcWrite(PWM_CHANNEL_R, 0); - ledcWrite(PWM_CHANNEL_L, 0); + ledcWrite(_pins.pwm_channel_r, 0); + ledcWrite(_pins.pwm_channel_l, 0); resetStallDetection(); - Serial.println("Motor: STOPPED (manual)"); + Serial.printf("%s: STOPPED (manual)\n", _name); } void MotorController::update() { @@ -76,8 +92,8 @@ void MotorController::update() { const int dotInterval = 5; // Print dot every N idle readings // Read current sensors - _currentRight = readCurrentSense(R_IS_PIN); - _currentLeft = readCurrentSense(L_IS_PIN); + _currentRight = readCurrentSense(_pins.r_is); + _currentLeft = readCurrentSense(_pins.l_is); // Log current readings frequently for data collection unsigned long now = millis(); @@ -90,8 +106,8 @@ void MotorController::update() { if (isIdle) { if (!lastWasIdle) { // First idle after non-idle: print full line once - Serial.printf("CURRENT: R=%.2fA L=%.2fA dir=%d spd=%d\n", - _currentRight, _currentLeft, _direction, _speed); + Serial.printf("%s CURRENT: R=%.2fA L=%.2fA dir=%d spd=%d\n", + _name, _currentRight, _currentLeft, _direction, _speed); idleDotCount = 0; } else { // Subsequent idle: print dot every N readings @@ -109,8 +125,8 @@ void MotorController::update() { if (lastWasIdle && idleDotCount > 0) { Serial.println(); // Newline after dots before full reading } - Serial.printf("CURRENT: R=%.2fA L=%.2fA dir=%d spd=%d\n", - _currentRight, _currentLeft, _direction, _speed); + Serial.printf("%s CURRENT: R=%.2fA L=%.2fA dir=%d spd=%d\n", + _name, _currentRight, _currentLeft, _direction, _speed); lastWasIdle = false; idleDotCount = 0; } @@ -146,6 +162,10 @@ float MotorController::getCurrentActive() { return 0.0f; } +const char* MotorController::getName() { + return _name; +} + // Stall detection bool MotorController::isStalled() { return _stalled; @@ -181,8 +201,8 @@ void MotorController::checkStall() { if (_stallConfirmCount >= STALL_CONFIRM_SAMPLES && !_stalled) { _stalled = true; - Serial.printf("STALL DETECTED! Current=%.2fA (threshold=%.1fA)\n", - activeCurrent, STALL_THRESHOLD); + Serial.printf("%s STALL DETECTED! Current=%.2fA (threshold=%.1fA)\n", + _name, activeCurrent, STALL_THRESHOLD); // Call stall callback if registered if (_stallCallback != nullptr) { @@ -196,7 +216,7 @@ void MotorController::checkStall() { // Clear stall flag if current returns to normal if (_stalled) { _stalled = false; - Serial.println("Stall condition cleared"); + Serial.printf("%s: Stall condition cleared\n", _name); } } } @@ -213,19 +233,19 @@ void MotorController::applyMotorState() { if (_direction == 0 || _speed == 0) { // Stop - both PWM outputs off - ledcWrite(PWM_CHANNEL_R, 0); - ledcWrite(PWM_CHANNEL_L, 0); + ledcWrite(_pins.pwm_channel_r, 0); + ledcWrite(_pins.pwm_channel_l, 0); } else if (_direction > 0) { // Forward - RPWM active, LPWM off - ledcWrite(PWM_CHANNEL_R, pwmValue); - ledcWrite(PWM_CHANNEL_L, 0); + ledcWrite(_pins.pwm_channel_r, pwmValue); + ledcWrite(_pins.pwm_channel_l, 0); } else { // Reverse - LPWM active, RPWM off - ledcWrite(PWM_CHANNEL_R, 0); - ledcWrite(PWM_CHANNEL_L, pwmValue); + ledcWrite(_pins.pwm_channel_r, 0); + ledcWrite(_pins.pwm_channel_l, pwmValue); } - Serial.printf("Motor: dir=%d, speed=%d%%, pwm=%d\n", _direction, _speed, pwmValue); + Serial.printf("%s: dir=%d, speed=%d%%, pwm=%d\n", _name, _direction, _speed, pwmValue); } float MotorController::readCurrentSense(int pin) { @@ -234,7 +254,7 @@ float MotorController::readCurrentSense(int pin) { int adcValue = analogRead(pin); // Subtract zero-current offset (calibrated at startup) - int offset = (pin == R_IS_PIN) ? _adcOffsetRight : _adcOffsetLeft; + int offset = (pin == _pins.r_is) ? _adcOffsetRight : _adcOffsetLeft; adcValue = adcValue - offset; if (adcValue < 0) adcValue = 0; // Clamp to zero @@ -263,15 +283,15 @@ void MotorController::calibrateCurrentOffset() { long sumLeft = 0; for (int i = 0; i < OFFSET_CALIBRATION_SAMPLES; i++) { - sumRight += analogRead(R_IS_PIN); - sumLeft += analogRead(L_IS_PIN); + sumRight += analogRead(_pins.r_is); + sumLeft += analogRead(_pins.l_is); delay(5); // Small delay between samples } _adcOffsetRight = sumRight / OFFSET_CALIBRATION_SAMPLES; _adcOffsetLeft = sumLeft / OFFSET_CALIBRATION_SAMPLES; - Serial.printf("Current sense offset calibrated: R=%d L=%d (ADC counts)\n", - _adcOffsetRight, _adcOffsetLeft); + Serial.printf("%s: Current sense offset calibrated: R=%d L=%d (ADC counts)\n", + _name, _adcOffsetRight, _adcOffsetLeft); #endif } diff --git a/src/webserver.cpp b/src/webserver.cpp index 2464c5d..30e54b6 100644 --- a/src/webserver.cpp +++ b/src/webserver.cpp @@ -5,13 +5,13 @@ WebServer server(HTTP_PORT); -// HTML page for motor control +// HTML page for dual motor control const char index_html[] PROGMEM = R"rawliteral(
-