#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; }; struct Time { uint8_t hour; uint8_t minute; uint8_t second; }; struct DateTime : Date, Time { }; 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 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); } 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 ®) { 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"); if constexpr (Address == TIME_REG_ADDR) { static_assert(util::is_same_v, "Invalid register type"); } else if constexpr (Address == ALARM1_REG_ADDR) { static_assert(util::is_same_v, "Invalid register type"); } else if constexpr (Address == ALARM2_REG_ADDR) { static_assert(util::is_same_v, "Invalid register type"); } else if constexpr (Address == CONTROL_REG_ADDR) { static_assert(util::is_same_v, "Invalid register type"); } else if constexpr (Address == CONTROL_STATUS_REG_ADDR) { static_assert(util::is_same_v, "Invalid register type"); } else if constexpr (Address == AGING_OFFSET_REG_ADDR) { static_assert(util::is_same_v, "Invalid register type"); } else if constexpr (Address == TEMP_REG_ADDR) { static_assert(util::is_same_v, "Invalid register type"); } else { static_assert(util::always_false_v, "Invalid register address"); } i2c_t::template start(false); i2c_t::write(Address + StartOffset); i2c_t::template writeBytes(reinterpret_cast(®) + StartOffset); i2c_t::stop(); } 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