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-2021 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 
86 namespace fabgl {
87 
88 
89 MC146818::MC146818()
90  : m_nvs(0),
91  m_interruptCallback(nullptr),
92  m_periodicIntTimerHandle(nullptr),
93  m_endUpdateIntTimerHandle(nullptr)
94 {
95 }
96 
97 
98 MC146818::~MC146818()
99 {
100  stopPeriodicTimer();
101  stopEndUpdateTimer();
102  if (m_nvs)
103  nvs_close(m_nvs);
104 }
105 
106 
107 void 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
129 void 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
141 void 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
151 uint8_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
173 void 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)
192 static 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
199 void 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 
240 void 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 
278 void 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 
288 void 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
299 void 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
316 void 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
Definition: canvas.cpp:36