- Track last OLED activity to avoid double timeout; keep power gating on transitions - Copy TZ before setenv() in timegm_fallback to avoid invalid pointer reuse - Add BATTERY_SAMPLE_INTERVAL_MS and only refresh cache at batch start when due - Keep battery sampling to a single ADC read (Arduino core lacks explicit ADC power gating)
126 lines
3.2 KiB
C++
126 lines
3.2 KiB
C++
#include "rtc_ds3231.h"
|
|
#include "config.h"
|
|
#include <Wire.h>
|
|
#include <string>
|
|
#include <time.h>
|
|
|
|
static constexpr uint8_t DS3231_ADDR = 0x68;
|
|
|
|
static uint8_t bcd_to_dec(uint8_t val) {
|
|
return static_cast<uint8_t>((val >> 4) * 10 + (val & 0x0F));
|
|
}
|
|
|
|
static uint8_t dec_to_bcd(uint8_t val) {
|
|
return static_cast<uint8_t>(((val / 10) << 4) | (val % 10));
|
|
}
|
|
|
|
static time_t timegm_fallback(struct tm *tm_utc) {
|
|
if (!tm_utc) {
|
|
return static_cast<time_t>(-1);
|
|
}
|
|
const char *old_tz = getenv("TZ");
|
|
// getenv() may return a pointer into mutable storage that becomes invalid after setenv().
|
|
std::string old_tz_copy = old_tz ? old_tz : "";
|
|
setenv("TZ", "UTC0", 1);
|
|
tzset();
|
|
time_t t = mktime(tm_utc);
|
|
if (!old_tz_copy.empty()) {
|
|
setenv("TZ", old_tz_copy.c_str(), 1);
|
|
} else {
|
|
unsetenv("TZ");
|
|
}
|
|
tzset();
|
|
return t;
|
|
}
|
|
|
|
static bool read_registers(uint8_t start_reg, uint8_t *out, size_t len) {
|
|
if (!out || len == 0) {
|
|
return false;
|
|
}
|
|
Wire.beginTransmission(DS3231_ADDR);
|
|
Wire.write(start_reg);
|
|
if (Wire.endTransmission(false) != 0) {
|
|
return false;
|
|
}
|
|
size_t read = Wire.requestFrom(DS3231_ADDR, static_cast<uint8_t>(len));
|
|
if (read != len) {
|
|
return false;
|
|
}
|
|
for (size_t i = 0; i < len; ++i) {
|
|
out[i] = Wire.read();
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static bool write_registers(uint8_t start_reg, const uint8_t *data, size_t len) {
|
|
if (!data || len == 0) {
|
|
return false;
|
|
}
|
|
Wire.beginTransmission(DS3231_ADDR);
|
|
Wire.write(start_reg);
|
|
for (size_t i = 0; i < len; ++i) {
|
|
Wire.write(data[i]);
|
|
}
|
|
return Wire.endTransmission() == 0;
|
|
}
|
|
|
|
bool rtc_ds3231_init() {
|
|
Wire.begin(PIN_OLED_SDA, PIN_OLED_SCL);
|
|
Wire.setClock(100000);
|
|
return rtc_ds3231_is_present();
|
|
}
|
|
|
|
bool rtc_ds3231_is_present() {
|
|
Wire.beginTransmission(DS3231_ADDR);
|
|
return Wire.endTransmission() == 0;
|
|
}
|
|
|
|
bool rtc_ds3231_read_epoch(uint32_t &epoch_utc) {
|
|
uint8_t regs[7] = {};
|
|
if (!read_registers(0x00, regs, sizeof(regs))) {
|
|
return false;
|
|
}
|
|
|
|
uint8_t sec = bcd_to_dec(regs[0] & 0x7F);
|
|
uint8_t min = bcd_to_dec(regs[1] & 0x7F);
|
|
uint8_t hour = bcd_to_dec(regs[2] & 0x3F);
|
|
uint8_t day = bcd_to_dec(regs[4] & 0x3F);
|
|
uint8_t month = bcd_to_dec(regs[5] & 0x1F);
|
|
uint16_t year = 2000 + bcd_to_dec(regs[6]);
|
|
|
|
struct tm tm_utc = {};
|
|
tm_utc.tm_sec = sec;
|
|
tm_utc.tm_min = min;
|
|
tm_utc.tm_hour = hour;
|
|
tm_utc.tm_mday = day;
|
|
tm_utc.tm_mon = month - 1;
|
|
tm_utc.tm_year = year - 1900;
|
|
tm_utc.tm_isdst = 0;
|
|
|
|
time_t t = timegm_fallback(&tm_utc);
|
|
if (t <= 0) {
|
|
return false;
|
|
}
|
|
epoch_utc = static_cast<uint32_t>(t);
|
|
return true;
|
|
}
|
|
|
|
bool rtc_ds3231_set_epoch(uint32_t epoch_utc) {
|
|
time_t t = static_cast<time_t>(epoch_utc);
|
|
struct tm tm_utc = {};
|
|
if (!gmtime_r(&t, &tm_utc)) {
|
|
return false;
|
|
}
|
|
|
|
uint8_t regs[7] = {};
|
|
regs[0] = dec_to_bcd(static_cast<uint8_t>(tm_utc.tm_sec));
|
|
regs[1] = dec_to_bcd(static_cast<uint8_t>(tm_utc.tm_min));
|
|
regs[2] = dec_to_bcd(static_cast<uint8_t>(tm_utc.tm_hour));
|
|
regs[3] = dec_to_bcd(static_cast<uint8_t>(tm_utc.tm_wday + 1));
|
|
regs[4] = dec_to_bcd(static_cast<uint8_t>(tm_utc.tm_mday));
|
|
regs[5] = dec_to_bcd(static_cast<uint8_t>(tm_utc.tm_mon + 1));
|
|
regs[6] = dec_to_bcd(static_cast<uint8_t>((tm_utc.tm_year + 1900) - 2000));
|
|
|
|
return write_registers(0x00, regs, sizeof(regs));
|
|
}
|