// 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 #include 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(alarm1_rate::once_per_second) == 0b01111); static_assert(static_cast(alarm1_rate::seconds_match) == 0b01110); static_assert(static_cast(alarm1_rate::minutes_seconds_match) == 0b01100); static_assert(static_cast(alarm1_rate::time_match) == 0b01000); static_assert(static_cast(alarm1_rate::date_time_match) == 0b00000); static_assert(static_cast(alarm1_rate::weekday_time_match) == 0b10000); using ds3231::alarm2_rate; static_assert(static_cast(alarm2_rate::once_per_minute) == 0b0111); static_assert(static_cast(alarm2_rate::minutes_match) == 0b0110); static_assert(static_cast(alarm2_rate::time_match) == 0b0100); static_assert(static_cast(alarm2_rate::date_time_match) == 0b0000); static_assert(static_cast(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(alarm2_rate::once_per_minute) == static_cast(alarm1_rate::once_per_second) >> 1); static_assert(static_cast(alarm2_rate::weekday_time_match) == static_cast(alarm1_rate::weekday_time_match) >> 1); // 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