feat: add ESP32 BTS7960 motor controller with web interface
- Implement MotorController class with PWM speed control (0-100%) - Add bidirectional control (forward/reverse) via LEDC PWM at 20kHz - Include optional current sensing and stall detection - Create responsive web UI with speed slider and direction buttons - Configure WiFi with static IP (10.81.2.185) - Use built-in WebServer library for ESP32 Arduino 3.x compatibility
This commit is contained in:
182
src/motor.cpp
Normal file
182
src/motor.cpp
Normal file
@@ -0,0 +1,182 @@
|
||||
#include "motor.h"
|
||||
|
||||
// Set to true to enable current sensing (requires R_IS and L_IS connected)
|
||||
#define CURRENT_SENSING_ENABLED false
|
||||
|
||||
MotorController motor;
|
||||
|
||||
void MotorController::begin() {
|
||||
// Configure enable pins as outputs
|
||||
pinMode(R_EN_PIN, OUTPUT);
|
||||
pinMode(L_EN_PIN, OUTPUT);
|
||||
|
||||
// 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);
|
||||
|
||||
// Attach PWM channels to GPIO pins
|
||||
ledcAttachPin(RPWM_PIN, PWM_CHANNEL_R);
|
||||
ledcAttachPin(LPWM_PIN, PWM_CHANNEL_L);
|
||||
|
||||
#if CURRENT_SENSING_ENABLED
|
||||
// Configure current sense pins as analog inputs
|
||||
// GPIO34 and GPIO35 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);
|
||||
Serial.println("Current sensing enabled");
|
||||
#endif
|
||||
|
||||
// Start stopped
|
||||
stop();
|
||||
|
||||
Serial.println("Motor controller initialized");
|
||||
}
|
||||
|
||||
void MotorController::setSpeed(int speed) {
|
||||
_speed = constrain(speed, 0, 100);
|
||||
_stalled = false; // Reset stall state on new command
|
||||
_stallStartTime = 0;
|
||||
applyMotorState();
|
||||
}
|
||||
|
||||
void MotorController::setDirection(int dir) {
|
||||
_direction = constrain(dir, -1, 1);
|
||||
_stalled = false; // Reset stall state on new command
|
||||
_stallStartTime = 0;
|
||||
applyMotorState();
|
||||
}
|
||||
|
||||
void MotorController::stop() {
|
||||
_speed = 0;
|
||||
_direction = 0;
|
||||
_stalled = false;
|
||||
_stallStartTime = 0;
|
||||
ledcWrite(PWM_CHANNEL_R, 0);
|
||||
ledcWrite(PWM_CHANNEL_L, 0);
|
||||
}
|
||||
|
||||
void MotorController::update() {
|
||||
#if CURRENT_SENSING_ENABLED
|
||||
// Read current sensors
|
||||
_currentRight = readCurrentSense(R_IS_PIN);
|
||||
_currentLeft = readCurrentSense(L_IS_PIN);
|
||||
|
||||
// Check for stall condition
|
||||
checkStall();
|
||||
#endif
|
||||
}
|
||||
|
||||
int MotorController::getSpeed() {
|
||||
return _speed;
|
||||
}
|
||||
|
||||
int MotorController::getDirection() {
|
||||
return _direction;
|
||||
}
|
||||
|
||||
float MotorController::getCurrentRight() {
|
||||
return _currentRight;
|
||||
}
|
||||
|
||||
float MotorController::getCurrentLeft() {
|
||||
return _currentLeft;
|
||||
}
|
||||
|
||||
float MotorController::getCurrentActive() {
|
||||
if (_direction > 0) {
|
||||
return _currentRight;
|
||||
} else if (_direction < 0) {
|
||||
return _currentLeft;
|
||||
}
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
bool MotorController::isStalled() {
|
||||
return _stalled;
|
||||
}
|
||||
|
||||
void MotorController::setStallCallback(void (*callback)(float current)) {
|
||||
_stallCallback = callback;
|
||||
}
|
||||
|
||||
void MotorController::applyMotorState() {
|
||||
// Convert percentage to 8-bit PWM value
|
||||
int pwmValue = map(_speed, 0, 100, 0, 255);
|
||||
|
||||
if (_direction == 0 || _speed == 0) {
|
||||
// Stop - both PWM outputs off
|
||||
ledcWrite(PWM_CHANNEL_R, 0);
|
||||
ledcWrite(PWM_CHANNEL_L, 0);
|
||||
} else if (_direction > 0) {
|
||||
// Forward - RPWM active, LPWM off
|
||||
ledcWrite(PWM_CHANNEL_R, pwmValue);
|
||||
ledcWrite(PWM_CHANNEL_L, 0);
|
||||
} else {
|
||||
// Reverse - LPWM active, RPWM off
|
||||
ledcWrite(PWM_CHANNEL_R, 0);
|
||||
ledcWrite(PWM_CHANNEL_L, pwmValue);
|
||||
}
|
||||
|
||||
Serial.printf("Motor: dir=%d, speed=%d%%, pwm=%d\n", _direction, _speed, pwmValue);
|
||||
}
|
||||
|
||||
float MotorController::readCurrentSense(int pin) {
|
||||
#if CURRENT_SENSING_ENABLED
|
||||
// Read ADC value (12-bit, 0-4095)
|
||||
int adcValue = analogRead(pin);
|
||||
|
||||
// Convert to voltage (0-3.3V)
|
||||
float voltage = (adcValue / ADC_MAX) * ADC_VREF;
|
||||
|
||||
// Calculate current
|
||||
// IS pin outputs: I_sense = I_load / CURRENT_SENSE_RATIO
|
||||
// With sense resistor: V = I_sense * R_sense
|
||||
// Therefore: I_load = V * CURRENT_SENSE_RATIO / R_sense
|
||||
float current = (voltage * CURRENT_SENSE_RATIO) / SENSE_RESISTOR;
|
||||
|
||||
return current;
|
||||
#else
|
||||
return 0.0f;
|
||||
#endif
|
||||
}
|
||||
|
||||
void MotorController::checkStall() {
|
||||
#if CURRENT_SENSING_ENABLED
|
||||
// Only check stall when motor should be running
|
||||
if (_direction == 0 || _speed == 0) {
|
||||
_stalled = false;
|
||||
_stallStartTime = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
float activeCurrent = getCurrentActive();
|
||||
|
||||
// Check if current exceeds stall threshold
|
||||
if (activeCurrent > STALL_CURRENT_THRESHOLD) {
|
||||
if (_stallStartTime == 0) {
|
||||
// Start timing potential stall
|
||||
_stallStartTime = millis();
|
||||
} else if ((millis() - _stallStartTime) > STALL_DETECT_TIME_MS) {
|
||||
// Stall confirmed
|
||||
if (!_stalled) {
|
||||
_stalled = true;
|
||||
Serial.printf("STALL DETECTED! Current: %.2fA\n", activeCurrent);
|
||||
|
||||
// Call callback if registered
|
||||
if (_stallCallback != nullptr) {
|
||||
_stallCallback(activeCurrent);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Current normal, reset stall detection
|
||||
_stallStartTime = 0;
|
||||
_stalled = false;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
Reference in New Issue
Block a user