Head Firmware (XIAO RP2350)¶
1. Overview¶
The XIAO RP2350 is the head's local controller, sitting between the Raspberry Pi 5 (choreographer) and the head's actuators/sensors. It does not make high-level decisions: the Pi computes target positions and behaviors, the XIAO executes them, protects the hardware, and reports sensor/servo state back.
It talks to:
- The Pi, over I2C (see Internal buses → Pi 5 ↔ XIAO RP2350), for commands and telemetry.
- The 5 head servos, over a dedicated serial bus.
- The two touch sensors and the vibrator, over digital/PWM GPIO.
- A distance sensor and an IMU, over a second, independent I2C bus (see §7).
flowchart TB
pi["Raspberry Pi 5"] <--> bus["I2C bus (0x30)"]
bus --> hc["Head controller"]
hc --> sm["Servo control<br>5 axes"]
hc --> vib["Vibrator"]
hc -.-> eye["Eye LEDs<br>not implemented"]
wd["Watchdog<br>200 ms timeout"] --> fs["Failsafe"]
fs --> sm
fs --> vib
touch["Touch sensors"] --> tel["Telemetry"]
sm --> tel
imu["IMU"] --> tel
dist["Distance sensor"] --> tel
bus --> tel
tel --> pi
classDef done fill:#BBDEFB,color:#000000
classDef todo fill:#F1EFE8,stroke-dasharray: 4 2,color:#5F5E5A
class bus,hc,sm,wd,fs,vib,touch,tel,imu,dist done
class eye todo
| Feature | Status |
|---|---|
| Servo control (targets, limits, motion smoothing, "alive idle" wander) | Implemented |
| Vibrator | Not implemented |
| Touch sensors | Implemented |
| Distance sensor + IMU | Implemented (raw values only, see §7) |
| Failsafe / watchdog | Implemented |
| Eye LEDs | Not implemented — the Pi's eye_mode byte is accepted but has no effect yet |
2. I2C bus with the Raspberry Pi 5¶
The command/telemetry frame layout is defined once, in Internal buses → Pi 5 ↔ XIAO RP2350 — not repeated here.
The XIAO is the I2C slave at address 0x30. Incoming commands are captured as soon as the Pi sends them, so command handling isn't tied to the head firmware's own update rate.
3. Servo control¶
3.1 Mapping¶
The 5 target fields map to the physical servos as follows:
| Wire field | Physical servo |
|---|---|
servo_1_target |
Right antenna (mounted mirrored — see below) |
servo_2_target |
Left antenna |
servo_3_target |
Neck 1 |
servo_4_target |
Neck 2 (pitch) |
servo_5_target |
Neck 3 (rotation) |
The right antenna is mounted in mirror orientation, so the firmware flips its target before applying it, keeping the same convention (0 = back, 1023 = front) on both antennas from the Pi's point of view.
3.2 Position limits¶
Each servo has its own mechanical range, enforced regardless of what the Pi requests:
| Servo | Center | Min | Max |
|---|---|---|---|
| Antennas (both) | 512 | 0 | 1023 |
| Neck 1 | 585 | 490 | 680 |
| Neck 2 (pitch) | 540 | 420 | 570 |
| Neck 3 (rotation) | 485 | 297 | 673 |
3.3 Armed / emergency stop¶
- Armed must be set by the Pi on every frame for targets to be applied. If not, servos hold their last position.
- Emergency stop overrides armed the same way.
- Either case also forces the vibrator off.
3.4 Motion profile¶
Servo moves are acceleration-limited rather than instant jumps: each axis ramps up to a maximum speed and decelerates into its target without overshooting. The antennas move fastest; the neck pitch axis is the slowest. The reported servo-position telemetry reflects this in-flight motion, not the raw target the Pi last sent.
3.5 "Alive idle" wander¶
When the Pi requests alive-idle mode (and the head is armed), the two antennas switch to an autonomous idle behavior instead of tracking the Pi's targets: each antenna drifts smoothly around a resting center using a deterministic noise curve — no sudden jumps, no jitter — at its own, gentler speed limits than a direct Pi command. The three neck axes are unaffected and keep following the Pi normally. Leaving alive mode resumes from wherever the antenna actually is, rather than snapping back to a stale target.
3.6 Servo bus recovery¶
At boot, and continuously while running, the firmware checks that all 5 servos are responding. If any are missing, no servos are driven until all 5 are found again, at which point motion resumes from the servos' actual positions rather than jumping.
4. Vibrator¶
The vibrator level is applied directly from the Pi's command on every frame, forced off whenever the head isn't armed, is emergency-stopped, or the failsafe is active.
5. Touch sensors¶
Both touch sensors are simple on/off digital inputs, reported live in the telemetry.
6. Safety behaviors¶
- Watchdog: if no valid command is received from the Pi for 200 ms (crash, freeze, bus loss), the head enters failsafe.
- Failsafe: servo torque is cut — the head goes limp rather than holding or fighting a stale position — and the vibrator is forced off. Normal operation resumes automatically as soon as a new valid command arrives.
- The telemetry status byte reflects whether the failsafe is currently active, so the Pi can detect a head-side fault.
- Eye LEDs aren't turned off on failsafe, since they aren't implemented at all yet.
7. Sensor bus¶
A distance sensor and an IMU sit on their own, separate I2C bus so their traffic doesn't interfere with the Pi's:
- Distance sensor: continuous time-of-flight ranging in long-range mode; holds its last reading if it drops out rather than reporting garbage.
- IMU: raw accelerometer and gyroscope, sampled around 100 Hz.
Neither sensor's raw units are converted to physical units (g, °/s) on the XIAO — that conversion, if needed, is left to the Pi side.
8. Build¶
Board: Seeed XIAO RP2350, Arduino framework. Third-party libraries handle the servo bus and the distance sensor; the IMU is driven directly with no external library.