Skip to content

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.