StateController
StateController closes a loop on the whole state vector at once, with an independent gain per state, rather than on a single error signal.9 minute read
StateController closes a loop on the whole state vector at once, with an
independent gain per state, rather than on a single error signal. Given a pole
placement or LQR design done offline, it gives a response no cascade of
single-loop controllers can. Pair it with Observer when the
states you need are not all measured.
It is purely algebraic — no integration, no memory. Its output depends only on
this cycle’s inputs, and it has no integral action, so a constant
disturbance leaves a constant error. Add a PID alongside it if you
need that error removed.
flowchart LR
i1(["stateVector — the measured or estimated states"]) --> B["StateController"]
B --> o1(["stateErrorVector — error per state"])
B --> o2(["controlOutputVector — the control output"])
B --> o3(["controlOutput — first element, as a single value"])
The control law is $u = -K(x - x_d)$, with $x_d$ =
stateTargetVectorand $K$ =controllerGainVector. The closed-loop poles are set by your plant and that gain together, and the design is done offline — this block only applies the result.controllerGainVectorat zero makes the block inert.
controllerGainVector is written as one flat list, filled column by
column. For a 4-state 2-input system the first four values are the gains for
the first input.
Both limiters default to zero at both ends. Switching either on without setting its bounds forces the limited quantity to zero — see Limits and errors. The block has no enable and no disable.
Signals
Inputs
| Path | Unit | Range | Description |
|---|---|---|---|
stateVector |
model state units | unbounded, or the input limiter’s bounds | The measured or estimated state, one element per state. While the input limiter is on, this path is overwritten with the clamped value, so a trace shows the limited state and not what was linked in. |
Outputs
| Path | Unit | Description |
|---|---|---|
stateErrorVector |
model state units | The state error, one element per state: stateVector minus stateTargetVector. The most useful signal for confirming the controller sees what you think it does. |
controlOutputVector |
actuator units | The control output, one element per input. It appears at full value on the first cycle — there is no ramp, because the block has no memory. |
controlOutput |
actuator units | Element 0 of controlOutputVector, as a single value, for the common single-input case. |
Parameters
| Path | Unit | Default | Range | Effect |
|---|---|---|---|---|
controllerGainVector |
actuator units per state unit | all zeros | any | The gain, and the whole of the design. States × inputs, column-major. Comes from your offline pole placement or LQR. Zero makes the block inert. |
stateTargetVector |
model state units | all zeros | any | The state to drive toward. This is configuration, not a linked signal — it cannot be driven from another block, only written directly. |
inputLimiterEnable |
- | false | - | Bounds each element of stateVector before the error is formed. Set the bounds before switching this on. |
inputLimiterMin |
model state units | all zeros | below inputLimiterMax |
Lower bound per state. |
inputLimiterMax |
model state units | all zeros | above inputLimiterMin |
Upper bound per state. Equal to the minimum — as both defaults are — forces every state to that value. |
outputLimiterEnable |
- | false | - | Bounds each element of controlOutputVector. This is the only thing that limits what reaches the actuator. |
outputLimiterMin |
actuator units | all zeros | below outputLimiterMax |
Lower bound per input. |
outputLimiterMax |
actuator units | all zeros | above outputLimiterMin |
Upper bound per input. Same trap as the input limiter. |
All eight parameters are persistent and survive a controller restart. The input and outputs do not. No parameters exist below this block.
Setup
-
Do the design offline. Choose the closed-loop poles you want from your plant model and compute the gain. This block applies a gain; it cannot help you find one.
-
Read the length of
stateVectorfrom the parameter tree and confirm it matches your model’s state count. It is fixed when the machine is built. -
Leave
controllerGainVectorat zeros, and leave both limiter enables false. -
Link
stateVectorfrom your measurement or from an observer’s estimate. Confirm each element on a trace against what you expect it to be — a state in the wrong slot is the most common wiring error and produces a plausible, wrong controller. -
Set
stateTargetVectorto the state you want held. ConfirmstateErrorVectorreads the difference you expect, element by element. -
Set
outputLimiterMinandoutputLimiterMaxto what the actuator can accept, then setoutputLimiterEnabletrue. ConfirmcontrolOutputVectoris still zero — the gain is still zeros. -
Write
controllerGainVectorfrom your design, column by column. For a single-input system it is simply one value per state, in state order.Step 7 produces full control output on the very first cycle. The block is algebraic, so there is no ramp and no transient — if the state is away from the target when you write the gain, the actuator gets the whole commanded output at once. Write it with the axis at rest and near its target, and rely on step 6’s limiter.
-
Confirm the closed loop behaves as the offline design predicted. If it does not, check step 4 and the column order in step 7 before touching the gain.
Tuning
There is nothing to tune here in the usual sense. The gain comes from an offline design, and this block applies it.
- Verify the state wiring before the gain, per Setup step 4. Every diagnosis below assumes the states are in the right slots.
- Confirm the sign. A gain of the wrong sign is positive feedback, and the symptom is an axis that runs away as soon as the gain is written. Set the output limiter first so this is survivable.
- Check the column order with a deliberately asymmetric gain on a multi-input system. A symmetric test gain cannot tell a transposed matrix from a correct one.
- Scale the whole gain vector down by a factor of 2 or 4 for the first trial, then work back up to the designed value. This is the safe way to approach a design you have not run on hardware.
- Compare the closed-loop response against your offline prediction. A mismatch in shape means the state wiring or the column order; a mismatch in magnitude means the gain or the plant model.
- Accept the steady-state error, or add integral action elsewhere. This block has none and cannot acquire any.
- Use the input limiter only to protect against a bad estimate — a wild value from an observer that has not converged, say. It is not a tuning knob, and it changes what the controller sees.
- Nothing here needs re-checking after a task-rate change. The block does not integrate and has no task-rate-dependent limit.
Read where each line flattens as the point the output limiter took over.
| Symptom | Cause | Action |
|---|---|---|
| Everything reads zero | Expected: controllerGainVector defaults to zeros |
Write the gain from your design |
| Every state reads zero and the controller drives hard | The input limiter is on with its default bounds of zero, so it forces every state to zero | Set inputLimiterMin and inputLimiterMax, or switch the limiter off |
controlOutputVector is stuck at zero with a non-zero gain and error |
The output limiter is on with its default bounds of zero | Set outputLimiterMin and outputLimiterMax |
| The axis ran away as soon as the gain was written | The gain has the wrong sign — this is positive feedback | Negate the gain; set the output limiter before retrying |
| The response is nothing like the offline design | A state is in the wrong slot, or the gain is transposed | Check stateErrorVector element by element, then the column order |
| One state seems to affect the wrong actuator | controllerGainVector is transposed |
Rewrite it column-major: states × inputs |
| A constant error never goes away | Expected: this block has no integral action | Add a PID alongside it, or add an integrating state to your model |
stateVector on a trace does not match what is linked into it |
Expected: the input limiter overwrites this path in place | Switch the input limiter off, or read the source signal instead |
| The output jumped to full value the instant the gain was written | Expected: the block is algebraic and has no ramp | Write the gain at rest, near the target, with the output limiter set |
| The output is noisy | The state estimate is noisy, and the gain passes it straight through | Fix the estimate; this block has no filtering |
| The response changed after a task-rate change | Not possible — this block is task-rate independent | Look for the change elsewhere in the loop |
| A limiter behaved oddly with the minimum above the maximum | The minimum wins, so the value is pinned at the minimum | Keep every minimum below its maximum |
controlOutput disagrees with controlOutputVector |
Not possible — it is element 0 of that vector | Check you are reading the element you mean |
| A non-numeric value appeared and then cleared itself | Expected: the block holds no state, so it recovers as soon as the input is clean | Fix the source |
| You need a different number of states | Not possible — the dimensions are fixed when the machine is built | It needs a configuration change |
A starting point for a two-state axis — position and velocity — with one actuator input, on any task rate. The gain is scaled to a quarter of a designed value for the first trial:
controllerGainVector = 25.0, 1.5
stateTargetVector = 0.0, 0.0
outputLimiterEnable = true
outputLimiterMin = -10.0
outputLimiterMax = 10.0
inputLimiterEnable = false
Work up to the designed gain as Tuning step 4 describes. This is a starting point, not a final tuning.
Limits and errors
| Limit | Set by | What happens | Reported |
|---|---|---|---|
| Both limiters' bounds | Nothing | Not checked, and both default to zero at each end. Enabling a limiter without setting its bounds forces the limited quantity to zero. Set the bounds first, always | Not reported |
| Limiter minimum below maximum | Nothing | Not checked. With the minimum above the maximum the value is pinned at the minimum | Not reported |
controlOutputVector |
outputLimiterMin, outputLimiterMax while enabled |
Clamped per element. With the limiter off it is unbounded — whatever the gain and error produce reaches the actuator | Not reported |
stateVector |
inputLimiterMin, inputLimiterMax while enabled |
Clamped per element, and the input path is overwritten with the clamped value | Not reported |
| Closed-loop stability | Nothing | Not checked, and cannot be — the block knows nothing about your plant. A gain that destabilises the machine is accepted without comment. Your offline design is the only safeguard | Not reported |
| Gain element order | Fixed | Column-major, always. A row-major list silently produces a transposed gain | Not reported |
| Steady-state error | Inherent | There is no integral action, so a constant disturbance leaves a constant error | Not reported |
stateTargetVector |
Fixed | Configuration only. It cannot be driven from another block | Not reported |
| Dimensions | Machine configuration | Fixed when the machine is built. Read the array lengths to discover them | Not reported |
| Block state | None | The block has no memory, so there is nothing to reset and nothing survives a stop | Not applicable |
The block raises no errors or warnings and logs nothing. Every failure above shows as a value on a trace, not as a message.
Verified against motorcortex-control3 3.30.0 (bc348fd).