ObserverKalmanFilter
ObserverKalmanFilter estimates a joint’s position, velocity and torque from noisy sensor readings, using a model of the joint to reject noise the sensor cannot distinguish from signal.9 minute read
ObserverKalmanFilter estimates a joint’s position, velocity and torque from
noisy sensor readings, using a model of the joint to reject noise the sensor
cannot distinguish from signal. Its purpose is torque estimation: turning a
noisy torque reading into a smooth one you can control against.
Unlike Observer, you do not tune a gain. You tell the filter
how much to trust the model and how much to trust the sensors, and it works the
gain out for itself, every update. That is the right structure when the model
is approximate and the sensors are noisy.
flowchart LR
i1(["inputVector — commanded torque"]) --> B["ObserverKalmanFilter"]
i2(["measurementVector — measured position, velocity, torque"]) --> B
i3(["stateVectorInit"]) --> B
i4(["disable"]) --> B
B --> o1(["stateVector — estimated position, velocity, torque"])
B --> o2(["covarianceMatrix — the filter's own uncertainty"])
B --> o3(["isEnabled"])
The model is $I\ddot{\theta} + b\dot{\theta} + k\theta = \tau - F$, where the friction force $F$ is $c_v\dot{\theta} + c_c\operatorname{sign}(\dot{\theta})$ — a velocity-proportional part plus a constant-magnitude part. Tuning is the ratio of
processNoiseCovariancetomeasurementNoiseCovariance: raise the first to follow the sensors, raise the second to trust the model and smooth harder. The third diagonal element ofprocessNoiseCovarianceis what decides how fast the torque estimate may move.
measurementVector needs all three values — position, velocity and
torque. Leaving the velocity element at zero tells the filter the joint is
measured to be stationary, which fights its own estimate.
Never set measurementNoiseCovariance to zero. It is the natural way to
say “trust the sensors completely” and it makes the filter’s arithmetic
ill-conditioned. Never set frequencyDivider to zero either — see Limits
and errors.
Signals
Inputs
| Path | Unit | Range | Description |
|---|---|---|---|
inputVector |
N·m | unbounded | The torque commanded into the joint. The filter needs it to predict how the joint will respond. A single value. |
measurementVector |
mixed: rad, rad/s, N·m | unbounded | What the sensors read, as three values in this order: position, velocity, torque. All three are used. |
stateVectorInit |
mixed: rad, rad/s, N·m | unbounded | Intended as a starting estimate. It has no effect — nothing in the block reads it, because there is no reset to apply it. |
disable |
- | - | True freezes the filter: the estimate and the uncertainty both hold their last values and nothing advances. It is not a bypass and not a zeroing. |
Outputs
| Path | Unit | Description |
|---|---|---|
stateVector |
mixed: rad, rad/s, N·m | The estimate, as three values: position, velocity, and filtered torque — the reason to use this block. It starts at zero after a controller start and converges from there. Not cleared by a stop. |
covarianceMatrix |
mixed | The filter’s own estimate of its uncertainty, 3×3. Useful for watching convergence — the diagonal should settle and stay settled. Do not read it as a true confidence bound; see Limits and errors. |
isEnabled |
- | True when enable is true and disable is false. |
Parameters
| Path | Unit | Default | Range | Effect |
|---|---|---|---|---|
enable |
- | false | - | Off by default. False freezes the filter entirely. |
processNoiseCovariance |
mixed | identity | any positive-definite | How much you distrust the model, 3×3. Raise it to let the estimate follow the sensors more closely. Its third diagonal element governs how fast the torque estimate may change. |
measurementNoiseCovariance |
mixed | identity | positive-definite; never zero | How much you distrust the sensors, 3×3. Raise it to smooth harder and converge more slowly. |
frequencyDivider |
cycles | 1 | 1 or more | Task cycles between corrections. The prediction runs every cycle; the correction — the expensive part — runs every Nth. 0 faults the controller. |
model/inertia |
kg·m² | 1.0 | above 0 | Joint inertia. Zero is not checked and breaks the filter. |
model/damping |
N·m·s/rad | 10.0 | 0 upward | Viscous damping in the joint. |
model/stiffness |
N·m/rad | 0.0 | 0 upward | Joint stiffness. Leave at 0 for a rigid joint. |
model/viscousFrictionCoefficient |
N·m·s/rad | 0.10 | 0 upward | Velocity-proportional friction. |
model/coulombFrictionCoefficient_ |
N·m | 0.10 | 0 upward | Constant-magnitude friction. Note the trailing underscore in the path name — it is not a typo in your configuration. |
All nine parameters are persistent and survive a controller restart. The inputs
and outputs do not. No parameters exist below this block other than the
five model/ entries listed above.
Setup
-
Measure or estimate the joint’s inertia, damping and stiffness. These are physical properties, not tuning knobs — get them roughly right before touching the covariances.
-
Set
model/inertia,model/dampingandmodel/stiffness.model/inertiamust be above zero. -
Set the two friction coefficients from the joint’s own friction measurement, or leave them at their defaults if you have no figure.
-
Link
inputVectorfrom the commanded torque, and all three elements ofmeasurementVectorfrom the sensors. -
Confirm all three measurement elements move. Trace each one. A stuck zero on the velocity element is the most damaging misconfiguration of this block, because the filter treats it as a measurement of standstill.
-
Leave
frequencyDividerat 1, and both covariances at identity. -
Set
enabletrue and watch element 2 ofstateVectoragainst the raw torque measurement on one trace. The estimate should be a smoothed version of the measurement, following it without the noise.Step 7 is the only check that the model is right. If the estimate drifts away from the measurement rather than smoothing it, the model is wrong — go back to step 1. No covariance setting fixes a wrong model.
-
Raise
frequencyDivideronly if the block costs more processing time than you can afford. Its correction step is the expensive part.
Tuning
- Get the model right first, per Setup step 7. The covariances trade noise against lag; they cannot correct a wrong inertia.
- Tune the ratio, not the absolute values. Only the ratio of
processNoiseCovariancetomeasurementNoiseCovarianceaffects the result, so leave one at identity and move the other. - Start by raising
measurementNoiseCovariance— multiply the whole matrix by 10 — and watch element 2 ofstateVector. It should get smoother and slower. - Stop when the lag becomes unacceptable for whatever consumes the estimate. Smoothness and lag are the whole trade here.
- If you need the torque estimate to respond faster without touching the
others, raise only the third diagonal element of
processNoiseCovariance. That element governs the torque state alone. - Watch the diagonal of
covarianceMatrixwhile you tune. It should settle to steady values. A diagonal that keeps growing means the filter is not getting useful corrections — check step 5 of Setup. - Tune empirically, from the traces. Do not compute the covariances from sensor datasheets and expect the theoretical result: this filter’s internal gain does not correspond exactly to the model it runs, so the numbers that work will not be the numbers theory predicts.
- Re-check nothing after a task-rate change to the covariances themselves, but
do re-check
frequencyDivider— it counts cycles, so the correction rate in hertz moves with the task period.
Read the trade off any curve: smoother means slower.
| Symptom | Cause | Action |
|---|---|---|
The controller faulted as soon as frequencyDivider was written |
It was set to 0 | Set it to 1 or more and restart the controller |
| Everything reads zero and never moves | enable is false, or disable is true |
Read isEnabled |
| The estimate is frozen at an old value | Expected: a disabled filter freezes rather than zeroing | Set enable true and disable false |
| The estimate drifts away from the torque measurement | The model is wrong — most often model/inertia |
Re-measure the inertia; no covariance setting fixes this |
| The estimate fights the measured velocity | The velocity element of measurementVector is stuck at zero, so the filter reads standstill |
Link all three measurement elements |
| The estimate is as noisy as the raw sensor | measurementNoiseCovariance too low relative to processNoiseCovariance |
Multiply measurementNoiseCovariance by 10 |
| The estimate lags the real torque badly | measurementNoiseCovariance too high |
Lower it, or raise the third diagonal element of processNoiseCovariance |
| The torque estimate is slow but position and velocity are fine | The third diagonal element of processNoiseCovariance is too small |
Raise that element alone |
| The estimate went to a non-numeric value and stayed there | model/inertia is 0, or a non-numeric value reached an input |
Set a non-zero inertia, then restart the controller |
covarianceMatrix looks healthy but the estimate is nonsense |
The filter protects its uncertainty from bad values but not its estimate | Restart the controller |
The filter behaved oddly after measurementNoiseCovariance was set to zero |
Expected: zero makes the internal arithmetic ill-conditioned | Use a small positive value instead of zero |
covarianceMatrix’s diagonal keeps growing |
The corrections are not helping — check the measurements and frequencyDivider |
Work Setup step 5 |
| A stale estimate for a while after re-enabling | Expected: the filter resumes from where it froze, and still believes its old uncertainty | Re-enable with the joint near where it was, or restart |
| The estimate is wrong and there is no way to clear it | Expected: there is no reset in this block, and stateVectorInit has no effect |
Restart the controller |
| The torque estimate carries a small constant offset at standstill | Expected: the friction model applies its constant term even at zero velocity | Lower model/coulombFrictionCoefficient_, or accept the offset |
| The correction rate changed after a task-rate change | Expected: frequencyDivider counts cycles, not seconds |
Rescale it by the ratio of the task periods |
| Tuning from the sensor datasheet did not give the expected result | Expected: tune this filter empirically from traces | Work Tuning steps 2 to 4 |
A starting point for a joint with 0.5 kg·m² inertia on a 1 ms task, correcting every cycle, trusting the sensors and the model equally:
enable = true
frequencyDivider = 1
model/inertia = 0.5
model/damping = 10.0
model/stiffness = 0.0
model/viscousFrictionCoefficient = 0.10
model/coulombFrictionCoefficient_ = 0.10
processNoiseCovariance = identity
measurementNoiseCovariance = identity
Then work Tuning step 3. This is a starting point, not a final tuning.
Limits and errors
| Limit | Set by | What happens | Reported |
|---|---|---|---|
frequencyDivider ≥ 1 |
Nothing | Not checked. A value of 0 faults the controller. Always set 1 or more | Not reported |
model/inertia > 0 |
Nothing | Not checked. Zero makes the estimate and the uncertainty non-numeric. Only a restart clears the estimate | Not reported |
measurementNoiseCovariance |
Nothing | Not checked. Zero or near-zero makes the filter’s internal arithmetic ill-conditioned, and the bad result reaches the estimate unprotected | Not reported |
| Estimate protection | Partial | The filter protects its uncertainty from non-numeric values but not its estimate, so covarianceMatrix can look healthy while stateVector is ruined |
Not reported |
| Covariance meaning | Inherent | covarianceMatrix is useful for watching convergence but is not a true confidence bound — the filter’s internal gain does not correspond exactly to the model it runs. Tune empirically |
Not reported |
| Measurement count | Fixed | Three measurements are required: position, velocity, torque. Supplying fewer leaves zeros that the filter treats as real readings | Not reported |
| Model accuracy | Nothing | Not checked, and cannot be. A wrong model gives a confident, wrong estimate | Not reported |
| Estimate state | Nothing | There is no reset input. stateVectorInit has no effect. A restart is the only way to clear a wrong estimate |
Not reported |
| Estimate across a stop | Nothing | Not cleared by a stop or a start. A restart of the task resumes the old estimate | Not reported |
stateVector, covarianceMatrix |
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).