The help text was thirteen help_row(N) calls and the dispatch thirteen
case N labels, both counting positions in a list defined elsewhere. A
command is now one row of name and summary; the summaries become a second
NUL-separated flash blob printed in a loop, and every case label is
command_index("name"), which does not compile for a name the list lacks.
The help column derives from the longest name, the dotted padding is one
helper for labels and help rows alike, and the ADC scale, the monitor
interval and the uptime units are named. Output is unchanged; the image
goes from 8004 B to 7838 B, both modes byte-identical. The 32-bit
narrowing in dev/tasks.md is re-measured against the new image.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
106 lines
5.1 KiB
Markdown
106 lines
5.1 KiB
Markdown
# fantemp
|
||
|
||
**v2.2.** 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.
|
||
|
||
`show`, `statistics` and the histogram print one value per line behind a dotted
|
||
label, the way the original did — a run-on line is fine for one reading and
|
||
unreadable when `monitor` emits one a second. `curve` walks every whole degree
|
||
from 10 to 60 with a bar, because the curve is a cubic and five-degree samples
|
||
without a graph show none of its shape.
|
||
|
||
**Ctrl+C** abandons a half-typed line and gives a fresh prompt, echoing `^C`, and
|
||
it is what stops `monitor`. Stopping on *any* byte, which is what the port did
|
||
first, reads well right up until a host sends a line ending: `monitor\r\n` then
|
||
stopped itself on the `\n` it arrived with, one reading in.
|
||
|
||
## 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 Steinhart–Hart 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
|
||
```
|
||
|
||
The firmware is **7838 B** of flash, byte-identical between the generated and
|
||
reflect modes, and `ctest` holds it to that number.
|
||
|
||
## 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`.
|