Commit Graph

10 Commits

Author SHA1 Message Date
491ff76447 build: pureboot rides as a pinned submodule, and the libavr pin advances
pureboot moved to its own repo, so the loader this board hands over to stopped
being reachable through a sibling checkout of the bootloader repo. It is a
submodule here now: this board has no reset line and no programming header, so
the resident loader is the only way in, and the commit naming the firmware
should name the loader it has to reach.

The reachability check stops carrying its own copy of where that loader is.
0x7e00 was a literal beside pureboot's own geometry, which the submodule
exports as PUREBOOT_BASE_HEX - one source for the fact now, and the check reads
whichever slot the pinned loader actually has. The boot-section bound stays a
literal, being a fuse fact rather than a loader one.

Built and tested at both pins on the bench: 5/5, cross-mode identity included,
and the image deployed to the board verifies byte-for-byte through its loader.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-23 22:38:19 +02:00
0f5e40510c perf: the console's constants move to flash, -378 B and -214 B of RAM
8382 -> 8004 B of flash, 725 -> 511 B of RAM on a part that has 2048.

The command names were a std::to_array of string_view, and on a Harvard
machine that is the worst of both: the characters land in .data and so does
the table's own pointer-and-length pair for each of them, so the firmware
carried 216 B of RAM for thirteen words that never change - and paid for them
in flash too, since .data is copied out of an initialiser image at startup.

They are one NUL-separated blob in flash now, walked with lpm. Separators
rather than an offset table, because an offset table is the RAM this exists to
give back; the names sit in it in match order, so the walk that finds a name
is the same walk that compares it and measures it. Flash falls further than
RAM does: the initialiser image and the two tables were 216 B of it, and the
blob is 87.

The header said the names "cannot" be in flash because they are matched at run
time. Being matched at run time is not a reason to be in RAM on a machine with
two address spaces - only being *written* is, and nothing writes these.

Two smaller things came with it. `reset`'s exact-match rule was a bool on
every entry to protect one; it is an index found by searching the list, so
reordering the commands cannot move the protection onto a different one. And
`version` was the last string_view left, holding its own characters and a
pointer to them.

The matching is now pinned rather than assumed: lookup() is constexpr and the
battery asserts the load-bearing order the README documents - `s` is show and
not statistics, `st` is statistics, no abbreviation of `reset` resolves, and
`helpful` is not `help`. Red-checked by claiming `s` is statistics.

Both modes byte-identical, ten tests green.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-23 05:29:01 +02:00
bf18f99635 build: the libavr pin advances, and one adiw appears in the ring
43fc479 -> aec9955, 8380 -> 8382 B. Two bytes, and they are all of e226340:
the uart ring now declares its indices before its storage.

The library measured that reorder at 0 B and it is +2 here, so the difference
is worth stating. The reorder exists for the 0..63 displacement window, and at
32 entries the indices were never outside it - ldd Z+32 and ld Z are both two
bytes, so moving them to the front buys nothing. What it does do is take
storage off offset zero, and storage is the member reached by a computed
index: pop() loaded storage[tail] as X = Z + tail with the base free, and now
adds the base with an adiw.

So it is free where the indices were out of the window and a loss where they
were in it, and which of those a consumer gets depends on its ring size and on
whether pop() is out of line - fantemp's is. Filed upstream with the
disassembly; nothing to work around here, and 8382 of 32768 is not a budget
question.

Everything else crossed is inert for this firmware: no i2c, no eeprom writer,
no spare vectors, and percent_t already reached through ::of().

Five tests green in both modes, cross-mode identity held.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-23 02:20:57 +02:00
5244953654 test: the README's cross-mode claim is now checked, not just stated
The pin advances to the commit that gives consumers the gate's own image
comparison, and the claim written into this README a few commits ago - 8380 B,
byte-identical between the two modes - is registered against it. It compares
clean over every loadable section.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-16 20:50:00 +02:00
cde7e60170 test: the image's size stops being a number nobody holds
Every other port in the fleet states its image size and is held to it; this
one stated none, so a library advance could move the firmware and no check
would say a word. 8380 B is what the presets build today, byte-identical
between the two modes, and the README now says so.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-16 19:21:29 +02:00
ba37ecea04 test: the format and ASCII rules stop being a habit, and the stated sizes stop drifting
libavr's guidance binds this repo too, and until now nothing here checked it -
`ctest` runs `libavr_format_test()` over this tree's own sources now (rules 11
and 33), skipping rather than passing where clang-format is absent. It caught
drift on its first run: a file written this week and edited after formatting.

Where the README states a measured size, `libavr_size_claim_test()` holds it to
the image and holds the image to the prose: advancing the library pin moved
three of these across the fleet with nothing saying so, and re-recording one
now requires the sentence that quotes it to move too.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-12 23:02:40 +02:00
3e95160b00 fix: the quarter-degree rounding was wrong below zero, in three copies
`(quarters + 2) / 4` is round-half-up only for positive values: C truncates a
negative quotient toward zero, so -3.00 C read as -2, -1.00 C as 0 and -0.75 C
as 0 - nine of fifteen negative quarter-values off by a whole degree, always
toward zero. `(quarters + 2) >> 2` is an arithmetic shift, which floors, and
is right across the whole range. It is also smaller: the shift skips the bias
correction signed division needs.

The formula was written three times - thermistor::celsius(), which nothing
called, and inline at both live call sites - so the defect had three homes and
so would its fix. One `thermistor::whole_degrees()` now, called from both, and
the dead entry point is gone (rules 6, 27).

test/consteval.cpp is new and is what should have caught it: whole_degrees
across zero including both ties and both table limits, the curve's start and
saturation points and its monotonicity, and the thermistor table anchored
where the Beta equation fixes it - the count at which the divider reads the
thermistor's nominal resistance must read the nominal temperature - plus both
clamps and the fall across every step. Red-green: four assertions fire against
the old division.

Beside it: the cubic's three coefficients are named rather than inlined and
restated in prose (rule 5), the consteval table builders take explicit 32-bit
types (rule 25), the curve's clamp reads the table's own size (rule 36), and
the serial override says what expects the rate rather than what the board has
always done (rules 12, 13).

And bootloader::handle_reset()'s watchdog diversion is gone. Its own comment
called it a leftover "kept only because it is free and cannot hurt", and it
did not: pureboot peeks WDRF without clearing it and hands back on purpose, so
a watchdog reset arrives here with the flag still set and the diversion jumped
into the loader with MCUSR already cleared - opening the activation window
that policy exists to close. Clearing MCUSR is the whole job and stays.

8206 -> 8168 bytes, byte-identical between generated and reflect.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-12 16:23:05 +02:00
837b832bc7 build: the libavr pin advances past the audit sweep, and the numbers get names
The pin crosses libavr's phase-6 close and the guideline sweep behind it;
the image is byte-identical in both modes at 8206 bytes.

The port's own sweep, against the same rules. Every mutable `static inline`
takes `m_` - uptime's counter, the sampler's window, the controller's five,
the statistics histogram and the terminal's line state (rule 46; a private
`static constexpr` is a constant rather than state and keeps its bare name).
The command table is `std::to_array` and the serial config breaks one member
per line (rules 36, 40). And three numbers get the name they already had
somewhere: duty goes through `percent_t::of()` rather than a hand-built
basis-point count, the ADC's top count is `thermistor::adc_full_scale`
instead of 1023 in four places, and the two `0xffffffff` are `open_circuit`
- which was already declared five lines away - and `never_written`, which
replaces a comment explaining the literal (rules 5, 6, 41).

Measured, not assumed: rendering `adc_full_scale` into the `show` line
instead of leaving it in the message string cost 6 bytes, so the display
text stays text.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-12 14:40:33 +02:00
b1caf49522 build: the libavr pin advances past phase 6
The renames land (interrupt_guard, consume_reset_cause, set_duty), the
sampler binds its input in the new converter shape (the input pack plus
in<>::start() as free-running's one kick), and the console states
.allow_baud_error = true for the 115200-at-16-MHz this board has always
spoken - the receiver-tolerance table libavr now enforces is stricter
than the rate's own +2.1 %. The loader probe reads through
avr::flash_load instead of raw pgmspace, the terminal's line buffer is
std::array with backspace and delete named, the tree is reformatted
under InsertBraces, and the sources are ASCII.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-09 11:45:07 +02:00
c01e583597 console: the terminal is the original's again, and the way out is a jump
Six things the port had dropped or got wrong, and the one that matters is
the last.

The help is a table again — name, dots, description, one command per line
— instead of a single line of bare words that said nothing about what any
of them did. The layout is the original's, colons at column 12, which
`bootloader` at ten characters is what sets.

Abbreviations are back, and they were a feature: any prefix resolves to
the first command it matches, so `up` is uptime and `st` is statistics.
Order does the disambiguating, which is why the table is in the
original's dispatch order and new entries go on the end — appending
cannot take an abbreviation that already meant something. `reset` keeps
the original's exception and must be typed in full: `r` should not be
able to clear the histogram.

The histogram gets its resolution back. The bar was capped at 40 columns
where the original scaled to 100, and on a distribution this narrow that
threw away most of the difference between neighbouring buckets. Same
normalisation as before: divide by whatever makes the tallest bucket fit.
The sample count moves to a fixed ten-column field before the bar, so the
numbers read as a table instead of trailing off the ragged right end.

`version` exists again, and this is 2.1 — 2.0 being the port as it stood.

Added while here: `save`, to force the writeback that otherwise waits up to
thirty minutes; the resistance in `show`, which is the one number that
says *why* a temperature is wrong and which the original printed; a
report when a line overflows the buffer rather than silently acting on
its head; "no data yet" where there is none; and a blank line after each
command's output.

And the way out. `bootloader` now jumps rather than resetting, because
pureboot hands straight back on WDRF by design — so the legacy
watchdog-reset hand-over reaches it and opens no window, which on a board
with no reset line is a board that cannot be reflashed. Two more bugs in
the same three lines: the target was 0x7800, a 2 KB boot section's base,
which on this board's 512-byte section reads erased and made the check
false and the command a no-op; and UCSR0B was left set, which mutes a
loader that bit-bangs the pin the USART still owns. All three are now
read back out of the emitted image by ctest, the address and the watchdog
red-proven against exactly the legacy behaviour they exist to catch.

libavr advances to 71cfb2f. Verified on the board: FanTemp v2.1, min 0 C
/ max 74 C matching what 1.8b reported off the same EEPROM, the fan curve
within one percentage point of the legacy double-precision one at every
5 C from 15 to 60, and `bootloader` -> pureboot 7 -> back to a running
application.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-31 01:58:09 +02:00