diff --git a/.clang-format b/.clang-format index 63ebf38..87fe5e6 100644 --- a/.clang-format +++ b/.clang-format @@ -1,13 +1,15 @@ --- BasedOnStyle: LLVM +Standard: Latest ColumnLimit: 120 IndentWidth: 4 TabWidth: 4 UseTab: ForIndentation AlignEscapedNewlines: DontAlign AllowShortFunctionsOnASingleLine: Empty -AlwaysBreakTemplateDeclarations: true +BreakTemplateDeclarations: Yes BreakBeforeBraces: Custom BraceWrapping: AfterFunction: true +InsertBraces: true ... diff --git a/CMakeLists.txt b/CMakeLists.txt index f2b4ffa..697b98d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -12,7 +12,7 @@ if(NOT LIBAVR_ROOT) set(LIBAVR_ROOT ${CMAKE_CURRENT_SOURCE_DIR}/libavr) endif() if(NOT EXISTS ${LIBAVR_ROOT}/CMakeLists.txt) - message(FATAL_ERROR "libavr not found at ${LIBAVR_ROOT} — run: git submodule update --init libavr") + message(FATAL_ERROR "libavr not found at ${LIBAVR_ROOT} - run: git submodule update --init libavr") endif() add_subdirectory(${LIBAVR_ROOT} libavr-build) diff --git a/README.md b/README.md index 7286b6c..98c322b 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ using bus = dev::i2c<{.frequency = 100_kHz}>; using rtc = ds3231::device; auto now = rtc::read_clock(); // result -(void)rtc::set_alarm1({}, ds3231::alarm1_rate::once_per_second); +auto armed = rtc::set_alarm1({}, ds3231::alarm1_rate::once_per_second); ``` `example/main.cpp` is the full tour. libavr rides as the `libavr/` submodule, @@ -31,9 +31,9 @@ reflect). The legacy yazoalfa-based driver lives on the `master` branch. `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 — 1242 B on the ATmega328P, -1212 B on the ATtiny85 — with the flags mirrored by hand. CMake remains the -build system. +**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 diff --git a/example/main.cpp b/example/main.cpp index 57bd9b0..ae078e0 100644 --- a/example/main.cpp +++ b/example/main.cpp @@ -5,7 +5,7 @@ using namespace avr::literals; // One source for the tiny85 (open-drain software I2C on the USI pins) and // the mega328P (TWI hardware). On first power-up the clock is seeded and -// alarm 1 armed; then the LED mirrors the seconds parity — a stuck LED +// alarm 1 armed; then the LED mirrors the seconds parity - a stuck LED // means bus errors. using dev = avr::device<{.clock = 8_MHz}>; using bus = dev::i2c<{.frequency = 100_kHz}>; @@ -17,16 +17,21 @@ int main() avr::init(); if (auto stopped = rtc::oscillator_stopped(); stopped.value_or(false)) { - (void)rtc::write_clock({{2026, 1, 1}, {12, 0, 0}}); - (void)rtc::set_alarm1({}, ds3231::alarm1_rate::once_per_second); - (void)rtc::clear_oscillator_stopped(); + // A failed seed leaves the LED dark before the loop ever runs - the + // same signal a stuck bus gives it below. + const bool seeded = rtc::write_clock({{2026, 1, 1}, {12, 0, 0}}) && + rtc::set_alarm1({}, ds3231::alarm1_rate::once_per_second) && + rtc::clear_oscillator_stopped(); + led::write(seeded); } while (true) { - if (auto now = rtc::read_clock(); now.has_value()) + if (auto now = rtc::read_clock(); now.has_value()) { led::write(now->second % 2 == 0); - if (auto fired = rtc::alarm1_fired(); fired.value_or(false)) - (void)rtc::clear_alarm1(); + } + if (auto fired = rtc::alarm1_fired(); fired.value_or(false) && !rtc::clear_alarm1()) { + led::clear(); // a clear that failed holds the LED dark, like any bus error + } dev::delay<100_ms>(); } } diff --git a/include/ds3231/ds3231.hpp b/include/ds3231/ds3231.hpp index 8e94e5d..bd9d8b3 100644 --- a/include/ds3231/ds3231.hpp +++ b/include/ds3231/ds3231.hpp @@ -69,8 +69,9 @@ constexpr std::uint8_t hours_from_reg(std::uint8_t reg) if (reg & 0x40) { auto hour = from_bcd(reg & 0x1f); bool pm = reg & 0x20; - if (hour == 12) + if (hour == 12) { return pm ? 12 : 0; + } return static_cast(pm ? hour + 12 : hour); } return from_bcd(reg & 0x3f); @@ -81,20 +82,26 @@ constexpr std::uint8_t hours_from_reg(std::uint8_t reg) constexpr std::uint8_t weekday(std::uint16_t year, std::uint8_t month, std::uint8_t day) { constexpr std::uint8_t offsets[]{0, 3, 2, 5, 0, 3, 5, 1, 4, 6, 2, 4}; - if (month < 3) + if (month < 3) { --year; + } return static_cast((year + year / 4 - year / 100 + year / 400 + offsets[month - 1] + day) % 7 + 1); } } // namespace detail -// Maxim DS3231 RTC on any libavr i2c master (register map: datasheet -// 19-5170 Table 1). Every call is one bus transaction; errors surface as -// std::expected. Weekday registers maintain themselves from the date when -// SetWeekday is on. +// The one bus address a DS3231 answers on - hardwired in the die, no +// address pins (datasheet 19-5170, "I2C interface"). +inline constexpr std::uint8_t bus_address = 0x68; + +// Maxim DS3231 RTC on any libavr i2c bus (register map: datasheet +// 19-5170 Table 1): the master role is taken from the bus's own +// resolution, so a declared `dev::i2c<...>` is handed over whole. Every +// call is one bus transaction; errors surface as std::expected. Weekday +// registers maintain themselves from the date when SetWeekday is on. template class device { - using dev = avr::i2c::device; + using dev = avr::i2c::device; static constexpr std::uint8_t reg_clock = 0x00; static constexpr std::uint8_t reg_alarm1 = 0x07; @@ -110,8 +117,9 @@ class device { [[nodiscard]] static result read_clock() { std::array raw; - if (auto s = dev::read_regs(reg_clock, raw); !s) + if (auto s = dev::read_regs(reg_clock, raw); !s) { return std::unexpected(s.error()); + } date_time now; now.second = detail::from_bcd(raw[0] & 0x7f); now.minute = detail::from_bcd(raw[1] & 0x7f); @@ -125,16 +133,18 @@ class device { [[nodiscard]] static result read_date() { auto now = read_clock(); - if (!now) + if (!now) { return std::unexpected(now.error()); + } return static_cast(*now); } [[nodiscard]] static result read_time() { std::array raw; - if (auto s = dev::read_regs(reg_clock, raw); !s) + if (auto s = dev::read_regs(reg_clock, raw); !s) { return std::unexpected(s.error()); + } return time_of_day{detail::hours_from_reg(raw[2]), detail::from_bcd(raw[1] & 0x7f), detail::from_bcd(raw[0] & 0x7f)}; } @@ -183,8 +193,9 @@ class device { static_cast(detail::to_bcd(at.hour) | (((m >> 2) & 1) << 7)), static_cast(day_date(at.day, rate == alarm1_rate::weekday_time_match) | (((m >> 3) & 1) << 7))}; - if (auto s = dev::write_regs(reg_alarm1, raw); !s) + if (auto s = dev::write_regs(reg_alarm1, raw); !s) { return s; + } return enable_interrupt ? enable_alarm_interrupt(a1ie) : status{}; } @@ -196,16 +207,18 @@ class device { static_cast(detail::to_bcd(at.hour) | (((m >> 1) & 1) << 7)), static_cast(day_date(at.day, rate == alarm2_rate::weekday_time_match) | (((m >> 2) & 1) << 7))}; - if (auto s = dev::write_regs(reg_alarm2, raw); !s) + if (auto s = dev::write_regs(reg_alarm2, raw); !s) { return s; + } return enable_interrupt ? enable_alarm_interrupt(a2ie) : status{}; } [[nodiscard]] static result read_alarm1() { std::array raw; - if (auto s = dev::read_regs(reg_alarm1, raw); !s) + if (auto s = dev::read_regs(reg_alarm1, raw); !s) { return std::unexpected(s.error()); + } date_time at{}; at.second = detail::from_bcd(raw[0] & 0x7f); at.minute = detail::from_bcd(raw[1] & 0x7f); @@ -217,8 +230,9 @@ class device { [[nodiscard]] static result read_alarm2() { std::array raw; - if (auto s = dev::read_regs(reg_alarm2, raw); !s) + if (auto s = dev::read_regs(reg_alarm2, raw); !s) { return std::unexpected(s.error()); + } date_time at{}; at.minute = detail::from_bcd(raw[0] & 0x7f); at.hour = detail::hours_from_reg(raw[1] & 0x7f); @@ -258,12 +272,13 @@ class device { return clear_flag(osf); } - // Die temperature in quarter °C (updated every 64 s by the device). + // Die temperature in quarter C (updated every 64 s by the device). [[nodiscard]] static result temperature_quarters() { std::array raw; - if (auto s = dev::read_regs(reg_temp, raw); !s) + if (auto s = dev::read_regs(reg_temp, raw); !s) { return std::unexpected(s.error()); + } return static_cast((static_cast(static_cast(raw[0])) << 2) | (raw[1] >> 6)); } @@ -271,32 +286,36 @@ class device { private: static constexpr std::uint8_t day_date(std::uint8_t day, bool weekday_mode) { - if (weekday_mode) + if (weekday_mode) { return static_cast(0x40 | (day & 0x0f)); + } return detail::to_bcd(day) & std::uint8_t{0x3f}; } static status enable_alarm_interrupt(std::uint8_t enable_bit) { auto control = dev::read_reg(reg_control); - if (!control) + if (!control) { return std::unexpected(control.error()); + } return dev::write_reg(reg_control, static_cast((*control & ~bbsqw) | intcn | enable_bit)); } static result flag_set(std::uint8_t bit) { auto flags = dev::read_reg(reg_status); - if (!flags) + if (!flags) { return std::unexpected(flags.error()); + } return (*flags & bit) != 0; } static status clear_flag(std::uint8_t bit) { auto flags = dev::read_reg(reg_status); - if (!flags) + if (!flags) { return std::unexpected(flags.error()); + } return dev::write_reg(reg_status, static_cast(*flags & ~bit)); } }; diff --git a/libavr b/libavr index b719ed7..26109e1 160000 --- a/libavr +++ b/libavr @@ -1 +1 @@ -Subproject commit b719ed74d8d462ea4e6877fc55e8ba64335eb01a +Subproject commit 26109e172be4ab5cf17c9efb8c7e36d1f1caa485