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
|
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
|
||||||
...
|
...
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
2
libavr
Submodule libavr updated: b719ed74d8...26109e172b
Reference in New Issue
Block a user