31#define NSVKEY_REGS "MC146818"
35#define REG_SECONDS 0x00
36#define REG_SECONDS_ALARM 0x01
37#define REG_MINUTES 0x02
38#define REG_MINUTES_ALARM 0x03
40#define REG_HOURS_ALARM 0x05
41#define REG_DAYOFWEEK 0x06
42#define REG_DAYOFMONTH 0x07
47#define REG_CENTURY 0x32
91 m_interruptCallback(nullptr),
92 m_periodicIntTimerHandle(nullptr),
93 m_endUpdateIntTimerHandle(nullptr)
101 stopEndUpdateTimer();
107void 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));
129void 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;
141void MC146818::commit()
144 nvs_set_blob(m_nvs, NSVKEY_REGS, m_regs,
sizeof(m_regs));
151uint8_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) {
173void 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) ) {
192static uint8_t byteToBCD(uint8_t v)
194 return (v % 10) | ((v / 10) << 4);
199void 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);
240void 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);
278void MC146818::stopPeriodicTimer()
280 if (m_periodicIntTimerHandle) {
281 esp_timer_stop(m_periodicIntTimerHandle);
282 esp_timer_delete(m_periodicIntTimerHandle);
283 m_periodicIntTimerHandle =
nullptr;
288void MC146818::stopEndUpdateTimer()
290 if (m_endUpdateIntTimerHandle) {
291 esp_timer_stop(m_endUpdateIntTimerHandle);
292 esp_timer_delete(m_endUpdateIntTimerHandle);
293 m_endUpdateIntTimerHandle =
nullptr;
299void 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);
316void 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);