321 lines
8.8 KiB
C++
321 lines
8.8 KiB
C++
#pragma once
|
|
|
|
#include "../clock.hpp"
|
|
|
|
#include <stddef.h>
|
|
|
|
#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 <typename I2cDriver, bool SetDayOfWeek = true>
|
|
class DS3231 {
|
|
using i2c_t = i2c::I2c<I2cDriver>;
|
|
|
|
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<TIME_REG_ADDR>();
|
|
|
|
Date date;
|
|
date.year = timeReg.getYear();
|
|
date.month = timeReg.getMonth();
|
|
date.day = timeReg.getDate();
|
|
return date;
|
|
}
|
|
static auto getTime()
|
|
{
|
|
const auto timeReg = readRegister<TIME_REG_ADDR>();
|
|
|
|
Time time;
|
|
time.hour = timeReg.getHours();
|
|
time.minute = timeReg.getMinutes();
|
|
time.second = timeReg.getSeconds();
|
|
return time;
|
|
}
|
|
static auto getDateTime()
|
|
{
|
|
const auto timeReg = readRegister<TIME_REG_ADDR>();
|
|
|
|
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<DATE_START_OFFSET, DATE_END_OFFSET>(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<TIME_START_OFFSET, TIME_END_OFFSET>(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<START_OFFSET, END_OFFSET>(timeReg);
|
|
}
|
|
|
|
static bool checkAlarm1()
|
|
{
|
|
return checkAlarmHelper<detail::ControlStatusRegFlags::A1F>();
|
|
}
|
|
static bool checkAlarm2()
|
|
{
|
|
return checkAlarmHelper<detail::ControlStatusRegFlags::A2F>();
|
|
}
|
|
|
|
static void clearAlarm1()
|
|
{
|
|
clearAlarmHelper<detail::ControlStatusRegFlags::A1F>();
|
|
}
|
|
static void clearAlarm2()
|
|
{
|
|
clearAlarmHelper<detail::ControlStatusRegFlags::A2F>();
|
|
}
|
|
|
|
private:
|
|
template <uint8_t Address, typename Register>
|
|
static Register readRegisterHelper()
|
|
{
|
|
i2c_t::template start<I2C_ADDRESS>(false);
|
|
i2c_t::write(Address);
|
|
i2c_t::stop();
|
|
|
|
Register reg;
|
|
i2c_t::template start<I2C_ADDRESS>(true);
|
|
i2c_t::template readBytes<sizeof(Register)>(reinterpret_cast<uint8_t *>(®));
|
|
i2c_t::stop();
|
|
return reg;
|
|
}
|
|
|
|
template <uint8_t Address>
|
|
static auto readRegister()
|
|
{
|
|
if constexpr (Address == TIME_REG_ADDR) {
|
|
return readRegisterHelper<Address, detail::TimeReg>();
|
|
} else if constexpr (Address == ALARM1_REG_ADDR) {
|
|
return readRegisterHelper<Address, detail::Alarm1Reg>();
|
|
} else if constexpr (Address == ALARM2_REG_ADDR) {
|
|
return readRegisterHelper<Address, detail::Alarm2Reg>();
|
|
} else if constexpr (Address == CONTROL_REG_ADDR) {
|
|
return readRegisterHelper<Address, detail::ControlReg>();
|
|
} else if constexpr (Address == CONTROL_STATUS_REG_ADDR) {
|
|
return readRegisterHelper<Address, detail::ControlStatusReg>();
|
|
} else if constexpr (Address == AGING_OFFSET_REG_ADDR) {
|
|
return readRegisterHelper<Address, detail::AgingOffsetReg>();
|
|
} else if constexpr (Address == TEMP_REG_ADDR) {
|
|
return readRegisterHelper<Address, detail::TempReg>();
|
|
} else {
|
|
static_assert(util::always_false_v<decltype(Address)>, "Invalid register address");
|
|
}
|
|
}
|
|
|
|
template <uint8_t StartOffset, uint8_t EndOffset, typename Register>
|
|
static void writePartialRegister(const Register ®)
|
|
{
|
|
constexpr auto getRegisterAddress = []() {
|
|
if constexpr (util::is_same_v<Register, detail::TimeReg>) {
|
|
return TIME_REG_ADDR;
|
|
} else if constexpr (util::is_same_v<Register, detail::Alarm1Reg>) {
|
|
return ALARM1_REG_ADDR;
|
|
} else if constexpr (util::is_same_v<Register, detail::Alarm2Reg>) {
|
|
return ALARM2_REG_ADDR;
|
|
} else if constexpr (util::is_same_v<Register, detail::ControlReg>) {
|
|
return CONTROL_REG_ADDR;
|
|
} else if constexpr (util::is_same_v<Register, detail::ControlStatusReg>) {
|
|
return CONTROL_STATUS_REG_ADDR;
|
|
} else if constexpr (util::is_same_v<Register, detail::AgingOffsetReg>) {
|
|
return AGING_OFFSET_REG_ADDR;
|
|
} else if constexpr (util::is_same_v<Register, detail::TempReg>) {
|
|
return TEMP_REG_ADDR;
|
|
} else {
|
|
static_assert(util::always_false_v<Register>, "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<I2C_ADDRESS>(false);
|
|
i2c_t::write(ADDRESS + StartOffset);
|
|
i2c_t::template writeBytes<WRITE_SIZE>(reinterpret_cast<const uint8_t *>(®) + StartOffset);
|
|
i2c_t::stop();
|
|
}
|
|
|
|
template <typename Register>
|
|
static void writeRegister(const Register ®)
|
|
{
|
|
writePartialRegister<0, sizeof(Register) - 1>(reg);
|
|
}
|
|
|
|
template <detail::ControlStatusRegFlags AlarmFlag>
|
|
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<CONTROL_STATUS_REG_ADDR>();
|
|
return alarmStatus == AlarmFlag;
|
|
}
|
|
|
|
template <detail::ControlStatusRegFlags AlarmFlag>
|
|
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<CONTROL_STATUS_REG_ADDR>();
|
|
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
|