pureboot v8: one-wire on every backend

HALF_DUPLEX deploys a shared line per backend. The hardware USART takes
the library's .half_duplex turn-around — RXD and TXD tied off-chip, each
reply byte held to transmit-complete before the line can be released
(m8 404 B, m328P 440, 1284P 460; the window poll runs through the
outlined release-line call at 18 or 22 cycles a poll, measured off the
built loops and held per chip by pureboot.window.halfduplex). The
software and autobaud links fold onto the RX pin — RX == TX spells the
same — and cost nothing: the frame's direction wrap is what the dropped
second-pin init paid, and the worst image in the space is unchanged at
the 1284s' 502 of 512, now with its one-wire twin proven equal across
the exhaustive matrix. The host gains --one-wire, the echo discard a
shared line requires: the adapter's echo is matched byte for byte and a
reply interleaving a blind write — a loader already in session
re-prompts inside the knock — is held for the reader. The device runner
models the shared line by direction (drives only while the firmware's
DDR reads input, decodes only while the firmware owns it, supplies the
host-side echo), extends the USART pin-ownership model to RXEN's hold
on RXD, and starts the pty USART from the datasheet's zeroed UCSR#B:
simavr's TXEN-set reset plus its clear-UDRE-on-TXEN-drop otherwise
wedges the first transmitter after a receiver-only program, which the
half-duplex window gate caught as a banner that never came. v7 is
tagged at its era's last commit; v8 changes nothing on the wire.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-31 02:32:11 +02:00
parent 47419400f6
commit f71d76a815
13 changed files with 507 additions and 50 deletions

View File

@@ -26,8 +26,10 @@ Every axis moves per build — see *Configuration*. The Autobaud column is the
worst configuration the space produces for the chip: the clock-free build —
it alone carries the calibration machinery — with the `OSCCAL` trim baked
and, where the chip has a USART, the link deployed on that USART's own pins,
which the loader then has to release (*Pin ownership*). On default pins
without the trim the same loaders run 410 B smaller.
which the loader then has to release (*Pin ownership*). Folding the same
build onto a single pin (*One-wire*) measures identically on every chip, so
the column covers that twin too. On default pins without the trim the same
loaders run 410 B smaller.
| Chip | Flash | Loader at | Link | Stock | Autobaud |
|---|---|---|---|---|---|
@@ -75,6 +77,7 @@ repo's build and by a downstream project alike:
| `RX <pin>`, `TX <pin>` | software-UART pins | `pb0`, `pb1` |
| `TIMEOUT <s>` | the activation window | 8 |
| `OSCCAL <byte>` | a measured oscillator trim, applied before anything runs | none — no value, no code |
| `HALF_DUPLEX` | one-wire: both directions on one line (*One-wire* below) | off |
The default baud is the fastest of 115200/57600/38400/19200/9600 the clock
reaches within 2.5 % — the same U2X-included divisor search libavr's baud
@@ -126,6 +129,43 @@ another one and the host's retries eventually catch the pulse. That reads as far
more reliable than the same part with an application resident, which gets one
window per reset. Measure with an application in place.
## One-wire
`HALF_DUPLEX` puts both directions on one line — the deployment for a board
with a single spare pin, or a native-UART bootloader's shared-line wiring.
Each backend has its shape:
- **Software and autobaud links** fold onto the RX pin (`RX == TX` spells
the same deployment directly). The pin idles as the receiver's pull-up
input; each transmitted frame takes the pin's direction and hands it back
with the stop bit's level already on the pull-up, so neither flip makes
an edge. This costs nothing: the frame's direction wrap is exactly what
the dropped second-pin init paid, and the tightest image in the space —
the 1284s' autobaud + `OSCCAL` on their USART's RXD — measures the same
502 bytes one-wire as two-wire. On a USART's own pin the release applies
as ever, RXD included: `RXEN` forces that pin's direction (§20.7.3),
which a receive-only link could live with and a driven shared pin cannot.
- **The hardware USART** (`SERIAL hardware`/`auto` + `HALF_DUPLEX`) uses
libavr's `.half_duplex` turn-around — exactly one direction enabled at a
time, each written byte held to transmit-complete before the line can be
released — and needs RXD and TXD tied together off-chip. It costs
+42…50 B over the stock loader (m8 404, m328P 440, 1284P 460 — all far
inside the slot); the activation window is unchanged, its poll merely
runs through the release-line test (18 cycles a poll in bit-addressable
I/O, 22 in extended — measured, and held per chip by
`pureboot.window.halfduplex`).
Host wiring, for an FTDI-style adapter: **adapter TX through ~1 kΩ to the
line, adapter RX and the MCU pin directly on it.** The resistor lets the MCU
win the line while it answers; the price is that the adapter reads back every
byte it transmits. `pureboot.py --one-wire` consumes that echo byte for byte
— a missing echo is reported as the wiring fault it is, and a device reply
that lands between the echoes of the knock (a loader already in session
re-prompts mid-knock) is held for the reader. The knock is the protocol's
one blind multi-byte write, so on real wiring its second byte can be lost to
that collision outright; the tool's knock retries absorb it. Everything else
is ack-paced and cannot collide.
A downstream project brings its usual libavr setup (the `libavr` target, the
chip via the `LIBAVR_MCU` toolchain preset), consumes this directory, and
states its deployment — an ATmega328P on its shipped 1 MHz fuses with the
@@ -302,7 +342,12 @@ moves `J` onto the unified decode — it gains the selector byte the table
shows, which older loaders do not read, so the tool sends each form to the
version that speaks it — and re-homes the autobaud unit into the GPIOR pair
on the chips that have one (Session: what must not be written), which is
where `--info`'s measured clock now reads it on those parts.
where `--info`'s measured clock now reads it on those parts. **8** changes
nothing on the wire either: it marks the builds whose deployment may be
one-wire (*One-wire* above) — the hardware USART's half-duplex turn-around,
or a software link folded onto a single pin. The host-side trace is
`--one-wire`, the echo discard a shared line requires of any tool driving
it.
Every closed generation is tagged in this repo at its era's last commit — the
commit just before the next version bump, so a tag holds everything its
@@ -469,6 +514,11 @@ the loader's bit-period unit, decoded and multiplied by the session rate —
which is the number an `OSCCAL` bake or a fixed-baud build for the part is
held against; `--clock <hz>` states the drift against a nominal.
`--one-wire` marks the link as a shared line (*One-wire* above): the tool
reads back and verifies its own echoed bytes, whatever the backend.
It combines with everything, `--scan` included — undiscarded echoes would
answer every rate a scan probes.
`--scan` is the diagnosis once a fixed-baud loader has gone silent: it walks
±10 % around `--baud` in 2 % steps, nearest first, one probe per activation
window — reset the target as each probe announces itself (a board with DTR