FabGL
ESP32 Display Controller and Graphics Library
MC146818.cpp
1/*
2 Created by Fabrizio Di Vittorio (fdivitto2013@gmail.com) - <http://www.fabgl.com>
3 Copyright (c) 2019-2022 Fabrizio Di Vittorio.
4 All rights reserved.
5
6
7* Please contact fdivitto2013@gmail.com if you need a commercial license.
8
9
10* This library and related software is available under GPL v3.
11
12 FabGL is free software: you can redistribute it and/or modify
13 it under the terms of the GNU General Public License as published by
14 the Free Software Foundation, either version 3 of the License, or
15 (at your option) any later version.
16
17 FabGL is distributed in the hope that it will be useful,
18 but WITHOUT ANY WARRANTY; without even the implied warranty of
19 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 GNU General Public License for more details.
21
22 You should have received a copy of the GNU General Public License
23 along with FabGL. If not, see <http://www.gnu.org/licenses/>.
24 */
25
26
27
28#include "MC146818.h"
29
30
31#define NSVKEY_REGS "MC146818"
32
33
34// MC146818 registers
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
45
46// not MC146818 but anyway filled (to avoid Y2K bug)
47#define REG_CENTURY 0x32 // bcd: 19 or 20
48
49// status and control registers
50#define REG_A 0x0a
51#define REG_B 0x0b
52#define REG_C 0x0c
53#define REG_D 0x0d
54
55// bits of register A
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
64
65// bits of register B
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
74
75// bits of register C
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"
80
81// bits of register D
82#define REGD_VRT 0x80 // R/O, 1 = valid RAM and time
83
84
85
86namespace fabgl {
87
88
89MC146818::MC146818()
90 : m_nvs(0),
91 m_interruptCallback(nullptr),
92 m_periodicIntTimerHandle(nullptr),
93 m_endUpdateIntTimerHandle(nullptr)
94{
95}
96
97
98MC146818::~MC146818()
99{
100 stopPeriodicTimer();
101 stopEndUpdateTimer();
102 if (m_nvs)
103 nvs_close(m_nvs);
104}
105
106
107void MC146818::init(char const * NVSNameSpace)
108{
109 // load registers from NVS
110 if (!m_nvs) {
111 esp_err_t err = nvs_flash_init();
112 if (err == ESP_ERR_NVS_NO_FREE_PAGES || err == ESP_ERR_NVS_NEW_VERSION_FOUND) {
113 nvs_flash_erase();
114 nvs_flash_init();
115 }
116 nvs_open(NVSNameSpace, NVS_READWRITE, &m_nvs);
117 }
118 if (m_nvs) {
119 size_t len = sizeof(m_regs);
120 if (nvs_get_blob(m_nvs, NSVKEY_REGS, m_regs, &len) != ESP_OK) {
121 // first time initialization
122 memset(m_regs, 0, sizeof(m_regs));
123 }
124 }
125}
126
127
128// reload data from NVS
129void MC146818::reset()
130{
131 // set registers bits affected by 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; // power ok
135
136 m_regSel = 0;
137}
138
139
140// saves all data to NVS
141void MC146818::commit()
142{
143 if (m_nvs) {
144 nvs_set_blob(m_nvs, NSVKEY_REGS, m_regs, sizeof(m_regs));
145 }
146}
147
148
149// address:
150// 1 : register read
151uint8_t MC146818::read(int address)
152{
153 uint8_t retval = 0;
154 if (address == 1) {
155 if (m_regSel <= REG_YEAR || m_regSel == REG_CENTURY)
156 updateTime();
157 retval = m_regs[m_regSel];
158 if (m_regSel == REG_C) {
159 // timers are enabled when flags are read
160 enableTimers();
161 // flags are cleared on read (but after retval has been assigned!)
162 m_regs[REG_C] = 0;
163 }
164 //printf("MC146818::read(%02X) => %02X (sel=%02X)\n", address, retval, m_regSel);
165 }
166 return retval;
167}
168
169
170// address:
171// 0 : register address port (bits 0-6)
172// 1 : register write
173void MC146818::write(int address, uint8_t value)
174{
175 switch (address) {
176 case 0:
177 m_regSel = value & 0x7f;
178 break;
179 case 1:
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) ) {
183 // timers are enabled when Rate Selection > 0 or any interrupt is enabled
184 enableTimers();
185 }
186 break;
187 }
188}
189
190
191// convert decimal to packed BCD (v in range 0..99)
192static uint8_t byteToBCD(uint8_t v)
193{
194 return (v % 10) | ((v / 10) << 4);
195}
196
197
198// get time from system and fill date/time registers
199void MC146818::updateTime()
200{
201 if ((m_regs[REG_B] & REGB_SET) == 0) {
202
203 time_t now;
204 tm timeinfo;
205
206 time(&now);
207 localtime_r(&now, &timeinfo);
208
209 bool binary = m_regs[REG_B] & REGB_DM;
210 bool h24 = m_regs[REG_B] & REGB_H24;
211
212 int year = (1900 + timeinfo.tm_year); // 1986, 2021, ...
213 int century = year / 100; // 19, 20, ...
214
215 m_regs[REG_CENTURY] = byteToBCD(century);
216
217 if (binary) {
218 // binary format
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;
226 } else {
227 // BCD format
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);
235 }
236 }
237}
238
239
240void MC146818::enableTimers()
241{
242 esp_timer_init(); // can be called multiple times
243
244 // Setup Periodic Interrupt timer
245 stopPeriodicTimer();
246 int rate = m_regs[REG_A] & 0xf;
247 if (rate > 0) {
248 // supported divider (time base)?
249 int divider = (m_regs[REG_A] >> 4) & 7;
250 if (divider == 2) {
251 // we just support 32768Hz time base
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;
255 args.arg = this;
256 args.dispatch_method = ESP_TIMER_TASK;
257 esp_timer_create(&args, &m_periodicIntTimerHandle);
258 esp_timer_start_periodic(m_periodicIntTimerHandle, RATE2US[rate]);
259 //printf("MC146818: Periodic timer started\n");
260 } else {
261 printf("MC146818: Unsupported freq divider %d\n", divider);
262 }
263 }
264
265 // Setup Alarm and End of Update timer
266 if (!m_endUpdateIntTimerHandle) {
267 esp_timer_create_args_t args = { };
268 args.callback = endUpdateIntTimerFunc;
269 args.arg = this;
270 args.dispatch_method = ESP_TIMER_TASK;
271 esp_timer_create(&args, &m_endUpdateIntTimerHandle);
272 esp_timer_start_periodic(m_endUpdateIntTimerHandle, 1000000); // 1 second
273 //printf("MC146818: Alarm & End of Update timer started\n");
274 }
275}
276
277
278void MC146818::stopPeriodicTimer()
279{
280 if (m_periodicIntTimerHandle) {
281 esp_timer_stop(m_periodicIntTimerHandle);
282 esp_timer_delete(m_periodicIntTimerHandle);
283 m_periodicIntTimerHandle = nullptr;
284 }
285}
286
287
288void MC146818::stopEndUpdateTimer()
289{
290 if (m_endUpdateIntTimerHandle) {
291 esp_timer_stop(m_endUpdateIntTimerHandle);
292 esp_timer_delete(m_endUpdateIntTimerHandle);
293 m_endUpdateIntTimerHandle = nullptr;
294 }
295}
296
297
298// Handles Periodic events at specified rate
299void MC146818::periodIntTimerFunc(void * args)
300{
301 auto m = (MC146818 *) args;
302
303 // set periodic flag
304 m->m_regs[REG_C] |= REGC_PF;
305
306 // trig interrupt?
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);
310 }
311}
312
313
314// Fired every second
315// Handles Alarm and End Update events
316void MC146818::endUpdateIntTimerFunc(void * args)
317{
318 auto m = (MC146818 *) args;
319
320 if ((m->m_regs[REG_B] & REGB_SET) == 0) {
321
322 // updating flag
323 m->m_regs[REG_A] |= REGA_UIP;
324
325 m->updateTime();
326
327 // alarm?
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])) ) {
331 // yes, set flag
332 m->m_regs[REG_C] |= REGC_AF;
333 }
334
335 // always signal end update
336 m->m_regs[REG_C] |= REGC_UF;
337
338 // updating flag
339 m->m_regs[REG_A] &= ~REGA_UIP;
340
341 // trig interrupt?
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)) ) {
344 // yes
345 m->m_regs[REG_C] |= REGC_IRQF;
346 m->m_interruptCallback(m->m_context);
347 }
348
349 }
350}
351
352
353
354} // fabgl namespace