Skip to content

feat(hw): Arduino parity — bulk I2C, prefs, servo, and hardware on the display host - #18

Merged
shbmx merged 2 commits into
mainfrom
feat/arduino-parity
Aug 15, 2026
Merged

feat(hw): Arduino parity — bulk I2C, prefs, servo, and hardware on the display host#18
shbmx merged 2 commits into
mainfrom
feat/arduino-parity

Conversation

@shbmx

@shbmx shbmx commented Aug 15, 2026

Copy link
Copy Markdown
Owner

Summary

  • The display host gets the full hardware surface (it had print/delay/millis only — a program with a screen could not read a sensor). One shared natives file for both ESP hosts; on the display board I2C adopts the panel's own bus, since touch/IMU/RTC share its pins.
  • Bulk I2C (i2cReadBytes/i2cWriteBytes + I2c.readBytes/writeBytes) — Wire.requestFrom parity; empty-list/false on no answer.
  • Prefs (prefsGetInt/SetInt + Prefs) — NVS-backed persistence; EEPROM parity.
  • Servo (servoAttach/servoMicroseconds + Servo) — 50Hz on its own LEDC timer; degrees mapping in readable Dart.
  • Push-aware delay(): sensor-style programs (no frame pump) were unpushable — the board received frames and never acted; delay() now polls the push channel in 50ms slices.
  • VM gains a public list API (moth_new_list/append/is_list/length/at) so natives can produce and consume List<int>.

Test plan

  • hw_parity golden: both layers (natives + classes) through the sim's fake register file — round-trip, no-answer, prefs fallback/set/get/invalid-key, servo clamps.
  • On hardware via mothc run (dogfooding): scanned all 8 onboard I2C devices, bulk-read registers through the adopted panel bus; prefs counter survived pushes and a reboot (3→4); restart of a delay()-looping program in 107ms.
  • natives drift-test + verbatim signature pins prove the C and Dart workstreams agree; make test fully green; both ESP builds + fps build compile.

shbmx added 2 commits August 15, 2026 18:23
gets hardware at all

The display firmware registered print, delay and millis — nothing
else. A program with a screen could not read a sensor, blink a pin, or
open a UART; the full hardware surface existed only on the headless
host. The natives now live in one shared file (vm/esp/main/
hw_natives.c) both ESP hosts register, so the same program behaves the
same whichever firmware is flashed. On the display board the sensors
share the panel's I2C pins, so the natives ADOPT the bus the panel
driver already owns (panel_i2c_bus + moth_hw_adopt_i2c_bus) rather
than fighting it with a second master — verified live: a Dart program
scanned all 8 onboard devices and bulk-read their registers through
the adopted bus.

New natives, all three hosts (the VM gained a public list API so a
native can build and read List<int>):
- i2cReadBytes/i2cWriteBytes — Wire.requestFrom's shape; empty list /
  false when the device does not answer; 64-byte clamp
- prefsGetInt/prefsSetInt — NVS on boards, in-memory in the simulator;
  15-char key limit is NVS's own. Verified on hardware: a push counter
  survived program swaps and a reboot
- servoAttach/servoMicroseconds — 50Hz on LEDC timer 2 (analogWrite's
  5kHz and tone's frequency never fight it), 14-bit duty, 500..2500us
  clamp, two channels

Exposing sensor programs on the display host surfaced a real hole:
push frames are serviced on the frame hook, and a program looping on
delay() with no UI never pumps one — the board received restart
frames and never acted. delay() now chunks long waits and polls the
push channel, so `mothc run` restarts a sensor program in ~107ms
(measured live).

Dart layer (built in parallel by a second workstream against the same
contract; the natives drift-test is what proves the two agree):
I2c.readBytes/writeBytes, I2cDevice equivalents, Prefs, Servo with
the degrees mapping in readable Dart. arduino-parity.md's "Not yet"
table shrinks to attachInterrupt (the event-loop milestone, stated)
and SPI (the display owns the bus on the verified board; an
unverifiable claim is worse than a missing feature). The hw_parity
golden exercises BOTH layers — raw natives and the classes over them.
i2cBegin, list-API hazard stated correctly

micros() and delayMicroseconds() were unregistered on the display
firmware — the host this branch exists to give sensors to, and the
primitives every bit-banged driver is built from. A program that
compiled, analyzed clean, and matched the parity table was answered
MPRJ at push time. The timing natives (millis/micros/
delayMicroseconds) now live in hw_natives.c so the two firmwares can
never diverge on them again; delay() stays host policy, because the
display host's delay also services the push channel. Verified live:
delayMicroseconds(1000) measured as 1025us by micros() on the display
firmware. The hw_parity golden now calls all three, so their
registration is executable on every host rather than resting on a
table row.

From the same review: noTone on a pin that never played no longer
consumes a PWM channel (lookup-only helper); i2cBegin says "bus
already started; new pins ignored" instead of silently keeping the
old pins; and the list API's GC warning now states the actual hazard
— creating a SECOND object while holding an unrooted list — and
records that append never collecting is part of the API's promise,
not an allocator accident.
@shbmx
shbmx merged commit 1ec60f9 into main Aug 15, 2026
2 checks passed
@shbmx
shbmx deleted the feat/arduino-parity branch August 15, 2026 13:05
shbmx added a commit that referenced this pull request Aug 15, 2026
feat(hw): Arduino parity — bulk I2C, prefs, servo, and hardware on the display host
shbmx added a commit that referenced this pull request Aug 15, 2026
feat(hw): Arduino parity — bulk I2C, prefs, servo, and hardware on the display host
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant