pureboot 7: the same features in fewer words on every chip
Four cuts, none touching what the loader can do. The entry stub stops re-doing the reset logic's own SP write where the datasheet guarantees RAMEND (stack::hardware — the classic megas keep theirs). The autobaud unit moves into GPIOR2:GPIOR1 wherever the chip has the pair: one-word accesses, no RAM object, and the host's measured-clock peek follows it by version and geometry. 'J' rides the unified decode, carrying a selector it ignores so its address is the same two reads as every other command — the tool sends the bare form to older residents. run_app stops insisting on a body of its own. The fleet lands at 358–410 B stock and 438–474 B autobaud; the tightest image in the space — the 1284s' autobaud on a USART's own pins with the OSCCAL trim — drops from 510 to 484 of its 512. Every chip's suite is green on the wire that changed, and the README's table is machine-checked against the built images. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -26,7 +26,7 @@ else:
|
||||
import termios
|
||||
|
||||
PROMPT = b"+"
|
||||
VERSION = 6 # this tool's own version — free to drift from a loader's
|
||||
VERSION = 7 # this tool's own version — free to drift from a loader's
|
||||
# The loader versions this tool can drive. A pureboot version implies its wire
|
||||
# protocol, which carries no number of its own, so this window is where that
|
||||
# map lives: the tool keeps a decoder for every generation in it (1–4 speak
|
||||
@@ -34,7 +34,7 @@ VERSION = 6 # this tool's own version — free to drift from a loader's
|
||||
# builds and changes nothing on the wire), and a version it has no decoder
|
||||
# for moves the floor.
|
||||
OLDEST_LOADER = 1
|
||||
NEWEST_LOADER = 6
|
||||
NEWEST_LOADER = 7
|
||||
SLOT = 512 # the loader slot, on every chip
|
||||
RETRIES = 3 # rewrites of a page that reads back wrong, before the run stops
|
||||
|
||||
@@ -45,11 +45,13 @@ RETRIES = 3 # rewrites of a page that reads back wrong, before the run stops
|
||||
UNIFIED_LOADER = 5
|
||||
SP_FLASH, SP_EEPROM, SP_RAM, SP_FUSE, SP_SPM = 0, 1, 2, 3, 4
|
||||
|
||||
# A v5+ autobaud loader keeps its measured bit period at ram_start, encoded
|
||||
# as delay-loop counts: (bit cycles − UNIT_DISCOUNT) / UNIT_LOOP_CYCLES,
|
||||
# An autobaud loader keeps its measured bit period readable, encoded as
|
||||
# delay-loop counts: (bit cycles − UNIT_DISCOUNT) / UNIT_LOOP_CYCLES,
|
||||
# floored — the spin granule and per-bit overhead of libavr's software UART.
|
||||
# --info undoes the encoding to report the true clock, which therefore sits
|
||||
# within one granule below it.
|
||||
# v5/v6 keep it at ram_start; v7 moves it into GPIOR2:GPIOR1 on the chips
|
||||
# that have the pair (their data addresses are in the geometry) and keeps
|
||||
# ram_start only where they do not exist. --info undoes the encoding to
|
||||
# report the true clock, which therefore sits within one granule below it.
|
||||
UNIT_LOOP_CYCLES, UNIT_DISCOUNT = 4, 8
|
||||
|
||||
# A selector's high nibble is the flash bank — the address bits above the 16-bit
|
||||
@@ -77,36 +79,40 @@ CALIBRATE = 0xC0
|
||||
# from its chip database at build time). Die revisions that share a signature
|
||||
# share this row, as they share the silicon.
|
||||
CHIP_GEOMETRY = {
|
||||
# signature : (flash, page, eeprom, patch_vector, ram_start)
|
||||
# signature : (flash, page, eeprom, patch_vector, ram_start, gpior1)
|
||||
# ram_start is where SRAM begins in data space: the classic megas and the
|
||||
# tinies keep it right after the plain I/O registers (0x60), the x8/x4
|
||||
# generations past their extended I/O file (0x100). An autobaud loader's
|
||||
# measured bit period lives at exactly ram_start (its only RAM object;
|
||||
# the loader's own build pins the layout), which is what --info reads.
|
||||
(0x1E, 0x90, 0x07): (1024, 32, 64, True, 0x60), # ATtiny13/13A
|
||||
(0x1E, 0x91, 0x08): (2048, 32, 128, True, 0x60), # ATtiny25
|
||||
(0x1E, 0x92, 0x06): (4096, 64, 256, True, 0x60), # ATtiny45
|
||||
(0x1E, 0x93, 0x0B): (8192, 64, 512, True, 0x60), # ATtiny85
|
||||
(0x1E, 0x92, 0x05): (4096, 64, 256, True, 0x100), # ATmega48/48A
|
||||
(0x1E, 0x92, 0x0A): (4096, 64, 256, True, 0x100), # ATmega48P/48PA
|
||||
(0x1E, 0x93, 0x07): (8192, 64, 512, False, 0x60), # ATmega8/8A
|
||||
(0x1E, 0x93, 0x0A): (8192, 64, 512, False, 0x100), # ATmega88/88A
|
||||
(0x1E, 0x93, 0x0F): (8192, 64, 512, False, 0x100), # ATmega88P/88PA
|
||||
(0x1E, 0x94, 0x03): (16384, 128, 512, False, 0x60), # ATmega16/16A
|
||||
(0x1E, 0x94, 0x06): (16384, 128, 512, False, 0x100), # ATmega168/168A
|
||||
(0x1E, 0x94, 0x0B): (16384, 128, 512, False, 0x100), # ATmega168P/168PA
|
||||
(0x1E, 0x94, 0x0A): (16384, 128, 512, False, 0x100), # ATmega164P/164PA
|
||||
(0x1E, 0x94, 0x0F): (16384, 128, 512, False, 0x100), # ATmega164A
|
||||
(0x1E, 0x95, 0x02): (32768, 128, 1024, False, 0x60), # ATmega32/32A
|
||||
(0x1E, 0x95, 0x0F): (32768, 128, 1024, False, 0x100), # ATmega328P
|
||||
(0x1E, 0x95, 0x14): (32768, 128, 1024, False, 0x100), # ATmega328
|
||||
(0x1E, 0x95, 0x08): (32768, 128, 1024, False, 0x100), # ATmega324P
|
||||
(0x1E, 0x95, 0x11): (32768, 128, 1024, False, 0x100), # ATmega324PA
|
||||
(0x1E, 0x95, 0x15): (32768, 128, 1024, False, 0x100), # ATmega324A
|
||||
(0x1E, 0x96, 0x09): (65536, 256, 2048, False, 0x100), # ATmega644/644A
|
||||
(0x1E, 0x96, 0x0A): (65536, 256, 2048, False, 0x100), # ATmega644P/644PA
|
||||
(0x1E, 0x97, 0x05): (131072, 256, 4096, False, 0x100),# ATmega1284P
|
||||
(0x1E, 0x97, 0x06): (131072, 256, 4096, False, 0x100),# ATmega1284
|
||||
# generations past their extended I/O file (0x100). gpior1 is GPIOR1's
|
||||
# data address — 0x32 on the t25/45/85, 0x4A from the x8 generation on,
|
||||
# None where the chip has no pair (t13, classic megas). A v7 autobaud
|
||||
# loader's measured bit period lives in GPIOR2:GPIOR1 where they exist
|
||||
# and at exactly ram_start elsewhere (its only RAM object; the loader's
|
||||
# own build pins the layout); v5/v6 always used ram_start. --info reads
|
||||
# whichever home the answering version implies.
|
||||
(0x1E, 0x90, 0x07): (1024, 32, 64, True, 0x60, None), # ATtiny13/13A
|
||||
(0x1E, 0x91, 0x08): (2048, 32, 128, True, 0x60, 0x32), # ATtiny25
|
||||
(0x1E, 0x92, 0x06): (4096, 64, 256, True, 0x60, 0x32), # ATtiny45
|
||||
(0x1E, 0x93, 0x0B): (8192, 64, 512, True, 0x60, 0x32), # ATtiny85
|
||||
(0x1E, 0x92, 0x05): (4096, 64, 256, True, 0x100, 0x4A), # ATmega48/48A
|
||||
(0x1E, 0x92, 0x0A): (4096, 64, 256, True, 0x100, 0x4A), # ATmega48P/48PA
|
||||
(0x1E, 0x93, 0x07): (8192, 64, 512, False, 0x60, None), # ATmega8/8A
|
||||
(0x1E, 0x93, 0x0A): (8192, 64, 512, False, 0x100, 0x4A), # ATmega88/88A
|
||||
(0x1E, 0x93, 0x0F): (8192, 64, 512, False, 0x100, 0x4A), # ATmega88P/88PA
|
||||
(0x1E, 0x94, 0x03): (16384, 128, 512, False, 0x60, None), # ATmega16/16A
|
||||
(0x1E, 0x94, 0x06): (16384, 128, 512, False, 0x100, 0x4A), # ATmega168/168A
|
||||
(0x1E, 0x94, 0x0B): (16384, 128, 512, False, 0x100, 0x4A), # ATmega168P/168PA
|
||||
(0x1E, 0x94, 0x0A): (16384, 128, 512, False, 0x100, 0x4A), # ATmega164P/164PA
|
||||
(0x1E, 0x94, 0x0F): (16384, 128, 512, False, 0x100, 0x4A), # ATmega164A
|
||||
(0x1E, 0x95, 0x02): (32768, 128, 1024, False, 0x60, None), # ATmega32/32A
|
||||
(0x1E, 0x95, 0x0F): (32768, 128, 1024, False, 0x100, 0x4A), # ATmega328P
|
||||
(0x1E, 0x95, 0x14): (32768, 128, 1024, False, 0x100, 0x4A), # ATmega328
|
||||
(0x1E, 0x95, 0x08): (32768, 128, 1024, False, 0x100, 0x4A), # ATmega324P
|
||||
(0x1E, 0x95, 0x11): (32768, 128, 1024, False, 0x100, 0x4A), # ATmega324PA
|
||||
(0x1E, 0x95, 0x15): (32768, 128, 1024, False, 0x100, 0x4A), # ATmega324A
|
||||
(0x1E, 0x96, 0x09): (65536, 256, 2048, False, 0x100, 0x4A), # ATmega644/644A
|
||||
(0x1E, 0x96, 0x0A): (65536, 256, 2048, False, 0x100, 0x4A), # ATmega644P/644PA
|
||||
(0x1E, 0x97, 0x05): (131072, 256, 4096, False, 0x100, 0x4A),# ATmega1284P
|
||||
(0x1E, 0x97, 0x06): (131072, 256, 4096, False, 0x100, 0x4A),# ATmega1284
|
||||
}
|
||||
|
||||
VERBOSE = False
|
||||
@@ -450,7 +456,7 @@ class Info:
|
||||
if geometry is None:
|
||||
sig = " ".join(f"{b:02x}" for b in signature)
|
||||
raise Error(f"unknown signature {sig} — this tool has no geometry for it")
|
||||
flash, page, eeprom, patch, _ = geometry
|
||||
flash, page, eeprom, patch, _, _ = geometry
|
||||
base = flash - SLOT
|
||||
word_flash = flash > 0x10000
|
||||
wire_base = base // 2 if word_flash else base
|
||||
@@ -491,6 +497,11 @@ class Info:
|
||||
# permits where from_identity refuses.
|
||||
geometry = CHIP_GEOMETRY.get(tuple(self.signature))
|
||||
self.ram = geometry[4] if geometry else None
|
||||
# Where this loader keeps the measured bit period (None when a fixed
|
||||
# signature row is missing): the GPIOR pair from v7 where the chip
|
||||
# has one, ram_start before that and everywhere without the pair.
|
||||
gpior1 = geometry[5] if geometry else None
|
||||
self.unit_home = gpior1 if self.version >= 7 and gpior1 is not None else self.ram
|
||||
|
||||
def describe(self):
|
||||
sig = " ".join(f"{b:02x}" for b in self.signature)
|
||||
@@ -745,8 +756,13 @@ class Loader:
|
||||
return self._command(b"F", 4, 2.0)
|
||||
|
||||
def jump(self, word_address):
|
||||
"""The device acks, then execution continues at the word address."""
|
||||
self.port.write(bytes((ord("J"), word_address & 0xFF, word_address >> 8)))
|
||||
"""The device acks, then execution continues at the word address.
|
||||
From v7 'J' rides the unified decode, so it carries a selector byte
|
||||
the loader ignores; older loaders take the bare address."""
|
||||
if self.info.version >= 7:
|
||||
self.port.write(bytes((ord("J"), 0, word_address & 0xFF, word_address >> 8)))
|
||||
else:
|
||||
self.port.write(bytes((ord("J"), word_address & 0xFF, word_address >> 8)))
|
||||
self._expect_prompt()
|
||||
|
||||
def enter_copy(self, byte_address, wait, link=None):
|
||||
@@ -1541,14 +1557,14 @@ def main():
|
||||
print("device:")
|
||||
for line in info.lines():
|
||||
print(f" {line}")
|
||||
if args.autobaud and info.ram is not None:
|
||||
# The whole of the loader's RAM is the measured bit period at
|
||||
# ram_start; decoded and times the rate this session drives,
|
||||
if args.autobaud and info.unit_home is not None:
|
||||
# The measured bit period, from wherever this version keeps it
|
||||
# (unit_home); decoded and times the rate this session drives,
|
||||
# that is the true clock — the number to hold an OSCCAL bake
|
||||
# or a fixed-baud build against (README.md: deployment). The
|
||||
# autobaud identity path refuses unknown signatures, so ram is
|
||||
# always known here; the guard states that dependency.
|
||||
unit = int.from_bytes(loader.read_ram(info.ram, 2), "little")
|
||||
# autobaud identity path refuses unknown signatures, so the
|
||||
# home is always known here; the guard states that dependency.
|
||||
unit = int.from_bytes(loader.read_ram(info.unit_home, 2), "little")
|
||||
cycles = unit * UNIT_LOOP_CYCLES + UNIT_DISCOUNT
|
||||
clock = cycles * args.baud
|
||||
offset = f", {(clock / args.clock - 1) * 100:+.1f} % of {args.clock}" if args.clock else ""
|
||||
|
||||
Reference in New Issue
Block a user