fix: an alarm clear keeps the other alarm's flag, and arming keeps BBSQW
A1F and A2F ignore a written 1, so the status write-back now writes the flag not being cleared as 1: an alarm firing between the read and the write was cleared with the other. BBSQW gates the alarm interrupt on battery as well as the square wave (19-5170 rev. 4), so arming an alarm no longer clears it. Both register computations are consteval-tested. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
50c424d3d2
commit
4df536d0a5
2 files changed
+52
-22
No files matched your search
+34
-22
@@ -101,6 +101,24 @@ constexpr std::uint8_t weekday(std::uint16_t year, std::uint8_t month, std::uint
|
||||
return static_cast<std::uint8_t>((year + year / 4 - year / 100 + year / 400 + offsets[month - 1] + day) % 7 + 1);
|
||||
}
|
||||
|
||||
// Control and status bits, datasheet 19-5170 Figure 1.
|
||||
inline constexpr std::uint8_t a1ie = 0x01, a2ie = 0x02, intcn = 0x04;
|
||||
inline constexpr std::uint8_t a1f = 0x01, a2f = 0x02, osf = 0x80;
|
||||
|
||||
constexpr std::uint8_t control_with(std::uint8_t control, std::uint8_t enable_bit, alarm_interrupt interrupt)
|
||||
{
|
||||
if (interrupt == alarm_interrupt::on) {
|
||||
return static_cast<std::uint8_t>(control | intcn | enable_bit);
|
||||
}
|
||||
return static_cast<std::uint8_t>(control & ~enable_bit);
|
||||
}
|
||||
|
||||
// A1F and A2F ignore a written 1, so writing the other one as 1 cannot lose it.
|
||||
constexpr std::uint8_t status_clearing(std::uint8_t status, std::uint8_t flag)
|
||||
{
|
||||
return static_cast<std::uint8_t>((status | a1f | a2f) & ~flag);
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
|
||||
// The one bus address a DS3231 answers on - hardwired in the die, no
|
||||
@@ -125,9 +143,6 @@ class device {
|
||||
static constexpr std::uint8_t reg_status = 0x0f;
|
||||
static constexpr std::uint8_t reg_temp = 0x11;
|
||||
|
||||
static constexpr std::uint8_t a1ie = 0x01, a2ie = 0x02, intcn = 0x04, bbsqw = 0x40;
|
||||
static constexpr std::uint8_t a1f = 0x01, a2f = 0x02, osf = 0x80;
|
||||
|
||||
public:
|
||||
[[nodiscard]] static result<date_time> read_clock()
|
||||
{
|
||||
@@ -215,7 +230,7 @@ class device {
|
||||
if (auto s = dev::write_regs(reg_alarm1, raw); !s) {
|
||||
return s;
|
||||
}
|
||||
return set_alarm_interrupt(a1ie, interrupt);
|
||||
return set_alarm_interrupt(detail::a1ie, interrupt);
|
||||
}
|
||||
|
||||
[[nodiscard]] static status set_alarm2(const date_time &at, alarm2_rate rate,
|
||||
@@ -229,7 +244,7 @@ class device {
|
||||
if (auto s = dev::write_regs(reg_alarm2, raw); !s) {
|
||||
return s;
|
||||
}
|
||||
return set_alarm_interrupt(a2ie, interrupt);
|
||||
return set_alarm_interrupt(detail::a2ie, interrupt);
|
||||
}
|
||||
|
||||
[[nodiscard]] static result<date_time> read_alarm1()
|
||||
@@ -261,34 +276,34 @@ class device {
|
||||
|
||||
[[nodiscard]] static result<bool> alarm1_fired()
|
||||
{
|
||||
return flag_set(a1f);
|
||||
return flag_set(detail::a1f);
|
||||
}
|
||||
|
||||
[[nodiscard]] static result<bool> alarm2_fired()
|
||||
{
|
||||
return flag_set(a2f);
|
||||
return flag_set(detail::a2f);
|
||||
}
|
||||
|
||||
[[nodiscard]] static status clear_alarm1()
|
||||
{
|
||||
return clear_flag(a1f);
|
||||
return clear_flag(detail::a1f);
|
||||
}
|
||||
|
||||
[[nodiscard]] static status clear_alarm2()
|
||||
{
|
||||
return clear_flag(a2f);
|
||||
return clear_flag(detail::a2f);
|
||||
}
|
||||
|
||||
// True after the oscillator was ever stopped (first power-up, battery
|
||||
// ran out): the clock needs to be set.
|
||||
[[nodiscard]] static result<bool> oscillator_stopped()
|
||||
{
|
||||
return flag_set(osf);
|
||||
return flag_set(detail::osf);
|
||||
}
|
||||
|
||||
[[nodiscard]] static status clear_oscillator_stopped()
|
||||
{
|
||||
return clear_flag(osf);
|
||||
return clear_flag(detail::osf);
|
||||
}
|
||||
|
||||
// Die temperature in quarter C (updated every 64 s by the device).
|
||||
@@ -307,19 +322,19 @@ class device {
|
||||
// other's route intact.
|
||||
[[nodiscard]] static status listen_alarm1()
|
||||
{
|
||||
return set_alarm_interrupt(a1ie, alarm_interrupt::on);
|
||||
return set_alarm_interrupt(detail::a1ie, alarm_interrupt::on);
|
||||
}
|
||||
[[nodiscard]] static status mute_alarm1()
|
||||
{
|
||||
return set_alarm_interrupt(a1ie, alarm_interrupt::off);
|
||||
return set_alarm_interrupt(detail::a1ie, alarm_interrupt::off);
|
||||
}
|
||||
[[nodiscard]] static status listen_alarm2()
|
||||
{
|
||||
return set_alarm_interrupt(a2ie, alarm_interrupt::on);
|
||||
return set_alarm_interrupt(detail::a2ie, alarm_interrupt::on);
|
||||
}
|
||||
[[nodiscard]] static status mute_alarm2()
|
||||
{
|
||||
return set_alarm_interrupt(a2ie, alarm_interrupt::off);
|
||||
return set_alarm_interrupt(detail::a2ie, alarm_interrupt::off);
|
||||
}
|
||||
|
||||
private:
|
||||
@@ -339,18 +354,15 @@ class device {
|
||||
return detail::to_bcd(day) & std::uint8_t{0x3f};
|
||||
}
|
||||
|
||||
// INTCN routes the alarms to INT/SQW and BBSQW is the square wave that
|
||||
// would otherwise drive it, so arming an alarm's interrupt takes the pin
|
||||
// and muting it gives up only that alarm's claim on it.
|
||||
// INTCN routes the alarms to INT/SQW, so arming an alarm's interrupt takes
|
||||
// the pin and muting it gives up only that alarm's claim on it.
|
||||
static status set_alarm_interrupt(std::uint8_t enable_bit, alarm_interrupt interrupt)
|
||||
{
|
||||
auto control = dev::read_reg(reg_control);
|
||||
if (!control) {
|
||||
return std::unexpected(control.error());
|
||||
}
|
||||
const auto wired = static_cast<std::uint8_t>((*control & ~bbsqw) | intcn | enable_bit);
|
||||
const auto muted = static_cast<std::uint8_t>(*control & ~enable_bit);
|
||||
return dev::write_reg(reg_control, interrupt == alarm_interrupt::on ? wired : muted);
|
||||
return dev::write_reg(reg_control, detail::control_with(*control, enable_bit, interrupt));
|
||||
}
|
||||
|
||||
static result<bool> flag_set(std::uint8_t bit)
|
||||
@@ -368,7 +380,7 @@ class device {
|
||||
if (!flags) {
|
||||
return std::unexpected(flags.error());
|
||||
}
|
||||
return dev::write_reg(reg_status, static_cast<std::uint8_t>(*flags & ~bit));
|
||||
return dev::write_reg(reg_status, detail::status_clearing(*flags, bit));
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -79,4 +79,22 @@ static_assert(static_cast<std::uint8_t>(alarm2_rate::once_per_minute) ==
|
||||
static_assert(static_cast<std::uint8_t>(alarm2_rate::weekday_time_match) ==
|
||||
static_cast<std::uint8_t>(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
|
||||
Reference in new issue
Block a user