From e834b3125007086075a05048af22779bc0865ee1 Mon Sep 17 00:00:00 2001 From: burhankhanzada Date: Wed, 30 Sep 2026 02:11:52 +0200 Subject: [PATCH 1/2] Document I2C devices that answer reads context.i2c gains serve() and setRegisters(): a part publishes a register bank and the bus answers the sketch's reads from it, pointer first and auto-incrementing, the way I2C sensors work. The limits section now says what is still missing (SPI, UART, 1-Wire). Co-Authored-By: Claude Opus 5.5 (1M context) --- doc/PDL.md | 40 ++++++++++++++++++++++++++++++++++++---- 1 file changed, 36 insertions(+), 4 deletions(-) diff --git a/doc/PDL.md b/doc/PDL.md index cca0e55..efb9011 100644 --- a/doc/PDL.md +++ b/doc/PDL.md @@ -403,6 +403,8 @@ on I²C means being on `A4` and `A5` specifically. | | | | --- | --- | | `drain(address)` | every transaction sent to `address` since the last call, oldest first; **asking claims the address** | +| `serve(address, size:, pointerBytes:)` | makes `address` answer the sketch's reads from a bank of `size` registers (default 256, pointer 1 byte); claims the address too | +| `setRegisters(address, offset, bytes)` | publishes `bytes` into those registers from `offset`, wrapping at the end | A bus is a different shape from everything else here. A pin has a *level* a part can sample whenever it likes; a bus has *traffic*, and traffic missed is @@ -415,8 +417,36 @@ sketch finds the devices on the canvas and nothing else. Traffic sent *before* anyone claims is still kept, because a sketch initialises its display inside `setup()` — before any logic has run. -Narrow like the rest: a part listens at its own address. It cannot see another -device's traffic and cannot write to the bus. Only the ATmega328P's hardware +**Answering reads.** A read has to be answered the moment the sketch clocks +it, in the middle of the CPU's frame, and a logic only runs between frames. So +a part that talks back does what real I²C devices do: it `serve`s registers +and keeps them current with `setRegisters`, and the bus answers from them. The +sketch writes a register pointer (the first `pointerBytes` bytes of a write, +most significant first), then reads, and the pointer advances after every +byte — which is how `Wire.requestFrom` fetches a six-byte accelerometer sample +in one go. Bytes the sketch writes after the pointer are stored, so a register +reads back what the sketch put there, as setting an RTC's clock expects; and +those writes still arrive through `drain`, so the part can react to them. +`pointerBytes: 0` is for devices with nothing to select (a PCF8574), whose +every read starts at register 0. Serving the same shape again every frame +keeps the registers; a reading refreshed each frame is at most 16 ms old. An +address nothing serves reads `0xFF`, like a pulled-up, undriven bus. + +```dart +void mpu6050(PartLogicContext context) { + const address = 0x68; + context.i2c.serve(address); // MPU6050: 1-byte pointer + context.i2c.setRegisters(address, 0x75, [0x68]); // WHO_AM_I + context.i2c.setRegisters(address, 0x3B, sample()); // accelerometer, 6 bytes + for (final write in context.i2c.drain(address)) { + // [0x6B, 0x00] wakes it up, [0x1C, range] sets the range… + } +} +``` + +Narrow like the rest: a part listens at and serves its own address. It cannot +see another device's traffic, and it never drives the bus — the sketch is +always the one clocking bytes. Only the ATmega328P's hardware TWI is emulated, which is `A4`/`A5`; a sketch bit-banging I²C on other pins is not modelled. @@ -497,8 +527,10 @@ Warnings load the part anyway: no pins at all, an unknown pin role, an unknown Honest about what is not there yet: - **One protocol, and only from Dart.** `context.i2c` gives a `LOGIC` the I²C - traffic addressed to it; SPI, UART, radio and IR have no equivalent, and no - protocol is expressible as `BEHAVIOR` rules. + traffic addressed to it and a register bank to answer reads from; SPI, UART, + 1-Wire, radio and IR have no equivalent, and no protocol is expressible as + `BEHAVIOR` rules. A device whose reply depends on the exact moment it is + read, rather than on registers, can only be approximated. - **A logic cannot drive the board.** It reads its own ports and the pins they reach; it cannot write them. Modelling a part that pulls a line low needs a capability that does not exist. From 59951b81af95f0d3e84118ffc4ea964451b08da1 Mon Sep 17 00:00:00 2001 From: burhankhanzada Date: Wed, 30 Sep 2026 14:51:49 +0200 Subject: [PATCH 2/2] List the new sensor parts and their logics Seven parts shipped in pinbench_parts: TMP36, LM35, a capacitive soil probe, a 9 V battery, and four I2C sensors (BH1750, MPU-6050, DS1307, AHT20) whose LOGIC serves registers the sketch reads. Co-Authored-By: Claude Opus 5.5 (1M context) --- doc/PDL.md | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/doc/PDL.md b/doc/PDL.md index efb9011..6989e82 100644 --- a/doc/PDL.md +++ b/doc/PDL.md @@ -27,6 +27,13 @@ shipped parts and draws them. | `cr2032` — coin cell | `PHYSICS voltageSource` | | `diode_1n4148` | `PHYSICS` only — no rules at all | | `pir_sensor` — PIR motion | `LOGIC` — a one-shot needing a clock | +| `tmp36`, `lm35` — analog temperature | `PHYSICS voltageSource` driven by a property, read by `analogRead` | +| `soil_moisture` — capacitive probe | an inverted, offset output range in two steps of state | +| `battery_9v` | `PHYSICS voltageSource`, like the coin cell | +| `bh1750` — light, I²C | `LOGIC` serving a pointer-less register bank; opcodes tracked in `STATE` | +| `mpu6050` — accelerometer + gyro, I²C | `LOGIC` serving registers the sketch configures; an address set by wiring AD0 | +| `ds1307` — real-time clock, I²C | `LOGIC` keeping time on the simulated clock; an `enum` property | +| `aht20` — temperature + humidity, I²C | `LOGIC` with no `STATE` at all: a reading republished every frame | ## A complete example @@ -489,8 +496,9 @@ emulator is. | `led` | lit from element current, brightness from the driving pin's duty | | `buzzer` | sounding and pitch from the frequency detected on its `plus` port | | `ssd1306` | an OLED display's registers and 1 KB of graphics RAM, decoded from the I²C traffic sent to its `I2C Address` | +| `bh1750`, `mpu6050`, `ds1307`, `aht20` | I²C sensors, in `i2c_sensors.dart`. Each answers only when its `vcc`/`gnd` reach the board's supply and ground and its `sda`/`scl` reach `A4`/`A5`; see `assets/parts//` | -The last four have no `.pdl` file — they are built-in parts with hand-written +`led`, `servo`, `buzzer` and `ssd1306` have no `.pdl` file — they are built-in parts with hand-written painters that declare `logic:` on their `PartModel`. They are what this mechanism replaced a per-part frame updater in the engine with.