A Stewart platform neck that follows a face
A six-servo Stewart platform built as a neck for the Unitree G1 humanoid, with a camera in its head that keeps a face in view. I designed the electrical system and wrote the tracking and control code.
Goal
A humanoid looks more attentive when it turns its head toward the person in front of it. The goal was a neck that does this on its own: a Stewart platform, six servos moving one plate, steered by a camera that tracks a face, within ±15° of yaw and ±10° of pitch. The platform's geometry was tuned in MATLAB, over more than three million configurations, to keep every pose within the servos' range.
What I built
Power and noise
Six JX PDI-6221MG servos run from a 6 V, 10 A switching supply through a PCA9685 PWM driver, commanded by an Arduino Uno. The first design ran everything on a Raspberry Pi 5, which failed from heat after long runs above 75 °C, so the vision moved to a PC and the Arduino kept the real-time servo control.
Moving together, the servos drew peaks above 7 A that pulled the 6 V rail below 4.5 V, resetting the Arduino and making the servos unstable, while noise from shared power and signal lines put jitter on the PWM. I added 1000 µF decoupling capacitors on the power distribution, routed signal and power cables apart, and split the ground into logic and power domains.
Tracking
A Haar cascade finds the face and a CSRT tracker follows it from frame to frame; if the tracker loses the face for more than 10 frames, a fresh detection takes over. The camera is calibrated with a chessboard of 11 × 11 inner corners and 3 cm squares, photographed 44 times, so the face's position is undistorted before it becomes a yaw and a pitch angle.
Control
The angles pass a dead zone of about 0.9°, so the head ignores small shifts, then exponential smoothing (α = 0.3) and clamping to the platform's range, with a tighter limit when both axes move at once. Inverse kinematics turns the head's pose into six servo-arm angles, and those into pulse widths, which go over UART at 115,200 bps, 50 times a second. The Arduino checks each against its servo's limits before writing it to the PCA9685. A bad value falls back to the neutral pulse, a lost face recentres the head, and servo control runs in its own thread, apart from the vision loop.
Result
The head follows a face at 30 FPS with 48.6 ms of end-to-end latency. With the power fixes, PWM jitter fell from ±8 µs to ±1.5 µs and the Arduino stopped resetting under full load. These results are from the bench; the platform has not yet been mounted on the G1.
What I learned
- Sizing a servo supply for peak current, not average: six servos moving together drew more than 7 A.
- Treating decoupling, cable routing and grounding as part of the design, since they decided whether the controller stayed up.
- Keeping real-time servo output on a microcontroller and the heavy vision on a separate computer.
- Making tracking feel steady: the dead zone, smoothing and limits mattered as much as the detector.
- Turning a head pose into servo commands through a Stewart platform's inverse kinematics.