Files
ds3231/test/consteval.cpp
BlackMark 923c118c6d test: the format and ASCII rules stop being a habit, and the stated sizes stop drifting
libavr's guidance binds this repo too, and until now nothing here checked it -
`ctest` runs `libavr_format_test()` over this tree's own sources now (rules 11
and 33), skipping rather than passing where clang-format is absent. It caught
drift on its first run: a file written this week and edited after formatting.

Where the README states a measured size, `libavr_size_claim_test()` holds it to
the image and holds the image to the prose: advancing the library pin moved
three of these across the fleet with nothing saying so, and re-recording one
now requires the sentence that quotes it to move too.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-12 23:02:40 +02:00

83 lines
3.9 KiB
C++

// Compile-only battery, built with the cross compiler so the target's 16-bit
// int is exercised. Everything in this driver that is arithmetic rather than a
// bus transaction lives here: the BCD both ways, the hours register's two
// formats, Sakamoto's weekday, and the alarm rate encodings as the datasheet
// tabulates them.
#include <cstdint>
#include <ds3231/ds3231.hpp>
namespace {
using ds3231::detail::from_bcd;
using ds3231::detail::hours_from_reg;
using ds3231::detail::to_bcd;
// BCD is a round trip over every value the registers hold, and the encoding is
// the datasheet's: tens in the high nibble, units in the low one.
consteval bool bcd_round_trips()
{
for (std::uint8_t value = 0; value < 100; ++value) {
const std::uint8_t packed = to_bcd(value);
if (from_bcd(packed) != value) {
return false;
}
if ((packed >> 4) != value / 10 || (packed & 0x0f) != value % 10) {
return false;
}
}
return true;
}
static_assert(bcd_round_trips());
// The hours register (19-5170 Table 1): bit 6 selects 12-hour mode and bit 5
// is then PM. Both noons and both midnights are the cases a naive decode gets
// wrong - 12 AM is hour 0 and 12 PM is hour 12, neither of which is 12 + 12.
static_assert(hours_from_reg(to_bcd(0)) == 0); // 24-hour midnight
static_assert(hours_from_reg(to_bcd(13)) == 13); // 24-hour afternoon
static_assert(hours_from_reg(to_bcd(23)) == 23);
static_assert(hours_from_reg(0x40 | to_bcd(12)) == 0); // 12 AM
static_assert(hours_from_reg(0x40 | 0x20 | to_bcd(12)) == 12); // 12 PM
static_assert(hours_from_reg(0x40 | to_bcd(1)) == 1); // 1 AM
static_assert(hours_from_reg(0x40 | 0x20 | to_bcd(1)) == 13); // 1 PM
static_assert(hours_from_reg(0x40 | 0x20 | to_bcd(11)) == 23); // 11 PM
// Sakamoto's method, against dates a calendar can be checked against rather
// than against this implementation run twice. Sunday is 1.
using ds3231::detail::weekday;
static_assert(weekday(2000, 1, 1) == 7); // Saturday
static_assert(weekday(2024, 1, 1) == 2); // Monday
static_assert(weekday(2024, 2, 29) == 5); // Thursday, the leap day
static_assert(weekday(2024, 3, 1) == 6); // Friday, the day after it
static_assert(weekday(2100, 3, 1) == 2); // Monday - 2100 is not a leap year
static_assert(weekday(2026, 8, 12) == 4); // Wednesday
// The alarm rate encodings (19-5170 Table 2), transcribed: the low bits are
// A1M4..A1M1 in register order and the high one is DY/DT. A rate is the mask
// pattern its row names, and a transcription slip here arms the wrong alarm.
using ds3231::alarm1_rate;
static_assert(static_cast<std::uint8_t>(alarm1_rate::once_per_second) == 0b01111);
static_assert(static_cast<std::uint8_t>(alarm1_rate::seconds_match) == 0b01110);
static_assert(static_cast<std::uint8_t>(alarm1_rate::minutes_seconds_match) == 0b01100);
static_assert(static_cast<std::uint8_t>(alarm1_rate::time_match) == 0b01000);
static_assert(static_cast<std::uint8_t>(alarm1_rate::date_time_match) == 0b00000);
static_assert(static_cast<std::uint8_t>(alarm1_rate::weekday_time_match) == 0b10000);
using ds3231::alarm2_rate;
static_assert(static_cast<std::uint8_t>(alarm2_rate::once_per_minute) == 0b0111);
static_assert(static_cast<std::uint8_t>(alarm2_rate::minutes_match) == 0b0110);
static_assert(static_cast<std::uint8_t>(alarm2_rate::time_match) == 0b0100);
static_assert(static_cast<std::uint8_t>(alarm2_rate::date_time_match) == 0b0000);
static_assert(static_cast<std::uint8_t>(alarm2_rate::weekday_time_match) == 0b1000);
// Alarm 2 has no seconds register, so its mask bits are one place lower than
// alarm 1's throughout - the property that makes one distribution loop wrong
// for the other.
static_assert(static_cast<std::uint8_t>(alarm2_rate::once_per_minute) ==
static_cast<std::uint8_t>(alarm1_rate::once_per_second) >> 1);
static_assert(static_cast<std::uint8_t>(alarm2_rate::weekday_time_match) ==
static_cast<std::uint8_t>(alarm1_rate::weekday_time_match) >> 1);
} // namespace