- Removed all STALL_* configuration parameters from config.h - Simplified motor.h: removed stall-related methods and member variables - Simplified motor.cpp: deleted checkStall(), resetStallDetection() - Added frequent current logging (100ms) for data collection - Removed stall callback system from main.cpp - Simplified pingpong mode: time-based only, removed stall-return option - Updated webserver: removed stall warning UI, removed stallReturn checkbox - Updated status JSON: removed stalled and ppStallReturn fields This version is for testing with new beefy PSU to collect current data before designing new stall detection algorithm.
268 lines
8.0 KiB
C++
268 lines
8.0 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
|
|
|
|
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);
|
|
|
|
// Calibrate zero-current offset (ESP32 ADC has inherent offset)
|
|
calibrateCurrentOffset();
|
|
Serial.println("Current sensing enabled");
|
|
#endif
|
|
|
|
// Start stopped
|
|
stop();
|
|
|
|
Serial.println("Motor controller initialized");
|
|
}
|
|
|
|
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(PWM_CHANNEL_R, 0);
|
|
ledcWrite(PWM_CHANNEL_L, 0);
|
|
}
|
|
|
|
void MotorController::update() {
|
|
#if CURRENT_SENSING_ENABLED
|
|
static unsigned long lastPrintTime = 0;
|
|
|
|
// Read current sensors
|
|
_currentRight = readCurrentSense(R_IS_PIN);
|
|
_currentLeft = readCurrentSense(L_IS_PIN);
|
|
|
|
// Log current readings frequently for data collection
|
|
unsigned long now = millis();
|
|
if (now - lastPrintTime >= CURRENT_LOG_INTERVAL_MS) {
|
|
lastPrintTime = now;
|
|
Serial.printf("CURRENT: R=%.2fA L=%.2fA dir=%d spd=%d\n",
|
|
_currentRight, _currentLeft, _direction, _speed);
|
|
}
|
|
#endif
|
|
|
|
// Update pingpong mode
|
|
updatePingpong();
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
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(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);
|
|
|
|
// Subtract zero-current offset (calibrated at startup)
|
|
int offset = (pin == R_IS_PIN) ? _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(R_IS_PIN);
|
|
sumLeft += analogRead(L_IS_PIN);
|
|
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);
|
|
#endif
|
|
}
|
|
|
|
// Pingpong mode implementation (time-based only)
|
|
void MotorController::startPingpong(int speed, int timeMs, int speedRandomPercent, int timeRandomPercent) {
|
|
_pingpongBaseSpeed = constrain(speed, 0, 100);
|
|
_pingpongBaseTime = constrain(timeMs, 100, 30000);
|
|
_pingpongSpeedRandomPercent = constrain(speedRandomPercent, 0, 100);
|
|
_pingpongTimeRandomPercent = constrain(timeRandomPercent, 0, 100);
|
|
|
|
_pingpongDirection = 1;
|
|
_pingpongCurrentSpeed = applyRandomness(_pingpongBaseSpeed, _pingpongSpeedRandomPercent);
|
|
_pingpongCurrentTime = applyRandomness(_pingpongBaseTime, _pingpongTimeRandomPercent);
|
|
_pingpongLastSwitch = millis();
|
|
_pingpongActive = true;
|
|
|
|
// Apply initial state
|
|
_speed = _pingpongCurrentSpeed;
|
|
_direction = _pingpongDirection;
|
|
applyMotorState();
|
|
|
|
Serial.printf("Pingpong started: speed=%d%% (base=%d, rand=%d%%), time=%dms (base=%d, rand=%d%%)\n",
|
|
_pingpongCurrentSpeed, _pingpongBaseSpeed, _pingpongSpeedRandomPercent,
|
|
_pingpongCurrentTime, _pingpongBaseTime, _pingpongTimeRandomPercent);
|
|
}
|
|
|
|
void MotorController::stopPingpong() {
|
|
_pingpongActive = false;
|
|
stop();
|
|
Serial.println("Pingpong stopped");
|
|
}
|
|
|
|
bool MotorController::isPingpongActive() {
|
|
return _pingpongActive;
|
|
}
|
|
|
|
int MotorController::getPingpongSpeed() {
|
|
return _pingpongBaseSpeed;
|
|
}
|
|
|
|
int MotorController::getPingpongTime() {
|
|
return _pingpongBaseTime;
|
|
}
|
|
|
|
int MotorController::getPingpongSpeedRandom() {
|
|
return _pingpongSpeedRandomPercent;
|
|
}
|
|
|
|
int MotorController::getPingpongTimeRandom() {
|
|
return _pingpongTimeRandomPercent;
|
|
}
|
|
|
|
void MotorController::updatePingpong() {
|
|
if (!_pingpongActive) return;
|
|
|
|
unsigned long now = millis();
|
|
|
|
// Time-based switching
|
|
if ((now - _pingpongLastSwitch) >= (unsigned long)_pingpongCurrentTime) {
|
|
// Switch direction
|
|
_pingpongDirection = -_pingpongDirection;
|
|
|
|
// Apply randomness for next cycle
|
|
_pingpongCurrentSpeed = applyRandomness(_pingpongBaseSpeed, _pingpongSpeedRandomPercent);
|
|
_pingpongCurrentTime = applyRandomness(_pingpongBaseTime, _pingpongTimeRandomPercent);
|
|
_pingpongLastSwitch = now;
|
|
|
|
// Apply new state
|
|
_speed = _pingpongCurrentSpeed;
|
|
_direction = _pingpongDirection;
|
|
applyMotorState();
|
|
|
|
Serial.printf("Pingpong switch: dir=%d, speed=%d%%, next_time=%dms\n",
|
|
_pingpongDirection, _pingpongCurrentSpeed, _pingpongCurrentTime);
|
|
}
|
|
}
|
|
|
|
int MotorController::applyRandomness(int baseValue, int randomPercent) {
|
|
if (randomPercent == 0) return baseValue;
|
|
|
|
int maxVariation = (baseValue * randomPercent) / 100;
|
|
int variation = random(-maxVariation, maxVariation + 1);
|
|
int result = baseValue + variation;
|
|
|
|
// Ensure result stays positive and reasonable
|
|
return max(1, result);
|
|
}
|