Angle control without nested velocity PID?

In the Angle control block diagram we can see that angle control feeds its error into the velocity controller which then generates the voltages to the motor.

The use of the velocity PID causes the motor to track the angle PID like a mass connected via a spring-dampener system. The angle PID then tracks the desired position much the same. This coupled dynamic is prone to oscillate and adds additional complexity to the tuning process. I am wondering why there is a need for this double-stacked approach.

A simpler approach would be to have the angle PID directly set the desired current by changing BLDCMotor.cpp like so:

    case MotionControlType::angle:
      // TODO sensor precision: this calculation is not numerically precise. The target value cannot express precise positions when
      //                        the angles are large. This results in not being able to command small changes at high position values.
      //                        to solve this, the delta-angle has to be calculated in a numerically precise way.
      // angle set point
      shaft_angle_sp = target;

      /*
      // calculate velocity set point
      shaft_velocity_sp = feed_forward_velocity + P_angle( shaft_angle_sp - shaft_angle );
      shaft_velocity_sp = _constrain(shaft_velocity_sp,-velocity_limit, velocity_limit);
      // calculate the torque command - sensor precision: this calculation is ok, but based on bad value from previous calculation
      current_sp = PID_velocity(shaft_velocity_sp - shaft_velocity); // if voltage torque control
      */

      current_sp = P_angle( shaft_angle_sp - shaft_angle );
      current_sp = _constrain(current_sp,-velocity_limit, velocity_limit);

      // if torque controlled through voltage
      if(torque_controller == TorqueControlType::voltage){

        // use voltage if phase-resistance not provided
        if(!_isset(phase_resistance))  voltage.q = current_sp;
        else  voltage.q =  _constrain( current_sp*phase_resistance + voltage_bemf , -voltage_limit, voltage_limit);
        // set d-component (lag compensation if known inductance)
        if(!_isset(phase_inductance)) voltage.d = 0;
        else voltage.d = _constrain( -current_sp*shaft_velocity*pole_pairs*phase_inductance, -voltage_limit, voltage_limit);
      }
      break;

This reduces the complexity of the controller and, at least to me, is more in line with its name. I have made the above modification to my code and it has removed the coupled dynamics that’s been giving me grief and is tracking the desired motor position just fine.

I am curious what problems the nested PID control approach addresses that a direct angle PID would be missing?

1 Like

Awesome! That’s exactly what I need for my case.

The nested PID control allows you to control the maximum velocity as well as the position (angle). It could be useful if you don’t want your motor spinning too fast when trying to reach the desired angle.

Also the approach of having the torque (current) proportional to the position error means that the torque will always be acting to accelerate the motor while the motor has not reached the position setpoint.

This approach works fine for some types of loads, which I like to call “friction loads”, which is where the force on the motor is always opposite direction to the velocity, for example, a drill motor. In this case the “friction force” will slow down the motor naturally, and the system will be stable.

Other types of loads may not work as well, such as what I like to call “inertia loads”, which is where the force on the motor is proportional to the acceleration, for example, a fan motor. In this case the motor will keep accelerating until it has passed the desired position because there is no force which is slowing it down, and the system will oscillate.

Most real world applications will be have mix of “friction loads” and “inertia loads”, including the two examples I gave. A velocity based controller will work fine with “inertia loads” because as the motor approaches the setpoint, if the current velocity is higher than the target velocity, the torque will act to decelerate the motor.

I assume the reason why your motor is tracking the position fine is because your load on the motor is closer to a “friction load”. If you try to attach a heavy weight to the motor to increase the inertia, it is likely your system will start to oscillate.

Although I admit the position control via velocity PID is not perfect, if you want to give it another try, I suggest decreasing the gain of the position controller, which should remove the oscillations at the expense of a slower motor. I found that the ki value in the speed PID controller also has a major impact on the oscillations (because integral of speed is position), if you play around with that you may be able to remove the oscillations.

If you want to improve your own position control algorithm, I suggest to add some sort of dependency on the velocity, so that in case of “inertia loads” the system will be stable. I am working on my custom implementation for a better position control algorithm, but my current implementation has velocity proportional to sqrt(position error), which allows the minimum move time under constant “inertia loads” assumption and when move time is limited by the maximum motor current, and is also stable with “friction loads”. In my implementation I added a threshold where is the position error is under the threshold the gain of the position loop is reduced significantly, this solves the oscillation problem for me.

3 Likes