Angle curve tuning - position control

Hi guys,

I have a question about position control that is most likely related to PID parameters tuning. I have a fully working motor setup in closed loop angle mode. The position control works ok, it just always seems to slow down dramatically before reaching the desired position.

It makes like 80% of the entire movement rather fast and than takes about the same amount of time for the remaining 20%. It looks like this:

I tried changing both velocity PID controller parameters and angle PID (only P and low pass filter constant). I also played with output_ramp setting, but nothing seems to reslove this.

Higher P values obviously make it go to the right position quicker, but at a cost of a relatively noticeable overshoot (which is not very acceptable for me)…

The curve looks something like this (orange one):


I borrowed the graph from this post.

I would like to get as close to the blue one as possible. Ideally make it behave
similarly to the open loop position control, which works super well and looks like this:

For completeness I am posting my closed loop code

#include <SimpleFOC.h>

// --- ENCODER ---
MagneticSensorI2C sensor(AS5600_I2C);


// --- DRIVER ---
#define INH_A 6
#define INH_B 5
#define INH_C 3
#define ENABLE A1

#define M_PWM 4
#define OC_ADJ A0

BLDCDriver3PWM driver(INH_A, INH_B, INH_C, ENABLE);

// --- BLDC MOTOR ---
#define POLE_PAIRS 20

BLDCMotor motor(POLE_PAIRS);

void setup() 
{
  Serial.begin(115200);

  SimpleFOCDebug::enable();

  sensor.init();
  motor.linkSensor(&sensor);

  // external driver setup
  pinMode(M_PWM, OUTPUT);
  digitalWrite(M_PWM, HIGH);

  pinMode(OC_ADJ, OUTPUT);
  digitalWrite(OC_ADJ, HIGH);

  // set driver voltage
  driver.voltage_power_supply = 20.0f;

  driver.init();
  motor.linkDriver(&driver);

  motor.foc_modulation = FOCModulationType::SpaceVectorPWM;

  // set torque control loop type and controller
  motor.controller = MotionControlType::angle;
  motor.torque_controller = TorqueControlType::voltage;

  motor.voltage_sensor_align = 0.6f;
  motor.voltage_limit = 2.5f;

  // velocity PID tuning (+ velocity limits)
  motor.PID_velocity.P = 1.1f;
  motor.PID_velocity.I = 6.0f;
  motor.PID_velocity.D = 0.01f;

  motor.PID_velocity.output_ramp = 300;
  motor.LPF_velocity.Tf = 0.2f;

  motor.velocity_limit = 8.0f;

  // angle PID tuning
  motor.P_angle.P = 4.0f;
  motor.P_angle.I = 0.0f;
  motor.P_angle.D = 0.0f;

  motor.P_angle.output_ramp = 15;
  motor.LPF_angle.Tf = 0.001f;

  // foc properties obtained through torque mode
  motor.sensor_direction = Direction::CW;
  motor.zero_electric_angle = 1.91f;

  motor.target = 0.0f;

  motor.init();
  motor.initFOC();

}

void loop() 
{
  if (Serial.available() > 0)
  {
    // PID tuning through serial line

    char component = Serial.read();
    float num = Serial.parseFloat();

    switch (component)
    {
      case 'P':
        motor.P_angle.P = num;
        break;
      case 'I':
        motor.P_angle.I = num;
        break;
      case 'D':
        motor.P_angle.D = num;
        break;
      case 'R':
        motor.P_angle.output_ramp = num;
        break;
      case 'F':
        motor.LPF_angle.Tf = num;
        break;
      case 'T':
        motor.target = num;
        break;
      default:
        break;
    }

    Serial.print("P: ");
    Serial.println(motor.P_angle.P, 3);

    Serial.print("I: ");
    Serial.println(motor.P_angle.I, 3);

    Serial.print("D: ");
    Serial.println(motor.P_angle.D, 3);

    Serial.print("R: ");
    Serial.println(motor.P_angle.output_ramp);

    Serial.print("Tf: ");
    Serial.println(motor.LPF_angle.Tf, 3);

    Serial.print("Target: ");
    Serial.println(motor.target);
    Serial.println();
  }

  motor.loopFOC();
  motor.move();
}

What I tried before posting this:

  • tuning the velocity mode separately (works very nice except changing speeds by setting motor.target. It just jerks to the new speed)
  • trying various P constants for angle mode (higher than 4 makes it overshoot, which is quite low compared to examples)
  • Measuring Atmega328p loops/second (I get around 700Hz with I2C set to 400kHz fast mode)
  • Repositioning the sensor and checking the magnet (correct magnet positioned as precisely as possible with 0.75mm gap between magnet and sensor)
  • Increasing motor’s voltage_limit
  • Increasing low pass filter time constant for both angle and velocity PID controller… Makes it even less responsive
  • Changing AS5600 polling time to “always on”
  • Enjoying nice movement of open loop :smiley:

Yeah, I tried a couple of things, but everything seems to make just worse, not better.

I would like to mention that I am using voltage_torque controller because my driver does not support current sensing right now. Could this be the issue?

I feel like this should be purely caused by the PID not being tuned correctly, but I spent more time than I am willing to admit on changing those.

Did anyone experience similar issues? I will appreciate every help now :smiley: .

Thanks,

Adam!

Higher angle P is the answer. The question is what’s causing the overshoot. Your velocity Tf is very high and likely a source of lag. I usually use .02-.05.

I would advise against using PID_velocity.D, unless you want to try editing the PID code. I think our current code only works properly when target is constant, due to a phenomenon called “derivative kick” http://brettbeauregard.com/blog/2011/04/improving-the-beginners-pid-derivative-kick/

Have you tried using PID_angle.I? I haven’t needed it, but it’s worth a shot.

I tried using I (even D :D) in the PID_angle. The I component seems to slow down the overshoot correction in my case.

I’ve set PID_velocity.D to zero as you advised (quite interesting thing the derivate kick, didn’t know it existed). It has very little to none effect on my velocity_control, but makes the angle mode bit more shaky during movement.

But the velocity Tf you mentioned has enormous effect. Value of 0.05 makes my motor shake like … :smiley: (though, 0.1 seems to work sort of ok). Is this related to sensor noise?

Try plotting motor.getVelocity() while running at constant speed in velocity_openloop and see if it’s smooth. (that is, use open loop to produce smooth motion, and see if the sensor reads it as such, or is noisy)

EDIT: Also check how much time the sensor communication is taking, since it could also potentially be a source of lag between reality and the PID update. Paste this in MagneticSensorI2C.cpp and see what it prints out:

int MagneticSensorI2C::getRawCount() {
  // read the angle register first MSB then LSB
	byte readArray[2];
	uint16_t readValue = 0;
  // notify the device that is aboout to be read
uint32_t t0 = _micros();
	wire->beginTransmission(_conf.chip_address);
	wire->write(_conf.angle_register);
  currWireError = wire->endTransmission(false);
uint32_t t1 = _micros();
  // read the data msb and lsb
	wire->requestFrom(_conf.chip_address, 2);
	for (byte i=0; i < 2; i++) {
		readArray[i] = wire->read();
	}
uint32_t t2 = _micros();
static uint32_t lastPrintTime = 0;
if (millis() - lastPrintTime > 1000){
  lastPrintTime = millis();
  Serial.print("Write time "); Serial.print(t1-t0);
  Serial.print(", read time "); Serial.println(t2-t1);
}
  readValue = (readArray[0] & _conf.msb_mask) << _conf.msb_shift;
  readValue |= (readArray[1] & _conf.lsb_mask) >> _conf.lsb_shift;
	return readValue;
}

Well, I think that this is pretty bad :smiley:

I used open loop velocity mode to turn the motor at 7.0 rad/s, which seems very smooth to me, but this is how it seems to the sensor.

I used separate instance of MagneticSensorI2C and getVelocity() function in the open loop code (hope that’s right). Can’t really use the fancy commander thing, since the arduino seems pretty slow for that.

I’ll try to print out the communication delay as well, but this is most likely it :smiley:

EDIT:

I think you may have some modified version of the source code… My version does not include any definition of _conf property in MagneticSensorI2C class :sweat_smile:

Yeah, my code could be out of date. But that velocity noise is definitely your problem, so I don’t think the I2C delay is worth worrying about.

Perhaps resolution is the issue? Assuming the actual speed is 1 revolution per second, that would be 4096/700Hz=5.85 steps per update. So some updates will see 5 steps and some 6, which is comparable magnitude to the noise you’re getting. But if that was the case, it should alternate between two values, not three.

Try plotting the angle while spinning at constant speed in open loop velocity mode and see if we can learn anything more from the stepping pattern. Or maybe sensor->getAngle() - motor.shaft_angle? i.e. the measured angle minus what it should be from the open loop setting, so the line isn’t perpetually moving upward and making the graph scale too high to see the small variations we’re interested in.

Yeah, plotting the angle seems like reasonable next step! Unfortunately college week is starting for me, so I am away from the setup for a while :sweat_smile:.

Nevertheless, I will try to get a new and perhaps better magnetic sensor during the week. So if the cheap AS5600 turns out to be the problem I will have something else to replace it with. (+ SPI interface might improve the loop time if nothing else)

Do you have any experience with AS5047 vs AS5048A?

Thanks for the great assistance, I will reach out with the graphs in a few days!

It does seem like most people have trouble with AS5600 and then change to one of those two, so probably not a bad plan to do the same.

I’ve only used hall sensors (digital and analog, the latter being my favorite). My machinery designs never have access to the end of the shaft for a magnet. Usually hollow shaft, or motor sticking off the end like this mini-spindle I made to regrind the chuck jaws on my lathe. I has a sensorless drone ESC, but if I did want to put a magnetic encoder on it, I’d have to make a ridiculous structure to reach around the motor and hold it over the end of the shaft.