BlackMark 78c9071f0c deps: the pins advance to the identity check that reads its own sibling
libavr 93d8b0e, pureboot 26a8621. The check is handed the sibling build tree now and asks its build system whether the image is current, so a comparison against a tree the run did not build skips rather than passing. Gated at the pin. Its own gate builds both modes, so the comparison still happens.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-03 08:23:59 +02: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

The firmware is 8004 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/):

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 367 KiB
Languages
C++ 100%