It’s now two weeks that I’ve been trying to tune my self stabilizing two wheeler to stay put, but to no avail. I’ve literally tried everything I could, including endless fine tuning and seeking internet material. As you’ll see below, I’m sending telemetry and have dived in depth to this, but still can’t figure out what’s wrong.
1x angle PID Actually, this is a PD controller as I'm only using Kp and Kd.
1x speed PID Actually, this is a P controller as I'm only using Kp, in some cases I added Ki too, though I first wanted to see that I manage to prevent it from falling before closing any steady state error.
I guess anyone would tell me that the first step is to tune the angle controller first, before moving on to speed control. This is what I did, and below is the system’s behavior (Please don’t try to follow the legend as it might be confusing, I’ll explain what’s showing):
Cyan is pitch angle. Purple is the speed controller - it’s zeroed out. Brown is the dominant term of the angle controller. The device oscillates around pitch=0. As time passes, it’s less frequent to visit both positive and negative pitch angles, and starts spending more time in positive pitches, briefly closing, until the low point right before diverging. At that low point the pitch is 0 and rate is 0 (not shown). This is where the command is the smallest, and the device now starts diverging forward. In my assumption this is due to the forward momentum of the car right at that point (you can see the pitch command is wide and strong), together with the small command due to the small angle. This causes the device to “lock” on very very small pitch and pitch rate - and then diverge and fall forwards without being able to correct back.
According to this instructional video on balancing two wheelers (I don’t speak Chinese but I had what he said translated), this is a natural phenomenon - a two wheeler CAN’T be kept put in place with just an angle controller, but MUST have a speed controller too.
So, assuming that these results are enough, I went to the next step, which is tuning the speed controller.
Question 1: Assumption correct?
Continuing. The cascaded version of the speed PID looks like this:
u_speed_hold = PID_Update(&speed_pid, target_speed, chassisData.xdot, 0.0f, dt_chassis); // 0.0f is acceleration - not available
…
float combined_angle_setpoint = (target_pitch_angle - u_speed_hold);
//float combined_angle_setpoint = (target_pitch_angle + u_speed_hold);
…
u_pitch_angle = PID_Update(&pitch_angle_pid, combined_angle_setpoint, pitch_kalman1x1.state_estimate, pitch_rate, dt);
…
float u_forward = u_pitch_angle; // To wheels
Assuming for a minute that there should be a - sign in the combined angle setpoint. After tuning the best I could, below are results that I found the most interesting (Please don’t try to follow the legend as it might be confusing, I’ll explain):
Cyan - pitch angle. Brown - dominant term of the overall pitch command. Red - Brown’s portion due to pitch angle. Purple - Brown’s portion due to speed.
Kind of like the one above with no speed controller right? First pitch oscillates, then as time passes moves to visit the positive angles more frequently, then slowly diverges. This is the point where I thought the “You MUST have a speed controller” should come to action. But it turns out the speed controller (purple), which is pretty weak in the case above, is eating away from the angle controller’s command to close theta, and can’t be tuned to both affect the speed and having the angle controller close the angle.
Keeping the “-” sign in the combined angle setpoint never lead me to any see any indication that there’s an effective speed controller, even after lots and lots of tuning efforts.
So in contrast to my own logic, I switched to adding the controls of the cascaded controller:
combined_angle_setpoint = (target_pitch_angle + u_speed_hold);
This did lead me to some cases where I thought I had seen the speed controller in action, as shown below, but still the robot ultimately gets “locked” on zero angle and zero rate, and slides to divergence:
This is forward → backward → forward movement to divergence. Yellow - chassis speed: kinda hard to see but it moves slowly forwards (~t=15-25), then backwards (~t=25-40), then forward and diverges.
Purple - the contribution of the cascaded speed controller to the overall pitch controller (through its proportional term).
Green - output of the speed controller. Blue: Kp term in the speed controller. Orange: Ki term in the speed controller.
Divergence happens right when the integral zeros out, and as shown below, on very very small values of pitch and pitch rate:
This is literally 0.01[degrees] measured by the IMU, and small rate. As mentioned above, this is where I thought the speed controller that “must exist” will take place and put the robot in place. But I never seem to be able to be able to get out of this situation where the angle commands are very small, and the robot “locks”. I’ve tried all the tuning I could, including telemetry analysis.
Question 2: Sign in combined_angle_setpoint when cascading the speed and angle PIDs?
Continuing. One might suggest, why cascading the controllers when you can just do superposition and sum their outputs:
u_speed_hold =...
u_pitch_angle = PID_Update(&pitch_angle_pid, target_pitch_angle, pitch_kalman1x1.state_estimate, pitch_rate, dt); // and not combined target
…
u_forward = u_pitch_angle + u_speed_hold; // To PWM
After tuning the best I could, below are results that I found the most interesting (Please don’t try to follow the legend as it might be confusing, I’ll try to explain):
Cyan: pitch angle, Yellow: chassis speed, Magenta: angle controller rate term, Green: speed controller output x100, grey: pitch controller output.
Note when the divergence happens: 0.2[deg] pitch angle, almost zero pitch rate.
Question 3: If the robot is moving forward with a positive pitch angle, the angle and pitch controllers should have conflicting directions. The angle controller would want to push forward to close the angle (small as it may be), the speed controller would want to push opposite to travel direction. This is the same case when cascaded, though in that case the quantities will be different, rather than the trends. Then how should they both work together? Speed control should be the biggest which is possible as long as it’s not interrupting the angle control from closing the angle? If so, why can’t I get there, based on all the information above? It makes sense that when the pitch angle and
rate are very small then its the speed controller’s “time to shine”. But I’m never able to avoid either the small angle and rate “lock” to divergence or, on the other side, give too strong of a control command that just results in massive unstable oscillations. I highly doubt this is only a “needs finer tuning” situation, because I scanned very finely.
Final note: Please don’t direct me to some internet material/post/code base/similar project :) I’ve read many. Still can’t explain the telemetry, sliding phenomenon, and how to overcome it.





