Introduction to autopilots
Rekindling my passion for flying stuff, I’ve recently decided to dust off the old Aerospace Engineering degree and grind my teeth at UAV swarm algorithms. At the same time, I realized that keeping a journal of my progress would be helpful. Nothing aids in keeping your knowledge and opinions consistent quite like having to put them in writing.
And rather than jumping straight into complicated UAV applications and nomenclature that nobody would be able to read, I decided to start from the ground up. So here is the first entry in the series, which is a simple introduction to automatic control.
There is indeed an abundance of sources which delve into this topic, however many of them seem to follow in the textbook tracks of treating this like a mathematical problem with mathematical solutions. Even engineering textbooks often broach this from the perspective of complex analysis, describing aircraft motion in terms of zeros and poles. My goal with this article in particular, is to explain the basics behind aircraft automation in a way that appeals to software developers too - not just to engineers.
Principles of control
Perhaps the most intuitive way to understand automatic control is to put oneself in the driver’s seat and attempt to cruise down the highway at the speed limit. Occasionally the road may temporarily slope downwards, leading to an inadvertent increase in speed, prompting the user to release some of pressure on the gas pedal to not exceed the speed limit. Later, the apparent drag on the vehicle may increase due to some change in wind direction, prompting the user to increase the pressure on the gas pedal to maintain the desired speed. What we just described is a sense-and-respond process, according to which when a value is more than I’d like I reduce my input, and when it is less I increase it. This is a kind of negative feedback loop.
The PID controller
Conventionally, the target speed that I’d like to drive in the example above is called a setpoint (SP). The actual speed measured and displayed on the dashboard is called the process value (PV). The difference between the two we typically call error (e).
$$e_t = SP_t - PV_t$$
Next we define the control variable (u), which in the current example represents the amount of pressure applied to the gas pedal. For example, a decimal value where at 0.0
we’ve stepped off the pedal, and at 1.0
we’ve pressed the pedal down to the floor.
So when driving and trying to maintain a speed of 100 km/h (SP), you look down at the dashboard and see 95 km/h (PV). You consider for a moment that the speed is 5 km/h (e) less than you’d like so you decide to increase u. But by how much? Surely you don’t release the gas pedal whenever you are slightly faster than you’d like, and then floor it the instant you are a bit slower. In reality, your response u depends on the magnitude of the error! When the error is only a few km/h you make small adjustments, and when it is large you apply more significant changes. Specifically, if you’d like to define your next control input decision based on your current error you’d write something like this.
$$u_{t+1} = K_p \cdot e_t$$
The above describes a simple Proportional controller. In practice though, that is but one of three terms, the other two being the Integral and Derivative. Without going into too much detail, a brief summary of these three term is the following.
- Proportional is the first and intuitive component we saw above, where the response is proportional to the error according to a constant
Kp
. - Integral is a term with is proportional to the integral of the error, i.e a running sum. We multiply this integral by its own constant
Ki
and add it tou
. The effect is to have a component of the response which eliminates residual errors left over by the proportional term (the residuals would grow the integral over time and eventually command a response). - Derivative is the term which as the name suggests captures the rate of change of the error. We multiply this rate by its own constant
Kd
and add it tou
. The effect is to have a component which responds
$$u_{t+1} = K_p \cdot e_t + K_i \cdot \int e_{\tau} d\tau + K_d \cdot \frac{d e_\tau}{d\tau}$$
Implementing the above in code is quite trivial, the tricky part however is tuning it. By tuning what we mean is selecting values for the Kp, Ki and Kd parameters so that the control variable u will actuate the system in such a way that the process value always reaches the setpoint without excess delay and then tracks it with as little error as possible. I will not go into tuning methods here since the process can be not only complicated, but also domain-dependent. For example, tuning the controller for a conveyor belt may look quite different to the tuning of a piston. A good starting point might be this article.
The control variable
Suppose that you are designing a cruise control system for a car. The objective is simple: given a target speed, the gas should be regulated so that that speed is reached and maintained. This calls for a typical PID controller, with the target speed and measured speed as inputs, and the output wired to the imaginary gas pedal. This however won’t work.
To understand why, consider what happens when the car is momentarily tracking the target speed perfectly, i.e that the measured error is zero. The output of the controller will of course also be zero, commanding the engine to essentially idle. Any driver knows though, that this not how you maintain speed in a car. On a level road, if you remove your foot from the pedal the speed will actually start to decrease due to drag and other losses. It’s evident that a certain foot-pressure on the gas pedal is required to counteract these losses in order to maintain the speed constant.
So our PID controller would have to, for zero error, output the throttle level needed to maintain the speed. This could be done by taking the PID output (which would be zero at this point) and add to it a precomputed value (offset) which we expect would hold the speed constant. For this we would need to know at any time and with great precision: the weight of the vehicle, the wind velocity and direction, the weight distribution of the load, the temperature, the performance model of the engine given that temperature (including any wear & tear), any losses from the wheel traction and so on. It is apparent then that we simply cannot predict how exactly a given setting on the throttle will affect the speed of the vehicle, and so computing such an offset is usually infeasible.
In such cases when an offset cannot be computed and added to the output, the controller might instead command increases or decreases in the actuator.
Autopilots
The term Autopilot seems quite overloaded in modern everyday speech. In my view, an autopilot is any unit which takes commands and sensor measurements as inputs, and outputs instructions for the vehicle’s actuators. As illustrated in the simple diagram below.
flowchart LR Sensors --> Autopilot Commands --> Autopilot Autopilot --> Actuators style Autopilot stroke:#FF0000,stroke-width:2px
By that definition, a simple pitch-hold controller is an autopilot. And so is a car’s cruise control system. They tend to become quite expansive in scope and responsibilities as well, such as the units fitted in modern avionics which can independently navigate an aircraft from point A to point B, both laterally and vertically, and even land it without any pilot input. So a common interpretation is to refer to autopilot units as collections of controllers, each of which takes the measurements that it needs as inputs and affects its corresponding actuators. For example an aircraft may have one controller reading the barometric pressure from the static port while displacing the elevator in order to maintain a specified altitude, and at the same time another controller reading the magnetic heading from a compass and commanding the ailerons in order to keep the plane flying a specified course.
Higher-order controllers
The PID controllers we mentioned before compared a measured quantity with a setpoint and used the error to command a change to the control unit of an actuator. For example, a vertical speed controller for a quadcopter will increase the throttle for all propellers if the measured vertical speed is lower than commanded, and reduce it when it is higher. This works best when the commanded actuator has a direct effect on the measured quantity, how in this case for example an increase on the RPM of the rotors increases the vertical speed of the vehicle. An intuitive way to think of this is that I am running too fast
should produce a command Run slower
.
flowchart LR vy@{ shape: text, label: "$$v_y$$" } vy_set@{ shape: text, label: "$$\\={v_y}$$" } rpm@{ shape: text, label: "rotor r.p.m." } vy_set --> PID vy --> PID --> rpm
There are other cases though, where we need to control a property indirectly.
For example think of an altitude controller for the same vehicle. Its output would be an error in terms of altitude, and we would have to use that error to command the throttle. An error of We are too high
would produce a command Descent faster
. A controller like this could of course work. Tuning it might be a bit trickier, but it wouldn’t be impossible. The problem though is that if we were to do this, we’d be completely discarding our vertical speed measurement, and we’d be relying on the altitude controller accurately commanding this value, basically blind.
What we can do instead, is take the error of We are too high
and use it to command Descent
.
In other words, use it to command the vertical speed!
flowchart LR alt@{ shape: text, label: "$$alt$$" } alt_pid[PID] alt_set@{ shape: text, label: "$$\\={alt}$$" } vy@{ shape: text, label: "$$v_y$$" } vy_pid[PID] vy_set@{ shape: text, label: "$$\\={v_y}$$" } rpm@{ shape: text, label: "rotor r.p.m." } vy_set --> vy_pid vy --> vy_pid --> rpm alt_set --> alt_pid alt --> alt_pid --> vy_set
Tuning these two controllers separately ends up being much more effective. Both because each gets to utilize the measurement which corresponds to its error output, and because it gets to command an actuator which directly affects its input measurement.
All our autopilot controllers will be structured this way going forward with these projects. And in the interest of organization and clarity, I will be grouping them into tiers.
- Tier 0 controllers which command the vehicle’s actuators.
- Tier 1 controllers command the setpoints of tier 0 controllers.
- Tier 2 controllers command the setpoints of tier 1 controllers.
- … and so on as high as needed