BlackMark 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
2020-03-29 01:29:59 +01:00
2020-03-29 01:29:59 +01:00

fantemp

v2.2. Temperature-controlled fan firmware (ATmega328P, 16 MHz), rewritten on 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:

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/):

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.

Description
Firmware for temperature controller board
Readme 317 KiB
Languages
C++ 100%