I'm trying to design a controller to balance a 2-wheels robot (around 13kg) and making it robust against external forces (e.g. if someone kicks it, it should not fall and not drift indefinitely forward/backward). I'm pretty experienced with most control techniques (LQR, Sliding Mode Control, PID etc), but I've seen online that most people use LQR for balancing 2-wheels robots, hence I'm going with LQR.
My problem is that, despite I'm able to make the robot not fall down, it rapidly starts going forward/backward indefinitely, and I don't how to make it mantain a certain position on the ground. What I want to achieve is that, when the robot gets kicked by an external force, it must be able to stop moving forward/backward while mantaining the balance (it's not necessary to mantain a position on the ground, I just want the robot to stop moving). The measurements to which I have access from the sensors are: position on both wheels (x), velocity of both wheels (x_dot), angular position of robot (theta), angular velocity of robot (theta_dot). Since now I tried 2 approaches:
Which approach would you suggest me to use? Should I implement some more sophisticated heuristics (any suggestion?) or should I just tune the LQR gain until I find the perfect one? Should I consider using an integrator (for controlling which states?) together with the LQR?
Jump straight down to the "In summary" section below. Here that section is, quoted, for convenience:
To keep your 2-wheeled robot balanced without letting it drift forward/backward, you should have a linear velocity controller with a command of 0 m/s, rather than just an angle controller with an angle command of 0 deg. Your linear velocity controller calculates a desired acceleration which feeds into your linear acceleration controller, which calculates a desired tilt angle, or pitch, which feeds into your pitch angle controller, which adjusts motor throttles to achieve that pitch (using, for example, a PID or LQR controller, or a physics-based controller you can come up with for this inner, self-balancing loop as well).
To achieve your objective you stated, of no longer drifting or rolling forwards, you'd stop there.
But, I'd go further: what if your vehicle has already drifted or rolled a little bit, wouldn't you want to move it back to where it started to "undo" that drift?
Here's how:...[add a position controller too, which feeds into the velocity controller]...
Details:
AKA: a full description of all of the necessary control loops for a robust vehicle controller, including for self-balancing systems like 2-wheeled self-balancing Segway-like robots, or quadcopters/drones.
In any complicated control system, you have to have multiple layers of controllers.
From inner-most to outer-most controller, here is what you need:
Pitch angle controller: In your case, your inner-most controller sounds like it is pitch angle: I think you are using an LQR controller to adjust wheel motor throttle to control pitch angle. You could also use a PID controller for this, or come up with a physics-based feed-forward solution instead, summed with a PID feedback solution to remove error.
If you make your pitch angle set-point 0 deg, then the robot will stay stationary standing straight up so long as no outside force acts upon it, and so long as it also began at rest. If you push the robot, it will start to translate linearly (ex: move forward or backwards), at the constant velocity you imparted upon it, while maintaining a fixed, upright angle. Essentially, keeping your pitch angle set-point at 0 degrees makes this the same as giving a shove to a motorless ball or cart--it will keep rolling in the direction you push it per Newton's 1st Law of Motion, which is about inertia: an object in motion stays in motion.
Linear acceleration controller: You need to add an outer controller where you adjust pitch angle to control linear acceleration (forwards or backwards).
Think about it like this: this is a physics problem: the more a 2-wheeled Segway-like robot is tilted forward, the faster gravity causes it to "fall forward". The faster it "falls forward", the faster you must drive those wheels to try to get those wheels back underneath it, to make it maintain a fixed tilt angle rather than continuing to tilt further until it hits the ground. Moving the wheels underneath it to prevent it from falling over causes it to accelerate in that direction.
For a tilted vehicle at a fixed altitude (for air vehicles; or on a level surface, for ground vehicles) and at a fixed tilt angle, the linear acceleration, a
, is: a = g*tan(theta)
, where g = acceleration due to gravity = 9.81 m/s^2
, and theta = tilt angle
. Here is a "Balance of Forces" diagram (side view: down is towards the ground and up is towards the sky) I just drew:
Solve for theta
(tilt angle), and you get: theta = atan(a/g)
.
So, a
in a translational (forwards or backwards) direction is in units of m/s^2
. Over time (s
), this translational acceleration results in a certain translational velocity (m/s^2 * s = m/s
). So, if you hold it tilted for a moment, let it accelerate, and then stand it back up straight again, you'll now be continuing forward at a fixed velocity, assuming level ground and no friction. That's what's happening to you when someone pushes or kicks it! To counter this translational velocity, you'll need a velocity controller which applies the necessary acceleration in the opposite direction in order to stop the vehicle's motion.
Linear velocity controller: The next controller is a velocity controller. You need to set a desired velocity (ex: 0 m/s
to stop the vehicle). In this controller, you adjust linear acceleration to control linear velocity.
You can then set a tuning parameter, tau [sec]
, which is the time constant over which period you'd like to achieve your desired velocity. Tune this to be very small for a fast response, and for systems with low inertia, and tune it to be very large for a slow response, and for systems with high inertia. If you tune it too low, the system will have bad jitter and respond to noise--kind of like setting your control loop too fast or your derivative gain too high in a PID controller. If you tune tau
too high, the system will be very slow and sluggish to respond. Essentially, tau
is like a "gain" tuning parameter, where response_gain = 1/tau
. Therefore, a large time constant, tau
, results in a slow response or "low gain", and a small tau
results in a fast response or "high gain".
You can see my tau
value in my quadcopter flight controller circled in yellow here:
(video link to this moment in time). As shown in the image, it is currently set to 0.75 sec
. My notes for that parameter in the image above say:
time_const, tau (sec) (gain is proportional to 1/tau):
0.75
<--(increase for "lower gain," decrease for "higher gain")
This "linear velocity controller" is therefore another layer with a physics-based controller. You need the physics equation of motion for this: dv [m/s] = a [m/s^2] * dt [sec]
. Solve for a
and you get a = dv/dt
. The time period, tau
, over which you'd like to accomplish this is your dt
(time change, in sec), resulting in a = dv/tau
.
So, if your actual velocity is 2.5 m/s
and your desired velocity is 0 m/s
, then the desired velocity change, dv
, that you need is dv = 2.5 m/s - 0 m/s = 2.5 m/s
. You ultimately need to tune tau
as a tuning parameter, through experimentation or simulation and modelling, or both. Let's assume you have chosen an initial tau
value of tau = 2 sec
. Then, the necessary acceleration you need to achieve this velocity change over that time period tau
is a = dv/dt = dv/tau = 2.5m/s / 2 sec = 1.25 m/s^2
.
This liner acceleration required by your linear velocity controller here is your input to the linear acceleration controller above. Solve for pitch angle, theta
, from the linear acceleration controller above: theta = atan(a/g) = atan(1.25 m/s^2 / 9.81 m/s^2) = atan(0.12742) = 0.1267 rad x 180 deg/pi rad = 7.26 deg
. So, input 7.25 deg
(with the correct sign, per your situation) as your set-point into your pitch angle controller in order to begin decelerating from 2.5 m/s to 0 m/s over a time period, tau, of 2 sec.
Run the inner control loop as fast as is reasonable, perhaps 50 to 500 Hz.
Run the outer control loops as fast as is reasonable, perhaps 25 to 50 Hz.
Note that the farther "outside" that your control loop is, the slower you can run your control loop. Self-driving cars, for instance, run their outer-most control loops at around 10 Hz, partly since this is good enough, and partly since the computational complexity is so high that this is all the computers can do.
To keep your 2-wheeled robot balanced without letting it drift forward/backward, you should have a linear velocity controller with a command of 0 m/s, rather than just an angle controller with an angle command of 0 deg. Your linear velocity controller calculates a desired acceleration which feeds into your linear acceleration controller, which calculates a desired tilt angle, or pitch, which feeds into your pitch angle controller, which adjusts motor throttles to achieve that pitch (using, for example, a PID or LQR controller, or a physics-based controller you can come up with for this inner, self-balancing loop as well).
To achieve your objective you stated, of no longer drifting or rolling forwards, you'd stop there.
But, I'd go further: what if your vehicle has already drifted or rolled a little bit, wouldn't you want to move it back to where it started to "undo" that drift?
Here's how: add yet another layer of control as a linear position controller outside your velocity controller!
Linear position controller: You will adjust linear velocity over time to control linear position. With your wheel encoders, you can figure out how far you've gone, and control position to get the robot to return back to where it started. Or, you can simply command any arbitrary position to have it drive certain distances and navigate around the room. This is another feed-forward controller based on simple physics/math, where the equation of motion is simply v*t = d
, where v [m/s]
is velocity, t [sec]
is time, and d [m]
is distance.
There are a variety of ways to do this.
If your goal is to go to a position and stop:
One way is to command a certain velocity for a given time in order to achieve a desired distance. Ex: command 0.5 m/s for 3 seconds to go 0.5 m/s * 3 sec = 1.5 m
. Then, command 0 m/s to stop at that point. You might have to use some empirical data and heuristics where you command the 0 m/s velocity a little early to give the vehicle time to respond and stop right where you want it rather than overshooting.
This might be called a "tick controller" (I'm inventing this term right now), where you write a function to drive N seconds at X velocity to achieve Y encoder "ticks" of distance movement in that direction, with empirical adjustments as necessary. You could tweak this controller to even be able to handle ticks as small as 1 encoder tick using a rapid velocity pulse for a short amount of time, so as to get to the exact position you want to be at as you get close to your commanded position. Each control loop iteration, you pass a new value of the desired number of distance encoder "ticks" to move, based on where you are now and where you want to be. The interesting thing about this physics-based "feed-forward" controller, therefore, is that it's implicitly also a type of "feedback" controller, which is weird. Some pedantic academics out there have probably come up with some special ways of talking about this, perhaps even with some special terms for it, but I don't know what they are.
An alternative approach would be to command a fixed velocity, ex: 0.5 m/s, until you are within some minimum distance error bound, say: 0.5m, then switch to a PID feedback controller which commands a velocity based on position error. This way, as your position error approaches zero, your commanded velocity will also approach zero, which makes sense. If your PID gains are strong enough, or conversely if your position error is large enough, this is the same thing as just using a PID feedback controller on position where you saturate the velocity command by clipping it to a fixed maximum value. Of course, even for weak gains, at some large enough distance error, the commanded velocity would still hit the maximum allowed (saturated) value and get clipped.
If your goal is to keep a fixed velocity while following a 2D path at this velocity:
Then you could set your velocity controller to a fixed value while changing your commanded heading to always point down the path. I do this using a "lead point" technique, or "pure pursuit" algorithm, as demonstrated in my 3 videos here: 1, 2, 3. Since my vehicle is a hovering quadcopter drone, however, I have the luxury of changing my commanded thrust vector rather than my heading, so I can just command a fixed heading if I want (ie: keep the drone always pointing North) while changing the commanded thrust vector to move in different 2D (x-y) directions.
While integrating velocity over time will obtain distance, numerical integration or estimating is best used in this case to calculate commanded velocity outputs for the feed-forward parts of your controller which will output a desired velocity command for a certain duration of time in order to achieve a desired change in position.
With your wheel encoders, you can measure the actual distance traveled instead of estimating it using velocity over time.
If you do need to estimate position traveled by integrating velocity over time, however, you should use trapezoidal numerical integration, as I explain here: Numerical derivation and integration in code for physics, mapping, robotics, gaming, dead-reckoning, and controls, since it is more accurate than rectangular integration and is trivial to implement. Again, for numerical integration, the simple idea is that velocity [m/s] * time [s] = distance [m]
.
Again, remember that you shouldn't necessarily integrate to estimate the actual distance traveled. Rather, measure the actual distance traveled by counting encoder ticks since that's a more-precise measurement.
Remember that for all these controllers:
RPM = freq_commutation/(num_magnetic_poles*120)
. The point is, the commutation frequency can be read in software by a microcontroller's input capture pin, and then converted to motor rotational velocity via a simple equation based on the number of permanent magnetic poles in the motor. Note that higher-end motor drivers (ESCs--Electronic Speed Controllers) use sinusoidal commutation waveforms, which are more efficient and have better torque, instead of trapezoidal, but the commutation frequency principal is the same.The types of controllers you will be using in your case are these, again, from inner-most to outer-most controller:
Optionally, add another layer of physics control to compensate for air resistance as velocity increases. Drag [N] = C_D*q*A
, where C_D [unitless]
is your experimental drag coefficient for your particular vehicle's shape properties and how that shape interacts with the fluid of interest (air in our case), q [N/m^2] = dynamic pressure = 1/2 * rho * v^2
, where rho [kg/m^3]
is air density, and v [m/s]
is velocity, and A [m^2]
is frontal area (which is geometry-based, since it decreases the more the robot is tilted).
Let's verify the drag equation with a quick units check: Drag [N = kg*m/s^2] = C_D [no units] * q [kg/m^3 * m^2/s^2 = kg*m*m/(m^3*s^2) = kg*m/s^2 * m/m^3 = N/m^2] * A [m^2] = [N/m^2 * m^2 = N]
Yep! It comes out correctly: the right side of the drag equation (C_D*q*A
) does indeed have units of Newtons ([N]
). I stated the equation correctly.
THEN, on top of that extra physics-based layer which accounts for air resistance as the robot speeds up:
Optionally, add a PID feedback on error of actual vs commanded linear velocity to tune it so that the actual linear velocity approaches the commanded linear velocity.
Sum the outputs of all controllers.
Anyway, the way I see it, that's the idea. That's the type of approach I took on my quadcopter controller: Quadrotor 2 - Physics-based Flight controller demo w/lead point navigation & Arduino interface to RC Tx, and that controller would also work perfectly for a 2-wheeled Segway-like self-balancing robot, too, since the quadcopter is governed by the same principles as the self-balancing robot. Controls has many options and layers. Physics should be part of many if not most of them.
I'll also mention that I believe the entire thing above could be done on almost any computational platform, from a single Arduino Nano (ATmega328 microcontroller), to a powerful desktop Linux laptop. It just requires the right amount of software skill, I think. Engineering is hard. Programming is complicated. But, if you know enough about both, you can do really complicated things on really weak processors like the ATmega328 (Arduino Uno, Nano, etc). I have done some really complicated things on those processors, and I still have a ton more I'd like to do and learn.
a = F/m = g*tan(tilt_angle)
. That's how I was able to remember how to draw my "Balance of Forces" diagram above.