1316 -> 1246. libavr took the finding this repo filed when its pin last moved: the shared transaction ladder's cost was never the ladder but the argument list reaching it - AVR passes eight bytes in call-clobbered registers and the rest in call-saved ones, so twelve bytes made every forwarder save four it never reads. A write has no read span, so the write path now passes eight. Worth recording that the candidate this repo suggested was measured and rejected upstream, and the numbers filed from here are why: it wins 66 B on a driver like this one and charges a single-byte reader 20, which is a class rather than one program. The mega328P does not move at 1480 - its TWI backend has its own ladder and was never charged. Net over the whole 22-commit advance the tiny85 is +8 B, and all of it is the bit-period fix that made the wire rate stop following the inliner. Sixteen tests green, cross-mode identity held on both chips. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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 1246 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.