Files
walker_control/src/motor.cpp
devdesk 25dc34c6e3 Replace stall/homing flow with signed slider drive control
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.
2026-02-20 14:30:46 +02:00

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
}