Files
ds3231/README.md
T
BlackMarkandClaude Opus 5.5 f9bc2cd8e2 refactor: one alarm encoder, named register bits, and the example 14 B smaller
Both alarms' registers come from one encoder that derives DY/DT and the
mask bits from the rate, and the battery checks the bytes each Table 2
row writes instead of restating the enum values. Register addresses and
bits are named, the year register is the year less 2000 rather than a
division, and an alarm day reads back with one decode for both forms.
Over the whole API the image is 10 B smaller on both parts; a rate only
known at run time costs set_alarm1 6 B more.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 21:55:02 +02:00

98 lines
4.2 KiB
Markdown

# ds3231
Maxim DS3231 RTC driver on [libavr](https://git.blackmark.me/avr/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`.
```cpp
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 a complete program. 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:
```sh
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, the alarm registers each rate writes and
the control and status write-backs, and holds the example to its stated size —
**1468 B** on the mega328P and **1256 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:
```cpp
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:
```cpp
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/`):
```sh
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 set the DY bit (the legacy
driver always cleared it), each register block is read or written in one
coherent bus transaction, and errors are reported instead of ignored.