build: the libavr pin advances past phase 6, and the driver takes the bus whole

The register sugar now rides the bus's own master role - device<Bus>
resolves avr::i2c::device<typename Bus::master, bus_address>, so a
consumer keeps handing over its declared dev::i2c<...> unchanged - and
the DS3231's hardwired 0x68 is a named constant. The example's four
discards became the LED's own error signal (a failed seed or a failed
alarm clear holds it dark, the same word a stuck bus says), the tree is
reformatted under InsertBraces, the sources are ASCII, and the README's
stale Studio byte counts are replaced by the claim its check-flags gate
holds.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-09 11:46:10 +02:00
parent 2909d0bc6c
commit 87e1f5e8a1
6 changed files with 60 additions and 34 deletions

View File

@@ -1,13 +1,15 @@
--- ---
BasedOnStyle: LLVM BasedOnStyle: LLVM
Standard: Latest
ColumnLimit: 120 ColumnLimit: 120
IndentWidth: 4 IndentWidth: 4
TabWidth: 4 TabWidth: 4
UseTab: ForIndentation UseTab: ForIndentation
AlignEscapedNewlines: DontAlign AlignEscapedNewlines: DontAlign
AllowShortFunctionsOnASingleLine: Empty AllowShortFunctionsOnASingleLine: Empty
AlwaysBreakTemplateDeclarations: true BreakTemplateDeclarations: Yes
BreakBeforeBraces: Custom BreakBeforeBraces: Custom
BraceWrapping: BraceWrapping:
AfterFunction: true AfterFunction: true
InsertBraces: true
... ...

View File

@@ -12,7 +12,7 @@ if(NOT LIBAVR_ROOT)
set(LIBAVR_ROOT ${CMAKE_CURRENT_SOURCE_DIR}/libavr) set(LIBAVR_ROOT ${CMAKE_CURRENT_SOURCE_DIR}/libavr)
endif() endif()
if(NOT EXISTS ${LIBAVR_ROOT}/CMakeLists.txt) 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() endif()
add_subdirectory(${LIBAVR_ROOT} libavr-build) add_subdirectory(${LIBAVR_ROOT} libavr-build)

View File

@@ -11,7 +11,7 @@ using bus = dev::i2c<{.frequency = 100_kHz}>;
using rtc = ds3231::device<bus>; using rtc = ds3231::device<bus>;
auto now = rtc::read_clock(); // result<date_time> auto now = rtc::read_clock(); // result<date_time>
(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, `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` `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 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, **byte-identical `.text`** against the CMake build (`check-flags.py` below is
1212 B on the ATtiny85 — with the flags mirrored by hand. CMake remains the what holds the flag sets equal, so the sizes are the presets' own), with the
build system. flags mirrored by hand. CMake remains the build system.
`avrdevice` is a project-level property in Studio, so a part means a project, not `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 a configuration: `ide/atmega328p/` and `ide/attiny85/`. They need separate

View File

@@ -5,7 +5,7 @@ using namespace avr::literals;
// One source for the tiny85 (open-drain software I2C on the USI pins) and // 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 // 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. // means bus errors.
using dev = avr::device<{.clock = 8_MHz}>; using dev = avr::device<{.clock = 8_MHz}>;
using bus = dev::i2c<{.frequency = 100_kHz}>; using bus = dev::i2c<{.frequency = 100_kHz}>;
@@ -17,16 +17,21 @@ int main()
avr::init<bus, led>(); avr::init<bus, led>();
if (auto stopped = rtc::oscillator_stopped(); stopped.value_or(false)) { if (auto stopped = rtc::oscillator_stopped(); stopped.value_or(false)) {
(void)rtc::write_clock({{2026, 1, 1}, {12, 0, 0}}); // A failed seed leaves the LED dark before the loop ever runs - the
(void)rtc::set_alarm1({}, ds3231::alarm1_rate::once_per_second); // same signal a stuck bus gives it below.
(void)rtc::clear_oscillator_stopped(); 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) { 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); 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>(); dev::delay<100_ms>();
} }
} }

View File

@@ -69,8 +69,9 @@ constexpr std::uint8_t hours_from_reg(std::uint8_t reg)
if (reg & 0x40) { if (reg & 0x40) {
auto hour = from_bcd(reg & 0x1f); auto hour = from_bcd(reg & 0x1f);
bool pm = reg & 0x20; bool pm = reg & 0x20;
if (hour == 12) if (hour == 12) {
return pm ? 12 : 0; return pm ? 12 : 0;
}
return static_cast<std::uint8_t>(pm ? hour + 12 : hour); return static_cast<std::uint8_t>(pm ? hour + 12 : hour);
} }
return from_bcd(reg & 0x3f); 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 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}; constexpr std::uint8_t offsets[]{0, 3, 2, 5, 0, 3, 5, 1, 4, 6, 2, 4};
if (month < 3) if (month < 3) {
--year; --year;
}
return static_cast<std::uint8_t>((year + year / 4 - year / 100 + year / 400 + offsets[month - 1] + day) % 7 + 1); return static_cast<std::uint8_t>((year + year / 4 - year / 100 + year / 400 + offsets[month - 1] + day) % 7 + 1);
} }
} // namespace detail } // namespace detail
// Maxim DS3231 RTC on any libavr i2c master (register map: datasheet // The one bus address a DS3231 answers on - hardwired in the die, no
// 19-5170 Table 1). Every call is one bus transaction; errors surface as // address pins (datasheet 19-5170, "I2C interface").
// std::expected. Weekday registers maintain themselves from the date when inline constexpr std::uint8_t bus_address = 0x68;
// SetWeekday is on.
// 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 <typename Bus, bool SetWeekday = true> template <typename Bus, bool SetWeekday = true>
class device { class device {
using dev = avr::i2c::device<Bus, 0x68>; using dev = avr::i2c::device<typename Bus::master, bus_address>;
static constexpr std::uint8_t reg_clock = 0x00; static constexpr std::uint8_t reg_clock = 0x00;
static constexpr std::uint8_t reg_alarm1 = 0x07; static constexpr std::uint8_t reg_alarm1 = 0x07;
@@ -110,8 +117,9 @@ class device {
[[nodiscard]] static result<date_time> read_clock() [[nodiscard]] static result<date_time> read_clock()
{ {
std::array<std::uint8_t, 7> raw; std::array<std::uint8_t, 7> 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 std::unexpected(s.error());
}
date_time now; date_time now;
now.second = detail::from_bcd(raw[0] & 0x7f); now.second = detail::from_bcd(raw[0] & 0x7f);
now.minute = detail::from_bcd(raw[1] & 0x7f); now.minute = detail::from_bcd(raw[1] & 0x7f);
@@ -125,16 +133,18 @@ class device {
[[nodiscard]] static result<date> read_date() [[nodiscard]] static result<date> read_date()
{ {
auto now = read_clock(); auto now = read_clock();
if (!now) if (!now) {
return std::unexpected(now.error()); return std::unexpected(now.error());
}
return static_cast<date>(*now); return static_cast<date>(*now);
} }
[[nodiscard]] static result<time_of_day> read_time() [[nodiscard]] static result<time_of_day> read_time()
{ {
std::array<std::uint8_t, 3> raw; std::array<std::uint8_t, 3> 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 std::unexpected(s.error());
}
return time_of_day{detail::hours_from_reg(raw[2]), detail::from_bcd(raw[1] & 0x7f), return time_of_day{detail::hours_from_reg(raw[2]), detail::from_bcd(raw[1] & 0x7f),
detail::from_bcd(raw[0] & 0x7f)}; detail::from_bcd(raw[0] & 0x7f)};
} }
@@ -183,8 +193,9 @@ class device {
static_cast<std::uint8_t>(detail::to_bcd(at.hour) | (((m >> 2) & 1) << 7)), static_cast<std::uint8_t>(detail::to_bcd(at.hour) | (((m >> 2) & 1) << 7)),
static_cast<std::uint8_t>(day_date(at.day, rate == alarm1_rate::weekday_time_match) | static_cast<std::uint8_t>(day_date(at.day, rate == alarm1_rate::weekday_time_match) |
(((m >> 3) & 1) << 7))}; (((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 s;
}
return enable_interrupt ? enable_alarm_interrupt(a1ie) : status{}; return enable_interrupt ? enable_alarm_interrupt(a1ie) : status{};
} }
@@ -196,16 +207,18 @@ class device {
static_cast<std::uint8_t>(detail::to_bcd(at.hour) | (((m >> 1) & 1) << 7)), static_cast<std::uint8_t>(detail::to_bcd(at.hour) | (((m >> 1) & 1) << 7)),
static_cast<std::uint8_t>(day_date(at.day, rate == alarm2_rate::weekday_time_match) | static_cast<std::uint8_t>(day_date(at.day, rate == alarm2_rate::weekday_time_match) |
(((m >> 2) & 1) << 7))}; (((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 s;
}
return enable_interrupt ? enable_alarm_interrupt(a2ie) : status{}; return enable_interrupt ? enable_alarm_interrupt(a2ie) : status{};
} }
[[nodiscard]] static result<date_time> read_alarm1() [[nodiscard]] static result<date_time> read_alarm1()
{ {
std::array<std::uint8_t, 4> raw; std::array<std::uint8_t, 4> 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()); return std::unexpected(s.error());
}
date_time at{}; date_time at{};
at.second = detail::from_bcd(raw[0] & 0x7f); at.second = detail::from_bcd(raw[0] & 0x7f);
at.minute = detail::from_bcd(raw[1] & 0x7f); at.minute = detail::from_bcd(raw[1] & 0x7f);
@@ -217,8 +230,9 @@ class device {
[[nodiscard]] static result<date_time> read_alarm2() [[nodiscard]] static result<date_time> read_alarm2()
{ {
std::array<std::uint8_t, 3> raw; std::array<std::uint8_t, 3> 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()); return std::unexpected(s.error());
}
date_time at{}; date_time at{};
at.minute = detail::from_bcd(raw[0] & 0x7f); at.minute = detail::from_bcd(raw[0] & 0x7f);
at.hour = detail::hours_from_reg(raw[1] & 0x7f); at.hour = detail::hours_from_reg(raw[1] & 0x7f);
@@ -258,12 +272,13 @@ class device {
return clear_flag(osf); 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<std::int16_t> temperature_quarters() [[nodiscard]] static result<std::int16_t> temperature_quarters()
{ {
std::array<std::uint8_t, 2> raw; std::array<std::uint8_t, 2> 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 std::unexpected(s.error());
}
return static_cast<std::int16_t>((static_cast<std::int16_t>(static_cast<std::int8_t>(raw[0])) << 2) | return static_cast<std::int16_t>((static_cast<std::int16_t>(static_cast<std::int8_t>(raw[0])) << 2) |
(raw[1] >> 6)); (raw[1] >> 6));
} }
@@ -271,32 +286,36 @@ class device {
private: private:
static constexpr std::uint8_t day_date(std::uint8_t day, bool weekday_mode) static constexpr std::uint8_t day_date(std::uint8_t day, bool weekday_mode)
{ {
if (weekday_mode) if (weekday_mode) {
return static_cast<std::uint8_t>(0x40 | (day & 0x0f)); return static_cast<std::uint8_t>(0x40 | (day & 0x0f));
}
return detail::to_bcd(day) & std::uint8_t{0x3f}; return detail::to_bcd(day) & std::uint8_t{0x3f};
} }
static status enable_alarm_interrupt(std::uint8_t enable_bit) static status enable_alarm_interrupt(std::uint8_t enable_bit)
{ {
auto control = dev::read_reg(reg_control); auto control = dev::read_reg(reg_control);
if (!control) if (!control) {
return std::unexpected(control.error()); return std::unexpected(control.error());
}
return dev::write_reg(reg_control, static_cast<std::uint8_t>((*control & ~bbsqw) | intcn | enable_bit)); return dev::write_reg(reg_control, static_cast<std::uint8_t>((*control & ~bbsqw) | intcn | enable_bit));
} }
static result<bool> flag_set(std::uint8_t bit) static result<bool> flag_set(std::uint8_t bit)
{ {
auto flags = dev::read_reg(reg_status); auto flags = dev::read_reg(reg_status);
if (!flags) if (!flags) {
return std::unexpected(flags.error()); return std::unexpected(flags.error());
}
return (*flags & bit) != 0; return (*flags & bit) != 0;
} }
static status clear_flag(std::uint8_t bit) static status clear_flag(std::uint8_t bit)
{ {
auto flags = dev::read_reg(reg_status); auto flags = dev::read_reg(reg_status);
if (!flags) if (!flags) {
return std::unexpected(flags.error()); return std::unexpected(flags.error());
}
return dev::write_reg(reg_status, static_cast<std::uint8_t>(*flags & ~bit)); return dev::write_reg(reg_status, static_cast<std::uint8_t>(*flags & ~bit));
} }
}; };

2
libavr

Submodule libavr updated: b719ed74d8...26109e172b