Files
fantemp/README.md
BlackMark 6abf0b5563 console: values in a column, the curve as a graph, and Ctrl+C
Three more things the original did better, and two bugs found doing them.

`curve` walks every whole degree from 10 to 60 with a bar, which is the
original's. The port sampled it every five degrees and printed a bare
percentage — ten numbers for a cubic, showing none of its shape. The bar
is the duty itself, so it needs no scale.

`show` and `statistics` print one value per line behind a dotted label
instead of a run-on line. That reads the same either way for a single
reading and is the whole difference when `monitor` emits one a second
forever. The label renderer is now shared with the help, since it is the
same thing three times; the flash overload takes its width from the
string's type, so the padding needs no hand-counted constant and the
labels stay out of SRAM. `statistics` gains the sample total, and says
"not available" rather than a zero it never measured.

Ctrl+C echoes `^C` and gives a fresh prompt, abandoning whatever was
half-typed, and it is what stops `monitor` now. Stopping on *any* byte
was the port's own invention and it reads fine until a host sends a line
ending: `monitor\r\n` stopped itself on the `\n` it arrived with, one
reading in, which is why monitoring looked broken from a script and fine
by hand.

The other bug is arithmetic. A temperature's fraction came from
`(quarters % 4) * 25`, and C++ gives a negative remainder for a negative
dividend — so -40.25 C printed as "-40.-25". The sign comes off first
now, and the fraction is two digits, so the column lines up: -40.00,
-40.25.

Verified against v1.8b on the board, which was flashed back to compare
against directly: same 51 curve rows over the same span with the same
100-column bars, agreeing within the one percentage point the consteval
table costs against the legacy runtime doubles.

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

103 lines
4.9 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.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 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`.