LapCounter / current build 0.2 / October 17, 2026
$211.96 planning budget
Parts + mounts + shipping + estimated tax + 20% reserve. Assembly tools and hired labor are extra.
Target-compiled firmware. Physical assembly and field verification still required.
Optical emitter across one lane from receiver gate, with Bluetooth absolute count updates to the wearable

LapCounter 0.2: build the optical prototype

This is the current build direction. It replaces the Bluetooth-proximity counting design and contractor pricing in the earlier 0.1 report. The user authorized the engineering choice to prioritize cost and reliable lap detection over matching the illustration exactly.

The kit has a modulated infrared emitter, a receiver gate that stores the count, and a wearable that displays and vibrates for accepted laps. Three identical standard XIAO nRF52840 boards keep purchasing and flashing simple. No custom PCB, paid firmware contractor, mobile app or cloud service is required for this version.

Firmware and build files are provided. A physical prototype has not been assembled or field-tested. Compiling firmware is not evidence of optical range, outdoor accuracy, battery life or charging safety. Someone still needs to assemble the parts and run the physical tests below. There is no labor charge included in the parts budget; hired assembly would be extra.

Budget

The itemized parts budget is in budget-parts.csv and budget.json. Core electronics use supplier listings checked on October 17, 2026. Cables, simple cases, stand hardware and small parts are labeled allowances. This is a planning budget, not an actual cart or stock reservation.

PartQtyUnitTotalBasis
Standard XIAO nRF52840 SKU 1020104483$9.90$29.70Published
Adafruit 3502 SHARP reflective display1$24.95$24.95Published
Adafruit 5940 TSSP77038 receiver1$3.95$3.95Published
Adafruit 5639 IR emitter breakout1$3.95$3.95Published
Adafruit 5648 MOSFET motor driver1$3.95$3.95Published
Adafruit 1201 disc motor1$1.95$1.95Published
Adafruit 1578 protected 500mAh LiPo3$7.95$23.85Published
Normally open buttons3$0.75$2.25Allowance
STEMMA 3-pin cables3$1.25$3.75Allowance
Battery JST adapters3$1.25$3.75Allowance
USB data cable1$4.00$4.00Allowance
100uF 6.3V+ capacitors2$0.50$1.00Allowance
Wire and insulated perfboard1$5.00$5.00Allowance
Generic cases3$3.00$9.00Allowance
Hook-and-loop wrist strap1$5.00$5.00Allowance
Optical clip mounts or stands2$6.00$12.00Allowance
Black shrouds and tape1$2.00$2.00Allowance
Fasteners1$2.00$2.00Allowance
Budget lineAmount
Parts, cases and mounts$142.05
Shipping allowance$20.00
9% tax allowance$14.58
Subtotal$176.63
20% contingency$35.33
Total planning budget$211.96

An existing laptop, USB power, soldering iron, solder, multimeter and hand tools are assumed. Borrowing those avoids buying the $100 tool allowance in the original estimate. If you need tools, budget separately. No parts have been bought, no outside engineer has been hired, and no vendor has been contacted.

For the first bench experiment, three boards plus the two optical breakouts cost $37.60 before freight and tax. Reused USB supplies/cables can power that experiment. Add the display, haptic parts, batteries, mounts and cases only after confirming the beam behaves correctly at the intended width in daylight. The three-board design is a practical low-cost candidate, not a proven lowest price across every available product.

Why this should be more dependable than proximity counting

The receiver decides when the beam was interrupted and restored. Bluetooth carries a repeatedly broadcast absolute total, not a one-shot "increment" message. The wearable ignores repeated totals, can catch up several missed updates, and shows completion when it receives the final stored count. The gate continues counting when the wrist is out of range.

This is a controlled single-runner system. It counts anyone or anything that qualifies as a beam interruption. It cannot identify a runner, determine crossing direction, prevent a shortcut or verify a full circuit. Another person walking through the gate can add a lap. It must not be used for official test results or a shared public lane without a different design and validation.

Place the optical units outside the lane, at the same height around 0.8-1.0m, facing each other across one lane. Start testing at 1-2m separation. Nothing is stretched across the runner's path. Use narrow matte-black receiver/emitter shrouds and stable mounts. Do not claim an all-lane range from the sensor's data sheet.

The modulated receiver is less vulnerable to simple ambient-light changes than a bare unmodulated phototransistor. Direct sun, reflections, wind movement, clothing and interfering IR sources still require measurement. A beam that is too weak or goes around a runner by reflection is a failed setup, even if the electronics power on.

Buy the exact board variant

Use standard Seeed XIAO nRF52840 SKU 102010448 for all three boards, with the factory nRF52840 UF2 bootloader. The compiled target is Seeeduino:nrf52:xiaonRF52840 using Seeed nRF52 core 1.1.13. These files are not for XIAO ESP32, nRF54, Plus, RP2040 or a generic Arduino Nano.

The standard board costs less than Sense because this design does not use an IMU or microphone. The supplied binaries target the standard board only. A Sense substitution needs its own compile and pin review; do not flash a similarly named board blindly.

Wiring: emitter

Board pointConnect toNote
XIAO D2Adafruit 5639 emitter SIGHardware-generated 38kHz carrier bursts
Protected battery positiveXIAO BAT+ and emitter VINBranch the protected cell after its connector; never use a bare cell
Battery negativeXIAO BAT- and emitter GNDCommon ground on this unit
100uF capacitorEmitter VIN to GNDPositive lead to VIN; rated at least 6.3V

For the initial bench test, use USB power and connect emitter VIN to the XIAO's 5V/VBUS pin instead of a battery branch. Do not join the 5V pin and battery positive. For a battery test use the protected 1S cell branch described above. Do not power the high-current emitter through the XIAO 3V3 regulator.

The emitter breakout includes its transistor and LED resistors. No laser is used. Firmware emits about 1ms of 38kHz carrier followed by at least 4ms off. Average LED duty is at most about 10%, accounting for the carrier's 50% duty. Measure the waveform and LED temperature on real hardware before extended use. The SDK's tone() function rejects 38kHz; the firmware intentionally uses hardware PWM instead.

Wiring: receiver gate

Board pointConnect toNote
XIAO 3V3Adafruit 5940 receiver VINReceiver signal then stays in 3.3V logic domain
XIAO GNDReceiver GND and both button returnsNo cross-lane ground wire
XIAO D2Receiver SIGInterrupt input; receiver is active low during each received burst
XIAO D0Normally-open button A to GNDTarget/start control
XIAO D1Normally-open button B to GNDPause/review/new-run control
Protected 500mAh cellBAT+ and BAT- through matching connectorVerify actual connector polarity before connecting

The onboard green LED indicates at least one second of stable beam alignment. Red blinking indicates review or a fault. LED polarity follows the selected board schematic and remains to be checked on the received hardware. Read the wearable or USB STATUS output if LED behavior is unclear.

Wiring: wearable

Board pointConnect toNote
XIAO 3V3Adafruit 3502 LCD VIN and Adafruit 5648 motor-driver VINRegulated motor supply; no raw LiPo on this rail
XIAO GNDLCD GND, motor-driver GND and button returnCommon ground on wrist unit
XIAO D8LCD CLKSerial clock
XIAO D10LCD DISerial data
XIAO D3LCD CSChip select
XIAO D2Motor-driver SIGOn/off haptic control
Motor-driver output +Motor red leadUse the switched output terminals, not MCU GPIO
Motor-driver output -Motor blue/negative leadBreakout includes flyback diode
100uF capacitorMotor-driver VIN to GNDObserve capacitor polarity
XIAO D0Normally-open button to GNDShort press mutes; hold two seconds and release resyncs
Protected 500mAh cellXIAO BAT+ / BAT-No cell compression or exposed battery joints

Use the reflective 144x168-native-pixel SHARP module in landscape rotation, producing a 168x144 UI. It costs more than a small generic OLED but avoids gambling on an unspecified display module and prioritizes daylight reading. There is no backlight for night use. The driver refreshes every 500ms to maintain VCOM inversion even when the count is unchanged.

The simple MOSFET haptic driver replaces the more expensive DRV2605L in the original design. It supports clear on/off patterns without I2C setup. The selected motor is powered at 3.3V. Verify rail droop and no resets when the motor starts. There is no battery percentage estimate in this build; the separate fuel gauge was removed to save cost. Measure runtime and check battery voltage before longer sessions.

Assemble without custom manufacturing

Use insulated perfboard/wire carriers and cheap generic plastic cases. Secure the boards so they cannot touch the battery. Keep the radio board near a plastic edge, with its antenna clear of the motor, battery and metal mounts. Put the receiver behind a narrow black tube, not behind clear plastic of unknown infrared transmission.

Strap the wrist case securely but comfortably with hook-and-loop material. It will be bulkier than the illustration. The supplied old 0.1 CAD is not the correct verified mounting layout for this revised kit. Do not order a custom print or PCB from that drawing. A custom sealed watch case is unnecessary for the first dry-weather proof.

Charge off the wrist, on a suitable surface, using the board's documented approximately 50mA charge setting. The supplied firmware selects the low-current setting and keeps the battery-read control low. Actual charge current, temperature and connector polarity must be checked. A 500mAh cell may take around 12-14 hours at 50mA including taper; this is an estimate. The board charger does not make the complete enclosure a certified wearable battery product.

Flash the supplied files

1. Disconnect the battery and motor while first flashing the wrist unit. Label the three boards EMITTER, GATE and WRIST.

2. Plug one board into the computer with a USB data cable. Double-press its reset button to enter the factory UF2 bootloader. A removable drive should appear.

3. Check that the board is the standard XIAO nRF52840. Copy only the matching file: binaries/emitter/emitter.uf2, binaries/gate/gate.uf2 or binaries/wearable/wearable.uf2.

4. Let the board finish and reboot. Repeat for the other two boards. Do not unplug during a copy.

5. The supplied application UF2 files assume the compatible factory bootloader and S140 7.3.0 SoftDevice. They do not install a bootloader or repair an incompatible SoftDevice. If the drive is absent or the application fails to start, use the pinned Arduino board package and its documented serial upload route. Do not flash another board's bootloader.

These are real target-compiled binaries, not files generated from the browser simulation. They have not been flashed to hardware in this workspace because no target board is connected. USB boot behavior and first boot remain physical checks.

Start an eight-lap run

1. Turn on the emitter and receiver. Align them while watching the gate's green indicator and the wrist's beam status. Keep the lane clear. A stable beam must be visible for a full second before start or resume.

2. Turn on the wrist unit. It finds kit ID 1 automatically. The screen shows the gate's target and count. There is no phone pairing step.

3. Default target is eight. While READY, a short press of gate A increases the target and wraps after 99. To use a lower number quickly, change it in source and rebuild, or cycle the button. Keep the wrist nearby to see the selected target.

4. Stand just before the start line. Hold gate A for two seconds and release. It starts when the beam is aligned. The first immediate crossing is ignored by the minimum-lap rule, leaving the initial count at zero.

5. Complete the full circuit. A qualifying crossing increments the gate's stored count, then the wrist receives the total and vibrates once. The target lap gives three longer pulses. No further automatic counts are accepted after completion.

6. A short gate-B press pauses. Another short B press resumes only with stable alignment and resets the minimum interval. Controls are deliberately on the gate in this cheap version; the wrist is a display/haptic receiver, not a long-range remote control.

7. Hold gate B for two seconds and release while PAUSED or COMPLETE to create a new run. It cannot reset a running or review-required session. Logs remain stored until explicitly erased.

Default minimum lap is 45 seconds. For a shorter loop or unusually fast runner, change it deliberately before a run with MIN <seconds> over USB, in READY state. Valid range is 10-600 seconds. A cooldown cannot prove a full circuit; the runner must follow the intended route.

Detection and recovery behavior

The emitter sends short modulated bursts. The receiver adapter requires three correctly spaced pulse intervals around the expected 5ms period and a fresh pulse within 10ms. A stuck-high or stuck-low receiver cannot establish beam readiness. Spurious pulses and timing variation still need real measurements; the 4-9ms interval tolerance is an initial setting.

After the inferred beam goes away, an obstruction must last at least 15ms. The beam must then return and remain clear for 80ms before a lap is accepted. The minimum-lap interval must also have elapsed. Glitches shorter than 15ms are discarded. Beam loss longer than 1.5 seconds enters REVIEW rather than inventing a lap. Initial settings target approximately 2-8m/s crossing speeds; test the actual runner and gate height.

Receiver reboot recovers the latest CRC-valid record into REVIEW. A short B press acknowledges it into PAUSED; a second short B press resumes after stable alignment. No automatic events occur during review or pause. If storage is corrupt or full, counting stops with a fault. Export the valid prefix, inspect the raw capture and deliberately erase only after preserving evidence.

The wearable receives absolute count snapshots. Duplicates do not vibrate again. If multiple counts arrive after a link gap, it shows the current total and a catch-up message. An initial sync to a run already in progress is silent. If you power the wrist off through the final lap, it can display the final total when powered back on, but it cannot retrospectively deliver a live cue. Hold its button for two seconds and release to resync if the gate has been replaced or its storage reinitialized.

USB log and review tools

Connect the receiver gate, not the emitter, at 115200 baud. Line endings must include newline. Commands are STATUS, EXPORT, MIN 10..600, SET 0..target and ERASE YES. MIN is allowed only in READY. SET is a manual count correction allowed only while PAUSED and recorded separately. EXPORT and ERASE are refused while running. ERASE permanently removes stored records; it is never called automatically by the exporter.

The gate appends a fixed 32-byte CRC-protected record before publishing each accepted count. It rereads the record and verifies the CRC before allowing the new count to reach the wrist. Records include session, event type, count, target, elapsed milliseconds and configuration. Power interruption around writes still needs hardware testing. Partial records or corrupt tails stop automatic counting rather than being silently skipped.

The journal is bounded at 400 records. Export and archive it between test sessions. No automatic log rotation destroys old data. The desktop tool below verifies every record CRC, sequence ordering and frame footer, and refuses to overwrite an existing output.

python3 -m pip install pyserial
python3 tools/export_gate.py --port /dev/cu.usbmodemYOUR_GATE --out run-001.csv

Run the commands from the firmware directory and use the actual receiver serial port. First pause the gate. The exporter preserves a raw text capture alongside the verified CSV and leaves the gate log intact. Without pyserial, a serial terminal can issue EXPORT and save the raw frame; the Python verifier is still needed before treating that export as checked.

Rebuild from source

Use the provided UF2 files for the simplest first build. To change target defaults, kit ID or timing parameters, install Arduino CLI, then run the following from the firmware directory. Dependencies are pinned, and installation stays local to this project. The script uses a temporary source path without spaces to work around an SDK build-recipe issue.

python3 tools/build.py --install-deps

Once dependencies are installed, python3 tools/build.py rebuilds all three images. Standard XIAO is the only tested compile target. Library versions are Seeed nRF52 1.1.13, SHARP Memory Display 1.1.4, Adafruit GFX 1.12.6 and BusIO 1.17.4. The build manifest records the actual compiler and binary hashes. No external CI account or paid compiler is needed.

Host checks can be run with a C++17 compiler and Python. They exercise the actual shared counting/protocol code, rather than a separately rewritten JavaScript model.

clang++ -std=c++17 -Wall -Wextra -Werror -fsanitize=address,undefined -I libraries/LapCounter/src test/gate_test.cpp -o /tmp/lapcounter-tests
/tmp/lapcounter-tests
python3 test/export_test.py

Physical acceptance checklist

1. Check board and connector identities, no supply shorts and correct cell polarity. First bring up on USB with batteries disconnected. Confirm regulated 3.3V before attaching the LCD or receiver.

2. Measure the emitter at D2: approximately 38kHz, 50% carrier duty, about 1ms on and at least 4ms off. Confirm the receiver's corresponding pulses and stable alignment. Inspect emitter temperature during a 30-minute run.

3. Verify the wrist LCD orientation, motor direction/connection, supply droop and absence of resets. Read the count outdoors at arm's length. Verify that the final haptic differs from an ordinary lap.

4. At the real venue, test shadow, bright daylight, different sun angles and intended lane width. Block the direct path completely; reflected IR must not keep the receiver seeing a clear beam. Test wind/vibration and record any loss of alignment.

5. Perform ten observed test crossings with a deliberate short-loop profile and enough interval between crossings. Log misses, duplicates and false counts individually. Reject any configuration that needs the runner to hold their wrist a special way.

6. Restore the proper full-track minimum interval. Run three 20-lap sessions in the designated lane, with different runners or clothing and at least two daylight conditions. Require zero misses, duplicates or unexplained events in this 60-lap demonstration before describing this configuration as a working prototype. This small demonstration does not prove 99.9% reliability.

7. Walk through immediately after start, wait in/near the beam, pause/resume, interrupt the emitter, reboot the gate and switch off/recover the wrist. No invented lap may occur. Prolonged beam loss must require review. On receiver reset, the last saved count must be recovered.

8. Interrupt power around journal writes on a protected bench setup and inspect raw logs. Export CRC checks must identify corruption. Verify recovery into review and deliberate erase after archiving. This has not been tested on a physical device here.

9. Measure operating and charge currents, full-charge behavior, run duration and cell temperature. Do not infer battery endurance from chip current specifications. Use dry weather until case protection has been designed and tested.

If the optical link does not pass at the actual site, the correct next step is an industrial outdoor photoelectric gate or another validated sensor, not an accuracy claim based on passing software tests. That may increase parts cost. Do not spend on miniaturization until this first physical result is known.

Sources

• Seeed XIAO standard and Sense listing. Standard SKU 102010448 is listed at $9.90.

• Seeed pin map, charging and board-package setup.

• Adafruit 3502 reflective memory LCD, $24.95.

• Adafruit 5940 modulated IR receiver, $3.95.

• Adafruit 5639 IR emitter, $3.95. Pulsed operation is required at its higher drive currents.

• Adafruit 5648 MOSFET motor driver, $3.95, with flyback protection.

• Adafruit 1201 motor, $1.95.

• Adafruit 1578 500mAh battery, $7.95. Confirm the protected variant and connector polarity.

• Vishay TSSP770 data sheet and light-barrier application note. Modulated reception supports a light-barrier design; practical sunlight performance depends on alignment and optical geometry.

Supplier pages establish parts and specifications, not this assembled kit's measured reliability. All timing thresholds, mounting dimensions, current expectations and field-test criteria above are engineering choices to verify.