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:
@@ -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
|
||||
...
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ using bus = dev::i2c<{.frequency = 100_kHz}>;
|
||||
using rtc = ds3231::device<bus>;
|
||||
|
||||
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,
|
||||
@@ -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
|
||||
|
||||
@@ -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<bus, led>();
|
||||
|
||||
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>();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<std::uint8_t>(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<std::uint8_t>((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 <typename Bus, bool SetWeekday = true>
|
||||
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_alarm1 = 0x07;
|
||||
@@ -110,8 +117,9 @@ class device {
|
||||
[[nodiscard]] static result<date_time> read_clock()
|
||||
{
|
||||
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());
|
||||
}
|
||||
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<date> read_date()
|
||||
{
|
||||
auto now = read_clock();
|
||||
if (!now)
|
||||
if (!now) {
|
||||
return std::unexpected(now.error());
|
||||
}
|
||||
return static_cast<date>(*now);
|
||||
}
|
||||
|
||||
[[nodiscard]] static result<time_of_day> read_time()
|
||||
{
|
||||
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 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<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) |
|
||||
(((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<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) |
|
||||
(((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<date_time> read_alarm1()
|
||||
{
|
||||
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());
|
||||
}
|
||||
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<date_time> read_alarm2()
|
||||
{
|
||||
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());
|
||||
}
|
||||
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<std::int16_t> temperature_quarters()
|
||||
{
|
||||
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 static_cast<std::int16_t>((static_cast<std::int16_t>(static_cast<std::int8_t>(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<std::uint8_t>(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<std::uint8_t>((*control & ~bbsqw) | intcn | enable_bit));
|
||||
}
|
||||
|
||||
static result<bool> 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<std::uint8_t>(*flags & ~bit));
|
||||
}
|
||||
};
|
||||
|
||||
2
libavr
2
libavr
Submodule libavr updated: b719ed74d8...26109e172b
Reference in New Issue
Block a user