DeterministicESPAsyncWebServer v6.27.1
Zero-allocation, bounded-execution async HTTP server for ESP32
Loading...
Searching...
No Matches
rtc.cpp
Go to the documentation of this file.
1// Copyright (C) 2026 Douglas Quigg (dstroy0) <dquigg123@gmail.com>
2// SPDX-License-Identifier: AGPL-3.0-or-later
3
4/**
5 * @file rtc.cpp
6 * @brief DS1307/DS3231 RTC driver - implementation. See rtc.h.
7 *
8 * The date math is H. Hinnant's proleptic-Gregorian days<->civil algorithms, so it is exact
9 * for any date and needs no lookup tables or stdlib time functions.
10 */
11
12#include "services/rtc/rtc.h"
13#include "ServerConfig.h"
14
15#if DETWS_ENABLE_RTC
16
17namespace
18{
19int bcd2int(uint8_t b)
20{
21 return (b >> 4) * 10 + (b & 0x0F);
22}
23uint8_t int2bcd(int v)
24{
25 return (uint8_t)(((v / 10) << 4) | (v % 10));
26}
27
28// Days from 1970-01-01 for a civil (y, m, d), and its inverse.
29long days_from_civil(int y, int m, int d)
30{
31 y -= m <= 2;
32 long era = (y >= 0 ? y : y - 399) / 400;
33 unsigned yoe = (unsigned)(y - era * 400);
34 unsigned doy = (153u * (unsigned)(m + (m > 2 ? -3 : 9)) + 2u) / 5u + (unsigned)d - 1u;
35 unsigned doe = yoe * 365u + yoe / 4u - yoe / 100u + doy;
36 return era * 146097L + (long)doe - 719468L;
37}
38void civil_from_days(long z, int *y, int *m, int *d)
39{
40 z += 719468;
41 long era = (z >= 0 ? z : z - 146096) / 146097;
42 unsigned doe = (unsigned)(z - era * 146097);
43 unsigned yoe = (doe - doe / 1460 + doe / 36524 - doe / 146096) / 365;
44 long yy = (long)yoe + era * 400;
45 unsigned doy = doe - (365u * yoe + yoe / 4u - yoe / 100u);
46 unsigned mp = (5u * doy + 2u) / 153u;
47 *d = (int)(doy - (153u * mp + 2u) / 5u + 1u);
48 *m = (int)(mp < 10 ? mp + 3 : mp - 9);
49 *y = (int)(yy + (*m <= 2));
50}
51} // namespace
52
53bool rtc_regs_to_epoch(const uint8_t r[RTC_REG_COUNT], uint32_t *epoch)
54{
55 if (!r || !epoch)
56 return false;
57 int sec = bcd2int(r[0] & 0x7F); // mask the DS1307 clock-halt bit
58 int min = bcd2int(r[1] & 0x7F);
59 int hour;
60 if (r[2] & 0x40) // 12-hour mode
61 {
62 int h12 = bcd2int(r[2] & 0x1F);
63 if (h12 < 1 || h12 > 12)
64 return false;
65 bool pm = (r[2] & 0x20) != 0;
66 hour = (h12 % 12) + (pm ? 12 : 0);
67 }
68 else
69 {
70 hour = bcd2int(r[2] & 0x3F);
71 }
72 int date = bcd2int(r[4] & 0x3F);
73 int month = bcd2int(r[5] & 0x1F); // mask the DS3231 century bit
74 int year = 2000 + bcd2int(r[6]);
75 if (sec > 59 || min > 59 || hour > 23 || date < 1 || date > 31 || month < 1 || month > 12)
76 return false;
77 // int64: days*86400 exceeds a 32-bit long (Windows host and ESP32 both) past ~2038.
78 int64_t t = (int64_t)days_from_civil(year, month, date) * 86400 + hour * 3600 + min * 60 + sec;
79 if (t < 0 || t > 0xFFFFFFFFLL)
80 return false;
81 *epoch = (uint32_t)t;
82 return true;
83}
84
85void rtc_epoch_to_regs(uint32_t epoch, uint8_t r[RTC_REG_COUNT])
86{
87 long days = (long)(epoch / 86400u);
88 int rem = (int)(epoch % 86400u);
89 int y = 0;
90 int m = 0;
91 int d = 0;
92 civil_from_days(days, &y, &m, &d);
93 r[0] = int2bcd(rem % 60);
94 r[1] = int2bcd((rem % 3600) / 60);
95 r[2] = int2bcd(rem / 3600); // 24-hour mode (bit 6 clear)
96 r[3] = (uint8_t)((((days % 7) + 3) % 7) + 1); // 1 = Mon .. 7 = Sun
97 r[4] = int2bcd(d);
98 r[5] = int2bcd(m); // century bit clear
99 r[6] = int2bcd(y - 2000);
100}
101
102// ---------------------------------------------------------------------------
103// I2C binding
104// ---------------------------------------------------------------------------
105
106#if defined(ARDUINO)
107
108#include "services/i2c.h"
109#include <Wire.h>
110
111bool rtc_begin()
112{
114 return true;
115}
116
117uint32_t rtc_read_epoch()
118{
119 Wire.beginTransmission(DETWS_RTC_I2C_ADDR);
120 Wire.write((uint8_t)0x00); // point at register 0 (seconds)
121 if (Wire.endTransmission() != 0)
122 return 0; // no RTC on the bus
123 if (Wire.requestFrom((int)DETWS_RTC_I2C_ADDR, (int)RTC_REG_COUNT) != RTC_REG_COUNT)
124 return 0;
125 uint8_t r[RTC_REG_COUNT];
126 for (int i = 0; i < RTC_REG_COUNT; i++)
127 r[i] = (uint8_t)Wire.read();
128 uint32_t e = 0;
129 return rtc_regs_to_epoch(r, &e) ? e : 0;
130}
131
132bool rtc_set_epoch(uint32_t epoch)
133{
134 uint8_t r[RTC_REG_COUNT];
135 rtc_epoch_to_regs(epoch, r);
136 Wire.beginTransmission(DETWS_RTC_I2C_ADDR);
137 Wire.write((uint8_t)0x00);
138 for (int i = 0; i < RTC_REG_COUNT; i++)
139 Wire.write(r[i]);
140 return Wire.endTransmission() == 0;
141}
142
143uint32_t rtc_time_source()
144{
145 return rtc_read_epoch();
146}
147
148#else // host build: no I2C. The BCD<->epoch conversions above are host-tested.
149
150bool rtc_begin()
151{
152 return true;
153}
154uint32_t rtc_read_epoch()
155{
156 return 0;
157}
158bool rtc_set_epoch(uint32_t)
159{
160 return false;
161}
162uint32_t rtc_time_source()
163{
164 return 0;
165}
166
167#endif // ARDUINO
168
169#endif // DETWS_ENABLE_RTC
User-facing configuration for DeterministicESPAsyncWebServer.
#define DETWS_RTC_I2C_ADDR
I2C address of the RTC (DS1307/DS3231 are fixed at 0x68).
The one owner of the shared I2C bus bring-up for the peripheral drivers.
void detws_i2c_begin()
Bring up the shared I2C bus on DETWS_I2C_SDA_PIN / DETWS_I2C_SCL_PIN (-1 = default).
Definition i2c.h:33
I2C real-time-clock driver (DS1307 / DS3231) - a battery-backed time source.
uint32_t rtc_time_source()
A TimeSourceFn wrapper (returns rtc_read_epoch()) to register with detws_time_source_add().
#define RTC_REG_COUNT
Number of time registers read from the RTC (seconds..year).
Definition rtc.h:28
uint32_t rtc_read_epoch()
Read the current time from the RTC over I2C.
bool rtc_set_epoch(uint32_t epoch)
Set the RTC to epoch over I2C.
bool rtc_regs_to_epoch(const uint8_t regs[RTC_REG_COUNT], uint32_t *epoch)
Convert the 7 raw RTC time registers (BCD: sec, min, hour, dow, date, month, year) to a Unix timestam...
void rtc_epoch_to_regs(uint32_t epoch, uint8_t regs[RTC_REG_COUNT])
Convert a Unix timestamp to the 7 RTC time registers (BCD, 24-hour). Pure - no I2C....
bool rtc_begin()
Initialize the I2C bus for the RTC.