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.
202 lines
5.9 KiB
C++
202 lines
5.9 KiB
C++
#include "motor.h"
|
|
|
|
// Set to true to enable current sensing (requires R_IS and L_IS connected)
|
|
#define CURRENT_SENSING_ENABLED true
|
|
|
|
// Number of samples for offset calibration
|
|
#define OFFSET_CALIBRATION_SAMPLES 16
|
|
|
|
// 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 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)");
|
|
}
|
|
|
|
// 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(_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, 35, 36, 39 are input-only pins, perfect for ADC
|
|
analogSetAttenuation(ADC_11db); // Full range 0-3.3V
|
|
pinMode(_pins.r_is, INPUT);
|
|
pinMode(_pins.l_is, INPUT);
|
|
|
|
// Calibrate zero-current offset (ESP32 ADC has inherent offset)
|
|
calibrateCurrentOffset();
|
|
Serial.printf("%s: Current sensing enabled\n", _name);
|
|
#endif
|
|
|
|
// Start stopped
|
|
stop();
|
|
|
|
Serial.printf("%s: Controller initialized (RPWM=%d, LPWM=%d)\n",
|
|
_name, _pins.rpwm, _pins.lpwm);
|
|
}
|
|
|
|
void MotorController::setSpeed(int speed) {
|
|
_speed = constrain(speed, 0, 100);
|
|
applyMotorState();
|
|
}
|
|
|
|
void MotorController::setDirection(int dir) {
|
|
_direction = constrain(dir, -1, 1);
|
|
applyMotorState();
|
|
}
|
|
|
|
void MotorController::stop() {
|
|
// Don't reset _speed - keep last speed setting
|
|
_direction = 0;
|
|
ledcWrite(_pins.pwm_channel_r, 0);
|
|
ledcWrite(_pins.pwm_channel_l, 0);
|
|
Serial.printf("%s: STOPPED (manual)\n", _name);
|
|
}
|
|
|
|
void MotorController::update() {
|
|
#if CURRENT_SENSING_ENABLED
|
|
static unsigned long lastPrintTime = 0;
|
|
|
|
// Read current sensors
|
|
_currentRight = readCurrentSense(_pins.r_is);
|
|
_currentLeft = readCurrentSense(_pins.l_is);
|
|
|
|
// Log current readings only when motor is running (direction != 0)
|
|
unsigned long now = millis();
|
|
if (_direction != 0 && now - lastPrintTime >= CURRENT_LOG_INTERVAL_MS) {
|
|
lastPrintTime = now;
|
|
Serial.printf("%s CURRENT: R=%.2fA L=%.2fA dir=%d spd=%d\n",
|
|
_name, _currentRight, _currentLeft, _direction, _speed);
|
|
}
|
|
|
|
#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;
|
|
}
|
|
|
|
const char* MotorController::getName() {
|
|
return _name;
|
|
}
|
|
|
|
void MotorController::applyMotorState() {
|
|
// Apply minimum PWM when motor is running
|
|
int effectiveSpeed = _speed;
|
|
if (_speed > 0 && _speed < MIN_PWM_PERCENT) {
|
|
effectiveSpeed = MIN_PWM_PERCENT;
|
|
}
|
|
|
|
// Convert percentage to 8-bit PWM value
|
|
int pwmValue = map(effectiveSpeed, 0, 100, 0, 255);
|
|
|
|
if (_direction == 0 || _speed == 0) {
|
|
// Stop - both PWM outputs off
|
|
ledcWrite(_pins.pwm_channel_r, 0);
|
|
ledcWrite(_pins.pwm_channel_l, 0);
|
|
} else if (_direction > 0) {
|
|
// Forward - RPWM active, LPWM off
|
|
ledcWrite(_pins.pwm_channel_r, pwmValue);
|
|
ledcWrite(_pins.pwm_channel_l, 0);
|
|
} else {
|
|
// Reverse - LPWM active, RPWM off
|
|
ledcWrite(_pins.pwm_channel_r, 0);
|
|
ledcWrite(_pins.pwm_channel_l, pwmValue);
|
|
}
|
|
|
|
Serial.printf("%s: dir=%d, speed=%d%%, pwm=%d\n", _name, _direction, _speed, pwmValue);
|
|
}
|
|
|
|
float MotorController::readCurrentSense(int pin) {
|
|
#if CURRENT_SENSING_ENABLED
|
|
// Read ADC value (12-bit, 0-4095)
|
|
int adcValue = analogRead(pin);
|
|
|
|
// Subtract zero-current offset (calibrated at startup)
|
|
int offset = (pin == _pins.r_is) ? _adcOffsetRight : _adcOffsetLeft;
|
|
adcValue = adcValue - offset;
|
|
if (adcValue < 0) adcValue = 0; // Clamp to zero
|
|
|
|
// 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;
|
|
|
|
// Apply calibration factor (adjust for real-world measurement differences)
|
|
current *= CURRENT_CALIBRATION;
|
|
|
|
return current;
|
|
#else
|
|
return 0.0f;
|
|
#endif
|
|
}
|
|
|
|
void MotorController::calibrateCurrentOffset() {
|
|
#if CURRENT_SENSING_ENABLED
|
|
// Sample ADC multiple times and average to get stable offset
|
|
long sumRight = 0;
|
|
long sumLeft = 0;
|
|
|
|
for (int i = 0; i < OFFSET_CALIBRATION_SAMPLES; i++) {
|
|
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("%s: Current sense offset calibrated: R=%d L=%d (ADC counts)\n",
|
|
_name, _adcOffsetRight, _adcOffsetLeft);
|
|
#endif
|
|
}
|