Files
fantemp/README.md
BlackMark 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

92 lines
4.3 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# fantemp
**v2.1.** Temperature-controlled fan firmware (ATmega328P, 16 MHz), rewritten on
[libavr](https://git.blackmark.me/avr/libavr): thermistor on ADC0 sampled
free-running and averaged over 1000 conversions, fan on OC0B at 50 kHz,
115200 Bd serial console (`help` lists the commands), temperature
histogram persisted to EEPROM, and a direct jump into a boot-section
bootloader at `0x7e00`.
The EEPROM format is the legacy firmware's, unchanged: 100 little-endian
`uint32` buckets at address 0, one per °C. A board carrying years of history
from FanTemp 1.8b keeps every count — verified on hardware, all 67 non-empty
buckets byte-identical across the conversion.
## The console
Commands may be abbreviated to any unambiguous-by-order prefix, as the legacy
firmware allowed: `up` is `uptime`, `st` is `statistics`, `sa` is `save`. The
table order resolves ties, so `s` is `show` — and `reset` is deliberately the one
command that cannot be abbreviated, because `r` should not be able to wipe the
histogram. `save` (new) forces a writeback, which otherwise happens every 30
minutes and on the way into the bootloader.
## Reaching the bootloader
`bootloader` **jumps**; it does not reset. That is not a style choice:
- **pureboot hands straight back on WDRF**, by design — an unattended board that
watchdog-resets in a loop must not sit in a loader. So the legacy
watchdog-reset hand-over arrives and opens no window at all, and on a board
with no reset line that is a board that cannot be reflashed.
- The address is `0x7e00`, the top 512 bytes. The legacy firmware used `0x7800`,
a 2 KB boot section's base, which on a board with a 512-byte boot section reads
erased — so its `bootloader` command silently never arrived anywhere.
- `UCSR0B` is cleared first. While `TXEN0` is set the USART owns PD1, so a loader
that bit-bangs the same pin receives perfectly and answers into nothing.
`ctest` reads all three back out of the emitted image (`test/check_reachability.py`),
because none of them is visible from the source alone and the failure mode is an
unreflashable board. Both the address and the watchdog checks are red-proven
against the legacy behaviour they exist to catch.
The SteinhartHart math of the legacy firmware (runtime doubles + libm
log) is gone: the Beta equation and the cubic fan curve are evaluated
consteval into flash tables — the firmware itself never touches floating
point.
libavr rides as the `libavr/` submodule, pinned to the commit this firmware
builds against; `LIBAVR_ROOT` (cache or environment) overrides it for
development against a working tree:
```sh
git submodule update --init libavr
cmake --preset atmega328p-generated
cmake --build --preset atmega328p-generated
```
## Atmel Studio
`master` carries a Studio solution, so this branch does too: `ide/fantemp.atsln`
builds the same firmware — byte-identical `.text` and `.data` to the CMake
build — from the same sources, with the flags mirrored by hand.
Studio finds libavr in the **submodule**, at
`$(MSBuildProjectDirectory)\..\libavr\include` — correct by construction, and
anchored to the project rather than written relative to the generated makefile,
which runs from the configuration's output directory and would need a different
number of `..`. Unlike the CMake build there is no `LIBAVR_ROOT` to point
elsewhere: an environment variable set in a shell is not visible to Studio
launched from the Start menu — which is what the submodule answers.
It also needs a GCC 16.1 toolchain registered as flavour `avr-g++-16.1.0`;
nothing older can compile `-std=c++26`.
One generated file is required before the project will load, and one command
checks the flags have not drifted (both from libavr's `tools/atmelstudio/`):
```sh
python ../libavr/tools/atmelstudio/componentinfo.py \
ide/fantemp.componentinfo.xml --device ATmega328P
python ../libavr/tools/atmelstudio/check-flags.py --solution ide/fantemp.atsln \
--compile-commands build/atmega328p-generated/compile_commands.json \
--log build/atmelstudio.log
```
CMake remains the build system; the solution is there so the project opens in
Studio as its predecessor did. Only the Release configuration is gated against
CMake — the presets define no debug build — and Debug carries the `-Og
-gdwarf-4` pair libavr's own debug preset uses.
Legacy (yazoalfa submodules) stays on `master`.