diff --git a/ds3231.hpp b/ds3231.hpp new file mode 100644 index 0000000..62bf497 --- /dev/null +++ b/ds3231.hpp @@ -0,0 +1,130 @@ +#pragma once + +#include "../clock.hpp" + +#include "../i2c/i2c.hpp" +#include "../type/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 { + 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() + { + I2cDriver::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) {} + static void setTime(const Time &time) {} + static void setDateTime(const DateTime &dateTime) {} + + private: + template + static Register readRegisterHelper() + { + I2cDriver::template start(false); + I2cDriver::write(Address); + I2cDriver::stop(); + + Register reg; + I2cDriver::template start(true); + I2cDriver::template readBytes(reinterpret_cast(®)); + I2cDriver::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(type::always_false_v, "Invalid register address"); + } + } +}; + +} // namespace rtc diff --git a/flags.hpp b/flags.hpp new file mode 100644 index 0000000..12d58c8 --- /dev/null +++ b/flags.hpp @@ -0,0 +1,71 @@ +#pragma once + +namespace rtc { + +namespace detail { + +template +struct [[gnu::packed]] FlagsImpl +{ + static_assert(sizeof(FlagsT) == sizeof(uint8_t), "Must use uint8_t enum class flags"); + + uint8_t data; + using Flags = FlagsT; + + FlagsImpl() : data(0) {} + FlagsImpl(const Flags &flag) : data(static_cast(flag)) {} + + FlagsImpl(const FlagsImpl &other) : data(other.data) {} + FlagsImpl(const FlagsImpl &&) = delete; + + FlagsImpl &operator=(const FlagsImpl &rhs) + { + data = rhs.data; + return *this; + } + FlagsImpl &operator=(const FlagsImpl &&) = delete; + + FlagsImpl &operator=(const FlagsT &rhs) + { + data = static_cast(rhs); + return *this; + } + + FlagsImpl &operator|=(const FlagsT &flag) + { + data |= static_cast(flag); + return *this; + } + + FlagsImpl &operator&=(const FlagsT &flag) + { + data &= static_cast(flag); + return *this; + } + + FlagsImpl &operator~() + { + data = ~data; + return *this; + } +}; + +template +FlagsT operator|(const FlagsT &lhs, const FlagsT &rhs) +{ + const auto lhsInt = static_cast(lhs); + const auto rhsInt = static_cast(rhs); + return static_cast(lhsInt | rhsInt); +} + +template +FlagsT operator&(const FlagsT &lhs, const FlagsT &rhs) +{ + const auto lhsInt = static_cast(lhs); + const auto rhsInt = static_cast(rhs); + return static_cast(lhsInt & rhsInt); +} + +} // namespace detail + +} // namespace rtc diff --git a/registers.hpp b/registers.hpp new file mode 100644 index 0000000..b1d83c8 --- /dev/null +++ b/registers.hpp @@ -0,0 +1,419 @@ +#pragma once + +#include + +#include "flags.hpp" + +namespace rtc { + +namespace detail { + +////////////////////////////////////////////////////////////////////////// + +template +static inline uint8_t toBcd(const uint8_t &data) +{ + return ((data / 10 * 16) + (data % 10)) & Mask; +} + +template +static inline uint8_t fromBcd(const uint8_t &data) +{ + const auto maskedData = data & Mask; + return ((maskedData / 16 * 10) + (maskedData % 16)); +} + +static inline uint8_t convertTo24Hour(const uint8_t &hoursReg) +{ + constexpr auto FLAG_12_HOUR = 6; + constexpr auto FLAG_PM = 5; + + const bool time12HourFormat = (hoursReg >> FLAG_12_HOUR) & 1; + + if (time12HourFormat) { + const auto pmFlag = (hoursReg >> FLAG_PM) & 1; + constexpr auto HOUR_12_MASK = 0b00011111; + const auto hour12 = fromBcd(hoursReg); + if (hour12 == 12 && !pmFlag) + return 0; + return hour12 + pmFlag ? 12 : 0; + } else // 24 hour format + { + constexpr auto HOUR_MASK = 0b00111111; + return fromBcd(hoursReg); + } +} + +template +static inline uint8_t getMaskedBcd(const uint8_t ®) +{ + return fromBcd(reg); +} + +template +static inline void setMaskedBcd(uint8_t ®, const uint8_t &value) +{ + reg &= ~Mask; + reg |= toBcd(value); +} + +static inline bool getEncodedDay(uint8_t &value, const uint8_t ®) +{ + constexpr auto DAY_FLAG = 6; + if ((reg >> DAY_FLAG) & 1) { + constexpr auto DAY_MASK = 0b00001111; + value = reg & DAY_MASK; + return true; + } + + return false; +} + +static inline bool getEncodedDate(uint8_t &value, const uint8_t ®) +{ + constexpr auto DAY_FLAG = 6; + if (!((reg >> DAY_FLAG) & 1)) { + value = getMaskedBcd<0b00111111>(reg); + return true; + } + + return false; +} + +static inline void setEncodedDay(uint8_t ®, const uint8_t &value) +{ + constexpr auto DAY_MASK = 0b11110000; + reg &= DAY_MASK; + reg |= value & DAY_MASK; + constexpr auto DAY_FLAG = 6; + reg |= (1 << DAY_FLAG); +} + +static inline void setEncodedDate(uint8_t ®, const uint8_t &value) +{ + setMaskedBcd<0b00111111>(reg, value); + constexpr auto DAY_FLAG = 6; + reg &= ~(1 << DAY_FLAG); +} + +////////////////////////////////////////////////////////////////////////// + +struct [[gnu::packed]] TimeReg +{ + uint8_t seconds; + uint8_t minutes; + uint8_t hours; + uint8_t day; + uint8_t date; + uint8_t month_century; + uint8_t year; + + ////////////////////////////////////////////////////////////////////////// + + inline uint8_t getSeconds() const + { + return getMaskedBcd(seconds); + } + inline uint8_t getMinutes() const + { + return getMaskedBcd(minutes); + } + inline uint8_t getHours() const + { + return convertTo24Hour(hours); + } + inline uint8_t getDay() const + { + return getMaskedBcd<0b00000111>(day); + } + inline uint8_t getDate() const + { + return getMaskedBcd<0b00111111>(date); + } + inline uint8_t getMonth() const + { + return getMaskedBcd<0b00011111>(month_century); + } + inline bool getCentury() const + { + constexpr auto CENTURY_FLAG = 7; + return (month_century >> CENTURY_FLAG) & 1; + } + inline uint16_t getYear() const + { + return 2000 + fromBcd(year); + } + + ////////////////////////////////////////////////////////////////////////// + + inline void setSeconds(uint8_t seconds) + { + setMaskedBcd(this->seconds, seconds); + } + inline void setMinutes(uint8_t minutes) + { + setMaskedBcd(this->minutes, minutes); + } + inline void setHours(uint8_t hours) + { + setMaskedBcd(this->hours, hours); + } + inline void setDay(uint8_t day) + { + this->day = day & 0b111; + } + inline void setDate(uint8_t date) + { + setMaskedBcd<0b00111111>(this->date, date); + } + inline void setMonth(uint8_t month) + { + setMaskedBcd<0b00011111>(month_century, month); + } + inline void setCentury(bool century) + { + constexpr auto CENTURY_POS = 7; + month_century &= ~(1 << CENTURY_POS); + month_century |= (century << CENTURY_POS); + } + inline void setYear(uint16_t year) + { + year = year % 100; + this->year = toBcd(year); + } +}; + +static_assert(sizeof(TimeReg) == 7, "Invalid time register size"); + +////////////////////////////////////////////////////////////////////////// + +struct [[gnu::packed]] Alarm1Reg +{ + uint8_t seconds; + uint8_t minutes; + uint8_t hours; + uint8_t day_date; + + enum class AlarmRate { + ONCE_PER_S = 0b1111, + WHEN_S_MATCH = 0b1110, + WHEN_M_S_MATCH = 0b1100, + WHEN_H_M_S_MATCH = 0b1000, + WHEN_DATE_H_M_S_MATCH = 0b00000, + WHEN_DAY_H_N_S_MATCH = 0b10000, + }; + + ////////////////////////////////////////////////////////////////////////// + + inline uint8_t getSeconds() const + { + return getMaskedBcd(seconds); + } + inline uint8_t getMinutes() const + { + return getMaskedBcd(minutes); + } + inline uint8_t getHours() const + { + return convertTo24Hour(hours); + } + inline bool getDay(uint8_t & day) const + { + return getEncodedDay(day, day_date); + } + inline bool getDate(uint8_t & date) const + { + return getEncodedDate(date, day_date); + } + inline AlarmRate getAlarmRate() const + { + constexpr auto M_FLAG = 7; + const auto m1 = (seconds >> M_FLAG) & 1; + const auto m2 = (minutes >> M_FLAG) & 1; + const auto m3 = (hours >> M_FLAG) & 1; + const auto m4 = (day_date >> M_FLAG) & 1; + const auto m = (m4 << 3) | (m3 << 2) | (m2 << 1) | (m1 << 0); + if (m == 0) { + constexpr auto DAY_FLAG = 6; + const auto dayFormat = ((day_date >> DAY_FLAG) & 1) << 4; + return static_cast(dayFormat); + } + return static_cast(m); + } + + ////////////////////////////////////////////////////////////////////////// + + inline void setSeconds(uint8_t seconds) + { + setMaskedBcd(this->seconds, seconds); + } + inline void setMinutes(uint8_t minutes) + { + setMaskedBcd(this->minutes, minutes); + } + inline void setHours(uint8_t hours) + { + setMaskedBcd(this->hours, hours); + } + inline void setDay(uint8_t day) + { + setEncodedDay(day_date, day); + } + inline void setDate(uint8_t date) + { + setEncodedDate(day_date, date); + } + inline void setAlarmRate(const AlarmRate &alarmRate) + { + const auto alarmRateFlags = static_cast(alarmRate); + constexpr auto M_FLAG = 7; + seconds &= ~(1 << M_FLAG); + seconds |= (alarmRateFlags & 1) << M_FLAG; + minutes &= ~(1 << M_FLAG); + minutes |= ((alarmRateFlags >> 1) & 1) << M_FLAG; + hours &= ~(1 << M_FLAG); + hours |= ((alarmRateFlags >> 2) & 1) << M_FLAG; + day_date &= ~(1 << M_FLAG); + day_date |= ((alarmRateFlags >> 3) & 1) << M_FLAG; + } +}; + +static_assert(sizeof(Alarm1Reg) == 4, "Invalid alarm1 register size"); + +////////////////////////////////////////////////////////////////////////// + +struct [[gnu::packed]] Alarm2Reg +{ + uint8_t minutes; + uint8_t hours; + uint8_t day_date; + + enum class AlarmRate { + ONCE_PER_M = 0b111, + WHEN_M_MATCH = 0b110, + WHEN_H_M_MATCH = 0b100, + WHEN_DATE_H_M_MATCH = 0b0000, + WHEN_DAY_H_N_MATCH = 0b1000, + }; + + ////////////////////////////////////////////////////////////////////////// + + inline uint8_t getMinutes() const + { + return getMaskedBcd(minutes); + } + inline uint8_t getHours() const + { + return convertTo24Hour(hours); + } + inline bool getDay(uint8_t & day) const + { + return getEncodedDay(day, day_date); + } + inline bool getDate(uint8_t & date) const + { + return getEncodedDate(date, day_date); + } + inline AlarmRate getAlarmRate() const + { + constexpr auto M_FLAG = 7; + const auto m2 = (minutes >> M_FLAG) & 1; + const auto m3 = (hours >> M_FLAG) & 1; + const auto m4 = (day_date >> M_FLAG) & 1; + const auto m = (m4 << 2) | (m3 << 1) | (m2 << 0); + if (m == 0) { + constexpr auto DAY_FLAG = 6; + const auto dayFormat = ((day_date >> DAY_FLAG) & 1) << 3; + return static_cast(dayFormat); + } + return static_cast(m); + } + + ////////////////////////////////////////////////////////////////////////// + + inline void setMinutes(uint8_t minutes) + { + setMaskedBcd(this->minutes, minutes); + } + inline void setHours(uint8_t hours) + { + setMaskedBcd(this->hours, hours); + } + inline void setDay(uint8_t day) + { + setEncodedDay(day_date, day); + } + inline void setDate(uint8_t date) + { + setEncodedDate(day_date, date); + } + inline void setAlarmRate(const AlarmRate &alarmRate) + { + const auto alarmRateFlags = static_cast(alarmRate); + constexpr auto M_FLAG = 7; + minutes &= ~(1 << M_FLAG); + minutes |= (alarmRateFlags & 1) << M_FLAG; + hours &= ~(1 << M_FLAG); + hours |= ((alarmRateFlags >> 1) & 1) << M_FLAG; + day_date &= ~(1 << M_FLAG); + day_date |= ((alarmRateFlags >> 2) & 1) << M_FLAG; + } +}; + +static_assert(sizeof(Alarm2Reg) == 3, "Invalid alarm2 register size"); + +////////////////////////////////////////////////////////////////////////// + +enum class ControlRegFlags : uint8_t { + N_EOSC = 1 << 7, + BBSQW = 1 << 6, + CONV = 1 << 5, + RS2 = 1 << 4, + RS1 = 1 << 3, + INTCN = 1 << 2, + A2IE = 1 << 1, + A1IE = 1 << 0, +}; + +struct [[gnu::packed]] ControlReg : FlagsImpl{}; + +static_assert(sizeof(ControlReg) == 1, "Invalid control register size"); + +////////////////////////////////////////////////////////////////////////// + +enum class ControlStatusRegFlags : uint8_t { + OSF = 1 << 7, + EN32KHZ = 1 << 3, + BSY = 1 << 2, + A2F = 1 << 1, + A1F = 1 << 0, +}; + +struct [[gnu::packed]] ControlStatusReg : FlagsImpl{}; + +static_assert(sizeof(ControlStatusReg) == 1, "Invalid control/status register size"); + +////////////////////////////////////////////////////////////////////////// + +struct [[gnu::packed]] AgingOffsetReg +{ + uint8_t data; +}; + +static_assert(sizeof(AgingOffsetReg) == 1, "Invalid aging offset register size"); + +////////////////////////////////////////////////////////////////////////// + +struct [[gnu::packed]] TempReg +{ + uint8_t msb_temp; + uint8_t lsb_temp; +}; + +static_assert(sizeof(TempReg) == 2, "Invalid temperature register size"); + +////////////////////////////////////////////////////////////////////////// + +} // namespace detail + +} // namespace rtc