Files
ds3231/README.md
BlackMark d778383d88 deps: the libavr pin advances, and the tiny85 image grows 24 B
The growth is not this bump's. It entered at libavr 32f8167, where the
software master started clocking the rate it is told: the phase padding has to
cover the shortest bit of a byte, and paying for it costs `shift_out` and
`read_acked` twelve bytes each on the tiny85. Those two symbols are the whole
of the move - an avr-nm diff across the two images names them and nothing else.

So the claim went unheld through two pin bumps. 1246 B was last measured at
libavr e697920; the tiny85 preset has read 1270 since 32f8167, which both
5a042a9 and f2205a7 pinned past without re-recording it. The number is stated
in test/CMakeLists.txt and in README.md, and this is what keeps them one fact -
a check that only ever ran on one of the two chips keeps neither.

The suite also stops depending on its host: libavr's launcher and skip marker
register every check everywhere, and one this machine cannot run reads as
Skipped rather than going missing.

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

4.2 KiB

ds3231

Maxim DS3231 RTC driver on libavr: clock and alarm read/write, alarm interrupts, oscillator-stop detection, die temperature. One source runs on every libavr chip — TWI hardware on the mega328P, open-drain software I2C on the tinies. All bus errors surface as std::expected.

using bus = dev::i2c<{.frequency = 100_kHz}>;
using rtc = ds3231::device<bus>;

auto now = rtc::read_clock();       // result<date_time>
auto armed = rtc::set_alarm1({}, ds3231::alarm1_rate::once_per_second);

example/main.cpp is the full tour. libavr rides as the libavr/ submodule, pinned to the commit this driver builds against; LIBAVR_ROOT (cache or environment) overrides it for development against a working tree:

git submodule update --init libavr
cmake --preset attiny85-generated
cmake --build --preset attiny85-generated

Presets cover attiny85/atmega328p in both libavr modes (generated and reflect); ctest runs the consteval battery over the BCD, the hours register's two formats, the weekday and the alarm rate encodings, and holds the example to its stated size — 1480 B on the mega328P and 1270 B on the tiny85, byte-identical across the two modes. The legacy yazoalfa-based driver lives on the master branch.

The weekday register is derived from the date by default. A program that assigns its own meaning to the device's 1..7 takes ownership of it, and no write here touches it:

using rtc = ds3231::device<bus, ds3231::weekday_source::external>;

An alarm arms its INT/SQW output unless told otherwise, and can be muted and re-wired without re-arming:

auto quiet = rtc::set_alarm2(at, ds3231::alarm2_rate::minutes_match,
                             ds3231::alarm_interrupt::off);
auto again = rtc::listen_alarm2();

Atmel Studio

master carries a Studio solution, so this branch does too: ide/ds3231.atsln builds example/main.cpp for both parts the presets cover, each to a byte-identical .text against the CMake build (check-flags.py below is what holds the flag sets equal, so the sizes are the presets' own), with the flags mirrored by hand. CMake remains the build system.

avrdevice is a project-level property in Studio, so a part means a project, not a configuration: ide/atmega328p/ and ide/attiny85/. They need separate directories rather than separate names — Studio builds into <project dir>/<Configuration> whatever OutputDirectory says, so two projects sharing one directory would also share one example/main.o, and building one after the other without a rebuild would link the other part's object.

Studio finds libavr in the submodule at $(MSBuildProjectDirectory)\..\..\libavr\include — correct by construction, and anchored to the project because a plain relative path is resolved against the generated makefile's directory (the configuration's output directory), not the project's. There is no LIBAVR_ROOT escape hatch: a variable exported in a shell is invisible 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 reaching -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/):

for mcu in atmega328p attiny85; do
    case $mcu in atmega328p) device=ATmega328P;; attiny85) device=ATtiny85;; esac
    python libavr/tools/atmelstudio/componentinfo.py \
        "ide/$mcu/ds3231-$mcu.componentinfo.xml" --device "$device"
    python libavr/tools/atmelstudio/check-flags.py --solution ide/ds3231.atsln \
        --project "ds3231-$mcu" \
        --compile-commands "build/$mcu-generated/compile_commands.json" \
        --log "build/as-$mcu.log"
done

One reference describes one part, hence --project. Release is what the gate compares, the presets defining no debug build; Debug carries the -Og -gdwarf-4 pair libavr's own debug preset uses.

Differences from legacy: weekday-rate alarms now actually set the DY bit (the old setAlarmHelper always cleared it), reads/writes are single coherent bus transactions, and errors are reported instead of ignored.