initial velocity control loop

Achieved with a PI controller checking the current velocity as output by the motors. Since I'm
using stepper motors, the angular velocity is exact (if the motors are not skipping steps) and does not need encoders on the
motors.

TODO: add a little deadzone either on the setpoint or on the velocity, to prevent
oscillations when around the equilibrium point.
velctrl
EmaMaker 2024-04-25 21:19:13 +02:00
parent 27ff883a37
commit 79cffdac4e
3 changed files with 20 additions and 5 deletions

View File

@ -4,7 +4,7 @@
//#define PRINT_ANGLES //#define PRINT_ANGLES
//#define PRINT_QUAT //#define PRINT_QUAT
#define PERFORM_CALIBRATION //Comment to disable startup calibration // #define PERFORM_CALIBRATION //Comment to disable startup calibration
#define IMU_ADDRESS 0x68 //Change to the address of the IMU #define IMU_ADDRESS 0x68 //Change to the address of the IMU
MPU6050 IMU; //Change to the name of any supported IMU! MPU6050 IMU; //Change to the name of any supported IMU!

View File

@ -22,18 +22,25 @@ constexpr double KP = 42;
constexpr double KI = KP * 10.8852; constexpr double KI = KP * 10.8852;
constexpr double KD = 0.3; constexpr double KD = 0.3;
// IMU little bit tilted // PI constants for outer velocity control loop
// TODO: Implement an outer control loop for angular velocity. But that requires encoders on the motors constexpr double KP_vel = 0.0025;
// TODO: try to achieve it crudely by just using a PI controller on the velocity given by the PID balance controller constexpr double KI_vel = 0.0005;
float setpoint = 0.0; float setpoint = 0.0;
float output = 0; float output = 0;
float input = 0; float input = 0;
float vel_setpoint = 0.0;
float vel_input = 0.0;
float yaw{ 0 }, pitch{ 0 }, roll{ 0 }; float yaw{ 0 }, pitch{ 0 }, roll{ 0 };
QuickPID pitchCtrl(&input, &output, &setpoint, KP, KI, KD, pitchCtrl.pMode::pOnError, pitchCtrl.dMode::dOnMeas, pitchCtrl.iAwMode::iAwCondition, pitchCtrl.Action::reverse); QuickPID pitchCtrl(&input, &output, &setpoint, KP, KI, KD, pitchCtrl.pMode::pOnError, pitchCtrl.dMode::dOnMeas, pitchCtrl.iAwMode::iAwCondition, pitchCtrl.Action::reverse);
// TODO: a little deadzone when the input is almost 0, just to avoid unnecessary "nervous" control and eventual oscillations
QuickPID velCtrl(&output, &setpoint, &vel_setpoint, KP_vel, KI_vel, 0, velCtrl.pMode::pOnMeas, velCtrl.dMode::dOnMeas, velCtrl.iAwMode::iAwCondition, velCtrl.Action::direct);
void setup() { void setup() {
Serial.begin(9600); Serial.begin(9600);
@ -57,6 +64,10 @@ void setup() {
pitchCtrl.SetMode(pitchCtrl.Control::automatic); pitchCtrl.SetMode(pitchCtrl.Control::automatic);
pitchCtrl.SetSampleTimeUs(1000); pitchCtrl.SetSampleTimeUs(1000);
velCtrl.SetOutputLimits(-0.15, 0.15);
velCtrl.SetMode(velCtrl.Control::automatic);
velCtrl.SetSampleTimeUs(10000);
digitalWrite(LED_BUILTIN, LOW); digitalWrite(LED_BUILTIN, LOW);
} }
@ -117,13 +128,17 @@ uint32_t current_time = millis(), last_time = millis();
void loop() { void loop() {
update_imu(); update_imu();
velCtrl.Compute();
input = pitch; input = pitch;
//if(abs(input -setpoint) <= 0.01) input = setpoint;
Serial.println(setpoint);
// I also modified the ArduPID library to use compute as a boolean. If calculations were done, it returns true. If not enough time has elapsed, it returns false // I also modified the ArduPID library to use compute as a boolean. If calculations were done, it returns true. If not enough time has elapsed, it returns false
if(pitchCtrl.Compute()){ if(pitchCtrl.Compute()){
double tvelocity = output; double tvelocity = output;
tvelocity = tvelocity / WHEEL_RADIUS * 180 / PI; tvelocity = tvelocity / WHEEL_RADIUS * 180 / PI;
frequency = tvelocity * 0.1125; frequency = tvelocity * 0.1125;
half_period0 = 1000000 / (2*frequency); half_period0 = 1000000 / (2*frequency);

Binary file not shown.