About a year ago I got my first FOC project working. The project is a reaction wheel inverted pendulum (hardware: motor: quanum 4008 gimbal motor; driver: simpleFOC shield v2.0.3; processor: STM32 F446RE; motor sensor: AMS AS5147; penulum angle sensor: AMS AS5147). Since then, I didn’t touch the code, and it’s been working just fine.
Recently, I’ve started looking to upgrade the project. Since I’m working on a different computer now, I just downloaded the latest SimpleFOC library (2.2.3) and tried to recompile my existing code to my board (STM32 Nucleo).
I found I had to make minor mods to my code to align with API changes between the latest release of the simpleFOC library and the version I’d originally been using (2.2.0). Specifically, I had to add a current_sense.linkDriver(&driver)
line in the setup, and also two sensor.update()
calls in the main loop (one for the motor sensor, one for the pendulum sensor).
This got my code back compiling / working. However, there seems to be one lurking change resulting from the updated library releases that I haven’t been able to identify. Basically, although my code does work, the PID controller I am using to balance the inverted pendulum is now (i.e. with the latest library release) performing far more aggressively than before - i.e. it’s like the controller gains are about 150% or something of what they were before. For clarity, I haven’t actually touched the values of the gains.
To figure out where the change happened, I downgraded my SimpleFOC library back to 2.2.0 (where the controller / pendulum work exactly how I expect), and then I incrementally updated. It seems the change happens between releases 2.2.1 (where I get expected performance) and 2.2.2 (where I get the aggressive controller behavior).
Now I could just re-tune my controller gains with the latest library and carry on, but it would be good to understand why this change in performance is happening. Does anyone have any ideas what changed between the mentioned releases that could result in such a performance change?
In case it helps, I include my code as follows:
#include <SimpleFOC.h>
#include <SparkFun_ADXL345.h>
//static const int noOfEnts = 10;
//int zVals[noOfEnts];
//int zValsNew[noOfEnts];
//float zSmooth;
//float zSum = 0;
float kdmod = 1; //prev 1
float P = 3; //prev 3
float I = 0;
float D = kdmod*1.8;
float pidLim = 2;
float pidRamp = 100;
float PV = kdmod*0.003;
float pendAngle;
float pendSetPoint = 4.62;
float pendAngError;
int onSwitch;
// BLDC motor & driver instance
BLDCMotor motor = BLDCMotor(11);
BLDCDriver3PWM driver = BLDCDriver3PWM(9, 3, 6, 8);
MagneticSensorSPI sensor = MagneticSensorSPI(AS5147_SPI, 10);
MagneticSensorSPI pendulum = MagneticSensorSPI(AS5147_SPI, 7);
InlineCurrentSense current_sense = InlineCurrentSense(0.01, 50.0, A0, A2);
// commander communication instance
Commander command = Commander(Serial);
void doMotor(char* cmd) {
command.motor(&motor, cmd);
}
void doSetPoint(char* cmd) {
command.scalar(&pendSetPoint, cmd);
}
void doRotationControl(char* cmd) {
command.scalar(&PV, cmd);
}
PIDController balance_pid = PIDController{P, I, D, pidRamp, pidLim};
void doPID(char* cmd) {
command.pid(&balance_pid, cmd);
}
void setup() {
// use monitoring with serial for motor init
// monitoring port
Serial.begin(9600);
// comment out if not needed
motor.useMonitoring(Serial);
//motor.monitor_downsample = 0; // initially disable the real-time monitor
pinMode(A1, INPUT);
// sensor config
sensor.spi_mode = SPI_MODE1; // spi mode - OPTIONAL
sensor.clock_speed = 5000000; // spi clock frequency - OPTIONAL
//sensor.min_elapsed_time = 0.0001;
pendulum.spi_mode = SPI_MODE1; // spi mode - OPTIONAL
pendulum.clock_speed = 5000000; // spi clock frequency - OPTIONAL
SPI.setMISO(PA6);
SPI.setMOSI(PA7);
SPI.setSCLK(PA5);
sensor.init();
pendulum.init();
// link the motor to the sensor
motor.linkSensor(&sensor);
// driver config
driver.voltage_power_supply = 12;
driver.init();
current_sense.linkDriver(&driver);
// link driver
motor.linkDriver(&driver);
// initialise motor
motor.init();
// current sense init and linking
current_sense.init();
motor.linkCurrentSense(¤t_sense);
motor.voltage_sensor_align = 3;
// set control loop type to be used
motor.torque_controller = TorqueControlType::foc_current;
motor.controller = MotionControlType::torque;
motor.voltage_limit = 12;
//motor.current_limit = 0.1;
// align encoder and start FOC
motor.initFOC();
//motor.initFOC(1.69, Direction::CW);
// subscribe motor to the commander
command.add('M', doMotor, "motor");
command.add('S', doSetPoint, "pendSetPoint");
command.add('C', doPID, "PID gains");
command.add('Q', doRotationControl, "Rotation control gain");
// Run user commands to configure and the motor (find the full command list in docs.simplefoc.com)
//Serial.println(F("Motor commands sketch | Initial motion control > torque/current : target 0Amps."));
_delay(1000);
}
void loop() {
// iterative setting FOC phase voltage
onSwitch = digitalRead(A1);
// handle switch to enable and disable motor
switch (onSwitch) {
case 0:
motor.disable();
break;
case 1:
if (motor.enabled == 0) motor.enable();
break;
}
motor.loopFOC();
pendulum.update();
sensor.update();
pendAngle = pendulum.getAngle();
//Serial.println(pendAngle, 4);
pendAngError = pendAngle - pendSetPoint;
motor.target = balance_pid(pendAngError) + PV * sensor.getVelocity();
// iterative function setting the outer loop target
motor.move();
// motor monitoring
//motor.monitor();
// user communication
command.run();
}