Tuning the reaction wheel PID controller

V. Hunter Adams

This webpage provides the recommended procedure for tuning the PID controller which balances the reaction wheel inverted pendulum. For more general information about PID controllers, see:


Procedure

1. Get the complementary filter running!

You cannot build a controller with a measurement of the pendulum's tilt angle. Start by implementing a complementary filter of the accelerometer and gyroscope measurements to estimate tilt angle. Small angle approximations are fine.

2. Implement proportional control

The proportional term of the PID controller sets the voltage (or PWM duty cycle) applied to the motor by scaling the error between the estimated tilt angle (from the complementary filter) and the desired tilt angle (0 degrees) by a constant $K_p$.

If you are starting from the PWM demo in the course demo code repository, then the PWM channel can be assigned a duty cycle in the range [0, 5000]. We want for the controller to work over a range of +/- 3-4 degrees off the vertical, so start with a proportional gain of approximately 1500. Here is some pseudocode:

// Compute the error
error = (desired_angle - complementary_angle) ;

// Compute duty cycle with P controller
duty_cycle = Kp * error ;

Use your hand to gently move the pendulum back and forth. Do you see the reaction wheel speed increase in one direction when you move the arm right, and in the other direction when you move it left? Do you feel the arm fighting against your rotation, or trying to make the arm rotate faster? If it feels like the wheel is trying to pull the arm faster, then switch the sign on $K_p$.

Your calculation of the duty cycle may yield a negative number. Remember that the sign of the duty cycle just indicates the direction that the motor should turn. In other words, the sign tells you which of the two PWM channels which drive the H-bridge you should command, and which you should set to zero.

3. Add integral control

Once proportional control is working as expected, add some integral control. The integral term of the PID controller computes a control input to the motors by integrating the error over time. To prevent integrator windup, we will clamp the integral error at a maximum value. This maximum value is a tunable parameter, but start at about a 50% duty cycle.

Recall that the proportional term of the PID controller only scales the instantaneous error, while the integral term scales the integral of the error. The integral of the error may be orders of magnitude larger than the instantaneous error! We'd like for the P term and the I term of the PID controller to both contribute to the control input, so we'll make $K_i$ a few orders of magnitude smaller than $K_p$. Start with 7-8.

If you then start the pendulum balanced, you will likely see it correct itself once, then slowly increase its rotation speed until it tips over (as shown below). We'll solve that problem in the next step.

// Compute the error
error = (desired_angle - complementary_angle) ;

// Integrate the error
error_accumulation += error ;

// Clamp the integrated error (start with Imax = max_duty_cycle/2)
if (error_accumulation>Imax) error_accumulation=Imax ;
if (error_accumulation<(-Imax)) error_accumulation=-Imax ;

// Compute duty cycle with PI controller
duty_cycle = (Kp * error) + (Ki * error_accumulation) ;

5. Add dithering

Recall that we only get torque from a reaction wheel from changes in its angular momentum. That is to say, when its rotation rate is increasing or decreasing. If the inverted pendulum is just off from its desired tilt angle, then the integral term of the PID controller will cause the wheel to slowly ramp up in speed. A slow ramp up means very little torque, which means it continues to tip over!

A simple way to solve this problem is with dithering. We constantly move the desired tilt angle very slightly away from the measured tilt angle, as shown in the code below. This way, the integrated error increases more quickly, so the I contribution to the PID controller increases more quickly, and the pendulum corrects itself.

Once you add dithering, you may be able to get the pendulum to balance (as shown below)! Now you'll want to continue to tune your controller to make it as snappy and robust as possible.

// Compute the error
error = (desired_angle - complementary_angle) ;

// Start with angle_increment = 0.0001
if (error < 0) {
    desired_angle -= angle_increment ;
}
else {
    desired_angle += angle_increment ;
}

// Integrate the error
error_accumulation += error ;

// Clamp the integrated error (start with Imax = max_duty_cycle/2)
if (error_accumulation>Imax) error_accumulation=Imax ;
if (error_accumulation<(-Imax)) error_accumulation=-Imax ;

// Approximate the rate of change of the error
error_deriv = (error - prev_error) ;

// Compute duty cycle with PID controller
duty_cycle = (Kp * error) + (Ki * error_accumulation) + (Kd * error_deriv) ; 

// Update prev_error
prev_error = error ;

4. Add derivative control

The way to make your system faster is to increase the values for $K_p$ and $K_i$. But! This will destabilize your system. If you make these gains too large, the arm will overcorrect and fall over.

You can mitigate overcorrection by adding the derivative term of the PID controller. This term scales the rate of change of the error. If the error is getting smaller very quickly (i.e. the arm is moving quickly toward vertical), the D term of the PID controller slows it down. Likewise, if the error is increasing quickly (the arm is falling over), then the D term speeds it up.

You can increase the snappiness and responsiveness of your PID controller by increasing $K_p$ and $K_i$, and combat the instability that this causes by adding a derivative term. Remember that the rate of change of the error will be orders of magnitude smaller than the instantaneous error, so $K_d$ will be orders of magnitude larger than $K_p$. Start with about 10000. Look out for sign errors!

// Compute the error
error = (desired_angle - complementary_angle) ;

// Start with angle_increment = 0.0001
if (error < 0) {
    desired_angle -= angle_increment ;
}
else {
    desired_angle += angle_increment ;
}

// Integrate the error
error_accumulation += error ;

// Clamp the integrated error (start with Imax = max_duty_cycle/2)
if (error_accumulation>Imax) error_accumulation=Imax ;
if (error_accumulation<(-Imax)) error_accumulation=-Imax ;


// Compute duty cycle with PID controller
duty_cycle = (Kp * error) + (Ki * error_accumulation) ; 

// Update prev_error
prev_error = error ;