Motor oscillates when in cascaded position-velocity control loop

Hi all,

I am currently working on a project in which I need to rotate a motor with a wheel attached to a desired position as fast as possible with little overshoot and/or oscillations. I am using the b-g431b-esc1 with a Cubemars GL30 290KV motor and an as5047p encoder in ABI mode. I have decided to start using PlatformIO for this project with Simplefoc version 2.3.1.

Initially, I used an Emax GB4114 42KV motor for some first tests. I wrote the code heavily based on the b-g431b-esc1 example code for position control (which btw has a mistake, as is says: motor.controller = MotionControlType::velocity). After some tuning this worked fine using the standard cascaded position-velocity controllers.

However, after switching to the Cubemars GL30 290KV I wasn’t able to control the motor to a desired position without oscillations. I tried to first tune the velocity controller in velocity mode and increased motor.LPF_velocity.Tf, but I wasn’t able to get rid of shaky behavior a low velocities. I checked the velocity signal when controlling in torque mode (which I think is voltage control, as I haven’t enabled current sensing as far as I know?), which is very noisy at low velocities.

As a solution I decided to not use the standard cascaded position-velocity controllers, but put it in torque mode and write my own direct position controller in the main loop:

void loop() {
  // PID position loop
  if ((millis() - t1) >= Dt) {
    float angle = encoder.getAngle();

    e = filter(target) - angle; // error
    ei = ei + e*(Dt/1000); // integrated error
    ed = (e - e_old)/(Dt/1000); // differentiated error

    // control command
    C = P*e + I*ei + D*ed;
    
    // update values
    e_old = e;
    t1 = millis();
  }

  // iterative FOC function
  motor.loopFOC();
  // Motion control function
  motor.move(C);
  // communicate with the user
  serialReceiveUserCommand();
}

For now the control loop only gets execute at every fixed Dt (currently at 2 ms) to make differentiating and integrating easier. All in all it works quite well, but it could be optimized by letting it run as fast as it can and tuning can always be optimized of course. The step response looks like this now:

step_response

I am quite happy with the results, however it does raise a couple of questions:

  1. Why is the standard position controller in SimpleFOC a cascaded version?
  2. Why is the velocity signal of the as5047p encoder so noisy at low speeds?
  3. And why was the Emax GB4114 42KV position control seemingly not effected by it? Is it because of faster dynamics of a higher KV motor? I got the feeling it might be a similar issue as posted below for a higher KV motor also using b-g431b-esc1:

I am happy to hear your thoughts!

Kind regards,

Jornel

Hi Jornel,

It’s interesting to read your experiences…

To answer your question:

its a common way to do it. When doing position control it allows you to also limit the velocity and acceleration. Other than that, I’d have to defer the question to the original author of the code :slight_smile:

at low speeds the ABI type encoders have a step-like response, the SPI mode would have extra resolution which could be helpful. Additionally the loop speed may have been quite high, while also not being exactly constant - this can contribute to errors on the time dimension.
Taken together and also depending on the sampling time and the LPF settings this might give a noisy signal.
Another cause can be motor cogging, which interacts with the PID to produce uneven motion. Then it’s less of a sensor problem and more to do with the physical setup.

Did you compare the ABI and SPI modes of the sensor?

It’s a common experience for users that the low KV high ohm gimbal motors are much easier to control than the low ohm motors. Higher KV setups are much more sensitive to issues with the control bandwidth, sensor bandwidth, etc…

A comment on the tuning and control:

I wonder if you tuned the standard SimpleFOC cascade the same way you tuned yours. On the B-G431-ESC1 a fixed loop time of 500Hz would correspond, in simpleFOC parameters to a large value of motor.motion_downsample, maybe with values between 10 to 20.
Correspondingly you would probably also raise the LPF filter value a bit higher than normal.

And some comments on the PID you’ve made:

  • it looks like you’re filtering the target? Shouldn’t you filter the angle value?
  • the voltage output by the PID isn’t limited, nor is the slew (delta-Voltage)
  • the ei term is not limited, it could grow very large (to the point where it affects the accuracy of the calculations)

But from the graph it looks like you’re getting a nice result!

I guess, this is a typo, my ESC1 with SPI sensor tops out at around 11kHz loop speed (after the optimizations found by the sensorless guys).With ABZ and hardware encoder class, you can reach something like 30kHz, but 500 probably not.

1 Like

Yes, you are absolutely right - I meant 500Hz :slight_smile:

Hi Runger,

Thanx for your extensive reply: gives new insights. Some answers to your questions and/or comment on some things:

Makes sense. However, makes tuning a lot harder as you have more knobs to turn. Could also be solved by shaping the reference (target value) such that you ramp to a certain value instead of applying a step, this can effectively also limit speed, acceleration, jerk, etc. This also answers the following question:

I already tried to smoothen out the reference a bit by applying a low pass filter. This is in general a good habit to get a smoother motion instead of applying a direct step. To be honest, I am not sure if it really has much of an effect yet, as I didn’t really check the effect. Nevertheless, filtering the angle value might be wise as well, but with a lower cut off frequency then.

I am not sure why the loop speed would influence the velocity calculation. You mean less averaging will take place? As far as I know, there is no easy way of using the SPI interface with the b-g431b-esc1, correct?

I initially thought the same, because the GL30 has indeed a bit more cogging than the GB4114. However, when I ran the motor in torque mode to check the velocity signal, the motor ran a lot smoother at low velocities, then when in velocity mode at low velocities. Nevertheless, I would like to investigate if I can compensate for the cogging.

Makes sense, maybe good to know that direct position control could improve performance as it seems.

I did play with these values a lot (both downsampling and LPF filter) and it did not really seem solve much, except for having a very slow response when the cut off frequency of the filter was low.

The voltage limit is set by motor.voltage_limit = 5;, this also works in torque mode correct? But indeed, might be nicer to just limit the voltage in the control law, probably a bit more clear. The slew rate is indeed not explicitly set, but as mentioned earlier, it is effected by filtering the reference. An additional controller output filter and/or real input shaper might also be of use.

Correct, still needs to be done. For now the steady state error seems to stay low and gets averaged out by both negative an positive errors. Therefore I have not observed aggresive effects by it.

What I could try though, is using the as5048 instead and use I2C interface.

Ok, fair enough, but to me this looks strange - I would have said that the target is normally computed and can therefore be computed using a limited slew rate if that’s what one wants. But of course in some setups the target itself might come from a noisy source, or, as you say, a LPF is a mathematical model for smoothing the input.

It has a large effect on it. The velocity calculation is a differentiation of the position, and as such depends on timestamps for delta-T. Depending on the MCU, the sensor used the timestamp associated with the measurements will be more or less accurate. Sometimes the error will cancel, for example if it’s a constant lag, and sometimes it won’t. Either way, it can be generally assumed that the time error is smaller (as a proportion) if the timespan between samples is larger.

So the faster you sample, the more error you introduce to the velocity.
This is especially true if the response of the sensor is not very smooth (like with hall sensors).

That’s not unexpected, I would say. The torque mode won’t react to the cogging, it will try to keep a constant Uq (or Iq). The velocity mode will pick up the velocity changes caused by the cogging and “react” to them. Ideally, the PID would smooth out the cogging, but I think in many setups it tends to cause oscillating behaviour that makes it worse instead…

Yes, the limit is applied.

I would not recommend that. The AS5047P has a SPI interface which is much better (faster) than I2C. ABZ is pretty much without lag. I2C is worse than either of these options, IMHO.

You can probably turn up the SPI frequency some and get more loop rate, if the sensor supports it. I’ve gotten as high as 22kHz with an SPI sensor on the same MCU.

I see, but this gives even more reason to leave the velocity loop out if you want to do position control, as obtaining velocity from a position signal is always quite noisy. A kalman filter could be a solution, but I can imaging that that is not super easy to implement in C++.

@Antun_Skuric, was there a specific reason to implement the SimpleFOC position control in a cascaded way, beside being able to put a saturation on the velocity?

It’s a good question, really… I’m not enough of a control theory expert to answer it.

The literature has examples of both - though the cascaded control scheme is very common. I can’t easily find a comparison of the two so far, it would be interesting.

On the one hard, the velocity (torque) is the quantity we can control, not the position. On the other hand the position is the quantity that we can measure via the sensor. We don’t have a separate velocity measurement, we differentiate the positions, introducing additional error and sampling concerns.

Perhaps a single PID position control is a good addition to the MotionControl modes?

I would be very interested to hear people’s opinions on this :question:

I don’t think it would be a very complex addition to the code-base if people think it is a good idea :slight_smile:

1 Like

Probably me being dumb, but what would be the difference between this new mode and setting velocity pid to a fixed (1, 0, 0).

Hey Owen,

The difference is that our velocity PID always operates on the velocity, which isn’t so stable and introduces error / instability.

Even if you set the terms to 1,0,0 the input is still the difference between the velocity set-point (as returned by the angle PID) and the actual velocity as obtained from the differentiated position and so the error gets introduced.

The proposal for the single PID would avoid this problem by using only the position signal, which is more stable, and never using the calculated velocity at all…

2 Likes

It is definitely different, simply put, a velocity controller with just a proportional gain at 1.0 is still a feedback controller and not the same as removing it. Does that answer you question sufficiently? Otherwise is can write down the equations of you want.

1 Like

Hey guys,

So the cascade scheme is the most used scheme in motion control. It is everywhere. :slight_smile:

I would bet that most of the motor control softwares, and especially BLDC high performing ones are implemented this way. However as you have nicely shown it is not the only one and even not the best on for all applications.

It has several benefits that are very nice:

  • you can limit every single variable (velocity, current, voltage)
  • you can add feed forward terms easily
  • you can easily switch between modes, position, velocity or torque

In theory the reason why this structure is us d that much is because when you can tune the controlled independently one from another starting from the current one and going up in the cascade. When you’re tuning the current control loop, you only tune the current response. When you tune the velocity control loop you can consider that the current control loop will do its job between any single call of the velocity control and in that case you could even consider your motor to have the current as the input and the velocity as the output. The current control loop is considered to be able to reach any target velocity set by the velocity control. For the position control it’s the same, considering that the motor is a system that has velocity as the input and the position as the output.

Now to reach this independence you would need to sample your current control loop at much higher frequency than the velocity control, which is in therm sampled in much higher frequency than the position control.

In the simplefoc case, and in case of most of the motor control libraries this is not the case, they are all executed at the same time. Which, give a high enough loop time is usually providing good results.

In the simplefoc we do offer a downsampling for the velocity+position control, leaving the torque control to run at the highest possible loop frequency.
This might help with the noisy velocity data, in my experience it works very well. You can set it using the parameter motor.motion_downsample.

Now in terms of using the pute position PID + current loop cascade, that is of course possible and might be a good option for some applications. I am not sure if it should be a part of the simplefoc though, as it’s a bit exotic (not too much though :smiley: )

However, what I do agree on is that we need a way to provide users with a simple way to introduce their own controllers. Maybe a GenericMotionConteol class where you’d be able to implement a function in your ino file which would then be executed in the motor.move() function. And maybe even add the possibility to provide multiple of them and chose between them in runtime.