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>
112 lines
5.4 KiB
C++
112 lines
5.4 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, the alarm registers each rate writes, and the
|
|
// control and status values written back.
|
|
|
|
#include <array>
|
|
#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 registers each rate writes (19-5170 Table 2): bit 7 of the n-th
|
|
// register is AxM(n+1), and DY/DT is bit 6 of the day register. Rows are read
|
|
// off the table, so a slip in the rate values or in their distribution over
|
|
// the registers arms the wrong alarm here first.
|
|
using ds3231::alarm1_rate;
|
|
using ds3231::alarm2_rate;
|
|
using ds3231::detail::alarm_registers;
|
|
constexpr ds3231::date_time at{{2026, 1, 15}, {12, 30, 45}};
|
|
constexpr ds3231::date_time on_weekday{{2026, 1, 3}, {7, 5, 0}};
|
|
|
|
template <typename Rate>
|
|
constexpr auto alarm1(const ds3231::date_time &value, Rate rate)
|
|
{
|
|
return alarm_registers<ds3231::detail::alarm1_registers>(value, static_cast<std::uint8_t>(rate));
|
|
}
|
|
|
|
template <typename Rate>
|
|
constexpr auto alarm2(const ds3231::date_time &value, Rate rate)
|
|
{
|
|
return alarm_registers<ds3231::detail::alarm2_registers>(value, static_cast<std::uint8_t>(rate));
|
|
}
|
|
|
|
static_assert(alarm1(at, alarm1_rate::once_per_second) == std::to_array<std::uint8_t>({0xc5, 0xb0, 0x92, 0x95}));
|
|
static_assert(alarm1(at, alarm1_rate::seconds_match) == std::to_array<std::uint8_t>({0x45, 0xb0, 0x92, 0x95}));
|
|
static_assert(alarm1(at, alarm1_rate::minutes_seconds_match) == std::to_array<std::uint8_t>({0x45, 0x30, 0x92, 0x95}));
|
|
static_assert(alarm1(at, alarm1_rate::time_match) == std::to_array<std::uint8_t>({0x45, 0x30, 0x12, 0x95}));
|
|
static_assert(alarm1(at, alarm1_rate::date_time_match) == std::to_array<std::uint8_t>({0x45, 0x30, 0x12, 0x15}));
|
|
static_assert(alarm1(on_weekday, alarm1_rate::weekday_time_match) ==
|
|
std::to_array<std::uint8_t>({0x00, 0x05, 0x07, 0x43}));
|
|
|
|
static_assert(alarm2(at, alarm2_rate::once_per_minute) == std::to_array<std::uint8_t>({0xb0, 0x92, 0x95}));
|
|
static_assert(alarm2(at, alarm2_rate::minutes_match) == std::to_array<std::uint8_t>({0x30, 0x92, 0x95}));
|
|
static_assert(alarm2(at, alarm2_rate::time_match) == std::to_array<std::uint8_t>({0x30, 0x12, 0x95}));
|
|
static_assert(alarm2(at, alarm2_rate::date_time_match) == std::to_array<std::uint8_t>({0x30, 0x12, 0x15}));
|
|
static_assert(alarm2(on_weekday, alarm2_rate::weekday_time_match) == std::to_array<std::uint8_t>({0x05, 0x07, 0x43}));
|
|
|
|
// A1F and A2F ignore a written 1 and OSF holds until written 0 (19-5170,
|
|
// Status Register): clearing one alarm must write the other as 1, or an alarm
|
|
// firing between the read and the write is lost.
|
|
using ds3231::detail::status_clearing;
|
|
static_assert(status_clearing(0x00, 0x01) == 0x02); // A2F set after the read survives
|
|
static_assert(status_clearing(0x03, 0x02) == 0x01);
|
|
static_assert(status_clearing(0x88, 0x01) == 0x8a); // OSF and EN32kHz kept as read
|
|
static_assert(status_clearing(0x80, 0x80) == 0x03);
|
|
|
|
// BBSQW keeps INT/SQW driven on battery for the alarm interrupt as well as the
|
|
// square wave (19-5170, Control Register and rev. 4), so arming an alarm must
|
|
// leave it as found.
|
|
using ds3231::alarm_interrupt;
|
|
using ds3231::detail::control_with;
|
|
static_assert(control_with(0x40, 0x01, alarm_interrupt::on) == 0x45);
|
|
static_assert(control_with(0x00, 0x02, alarm_interrupt::on) == 0x06);
|
|
static_assert(control_with(0x47, 0x01, alarm_interrupt::off) == 0x46);
|
|
|
|
} // namespace
|