Limonchito working document, published as-is. Prices and lead times are estimates from September 2026 and change often.
Lemon: programming/test fixture and factory test sequence (rev B)
Scope: the board-level fixture (station 2 in ASSEMBLY.md), used after JLCPCB assembly and hand-soldering of
the coil, LRA, dome carriers and SIM, before the board goes into the core. Also a short final test on
the sealed lemon. The command set matches firmware/src/factory.c. Every command prints one line
<TEST> KEY=VALUE ... RESULT=PASS|FAIL, which the host script parses. Pads and nets: NETLIST.md,
production/ELECTRONICS_REVB.md.
1. What the fixture contacts
All probing is from the bottom side except the RF probe and the LED sensor, which come from the top.
| Fixture contact | Board pad / net | Use |
|---|---|---|
| J1.1 | VBAT+ | voltage sense (after Q1) |
| J1.2 / J1.3 / J1.4 | SWDIO / SWDCLK / nRESET | programming (J-Link) |
| J1.5, TP15 | GND | ground |
| J1.6 / J1.7 | UART_TX (out of U1) / UART_RX | factory shell, 115200 8N1, 3.3 V TTL |
| TP8 | BAT_CONN (J2 pin 1 side, before Q1) | power in, reverse-polarity test |
| TP9 | 3V3 (3.4 V rail) | rail check |
| TP10 / TP11 | QI_5V / RECT | Qi receiver check |
| TP12 | ACC_INT1 | accelerometer interrupt continuity |
| TP13 | MCP_STAT | charger state |
| TP14 | LED_A | LED anode voltage (open/short check) |
| RF pogo (top) | ANT1 feed pad (x = 15…16) + GND pad | fixture antenna for the modem attach test |
| Photodiode (top) | above D1 (x 10.0, y 11.0) | LED on/off |
J1 is a 2×4 grid at 2.54 mm (position 8 empty). TPs are Ø1.0 mm pads. Use P75-series (Ø1.02 mm) pogo pins with
a 4-point crown or spear tip, 1.5 mm pre-travel, about 1.5 N each. Mount them in a 3 mm FR4 or CNC acetal plate drilled from
the placement CSV. Pogo positions come straight from drawings/pcb_placement.csv (J1 centre −18.8, 0 bottom,
TP8…TP15 rows). Mirror X for a bottom-up bed.
2. Fixture hardware (per fixture, about €650–900)
| Item | Part / suggestion | Est. € |
|---|---|---|
| Nest + clamp | 3D-printed nest locating on the PCB outline and the dome carriers; a toggle clamp (e.g. Kipp K0073 class) pressing a printed lid with soft foam pads on the parts. Leave the LRA free (foam cut-out), otherwise the LRA test reads low | 60 |
| Pogo bed | 8 × J1/TP pins + receptacles, 1 RF coax pogo (Ingun HFS or equivalent SMA-terminated spring probe) | 90 |
| Programmer | SEGGER J-Link BASE (commercial licence), or the nRF9151 DK's on-board J-Link used in "external target" mode through its Debug-out header. Not J-Link EDU (licence forbids production) | 0–450 |
| Power + current | Nordic PPK2 in source-meter mode (0.8–5 V, measures nA to 1 A) feeding TP8 | 100 |
| Relays / IO | RP2040 or Arduino-class board + 8-channel relay/MOSFET board: PPK2 ↔ TP8 path, polarity-reversal relay (DPDT), Qi puck USB power, dome actuators | 40 |
| Dome actuators | 2 × 5 V push solenoids (≈10 mm stroke) with Ø6 mm silicone tips over SWA/SWB, or operator presses | 25 |
| LED sensor | BPW34 photodiode + transimpedance op-amp into the RP2040 ADC, in a black tube over D1 | 10 |
| Qi transmitter | the same certified Qi puck used in the cradle, under the nest with a 3.3 mm acetal spacer (matches the lemon's coil-to-pad distance) | 20 |
| Fixture antenna | small wideband LTE stick antenna (698–2690 MHz) on the RF pogo's SMA | 15 |
| USB-UART | FTDI TTL-232R-3V3 or CP2102 (3.3 V) | 15 |
| Reverse-polarity source | 4.20 V from the relay board via 1.00 kΩ sense resistor + DPDT relay (measure the drop with the RP2040 ADC) | 5 |
| Reference cell (Qi step) | a known-good 602025 cell on a JST GH lead, switched onto TP8 during the charge test (the PPK2 cannot sink charge current) | 5 |
| Host | any laptop, Python 3.10+, pyserial, nrfutil (device + 91 commands), label printer |
– |
| Enclosure | the Qi puck and the modem attach work in the open. No RF shield box is needed for a pass/fail attach on a live network | – |
3. Factory test sequence (per board)
The host script (firmware/tools/fixture.py, owned by the firmware stream) runs these steps in order, stops at the first
FAIL, and writes one CSV row per unit: serial, IMEI, ICCID, modem fw, app fw, every measured value, PASS/FAIL,
timestamp, fixture ID, operator. The limits below are starting values; tighten them after 20 golden boards.
| # | Step | How | Pass limits | Time |
|---|---|---|---|---|
| 0 | Load | Insert punched nano-SIM, close hinged lid, place board, clamp | host sees clamp switch | 15 s |
| 1 | Power-off checks (unprogrammed) | (a) Reverse polarity: relay applies −4.20 V to TP8 through 1 k for 1 s. (b) Relay back to normal: PPK2 at 3.90 V on TP8, 50 mA limit | (a) | I |
| 2 | Program | nrfutil device recover (clean state), program the modem firmware if the SiP ships older than required (nrfutil 91 modem-firmware-upgrade, about 60–90 s), then the merged image (NSIB b0 + MCUboot + app with factory shell) and the b0 key-hash provisioning hex into UICR OTP. Verify, reset |
nrfutil exit 0; boot banner on UART within 5 s | 30 s (+90 s modem fw) |
| 3 | Identity | factory info |
RESULT=PASS (IMEI 15 digits), ICCID present (SIM seated), MFW = required version, FW = release, LOCKED=0 |
5 s |
| 4 | Battery sense | factory vbat with PPK2 at 3.90 V |
RESULT=PASS and MV = 3900 ± 60 (divider plus ADC tolerance); CHG=0 |
2 s |
| 5 | Accelerometer | factory accel (board flat in the nest) |
RESULT=PASS, DEV = lis2dw12, G_MG 900–1100, TRIG=irq (host rejects poll) |
2 s |
| 5b | Accel INT1 drive check | Right after step 5 (sensor disarmed, INT1 idles low, push-pull): the RP2040 applies a 100 kΩ pull-up to TP12 and reads it. TP12 must sit on the trace at the U1 end (layout note) | TP12 < 0.3 V with the pull-up, which proves U8 drives the net as far as the pad. A full interrupt proof needs factory accel irq (§5) |
1 s |
| 6 | LED | factory led on, read photodiode and TP14; factory led off, read again |
on: photodiode ≥ threshold (set from golden, about 5× dark), TP14 1.70–2.05 V, PPK2 current rises by 1.2–1.9 mA. off: photodiode < 1.5× dark, TP14 < 0.2 V | 2 s |
| 7 | Domes | factory domes 8000, host fires solenoid A then B (or the operator presses) |
DOMES A=1 B=1 RESULT=PASS; an initial STUCK_* line = FAIL |
3 s |
| 8 | Haptics | factory lra (auto-calibrate, store, play heartbeat, measure on the accelerometer) |
CAL=OK, VIB_MG ≥ CONFIG_LEMON_FACTORY_VIB_MIN_MG (firmware Kconfig), RESULT=PASS; the host also logs COMP/BEMF/GAIN for drift tracking |
3 s |
| 9 | Qi charge | Relay swaps TP8 from PPK2 to the reference cell (kept at 3.6–3.9 V, so the charger is in constant-current mode). Host powers the Qi puck, waits 6 s, then factory vbat and reads TP10/TP11/TP13. Puck off, wait 3 s, factory vbat |
on: CHG=1, TP10 QI_5V 4.90–5.10 V, TP11 RECT 5.0–8.0 V, TP13 STAT low (charging). off: CHG=0. Swap back to PPK2 |
12 s |
| 10 | Modem | factory modem 90 with the RF pogo on the fixture antenna |
ATTACH=OK, RSRP_DBM ≥ −105 (set from golden units at the workshop bench), IP present, T_S ≤ 60 |
20–60 s |
| 11 | Provision | Host creates a serial and a 128-bit random key (Python secrets), stores both in the production DB (encrypted at rest), then factory provision <serial> <key32hex> <server> <port>. It writes the pairing/IMEI/ICCID row for the backend |
KEY=1 RESULT=PASS; the key is never logged in the CSV |
2 s |
| 12 | Key check | factory seal 4026531840 1 0; host decrypts/verifies with its copy of the key (AES-128-CCM, nonce per lemon.h) |
tag verifies, plaintext type = 1 | 1 s |
| 13 | Lock + sleep current | factory lock (unit reboots, shell and UART gone). Leave on PPK2 at 3.90 V. Wait 45 s for attach + HELLO, then average current over 41 s (two eDRX cycles) |
reboot seen (UART goes silent); I_avg ≤ 200 µA in the fixture's coverage (floor is 60–120 µA); no current > 5 mA for more than 3 s after the first 45 s | 90 s |
| 14 | APPROTECT | nrfutil device protection-set SecureRegions then All (or nrfjprog --rbp ALL). Power-cycle. Then nrfutil device protection-get |
reports All; a memory read attempt fails |
10 s |
| 15 | Label + unload | print serial/IMEI label (inside the battery bay / on the core); CSV row closed | – | 10 s |
Cycle time: about 4.5 min with the modem firmware already current, or about 6 min with a modem firmware update. Steps 10 and 13 dominate. For 500 boards in about 5 working days, run 2 fixtures in parallel with one operator (≈20 boards/h). Or run steps 10 + 13 on a 4-slot "soak rack" that has just power, PPK2/INA219 current sense and a fixture antenna, which frees the pogo fixture after step 9.
Retest rule: a FAIL at steps 1, 3, 5, 6, 8 or 9 goes to rework (visual/X-ray of the part named in the
failing test). A FAIL at step 10 is retried once with the antenna probe reseated. Locked units (after step
13) that fail 13/14 can only be reused after nrfutil device recover (full erase) and a restart from step 2.
4. Final test on the sealed lemon (station "final functional test")
The unit is locked, so there is no shell. The test is black-box, through the backend:
| # | Step | Pass |
|---|---|---|
| F1 | Insert a charged cell into the battery bay, close it, place on the cradle | cradle LED shows charging; the backend sees HELLO and later a STATUS with FLAG_CHARGING |
| F2 | The backend admin CLI temporarily pairs the unit with the workshop's golden lemon | pairing row written |
| F3 | Squeeze the unit (both domes) | golden lemon buzzes within one eDRX cycle (≤25 s); unit gets the delivered tick |
| F4 | Squeeze the golden lemon; leave the unit untouched | unit buzzes; after the firmware's notice time the LED breathes through the skin window, visible under workshop light |
| F5 | Pick the unit up | LED stops (accelerometer wake works end to end) |
| F6 | Unpair from golden, assign the real partner serial, pack the pair | backend pairing confirmed |
Log the result against the serial in the same CSV/DB.
5. Notes and requests to the other streams
- Firmware (request):
factory accel irq(arm, wait up to N ms for an INT1 event, printIRQ=1/0) so step 5b can prove the INT1 trace. Andfactory ledcould print a supply-current-independentLED_MVif an ADC pin were spared. It is not in rev B, so TP14 plus the fixture measures instead. - Firmware:
factory vbatlimits (3300–4350 mV) are wide. The host applies the tight ±60 mV limit using the known PPK2 voltage. - Mechanical: the nest needs the dome carriers' positions (±13.4 mm) and clearance for the LRA (on flying leads, about −18.5, 0) and J2 (−1.0, 10.3, top-entry GH, about 4.3 mm). The light-sensor tube sits over D1 (10.0, 11.0) on top.
- EN 18031 evidence: keep the CSV of step 14 results per serial in the technical file.