# pureboot A serial bootloader on [libavr](https://git.blackmark.me/avr/libavr), pure by constraint: one C++ source, no inline assembly, no global register variables (attributes allowed), built for every chip libavr targets, **512 bytes on each** — 490 B on the ATtiny13A, 510 B on the ATtiny85, 484 B on the ATmega328P. The device speaks primitives; every composite — verify, erase, reset-vector surgery, timeout configuration — lives in the host tool (`pureboot.py`). ## Link | Chip | Serial | Baud | Clock assumed | |---|---|---|---| | ATmega328P | USART0, RXD/TXD = PD0/PD1 | 115200 8N1 | 16 MHz crystal | | ATtiny85 | software UART, RX = PB0, TX = PB1 | 57600 8N1 | 8 MHz internal RC | | ATtiny13A | software UART, RX = PB0, TX = PB1 | 57600 8N1 | 9.6 MHz internal RC | The tiny RX pin has its pull-up enabled; TX idles high. All multi-byte quantities on the wire are little-endian. ## Activation Reset enters the loader (BOOTRST on the mega, the patched reset vector on the tinies) — except a watchdog reset, which hands straight to the application (the application owns its watchdog; it must clear WDRF itself, which also releases the WDRF-forced WDE). The host then has one activation window per awaited byte to knock: `p` then `b`. Each awaited byte gets a fresh window; any other byte is discarded and awaited again (line noise cannot lock the loader, only delay it). A window expiring with an idle line boots the application. The window length in seconds is the **last EEPROM cell** (address `eeprom_size - 1`); `0x00` and the erased `0xff` both mean the 4 s default, so a full EEPROM erase resets the timeout rather than maxing it. The host changes it with the ordinary EEPROM-write command. ## Session After the knock the loader stays in its command loop until `G` or a reset. Before reading each command it waits for any pending EEPROM write to finish and sends the prompt `+` (0x2b) — the prompt is therefore also the completion ack of the previous command. A session is: await `+`, send a command, read its reply, repeat. | Cmd | Arguments | Reply | |---|---|---| | `b` | — | the 12-byte info block | | `R` | addr16, n8 | n flash bytes (n = 0 means 256) | | `W` | addr16, then one page of data | — (completion = next prompt) | | `r` | addr16, n8 | n EEPROM bytes (n = 0 means 256) | | `w` | addr16, n8, then n data bytes | `+` per byte, sent once its write has begun | | `F` | — | 4 bytes: low fuse, lock, extended fuse, high fuse | | `G` | — | `+`, then the application runs | | other | — | ignored; the loop re-prompts (send a junk byte, await `+`, to resync) | `W` streams exactly one SPM page (size from the info block) into the buffer, then erases and programs; the address must be page-aligned. Pages inside the loader's own 512 bytes are drained but never programmed — a broken host cannot brick the chip. `w` is host-paced: send the next byte only after the previous byte's `+`. `F` returns the bytes in the hardware's Z order; on a chip without an extended fuse byte (the ATtiny13A) that slot carries no meaning. Fuse *writing* does not exist: SPM reaches flash (and, on the mega, lock bits) only — fuse bytes are external-programming territory by hardware. The info block (`b`): | Offset | Content | |---|---| | 0–2 | `'P'`, `'B'`, protocol version (1) | | 3–5 | device signature | | 6 | SPM page size in bytes | | 7–8 | loader base — application flash ends here | | 9–10 | EEPROM size | | 11 | bit 0 set: host must patch the reset vector (no hardware boot section) | Composites are the host's job: verify = read back and compare, erase = write `0xff` (per page for flash, per byte for EEPROM), timeout = EEPROM write to the last cell. ## Deployment **ATmega328P**: program the loader at 0x7e00 with an external programmer; fuses BOOTSZ = 11 (256 words) and BOOTRST programmed. Applications are flashed unmodified — reset re-vectors to the loader in hardware, word 0 stays the application's own reset vector, and `G` jumps to 0. **Tinies** (no boot section): program the loader at `flash - 512`; erased flash below it walks up into the loader, so a virgin chip activates. When flashing an application the host performs reset-vector surgery: the application's own `rjmp` target is re-encoded as a trampoline `rjmp` in the word just below the loader (`base - 2`, where `G` jumps), and word 0 is rewritten to `rjmp` to the loader base. Every other vector stays the application's. Page 0 is written last, so an interrupted flash leaves word 0 erased and the chip still falls through to the loader on the next reset.