The servo

A Field-Oriented Control servo on an RP2040: SimpleFOC, an AS5600 magnetic encoder, and a TI DRV8311H 3-PWM stage on the final board. The work is safe bring-up, a dual-core split so the current loop does not jitter when you type, and a host web UI that captures step responses and suggests gains.

Phase 1 (SimpleFOC Mini on the bench) is done. Phase 2 is the real hardware: closed-loop FOC with soft defaults is active; current-sense calibration and richer autotune on that board are next.

Why dual-core

USB serial, I2C filter tweaks, and CSV dumps are slow compared to a 10 kHz FOC tick. Running loopFOC / move on Core 1 and leaving commands, printing, and the web stream on Core 0 was the change that cleaned up step traces while the host was still talking. Torque effort in voltage mode is sampled on Core 1 as motor.voltage.q and published over volatile for the UI.

Soft first-boot limits are not optional: 1.5 V voltage cap, gentle alignment, angle P=12 / D=1.5, velocity P=0.08 / I=0.25, ramps on. Raise voltage in tenths after the shaft feels smooth and cool. Hard max on the GUI is 4.0 V.

Try the tuner

A browser plant with the same control surface as driver_gui.py (port 8082 on the bench). Arm, move a setpoint, drag gains, run a step. Crank angle P with D near zero if you want to see why the defaults are boring on purpose. No Pico on this page.

RP2040 FOC · Phase 2

DRV8311H · AS5600 · closed-loop sim
pico-hw-foc · VID 2E8A · sim

Safety

Live

Armed
no
nFAULT
ok
Volt lim
1.50 V
Cores
C1 FOC / C0 UI
Angle rad
0.000
Target
0.000
Enc
0.0°
Vq
0.000

PID + step

Arm, then run a step.

Simulation only. A simple plant model, not USB serial to a Pico. Real path: pio run -e pico-hw-foc -t upload then python driver_gui.py.

Hardware

Pin map lives in one header (board_hw_v2.h). PWM GP11/12/13 → INHA/B/C, GP14 nSLEEP, GP9 nFAULT (pull-up), GP4/5 I2C to the AS5600, GP26/27/28 phase current, GP29 VIN. MODE and INLx tied to AVDD for 3x PWM. If INLx floats, the stage stays Hi-Z and you get a very quiet motor. Shared GND across MCU, driver, encoder, and motor PSU. Encoder at 3.3 V, not 5.

Bring-up is staged PlatformIO envs: encoder-only with nSLEEP held low, bare PWM smoke test, open-loop driver, then pico-hw-foc closed-loop. Motor PSU on before initFOC() so the alignment jog is real. Pole pairs 7 for a 2804-class gimbal.

Host tools

Phase 2 UI is a single Python process (driver_gui.py): ThreadingHTTPServer, no NiceGUI spawn, because Windows. Live @S stream, ARM/DISARM/E-stop, gain sliders, step capture with rise/overshoot/settle, grid and relay autotune. Bench leftover: gui.py, autotune.py, capture.py, and a folder of step CSVs from the Mini days.

On the bench

Closed-loop FOC on the DRV8311H board with soft gains. Dual-core split. Serial command set (pos, volt, soft, arm/disarm, step, stream, x). Bench Mini path complete including presets, LittleFS, and the older NiceGUI tuner.

Not this

Not current-mode torque yet. Phase ADCs are wired; amps/V calibration is the next hardware step. This is a bench servo, not a drone ESC or a signed field-update path. Voltage-mode Vq is a proxy for effort until the sense path is trusted.

Next hardware

VIN + current cal, then the Mini autotune command set on the final board, then current-loop torque. The architecture does not need to change when Vq becomes Iq. Keep the FOC tick on Core 1.

Start soft

A small RP2040 servo with a pin map, dual-core FOC, and a browser that shows the step you just took. Start soft. Measure the response before raising voltage.


references