Observer
Observer estimates every state of a plant, including the ones no sensor measures — a velocity from position alone, a load torque from motor current, a temperature inside a body from its surface.9 minute read
Observer estimates every state of a plant, including the ones no sensor
measures — a velocity from position alone, a load torque from motor current, a
temperature inside a body from its surface. It runs a model of the plant,
compares the model’s predicted measurement against the real one, and uses the
difference to pull the model back toward the truth every cycle.
It is a plant model plus one correction term. Without that term the model drifts away from reality; with it, the estimate converges and stays converged.
flowchart LR
i1(["inputVector — the plant's input"]) --> B["Observer"]
i2(["measurementVector — what the sensors read"]) --> B
B --> o1(["stateVector — the estimated states"])
B --> o2(["stateDotVector — rate of change of the estimate"])
B --> o3(["outputVector — the predicted measurement"])
The model is $\dot{\hat{x}} = A\hat{x} + Bu + L(y_m - \hat{y})$ and $\hat{y} = C\hat{x} + Du$. Tuning is entirely
observerGain($L$): it sets how fast the estimate is pulled toward the measurement. Place the observer 3 to 10 times faster than the plant. Faster converges sooner and passes more sensor noise into every estimate, and past a point it makes the block diverge — see Limits and errors.observerGainat zero leaves an open-loop simulation that ignores the measurement entirely.
Every matrix is written as one flat list, filled column by column. For a
4-state 2-measurement system the first four values of observerGain are the
gains from the first measurement to all four states.
This block has no enable, no disable and no reset you can reach. It always runs, and its estimate cannot be re-initialised from the parameter tree — see Limits and errors.
Signals
Inputs
| Path | Unit | Range | Description |
|---|---|---|---|
inputVector |
plant input units | unbounded | What is being commanded into the real plant, one element per input. The observer needs this to predict how the plant will respond. |
measurementVector |
sensor units | unbounded | What the sensors actually read, one element per measurement. This is the truth the estimate is corrected against. Note the block’s predicted measurement is outputVector — do not confuse the two. |
Outputs
| Path | Unit | Description |
|---|---|---|
stateVector |
model state units | The estimated state, one element per state — the reason to use this block. It starts at zero after a controller start and converges from there. It is not cleared by a stop, so after a restart it briefly holds an estimate unrelated to the machine. |
stateDotVector |
model state units per second | Rate of change of the estimate on this cycle. |
outputVector |
sensor units | The measurement the model predicts, for comparison against measurementVector. Subtract the two to see how well the observer is tracking: that difference should be small and settle toward zero. |
Parameters
| Path | Unit | Default | Range | Effect |
|---|---|---|---|---|
observerGain |
model state units per sensor unit per second | all zeros | any | The one thing to tune. How hard the measurement pulls the estimate. Higher converges faster and passes more noise; too high makes the estimate grow without bound. States × measurements, column-major. Zero leaves an open-loop simulation. |
matrixA |
1/s | all zeros | any | Plant dynamics, states × states, column-major. Continuous-time. |
matrixB |
state units per input unit per second | all zeros | any | Input matrix, states × inputs, column-major. |
matrixC |
sensor unit per state unit | all zeros | any | Which states the sensors see, and how. Measurements × states, column-major. A state no row of this matrix reaches cannot be estimated at any gain. |
matrixD |
sensor unit per input unit | all zeros | any | Direct feed-through from input to predicted measurement, measurements × inputs, column-major. Usually zeros. |
stateVectorInit |
model state units | all zeros | any | Intended as the state a reset restores. It has no effect — the reset is not reachable and the estimate always starts at zero. |
All six parameters are persistent and survive a controller restart. The inputs and outputs do not. No parameters exist below this block.
Setup
-
Write down your model’s dimensions: states, inputs, measurements. Read the lengths of
stateVector,inputVectorandmeasurementVectorfrom the parameter tree and confirm they match. They are fixed when the machine is built. -
Check that every state you want estimated is reachable through
matrixC. A state that no measurement depends on, directly or through the dynamics, cannot be estimated at any gain. -
Confirm your matrices are continuous-time. A discretised model will be wrong by a factor of the task period.
-
Write
matrixAthroughmatrixDas flat lists, column by column. For a 2×2 matrix the order is row 1 column 1, row 2 column 1, row 1 column 2, row 2 column 2. -
Leave
observerGainat zeros. LinkinputVectorfrom whatever commands the real plant, andmeasurementVectorfrom the sensors. -
Compare
outputVectoragainstmeasurementVectoron one trace. With zero gain the model runs open loop, so the two will drift apart — but they should start out the same shape. If they are not, the model is wrong; fix that before adding any gain. -
Raise
observerGainfrom zero untiloutputVectortracksmeasurementVectorclosely.Step 7 is where this block becomes unstable if you push it. Too high a gain does not merely pass noise — it makes the estimate grow without bound, and only a controller restart clears it. Raise it in steps and watch
stateVectorbetween each. -
Confirm
stateVectorsettles and stays settled with the machine at rest.
Tuning
- Get the model right first, with
observerGainat zero, per Setup step 6. No gain can rescue a wrong model — it will give you a confidently wrong estimate with a small-looking error. - Work out your plant’s fastest pole, in rad/s. Aim to place the observer 3 to 10 times faster than that. That factor, not a number, is the target.
- Raise
observerGainfrom zero in steps of roughly double. After each step, watchstateVectorfor a few seconds with the machine at rest. - Stop as soon as noise appears on
stateVector. Every measurement’s noise reaches every estimated state through the gain, and the states no sensor measures are usually the noisiest. - Judge convergence by subtracting
outputVectorfrommeasurementVector. That difference is the observer’s error signal: it should be small in steady state and settle quickly after a disturbance. - Test convergence deliberately: start the controller with the machine away
from zero, and time how long
stateVectortakes to reach the truth. That time is your observer’s response. - Check the gain against your task period. A gain fast enough to converge in a few task periods will make the block diverge — the practical ceiling is an observer response no faster than about a fifth of the task period.
- If you need both fast convergence and quiet estimates, and your model is uncertain, use a Kalman filter instead — it trades the two off explicitly.
Read the convergence time off any curve as the point it crosses 0.37.
| Symptom | Cause | Action |
|---|---|---|
stateVector grows without bound |
observerGain too high for the task period, or a wrong model |
Lower the gain, restart the controller, and re-check step 6 of Setup |
| Everything reads zero | Expected with unconfigured matrices — all default to zero | Write matrixA through matrixD |
outputVector never comes near measurementVector |
observerGain is zero, so the model runs open loop |
Raise observerGain |
outputVector tracks but one state never settles |
That state is not reachable through matrixC, so no gain can estimate it |
Add a measurement, or accept that the state is not observable |
| The estimate is noisy | Gain too high — every measurement’s noise reaches every state | Lower observerGain and accept slower convergence |
| The estimate converges too slowly | Gain too low | Raise observerGain, watching for noise and divergence |
| The estimate looks confident and is wrong | The model is wrong: a bad model produces a small error and a false estimate | Set observerGain to zero and re-verify the model open loop |
| The estimate is smooth but lags the machine | Gain too low, or the model’s dynamics are too slow | Raise the gain; if that adds noise, fix the model instead |
| The response has the right timescale but the wrong shape | A matrix was written row by row instead of column by column | Rewrite it column-major |
| One measurement seems to affect the wrong states | observerGain is transposed |
Rewrite it column-major: states × measurements |
| The response is far faster or slower than your offline design | Discretised matrices were supplied; this block wants continuous-time ones | Convert back to continuous time |
| A wrong estimate immediately after a controller restart | Expected: the estimate is not cleared by a stop and starts from wherever it was | Wait for it to converge, or gate the consumer for a few observer time constants |
stateVectorInit seems to be ignored |
Expected: it has no effect, because the reset that would apply it is not reachable | Ignore this parameter |
| You cannot re-initialise the estimate | Expected: there is no reset input | Restart the controller |
| A value went non-numeric and stayed there | A non-numeric value reached a matrix or an input | Restart the controller; there is no way to clear it in service |
| 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 estimating velocity from a position measurement on a 1 ms task. Two states — position and velocity — one input, one measurement:
matrixA = 0, 0, 1, 0
matrixB = 0, 1
matrixC = 1, 0
matrixD = 0
observerGain = 200, 10000
matrixA is column-major. Start with observerGain at zeros and work Setup
steps 6 and 7. This is a starting point, not a final tuning.
Limits and errors
| Limit | Set by | What happens | Reported |
|---|---|---|---|
observerGain against the task period |
Nothing | Not checked, and this is the bound you will hit while tuning. Too high a gain makes the estimate grow without bound. Only a controller restart clears it | Not reported |
| Task period against the model’s poles | Nothing | Not checked. The same limit applies to matrixA alone: the task period must stay below 2 divided by the largest pole magnitude in rad/s |
Not reported |
| Observability | Nothing | Not checked. A state no measurement reaches cannot be estimated at any gain, and the symptom is a state that simply drifts | Not reported |
| Model accuracy | Nothing | Not checked, and cannot be. A wrong model gives a confident, wrong estimate with a small error signal | Not reported |
| Matrix element order | Fixed | Column-major, always. A row-major list silently produces a transposed matrix | Not reported |
| Model time domain | Fixed | Continuous-time only | Not reported |
| Matrix dimensions | Machine configuration | Fixed when the machine is built. Read the array lengths to discover them | Not reported |
| Estimate state | Nothing | There is no reset input, so the estimate cannot be re-initialised in service. stateVectorInit has no effect. A restart is the only way |
Not reported |
stateVector, outputVector |
Nothing | Unbounded | Not reported |
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).