#pragma once #include "../clock.hpp" #include #include "../i2c/i2c.hpp" #include "../util/type.hpp" #include "registers.hpp" namespace rtc { struct Date { uint16_t year; uint8_t month; uint8_t day; inline bool operator==(const Date &rhs) const { if (day != rhs.day) return false; if (month != rhs.month) return false; if (year != rhs.year) return false; return true; } inline bool operator!=(const Date &rhs) { return !(*this == rhs); } }; struct Time { uint8_t hour; uint8_t minute; uint8_t second; inline bool operator==(const Time &rhs) const { if (second != rhs.second) return false; if (minute != rhs.minute) return false; if (hour != rhs.hour) return false; return true; } inline bool operator!=(const Time &rhs) { return !(*this == rhs); } }; struct DateTime : Date, Time { inline bool operator==(const DateTime &rhs) const { if (second != rhs.second) return false; if (minute != rhs.minute) return false; if (hour != rhs.hour) return false; if (day != rhs.day) return false; if (month != rhs.month) return false; if (year != rhs.year) return false; return true; } inline bool operator!=(const DateTime &rhs) { return !(*this == rhs); } }; template class DS3231 { using i2c_t = i2c::I2c; public: static constexpr auto I2C_ADDRESS = 0x68; static constexpr auto TIME_REG_ADDR = 0x00; static constexpr auto ALARM1_REG_ADDR = 0x07; static constexpr auto ALARM2_REG_ADDR = 0x0B; static constexpr auto CONTROL_REG_ADDR = 0x0E; static constexpr auto CONTROL_STATUS_REG_ADDR = 0x0F; static constexpr auto AGING_OFFSET_REG_ADDR = 0x10; static constexpr auto TEMP_REG_ADDR = 0x11; // Construction does not call init and is only available for convenience DS3231() = default; // Moving and copying ds3231 objects is not supported DS3231(const DS3231 &) = delete; DS3231(DS3231 &&) = delete; DS3231 &operator=(const DS3231 &) = delete; DS3231 &operator=(DS3231 &&) = delete; static inline void init() { i2c_t::init(); } static auto getDate() { const auto timeReg = readRegister(); Date date; date.year = timeReg.getYear(); date.month = timeReg.getMonth(); date.day = timeReg.getDate(); return date; } static auto getTime() { const auto timeReg = readRegister(); Time time; time.hour = timeReg.getHours(); time.minute = timeReg.getMinutes(); time.second = timeReg.getSeconds(); return time; } static auto getDateTime() { const auto timeReg = readRegister(); DateTime dateTime; dateTime.year = timeReg.getYear(); dateTime.month = timeReg.getMonth(); dateTime.day = timeReg.getDate(); dateTime.hour = timeReg.getHours(); dateTime.minute = timeReg.getMinutes(); dateTime.second = timeReg.getSeconds(); return dateTime; } static DateTime getAlarm1() { return getAlarmHelper(); } static DateTime getAlarm2() { return getAlarmHelper(); } static void setDate(const Date &date) { detail::TimeReg timeReg; timeReg.setYear(date.year); timeReg.setMonth(date.month); timeReg.setDate(date.day); if constexpr (SetDayOfWeek) timeReg.setDay(calcDayOfWeek(date.year, date.month, date.day) + 1); constexpr auto DATE_START_OFFSET = offsetof(detail::TimeReg, day); constexpr auto DATE_END_OFFSET = offsetof(detail::TimeReg, year); writePartialRegister(timeReg); } static void setTime(const Time &time) { detail::TimeReg timeReg; timeReg.setHours(time.hour); timeReg.setMinutes(time.minute); timeReg.setSeconds(time.second); constexpr auto TIME_START_OFFSET = offsetof(detail::TimeReg, seconds); constexpr auto TIME_END_OFFSET = offsetof(detail::TimeReg, hours); writePartialRegister(timeReg); } static void setDateTime(const DateTime &dateTime) { detail::TimeReg timeReg; timeReg.setYear(dateTime.year); timeReg.setMonth(dateTime.month); timeReg.setDate(dateTime.day); if constexpr (SetDayOfWeek) timeReg.setDay(calcDayOfWeek(dateTime.year, dateTime.month, dateTime.day) + 1); timeReg.setHours(dateTime.hour); timeReg.setMinutes(dateTime.minute); timeReg.setSeconds(dateTime.second); constexpr auto START_OFFSET = offsetof(detail::TimeReg, seconds); constexpr auto END_OFFSET = offsetof(detail::TimeReg, year); writePartialRegister(timeReg); } static void setAlarm1(const DateTime &alarmTime, const Alarm1Rate &alarmRate, bool enableInterrupt = true) { setAlarmHelper(alarmTime, alarmRate); if (enableInterrupt) enableInterruptHelper(); } static void setAlarm2(const DateTime &alarmTime, const Alarm2Rate &alarmRate, bool enableInterrupt = true) { setAlarmHelper(alarmTime, alarmRate); if (enableInterrupt) enableInterruptHelper(); } static bool checkAlarm1() { return checkAlarmHelper(); } static bool checkAlarm2() { return checkAlarmHelper(); } static void clearAlarm1() { clearAlarmHelper(); } static void clearAlarm2() { clearAlarmHelper(); } private: template static Register readRegisterHelper() { i2c_t::template start(false); i2c_t::write(Address); i2c_t::stop(); Register reg; i2c_t::template start(true); i2c_t::template readBytes(reinterpret_cast(®)); i2c_t::stop(); return reg; } template static auto readRegister() { if constexpr (Address == TIME_REG_ADDR) { return readRegisterHelper(); } else if constexpr (Address == ALARM1_REG_ADDR) { return readRegisterHelper(); } else if constexpr (Address == ALARM2_REG_ADDR) { return readRegisterHelper(); } else if constexpr (Address == CONTROL_REG_ADDR) { return readRegisterHelper(); } else if constexpr (Address == CONTROL_STATUS_REG_ADDR) { return readRegisterHelper(); } else if constexpr (Address == AGING_OFFSET_REG_ADDR) { return readRegisterHelper(); } else if constexpr (Address == TEMP_REG_ADDR) { return readRegisterHelper(); } else { static_assert(util::always_false_v, "Invalid register address"); } } template static void writePartialRegister(const Register ®) { constexpr auto getRegisterAddress = []() { if constexpr (util::is_same_v) { return TIME_REG_ADDR; } else if constexpr (util::is_same_v) { return ALARM1_REG_ADDR; } else if constexpr (util::is_same_v) { return ALARM2_REG_ADDR; } else if constexpr (util::is_same_v) { return CONTROL_REG_ADDR; } else if constexpr (util::is_same_v) { return CONTROL_STATUS_REG_ADDR; } else if constexpr (util::is_same_v) { return AGING_OFFSET_REG_ADDR; } else if constexpr (util::is_same_v) { return TEMP_REG_ADDR; } else { static_assert(util::always_false_v, "Invalid register type"); } }; constexpr auto ADDRESS = getRegisterAddress(); static_assert(StartOffset <= EndOffset, "Invalid offset range"); static_assert(StartOffset < sizeof(Register), "Start offset out of bounds"); static_assert(EndOffset < sizeof(Register), "End offset out of bounds"); constexpr auto WRITE_SIZE = EndOffset + 1 - StartOffset; static_assert(StartOffset + WRITE_SIZE <= sizeof(Register), "Writing out of bounds"); i2c_t::template start(false); i2c_t::write(ADDRESS + StartOffset); i2c_t::template writeBytes(reinterpret_cast(®) + StartOffset); i2c_t::stop(); } template static void writeRegister(const Register ®) { writePartialRegister<0, sizeof(Register) - 1>(reg); } template static inline auto getAlarmHelper() { constexpr auto IsAlarm1 = Address == ALARM1_REG_ADDR; static_assert(IsAlarm1 || Address == ALARM2_REG_ADDR, "Must use valid alarm address"); const auto alarmReg = readRegister
(); DateTime alarmTime = {}; alarmReg.getDate(alarmTime.day); alarmTime.hour = alarmReg.getHours(); alarmTime.minute = alarmReg.getMinutes(); if constexpr (IsAlarm1) alarmTime.second = alarmReg.getSeconds(); return alarmTime; } template static inline void setAlarmHelper(const DateTime &alarmTime, const AlarmRate &alarmRate) { constexpr auto IsAlarm1 = util::is_same_v; static_assert(IsAlarm1 || util::is_same_v, "Must use valid alarm rate"); using alarm_reg_t = util::conditional_t; alarm_reg_t alarmReg; alarmReg.setAlarmRate(alarmRate); alarmReg.setDate(alarmTime.day); alarmReg.setHours(alarmTime.hour); alarmReg.setMinutes(alarmTime.minute); if constexpr (IsAlarm1) alarmReg.setSeconds(alarmTime.second); writeRegister(alarmReg); } template static inline void enableInterruptHelper() { constexpr auto IsAlarm1Flag = AlarmFlag == detail::ControlRegFlags::A1IE; constexpr auto IsAlarm2Flag = AlarmFlag == detail::ControlRegFlags::A2IE; static_assert(IsAlarm1Flag || IsAlarm2Flag, "Must use valid alarm flag"); auto controlReg = readRegister(); using Flags = typename decltype(controlReg)::Flags; controlReg &= ~Flags::BBSQW; controlReg |= Flags::INTCN | AlarmFlag; writeRegister(controlReg); } template static inline bool checkAlarmHelper() { constexpr auto IsAlarm1Flag = AlarmFlag == detail::ControlStatusRegFlags::A1F; constexpr auto IsAlarm2Flag = AlarmFlag == detail::ControlStatusRegFlags::A2F; static_assert(IsAlarm1Flag || IsAlarm2Flag, "Must use valid alarm flag"); const auto alarmStatus = readRegister(); return alarmStatus == AlarmFlag; } template static inline void clearAlarmHelper() { constexpr auto IsAlarm1Flag = AlarmFlag == detail::ControlStatusRegFlags::A1F; constexpr auto IsAlarm2Flag = AlarmFlag == detail::ControlStatusRegFlags::A2F; static_assert(IsAlarm1Flag || IsAlarm2Flag, "Must use valid alarm flag"); auto controlStatusReg = readRegister(); controlStatusReg &= ~AlarmFlag; writeRegister(controlStatusReg); } static uint8_t calcDayOfWeek(uint16_t year, uint8_t month, uint16_t day) { day += month < 3 ? year-- : year - 2; const auto dayOfWeek = (23 * month / 9 + day + 4 + year / 4 - year / 100 + year / 400); return dayOfWeek % 7; } }; } // namespace rtc