The face board for the Open Duck Mini's head: eye LEDs, projector driver, I2S speaker amp and microphone, antenna servos, shaped to the head.
Part of HeyPCB's open robot boards. Designed end to end with the HeyPCB method on
KiCad 10 (headless pipeline, Freerouting 2.4.1); every number below is measured on the files in this repository.
Mentions of ./run.sh, docs/rules/, blocks/ or parts/ refer to the heypcb-kicad SDK that produced them.
| Layers | Outline | Parts | Nets | Connections routed | Vias | ERC errors | DRC errors / unconnected / parity |
|---|---|---|---|---|---|---|---|
| 4 | 128 x 51.4 mm | 55 | 33 | 96 -> 0 open | 86 | 0 | 0 / 0 / 0 |
Not fabricated, assembled or powered.
| KiCad project | kicad/ |
| Gerbers (unzipped and zipped), BOM, CPL, STEP, schematic PDF | fab/ |
| Printed parts (STEP/STL), CadQuery source, fit report | cad/ |
| Renders | top, bottom |
| Design process, every step as KiCad rendered it | media/design-process.mp4 |
Quack Face is the face board for the Open Duck Mini's head. It stands vertically 8 mm behind the
face wall, on the eight screw holes that hold the duck's eye parts today. Its outward face
carries the two eye LEDs and the microphone's sound port; everything else sits on the face
that looks into the head. It makes its own 5 V from the pack, drives the eyes, the projector,
the speaker and the two antenna servos, and listens through an I2S microphone. It talks to the
Quack Core brain board over one JST-GH 12-pin harness (J12) and takes power on a JST-XH 2-pin
(J13). It was designed with the HeyPCB method on KiCad 10 (10.0.6), by heypcb-kicad's headless
pipeline with Freerouting 2.4.1, from the brief in board.json and the research spec for the
duck (robots/research/duck/spec.md, section 4).
This board has not been fabricated, assembled or powered. Everything below is what KiCad,
the pipeline and cad/parts.py measured on the design files. Nothing here has been tested on
hardware or in the duck. Every gate number comes from report.json, and every
fit number from cad/fit.json, both written by the runs of 2026-10-02.
- Power: VBAT (2S pack, 6.0-8.4 V, fused 5 A on the Quack Core) on J13, with a 220 uF / 16 V electrolytic for the 330 mm harness. A TPS56339 buck (4.5-24 V in, 3 A, 500 kHz) makes 5 V with TI's 5 V divider (52.3k over 10.0k, 0.802 V reference: 4.996 V nominal), a 4.7 uH inductor and 2 x 22 uF out. TI's 5.6 uH is computed at 19 V in; its inductor equation gives 4.5 uH at the pack's 8.4 V. EN floats, as TI's pin table allows ("Float to enable"). An XC6206P332MR LDO makes 3.3 V for the microphone.
- Eyes: two white 2835 LEDs (XINGLIGHT XL-2835UWC-05, 150 mA / 500 mW maximum) on the
outward face, each centred on its 25 mm eye opening. Each is fed from 5 V through 33 ohm and
switched on its cathode by an AO3400A; the gate takes the Pi's GPIO through 100 ohm and
is held off by 100k. High = on, as
eyes.pydrives them. From the datasheet's 2.8-3.4 V forward voltage the 33 ohm sets about 48-67 mA (an estimate: the datasheet gives Vf at 150 mA). The GPIO no longer carries LED current. - Projector: the 1 W emitter stays in its reflector in
flash_light_module, outside the head's left wall. The board switches it the same way: 5 V through a 10 ohm 1 W 2512 to J15 pin 1, an AO3400A on pin 2. About 160-200 mA for a 3.0-3.4 V emitter (estimated; the emitter's own data is not known). - Speaker: a MAX98357A (I2S, class D, 3.2 W into 4 ohm per its catalogue row) on the Pi's I2S output. SD_MODE goes to VDD through 1 Mohm: with the datasheet's internal 100k pull-down that is 0.45 V, inside the (left + right)/2 window. GAIN_SLOT is open (9 dB). VDD has 10 uF + 100 nF behind a 0603 ferrite bead. The exposed pad is on GND. Speaker on J14.
- Microphone: a TDK ICS-43434 I2S MEMS microphone with a bottom port. It shares BCLK and LRCLK with the amplifier and drives the Pi's DIN (GPIO20) through 47 ohm, with the datasheet's 100k pull-down. LR is tied low, so it answers in the left half-frame. The part sits on the inner face and its 0.5 mm sound hole opens on the outward face.
- Antenna servos: two 1x3 2.54 mm headers (pin 1 GND, pin 2 5 V, pin 3 PWM, the spec's order), PWM through 220 ohm, and one 220 uF / 16 V on 5 V beside the left header.
- Board: 128.0 x 51.4 mm with a 28.0 x 27.1 mm camera notch and 1 mm fillets (a
board.shapepolygon), 4 layers, 1.6 mm. F.Cu (the inner face) is signal with a GND pour, In1 a GND plane, In2 signal with a 5 V pour, B.Cu (the outward face) a GND pour with two short LED slots and no other track. Eight 2.7 mm NPTH holes for M2.5.
The outward face is B.Cu. The pipeline always writes the title on B.SilkS, so making B the
face the viewer sees puts the board's name there with no SDK change. Only the two eye LEDs are
placed with "side": "bottom"; every other part, connectors included, is on F.Cu, facing the
inside of the head where the harness arrives. So the assembler places one face and two LEDs on
the other: a two-sided job at JLC.
On the outward face:
- B.Cu takes no tracks (
board.keepouts, B.Cu,tracks) except a 3.2 mm slot through each LED. - A 12 mm circle round each LED takes no via, on any layer.
- So each LED's anode leaves straight up and its cathode straight down on a short B.Cu track to a via outside that halo. The two eyes mirror each other.
- What remains on that face is the GND pour, the two LEDs, the eight holes, the sound port and the title, "Quack Face / face board for the Open Duck Mini / rev A heypcb-kicad".
- Copper stays 3 mm from every screw-hole centre on both outer faces, so the screw heads and the printed spacers sit on bare laminate.
- A 5 mm circle round the sound hole takes no via, so the printed acoustic boot seals on flat solder mask.
J12, to the Quack Core (JST-GH 12P, BM12B-GHS-TBT vertical, C2983891). The pinout is the
shared contract in the research spec, section 4.5, unchanged. These are the board's nets, read
back from the run's design.json:
| Pin | Net | Pi GPIO | Pin | Net | Pi GPIO | |
|---|---|---|---|---|---|---|
| 1 | GND | 7 | GND | |||
| 2 | I2S_BCLK | 18 | 8 | EYE_L | 24 | |
| 3 | GND | 9 | EYE_R | 23 | ||
| 4 | I2S_LRCLK | 19 | 10 | PROJ | 25 | |
| 5 | I2S_DOUT (to the amplifier) | 21 | 11 | ANT_L | 13 | |
| 6 | I2S_DIN (from the microphone) | 20 | 12 | ANT_R | 12 |
The GH mounting pads are on GND. DOUT and DIN are named from the Pi's side, as in the contract.
J13, power from the Quack Core (JST-XH 2P, B2B-XH-A, C158012): pin 1 VBAT, pin 2 GND.
| Ref | What | Part | Pins |
|---|---|---|---|
| J14 | Speaker, 4 ohm | JST-PH 2P B2B-PH-K-S (C131337) | 1 OUTP, 2 OUTN (bridge-tied: neither wire to GND) |
| J15 | Projector emitter | JST-PH 2P B2B-PH-K-S (C131337) | 1 anode (5 V through 10 ohm), 2 cathode (AO3400A) |
| J16 | Left antenna servo (ANT_L) | 1x3 PZ254V-11-03P (C2937625) | 1 GND, 2 5 V, 3 PWM |
| J17 | Right antenna servo (ANT_R) | 1x3 PZ254V-11-03P (C2937625) | 1 GND, 2 5 V, 3 PWM |
Placement follows the head:
- the projector connector and the left servo header are on the robot-left edge (the projector's feed-through is in the left wall);
- the speaker connector and the right servo header are on the robot-right edge (the speaker hangs on the right wall);
- J12 and J13 are beside the camera notch on the robot's right, where the head leaves about 40 mm behind the board (see the fit table).
| Spec | On the board | Why |
|---|---|---|
| Microphone hole suggested at CAD (y +20.0, z 160.0) | At (y -20.0, z 160.0), the same spot mirrored | With the mic left of the camera notch, every I2S net had to cross under the notch, and routing left I2S_BCLK open at the mic. On the right, no I2S net crosses it and routing closed (see docs/rules/pipeline.md). The hole clears the eye screws and camera bosses, by the spec's own criteria. |
| Eye openings at z 139.20 | LEDs at z 139.41 | cad/parts.py fits the 25.0 mm opening in a section of head.stl and finds it centred at z 139.41, midway between the screw rows (126.91 / 151.91). The LEDs sit on the fitted centre. |
| JST-GH 12P SM12B-GHS-TB (right-angle; no LCSC number resolved) | BM12B-GHS-TBT, top entry, C2983891 (11 553 in stock) | Found by MPN on the JLC mirror. The side-entry SM12B-GHS-TB is C265421 (519 in stock). The pinout is the contract's either way. |
| Either on-board emitters or JST-PH to the existing 1 W eyes | On-board white 2835 LEDs at roughly 50-70 mA (estimate above) | The outward face carries the eyes. The stock eyes run from bare GPIO, so their real current is not 330 mA. |
| M2.5 male-female hex standoffs | Printed 8 mm / 6 mm spacers on M2.5 x 16 through-screws (cad/) |
The task asked for printed standoffs. A through-screw clamps eye part, wall (and bulb ring), spacer and board in one stack; see the fit table for its thread engagement. |
| Optional 74LVC1G125 for a 5 V servo signal | Not fitted | Optional in the spec; the PWM swings 3.3 V through 220 ohm. |
| Gate | Result |
|---|---|
| Composition | 13 block instances (11 distinct, all new, prefixed quackface_) → 55 parts, 33 nets, 6 no-connects |
Part binding (lcsc_pinned.json) |
47 of 47 orderable parts bound, 0 unsourced, 0 identity conflicts. The eight holes are not in the BOM. 14 distinct Extended parts, about $42 in JLC loading fees |
ERC (kicad-cli sch erc --severity-all) |
0 errors, 1 warning (pin_to_pin: the MAX98357A's exposed pad, an "unspecified" pin, on the GND power net) |
| Netlist round trip | identical, 33 nets |
| Freerouting, rung 1, fanout off (kept) | 96 → 10 open; the last-mile router closed all 10 (45 tracks, 14 vias, kept on DRC's word) → 0 open |
| Freerouting, rung 1, fanout on (for comparison) | 96 → 8 open; the last mile closed 7 → 1 open |
| Copper | 327 tracks, 22 routing vias, 1392.3 mm of track (F.Cu 977.8, In2 387.5, B.Cu 27.0). No class was narrowed |
| Pours (only after a measured 0 open) | GND on F.Cu, In1 and B.Cu, 5 V on In2 (board.pours); 65 stitching vias (87 vias in all); 0 open after the pour, after stitching and at the end |
| Silkscreen | 9 words and the 3 title lines placed; 40 designators re-seated and 6 hidden; 1 stitching via displaced |
DRC (--severity-all --schematic-parity, KiCad 10.0.6) |
fresh, 0 errors, 0 warnings, 0 unconnected, 0 parity |
DFM (jlcpcb_4layer) |
0 errors, 1 warning (213 stock-footprint silk strokes at 0.12 mm; the fab prints them at its 0.15 mm minimum) |
| Fab gate | ready, no blockers. Four copper gerbers and a gerber job with LayerNumber 4; the JLC CPL corrected 6 rotations and lists D1 and D2 as Bottom |
The kept route depends on the last mile: Freerouting alone left 10 connections open, most of
them a few millimetres long (the buck's SW to its boot capacitor, the mic's LR pin to its GND
pin, I2S at J12). The brief routes the same way every run (docs/rules/pipeline.md, "One brief
routes one way"). A change as small as moving both eye halos 0.21 mm left 11 open on the
no-fanout solve and 13 on the fanout solve; a 12 mm halo at the same spot routed 0 open. Treat
any brief change as a new routing sample.
cad/parts.py loads the board's own STEP (fab/quack_face.step) and the
duck's print STLs (Open_Duck_Mini v2 @ b23317a, print/). It puts the board in the head's CAD
frame (outward face at x 109.0, inner face at x 107.4) and measures:
- circles fitted to sections of
head.stl, the eye parts andbulb.stl; - gaps and pilot depths, by rays cast through the meshes;
- clearances, by closest points both ways between the meshes;
- overlaps, by manifold3d booleans or a containment test;
- the swept neck, from the research step's voxel cavity (neck over head_roll +-30 deg and head_yaw +-160 deg, 1 mm voxels).
The camera is the spec's estimated outline, not a model.
| Check | Measured |
|---|---|
| Board holes vs the face wall's holes (8) | every board hole 0.02 mm from the wall hole fitted to the STL (wall holes 2.698 mm, at z 126.91 / 151.91) |
| Board holes vs the eye parts' pilots | 0.02 mm (left pilots 2.195 mm, right 2.045 mm, all blind, 3.0 mm deep) |
| Right-eye bulb ring holes | 0.02 mm (2.698 mm) |
| Gap from the board's outward face to the bearing face | 7.99 mm on the left eye (face wall, x 117.0), 5.99 mm on the right (bulb ring); the spacers are 8.0 and 6.0 mm |
| Screws | M2.5 x 16 pan head with two ISO 7089 washers (0.5 mm) under the head: grip 12.6 mm, 2.4 mm into the 3.0 mm blind pilot, 0.6 mm short of its bottom. Without washers no standard length fits: 14 mm engages 1.4 mm, 16 mm bottoms out |
| Screw heads + washers on the inner face (6.0 mm across, 2.75 mm tall) | 1.625 mm from the nearest inner-face part |
| Board + parts vs head shell, floor, head-roll mount, speaker stand | min 0.99 mm (the bottom corner by the rib and the floor: the spec's 1 mm edge allowance); 0 points inside any of them |
| Spacers vs head, floor, roll mount, bulb ring | 0.0 mm3 overlap (they bear on the wall and the bulb ring) |
| Board vs the swept neck (voxel cavity) | 0 of 81 190 points in an occupied voxel; nearest occupied voxel 1 voxel (1 mm) away |
| Eye LEDs vs the 25.0 mm openings | 0.0 mm off axis; LED face 7.21 mm from the wall's inner face; the opening subtends 60.0 deg half-angle (the LED's datasheet beam is 120 deg) |
| Right eye through the bulb ring | the ring's centre hole (radius 3.625 mm at x 116.75) cuts the cone to 27.5 deg half-angle (see Check these) |
| Eye parts | 10.19 mm (left) and 9.24 mm (right) from the board and its parts; the bulb ring 5.91 mm |
| Camera (estimated outline) | 1.7 mm from the notch; lens axis 12.03 mm from the nearest board edge (lens hole 7.75 mm, z 153.11) |
Projector (flash_light_module) |
16.65 mm |
| Mic port | at (y -20.0, z 160.0); 7.99 mm from the board's outward face to the wall, so the printed boot is 8.0 mm; the stock head.stl has no hole there |
| Room behind the tall inner-face parts (walking -x through the cavity) | J12 42.5 mm, J13 39.5, J14 39.5, J15 92.5, J16 90.5, J17 65.5, C1 44.5, C8 91.5, L1 more than 120 |
More previews: cad/img/fit_front_ghost.png (the head see-through, from the front) and
cad/img/fit_cut_z151.png (a cut through the upper screw row, from above). The KiCad renders
of both faces are in img/.
Printed parts (cad/print/, STEP and STL): standoff_left_8mm (4 off) and
standoff_right_6mm (4 off), both 5.5 mm across with a 2.9 mm bore and 0.3 mm chamfers. Also
mic_boot_8mm (4.6 mm across, 1.6 mm bore), only with a reprinted head. All three are
watertight meshes. cad/quack_face_in_head.step is the board's STEP with the
eight spacers, the boot and the two LED bodies, in the duck's CAD frame.
- Outward face: the three title lines centred under the camera notch, and L and R beside the eye LEDs (robot left and right), and the LED footprints' own silk outlines.
- Inner face: J12 CORE, J13 VBAT, PROJ, SPK, ANT L, ANT R and MIC beside their parts, and the remaining designators. The brief's + mark for J13 pin 1 did not land (the known silk gap: every spot the stage tries overlaps the XH pads). The footprint's own pin-1 mark is there.
The board passes ERC, the netlist round trip, routing, DRC (errors, unconnected and parity), DFM and the fab gate in KiCad, and the CAD fit above. It has not been fabricated, assembled, powered or fitted.
The interface
- J13's pin order is this board's reading of the contract. The contract says "VBAT + GND, JST-XH 2P" and nothing about pin numbers. Quack Face has pin 1 VBAT, pin 2 GND; the Quack Core's J13 must match. A reversed harness reverses the 220 uF and the buck: there is no reverse-polarity element here.
- J12's harness must join pin n to pin n. Pre-crimped JST-GH cables come both straight and reversed. J12 here is a top-entry (vertical) GH; the brain's J12 may be right-angle. The mating parts are the same either way.
- The microphone needs software: the runtime only reserves it (
duck_config.py), so it needs a full-duplex I2S overlay. The datasheet warns against clocking WS/SCK while VDD is off; both boards share the Quack Core's switched supply.
The eyes
- The two eyes will not look the same with the bulb ring in. The stock right eye keeps the
bulbring between the wall and the board, which is why the brief uses 6 mm spacers there. Its centre hole (radius 3.6 mm) now limits that LED to a 27.5 deg cone. The left eye sees the whole 25 mm opening (60 deg). For symmetric eyes, leave the ring out and fit 8 mm spacers on the right too (STANDOFF_RIGHTincad/parts.py; the face wall's inner face measures x 117.0 on both sides, where the left eye's gap is 7.99 mm), or reprint the ring with a larger hole. - LED polarity. The XL-2835UWC-05 datasheet's recommended land puts its wide pad under pin 1,
the cathode, as KiCad's
LED_PLCC_2835does (pad 1 is the 2.2 mm pad). Its bottom-view drawing shows the wide pad on the other side. Check one part against the board before reflow. - Brightness and heat are the 33 ohm resistors (estimated 48-67 mA, at most 0.15 W in each 0.25 W 1206). 22 ohm roughly doubles the light. Stay under the LED's 150 mA. The head is PLA.
- No LED or amplifier body in the 3D outputs. KiCad 10.0.6 has no 3D model for
LED_PLCC_2835or for the MAX98357A's TQFN-16 footprint, so the renders andfab/quack_face.stephave no body for D1, D2 or U3. The CAD fit adds the LEDs' datasheet outline (3.5 x 2.8 x 0.79 mm).
Power
- Buck loop with the bulk capacitor. TI's stability range is for ceramic output capacitors (L x COUT 93-334 uH x uF for 5 V; 4.7 uH x about 22.8 uF effective = 107). The 220 uF electrolytic on 5 V (360 mOhm at 100 kHz) sits at the left servo header, about 110 mm away. Check the step response on the bench.
- Inductor rating. The SWPA6045S4R7MT's catalogue row lists 3.3 A and 5.5 A without naming which is the saturation and which the heating current. The buck's high-side limit is 3.9-5.4 A (TI).
- Peak load (research spec 4.4, estimated): amplifier 0.8 A, two stalled servos 1.7 A, LEDs. It is at the buck's 3 A. 5 V runs on 0.5 mm tracks and the In2 pour.
- Amplifier heat. The MAX98357A's exposed pad has no via under it: the pipeline's via-in-pad option would also drill the LED, inductor and electrolytic pads. At 3.2 W and 92 % efficiency (catalogue row) that is about 0.28 W in the package.
- The projector current (about 160-200 mA, 0.26-0.4 W in its 1 W resistor) assumes a 3.0-3.4 V emitter. Measure the duck's emitter, or change R15.
- Servo signal. 3.3 V PWM through 220 ohm; MG90S-class servos usually accept it, but the duck's exact servo was not checked.
Mechanics
- The board edge is 1 mm from the head's ribs, floor and ceiling (measured 0.99 mm). Put it in square.
- Screws: M2.5 x 16 pan head plus two M2.5 washers per hole (8 sets). The eye parts' pilots are blind and 3.0 mm deep. The stock screws engage them no deeper, but do not overtighten.
- The camera numbers rest on the spec's estimated outline (y -14.5..9.4, z 140.7..165.7, rear parts to x 109.8). Check the real camera and its FFC against the notch.
- The mic port needs a reprinted
headwith a 2 mm hole at CAD (y -20.0, z 160.0). That is a modification of an Apache-2.0 file: mark it as changed (seecad/NOTICE). - Stackup. The gerber job lists KiCad's default 4-layer stackup (35 um copper, 0.48 mm FR4 dielectrics, 1.6 mm). The fab's real stackup is chosen when ordering.
Sourcing
- All 27 part numbers in
fab/quack_face_bom.csvcome fromparts/lcsc_pinned.json: 12 parts and 5 passives added on 2026-10-02 from liveheypcb parts lookuprows, the rest pinned earlier (2026-06-23 and 2026-09-30). The lowest stock: ICS-43434 (C5656610) 3 807, the 10 ohm 2512 (C47019) 5 030, the 30 ohm boot resistor (C2907026) 11 009. Stock and Basic/Extended class go stale; re-check on jlcpcb.com before ordering.
In this folder (committed):
board.json: the brief.cad/parts.py: the printed parts, the fit and the previews.cad/print/: the printed parts, STEP and STL.cad/img/: the fit previews.cad/NOTICEandcad/LICENSE-Open_Duck_Mini-Apache-2.0.txt: attribution and licence.img/: KiCad's raytraced renders of both faces.
The blocks are blocks/quackface_*.json; the part rows are in parts/lcsc_pinned.json.
Under build/quack_face/ (regenerated, not committed):
report.json: the gate report.quack_face.kicad_schandquack_face.kicad_pcb.fab/:quack_face_gerbers.zip;quack_face_bom.csv(28 rows, 47 parts);quack_face_cpl_jlc.csv(42 SMD parts, D1 and D2 on the bottom);quack_face_pos.csv;quack_face_schematic.pdf;quack_face.step.
cad/:fit.json, the printed parts,quack_face_in_head.stepand the previews.video/quack_face_design_process.mp4.
From the heypcb-kicad repository:
./run.sh --board boards/quack_face # every stage, with the renders and the video
./run.sh --board boards/quack_face --no-media # stop before the 3D frames, renders and video
DUCK_CAD=<duck folder> python cad/source/parts.py # a Python with CadQuery and trimeshparts.py needs CadQuery, trimesh, manifold3d, VTK, matplotlib and scipy. It reads the duck's
STLs from the folder DUCK_CAD names (default ./duck_cad): its cad/print/ is Open_Duck_Mini's
print/ at b23317a, and its fig/head_cavity.npz is the swept head cavity, which the research step
measured from those STLs and which is not shipped here.
Quack Face is a board for the Open Duck Mini. The duck is by Antoine Pirrone (apirrone) and
contributors (https://github.com/apirrone/Open_Duck_Mini), under the Apache License 2.0. The
board's outline, hole pattern, spacer lengths and the fit previews are derived from its CAD.
cad/NOTICE says what is derived and how, and cad/LICENSE-Open_Duck_Mini-Apache-2.0.txt is
the licence. No duck file is redistributed. The pin and port assignments follow the
Open_Duck_Mini_Runtime as interface facts; that repository has no licence file and none of its
code is copied here. The board uses no Disney or BD-X marks. KiCad's symbols and footprints are
CC-BY-SA-4.0 with the KiCad library exception.
Hardware: CERN-OHL-S v2. Files under cad/ that derive from third-party CAD carry their own notice there.





