Spaghetti
How To Use
Important implentation notes
- The telemetered effort is not scalled by K, it's the raw effort coming out of the compensator
- The telemetered time intervals are hella descetized cause I fucked up the precision
Layperson "how it works"
The central idea to spaghetti is an linear compensator that computes a control effort, given the history of measured altitudes. You don't need to know how it works in order to command it. This control effort is in kg/s. It this then scaled by a gain, called k. Since ballasting and venting are discrete on/off, and the commanded effort is continuous, this needs to be converted into a series of discrete actions, separated by a time interval. This is much like PWM, but instead of changing duty cycle directly, you are changing the time interval between events such that the action time is fixed to it's minimum acceptable value, and produce the least discretization (in the future this may want to be changed to conserve power and minimize the number of valve actions).
Constants
freq: The frequency that the controller is called out, which will be a constant 20Hz
k: Scallar gain of the controller. High gains mean more actions, tighter control. Low gain means less actions, looser control
b_dldt: Magnitude of change in lift vs time for ballast actions IN kg/s. This can be directly measured on vb, and us usually around 0.001kg/s
v_dldt: Magnitude of Change in lift vs time for vent actions IN kg/s. This is always an estimate as can not be measured directly. It's a magnitude, and therefor always positive, even though venting decreases net lift
rate_min: minimum threshold for commanding a dl/dt
rate_max: maximum threshold for commanding a dl/dt
b_tmin: the minimum time that a discrete ballast event can be called for. This sets the time that ballast event will be for.
v_tmin: the minimum time that a vent ballast event can be called for. This sets the time that ballast event will be for.
h_cmd: the command altitude for the controller to aim for
Flight Controller Guide
In the dev version of spaghetti, you can't really set bounds on your altitude as you can with the legacy controller. Instead, you set a target, and a gain, and the controller attempts to hit the target, using an effort proportional to the gain. What gain will give you the altitude range you want? Who knows.
You also have other things to tune. You want a positive effort to have an equal but opposite effect on the system as a negative effort. Thing is, you don't exactly know you dl/dt for vent actions. So, you have to guess. If it looks like venting isn't having much effect, and the controller is spending much more time above the commanded altitude than below, decrease the set dl/dt for the venting, as your estimate of it is too high.
Also, you don't want to act when your effort is really low and you are close to the target. There needs to be a minimum threshold before you start acting. This is rate_min. The higher you make rate_min, the more the controller acts like a bang-bang controller, where it waits till it's pretty late and then acts a bunch. about 1 order of magnitude below your max rate is probably a decent place to start for your rate min.
How it works
Feedback control
Spaghetti uses feedback to determine what actions it should take. This is pretty intuitive, as obviously it needs to feedback the current altitude in order to make calculation of the next desired action. Here is the control loop structure:
[ Diagram coming soon]
Compensator
The heart of the spaghetti controller is the linear compensator that calculates the control effort. The first thing you may wonder is: what do you mean by linear? and what do you mean by compensatory? First of, the compensator is a single input single output system. It calculates an output given a sequence of inputs. It's also what is called Linear Time Invariant (LTI). This means that for any input u1 that produces output y1, the input 2*u1 will produce an output 2*y1. In addition, and two different inputs u1 and u2, with corresponding outputs y1 and y2, the input u1 + u2 will have output y1 + y2. In math terms, this means that it's closed under addition and multiplication, and hence linear. The time invariant part means that if you shift an input in time, the output will be exactly the same, just also shifted in time.
Why is this so important? Because it allows us to analyze the system in the frequency domain. to be continued.
Implementation in code
If we switch gears into digital signal processing land, we realize that we can implement a linear system in discrete time using what's called a difference equation. This is just a simple equation of a linear combination of past inputs and outputs. The are magical things, and you can pick coefficients tons of different stuff.
The difference equation used for spaghetti is called a biquad filter. This forms a 2nd order difference equation, as it uses the last two inputs and outputs. With a biquad, we can place two poles and two zeros anywhere we want in the s-plane.
Fused ascent rate estimation
Another thing that spaghetti implements is an estimation of ascent rate to prevent overacting when it is far away from the setpoint. "Ascent rate" for valbal is actually a tricky measurement though, because at any given time, the true ascent rate is not particularly important. The reason for this being that valbal may rise and fall with turbulence, which isn't something we really care about. Instead, we want to estimate the component of the ascent rate that is due to lift.
There are numerous way's this can be achieved, but an extremely simply method is to just lowpass filter the altitude measurements and take the derivative. Averaging works as a low pass filter, but the buffer size that you would need is too way to large unless you downsample the data, which isn't ideal. Averaging also has a few other downsides. A better way to lowpass filter in this application is to create a 2nd order lowpass filter using a biquad. This smooths out the data for us, so that when we take the derivative it will be less noisy.
However, a problem arises. Any causal lowpass filter will have what is called group delay. This means that things sent through the filter will be delayed. This is intuitive, and the same thing happens with a running average as the output responds kind of slowly to the input, and is how it reduces the noise. This becomes a problem, especially when blasting or venting which will cause sudden changes in ascent rate. If our ascent rate estimate takes too long to respond, then we can't prevent overshoots.
How do we fix this? We can add in the information that we just ballasted to our filter. The optimal way to do so would be with some gaussian distribution math, through a kalman filter. However, picking variances for this can be very handwavy, and the end result may just be an over engineered lowpass filter. So instead, w do something simpler. we want to add in the equivalent ascent rate change that we would expect from our ballasting action, but then have this amount that we added in to decay over time to zero as we know our filtered ascent rate will eventually catch up. How can we do this? Well, exponential decay like this is exactly the behavior we would get from the impulse response of a first order LTI system with a single pole on the negative real axis. We can produce this with another recurrence relation, similar to the filter. The final estimate is simply the sum of these two.
As for how to actually pick values for the filter, like quality factor and cutoff frequency, we just "play" with old data and see what works well. Same goes for the rate of exponential decay for the estimate.
If you think controls are cool and wanna learn more
Relevant classes:
- EE102A Signal Processing and Linear Systems I (highly highly recommend, great intro class)
- EE102B Signal Processing and Linear Systems II
- ENGR105 Feedback Control
- CHEMENG 100 Chemical Process Modeling, Dynamics, and Control (second half controls, first half modeling systems/diff eqs)