31 #define NSVKEY_REGS "MC146818" 35 #define REG_SECONDS 0x00 // bin: 0..59, bcd: 00..59 36 #define REG_SECONDS_ALARM 0x01 // like REG_SECONDS or >=0xc0 for don't care 37 #define REG_MINUTES 0x02 // bin: 0..59, bcd: 00..59 38 #define REG_MINUTES_ALARM 0x03 // like REG_MINUTES or >=0xc0 for don't care 39 #define REG_HOURS 0x04 // bin: 1..12 or 0..23, bcd: 01..12 or 00..23 (ORed with 0x80 for PM when range is 1..12) 40 #define REG_HOURS_ALARM 0x05 // like REG_HOURS or >=0xc0 for don't care 41 #define REG_DAYOFWEEK 0x06 // bin: 1..7, bcd: 01..07, (sunday = 1) 42 #define REG_DAYOFMONTH 0x07 // bin: 1..31, bcd: 01..31 43 #define REG_MONTH 0x08 // bin: 1..12, bcd: 01..12 44 #define REG_YEAR 0x09 // bin: 0..99, bcd: 00..99 47 #define REG_CENTURY 0x32 // bcd: 19 or 20 56 #define REGA_RS0 0x01 // R/W, rate selection for square wave gen and Periodic Interrupt 57 #define REGA_RS1 0x02 // R/W 58 #define REGA_RS2 0x04 // R/W 59 #define REGA_RS3 0x08 // R/W 60 #define REGA_DV0 0x10 // R/W, input freq divider 61 #define REGA_DV1 0x20 // R/W 62 #define REGA_DV2 0x40 // R/W 63 #define REGA_UIP 0x80 // R/O, 1 = update in progress 66 #define REGB_DSE 0x01 // R/W, 1 = enabled daylight save 67 #define REGB_H24 0x02 // R/W, 1 = 24h mode, 0 = 12h mode 68 #define REGB_DM 0x04 // R/W, 1 = binary format, 0 = BCD format 69 #define REGB_SQWE 0x08 // R/W, 1 = enable SQWE output 70 #define REGB_UIE 0x10 // R/W, 1 = enable update ended interrupt 71 #define REGB_AIE 0x20 // R/W, 1 = enable alarm interrupt 72 #define REGB_PIE 0x40 // R/W, 1 = enable period interrupts 73 #define REGB_SET 0x80 // R/W, 1 = halt time updates 76 #define REGC_UF 0x10 // R/O, 1 = update ended interrupt flag 77 #define REGC_AF 0x20 // R/O, 1 = alarm interrupt flag 78 #define REGC_PF 0x40 // R/O, 1 = period interrupt flag 79 #define REGC_IRQF 0x80 // R/O, this is "UF & UIE | AF & AIE | PF & PIE" 82 #define REGD_VRT 0x80 // R/O, 1 = valid RAM and time 91 m_interruptCallback(nullptr),
92 m_periodicIntTimerHandle(nullptr),
93 m_endUpdateIntTimerHandle(nullptr)
101 stopEndUpdateTimer();
107 void MC146818::init(
char const * NVSNameSpace)
111 esp_err_t err = nvs_flash_init();
112 if (err == ESP_ERR_NVS_NO_FREE_PAGES || err == ESP_ERR_NVS_NEW_VERSION_FOUND) {
116 nvs_open(NVSNameSpace, NVS_READWRITE, &m_nvs);
119 size_t len =
sizeof(m_regs);
120 if (nvs_get_blob(m_nvs, NSVKEY_REGS, m_regs, &len) != ESP_OK) {
122 memset(m_regs, 0,
sizeof(m_regs));
129 void MC146818::reset()
132 m_regs[REG_B] &= ~(REGB_PIE | REGB_AIE | REGB_UIE | REGB_SQWE);
133 m_regs[REG_C] &= ~(REGC_IRQF | REGC_PF | REGC_AF | REGC_UF);
134 m_regs[REG_D] = REGD_VRT;
141 void MC146818::commit()
144 nvs_set_blob(m_nvs, NSVKEY_REGS, m_regs,
sizeof(m_regs));
151 uint8_t MC146818::read(
int address)
155 if (m_regSel <= REG_YEAR || m_regSel == REG_CENTURY)
157 retval = m_regs[m_regSel];
158 if (m_regSel == REG_C) {
173 void MC146818::write(
int address, uint8_t value)
177 m_regSel = value & 0x7f;
180 m_regs[m_regSel] = value;
181 if ( (m_regSel == REG_A && (value & 0xf) != 0) ||
182 (m_regSel == REG_B && (value & (REGB_UIE | REGB_AIE | REGB_PIE)) != 0) ) {
192 static uint8_t byteToBCD(uint8_t v)
194 return (v % 10) | ((v / 10) << 4);
199 void MC146818::updateTime()
201 if ((m_regs[REG_B] & REGB_SET) == 0) {
207 localtime_r(&now, &timeinfo);
209 bool binary = m_regs[REG_B] & REGB_DM;
210 bool h24 = m_regs[REG_B] & REGB_H24;
212 int year = (1900 + timeinfo.tm_year);
213 int century = year / 100;
215 m_regs[REG_CENTURY] = byteToBCD(century);
219 m_regs[REG_SECONDS] = imin(timeinfo.tm_sec, 59);
220 m_regs[REG_MINUTES] = timeinfo.tm_min;
221 m_regs[REG_HOURS] = h24 ? timeinfo.tm_hour : (((timeinfo.tm_hour - 1) % 12 + 1) | (timeinfo.tm_hour >= 12 ? 0x80 : 0x00));
222 m_regs[REG_DAYOFWEEK] = timeinfo.tm_wday + 1;
223 m_regs[REG_DAYOFMONTH] = timeinfo.tm_mday;
224 m_regs[REG_MONTH] = timeinfo.tm_mon + 1;
225 m_regs[REG_YEAR] = year - century * 100;
228 m_regs[REG_SECONDS] = byteToBCD(imin(timeinfo.tm_sec, 59));
229 m_regs[REG_MINUTES] = byteToBCD(timeinfo.tm_min);
230 m_regs[REG_HOURS] = h24 ? byteToBCD(timeinfo.tm_hour) : (byteToBCD((timeinfo.tm_hour - 1) % 12 + 1) | (timeinfo.tm_hour >= 12 ? 0x80 : 0x00));
231 m_regs[REG_DAYOFWEEK] = byteToBCD(timeinfo.tm_wday + 1);
232 m_regs[REG_DAYOFMONTH] = byteToBCD(timeinfo.tm_mday);
233 m_regs[REG_MONTH] = byteToBCD(timeinfo.tm_mon + 1);
234 m_regs[REG_YEAR] = byteToBCD(year - century * 100);
240 void MC146818::enableTimers()
246 int rate = m_regs[REG_A] & 0xf;
249 int divider = (m_regs[REG_A] >> 4) & 7;
252 static const int RATE2US[16] = { 0, 3906, 7812, 122, 244, 488, 976, 1953, 3906, 7812, 15625, 31250, 62500, 125000, 250000, 500000 };
253 esp_timer_create_args_t args = { };
254 args.callback = periodIntTimerFunc;
256 args.dispatch_method = ESP_TIMER_TASK;
257 esp_timer_create(&args, &m_periodicIntTimerHandle);
258 esp_timer_start_periodic(m_periodicIntTimerHandle, RATE2US[rate]);
261 printf(
"MC146818: Unsupported freq divider %d\n", divider);
266 if (!m_endUpdateIntTimerHandle) {
267 esp_timer_create_args_t args = { };
268 args.callback = endUpdateIntTimerFunc;
270 args.dispatch_method = ESP_TIMER_TASK;
271 esp_timer_create(&args, &m_endUpdateIntTimerHandle);
272 esp_timer_start_periodic(m_endUpdateIntTimerHandle, 1000000);
278 void MC146818::stopPeriodicTimer()
280 if (m_periodicIntTimerHandle) {
281 esp_timer_stop(m_periodicIntTimerHandle);
282 esp_timer_delete(m_periodicIntTimerHandle);
283 m_periodicIntTimerHandle =
nullptr;
288 void MC146818::stopEndUpdateTimer()
290 if (m_endUpdateIntTimerHandle) {
291 esp_timer_stop(m_endUpdateIntTimerHandle);
292 esp_timer_delete(m_endUpdateIntTimerHandle);
293 m_endUpdateIntTimerHandle =
nullptr;
299 void MC146818::periodIntTimerFunc(
void * args)
301 auto m = (MC146818 *) args;
304 m->m_regs[REG_C] |= REGC_PF;
307 if (m->m_regs[REG_B] & REGB_PIE) {
308 m->m_regs[REG_C] |= REGC_PF | REGC_IRQF;
309 m->m_interruptCallback(m->m_context);
316 void MC146818::endUpdateIntTimerFunc(
void * args)
318 auto m = (MC146818 *) args;
320 if ((m->m_regs[REG_B] & REGB_SET) == 0) {
323 m->m_regs[REG_A] |= REGA_UIP;
328 if ( ((m->m_regs[REG_SECONDS_ALARM] & 0xc0) == 0xc0 || (m->m_regs[REG_SECONDS_ALARM] == m->m_regs[REG_SECONDS])) &&
329 ((m->m_regs[REG_MINUTES_ALARM] & 0xc0) == 0xc0 || (m->m_regs[REG_MINUTES_ALARM] == m->m_regs[REG_MINUTES])) &&
330 ((m->m_regs[REG_HOURS_ALARM] & 0xc0) == 0xc0 || (m->m_regs[REG_HOURS_ALARM] == m->m_regs[REG_HOURS])) ) {
332 m->m_regs[REG_C] |= REGC_AF;
336 m->m_regs[REG_C] |= REGC_UF;
339 m->m_regs[REG_A] &= ~REGA_UIP;
342 if ( ((m->m_regs[REG_B] & REGB_UIE) && (m->m_regs[REG_C] & REGC_UF)) ||
343 ((m->m_regs[REG_B] & REGB_AIE) && (m->m_regs[REG_C] & REGC_AF)) ) {
345 m->m_regs[REG_C] |= REGC_IRQF;
346 m->m_interruptCallback(m->m_context);