Assistive hardware · control firmware

Cancel the shake. Keep the intent.

Control firmware for a 3D-printed self-stabilising spoon for people with hand tremor. It cancels the 4–12 Hz shake and follows the hand to the mouth.

Tremor attenuation−17.5 dB
Reduction86.7%
Voluntary error0.20°
Handle vs spoon · liveHandle   Spoon
The design correction

“Level” is the wrong target.

A spoon that holds itself level in the world fights you every time you lift it. In simulation that costs 15° of error on the movement you meant to make. Tremor and intent live in different frequency bands, so the controller separates them and cancels only the tremor.

FrequencyVoluntary reaching < 2 Hz   Tremor 4–12 Hz
Intent = low-pass at 1.2 Hz, used as the setpointTremor = (tilt − intent), conditioned 0.8–40 Hz
The latency trap

Reacting is too late.

Filtering, the control period and the motor all add delay. Oppose a shake you measured a moment ago and, past a certain lag, you add to it instead. Tremor is narrowband, so an adaptive oscillator (WFLC) locks onto it and predicts it ahead of time.

Single-tone modelTremor   Correction   What's left
−2.7 dBBarely helps

Idealised: one sine, correction delayed by the lag, residual = 2·|sin(π·f·lag)|. With prediction on it shows the full simulation's measured −17.5 dB. A reactive loop measured −5.6 dB there.

Measured in closed-loop simulation

Three controllers, one tremor.

30 s run, tremor chirping 5 → 9 Hz with a 25% second harmonic, on top of a 0.25 Hz reach. The plant models delay, friction, cogging, sensor noise and the tremor's own acceleration. Input tremor 2.63° rms.

Full design

WFLC predict + PID

Attenuation · −17.5 dB · 86.7% reduction
Voluntary error0.20°
No prediction

Reactive PID

Attenuation · −5.6 dB · 47.8% reduction
Voluntary error0.46°
The obvious spec

Hold level

Attenuation · −6.2 dB · 51.3% reduction
Voluntary error15.0° · fights you
Tuning on hardware

One number worth 10 dB.

How far ahead to predict is swept last, on the real device. It's the net phase alignment, pipeline lag minus front-end phase lead, so it can be negative. The curve has one clear minimum. Pick a bar.

Predict-ahead−17.5 dB
−3 ms · 86.7% reduction
Found by sweep

Five choices that looked harmless.

Each one costs real performance until you measure it. Tap a card for the detail.

01The low-pass is set by the harmonic, not the tremor.+ open

A 20 Hz filter looks generous for a 14 Hz signal, but a 9 Hz tremor's second harmonic sits at 18 Hz, where it loses 22% gain and 81° of phase. Rule: band_hi ≥ 3 × f_max.

Open-loop residual −13 dB → −21 dB

02Only the fundamental may steer the frequency.+ open

The WFLC gradient weights harmonic r by r, so a 25% second harmonic gets half the fundamental's vote and breaks the lock. Harmonics are still reconstructed, they just don't vote.

Frequency error 0.018 Hz vs 3.7 Hz

03The accelerometer is useless in the tremor band.+ open

A 6 Hz tremor makes about 7 m/s² of linear acceleration, close to gravity. The tilt observer crosses over at 0.3 Hz and gates samples that don't look like gravity.

31% of samples rejected during tremor, correctly

04The D-term setpoint weight must be 1, not 0.+ open

Textbook derivative-on-measurement avoids kick, but here the setpoint itself moves at tremor frequency and its derivative is known exactly. With c = 0 the D term fights every commanded motion.

c = 0 demands ~2.0 of command against a 1.0 limit

05A free gimbal already isolates on its own.+ open

A head that stays still in space needs no net torque, so on a direct-drive gimbal the acceleration feedforward should be zero. That makes low bearing friction the most important mechanical requirement.

accel_ff_gain = 0 direct drive, J/Kt geared

Architecture

From IMU to motor.

  1. IMU1 kHz gyro and accelerometer on the handle
  2. Tilt observerComplementary PI, 0.3 Hz, gated
  3. SplitIntent below 1.2 Hz, tremor 0.8–40 Hz
  4. WFLCLocks frequency, amplitude and phase
  5. PredictSetpoint and rate, ahead of the lag
  6. LimitsTravel, then slew at 12 rad/s
  7. PID → motorAnti-windup, D from measured rate
About 100 mm from a face

It must never fail into motion.

The control law is bounded whether or not it is working. Every failure degrades to an ordinary spoon.

Slew limit12 rad/s, about 3.5× what a real tremor needs, so no fault can become a fast swing.
Travel limitEnforced before the slew limit and backed by mechanical stops.
Latched faultIMU timeout, bad attitude or a power fault parks the joints until explicitly cleared.
Fail to coastingA braked joint passes every tremor to the food. A coasting one still isolates.
WatchdogFed only from the control task, never from the interrupt.
Food-safe headStainless steel, removable, dishwasher-safe. Printed parts never touch food.

Honest status. The numbers come from validate/validate.py, a line-for-line Python port of the C++ headers run against a plant model. The C++ itself hasn't been compiled yet, and the synthetic tremor is periodic, which flatters the method. This is evidence the control law is sound, not a medical device.

The whole controller is open.

Headers for the PID, filters, WFLC and safety state machine, a desktop simulation, and the Python probes that produced every number here.