Firmware SDK
twr_rtc.c
1 #include <twr_rtc.h>
2 #include <twr_irq.h>
3 #include <stm32l0xx.h>
4 
5 #define _TWR_RTC_LEAP_YEAR(year) ((((year) % 4 == 0) && ((year) % 100 != 0)) || ((year) % 400 == 0))
6 #define _TWR_RTC_DAYS_IN_YEAR(x) _TWR_RTC_LEAP_YEAR(x) ? 366 : 365
7 #define _TWR_RTC_CALENDAR_CENTURY 2000
8 #define _TWR_RTC_OFFSET_YEAR 1970
9 #define _TWR_RTC_SECONDS_PER_DAY 86400
10 #define _TWR_RTC_SECONDS_PER_HOUR 3600
11 #define _TWR_RTC_SECONDS_PER_MINUTE 60
12 
21 typedef union {
22  uint32_t i;
23  struct {
24  unsigned int SU : 4;
25  unsigned int ST : 3;
26  unsigned int res1 : 1;
27  unsigned int MNU : 4;
28  unsigned int MNT : 3;
29  unsigned int res2 : 1;
30  unsigned int HU : 4;
31  unsigned int HT : 2;
32  unsigned int PM : 1;
33  };
34 } RTC_TR;
35 
44 typedef union {
45  uint32_t i;
46  struct {
47  unsigned int DU : 4;
48  unsigned int DT : 2;
49  unsigned int res : 2;
50  unsigned int MU : 4;
51  unsigned int MT : 1;
52  unsigned int WDU : 3;
53  unsigned int YU : 4;
54  unsigned int YT : 4;
55  };
56 } RTC_DR;
57 
66 typedef union {
67  uint32_t i;
68  uint16_t SS;
69 } RTC_SSR;
70 
71 
73 
74 /*
75  * The following table can be used to quickly determine whether a particular
76  * year is a leap year. The index to the array is the year number within the
77  * century, e.g., is_leap[0] returns the leap year information for the year
78  * 2000.
79  *
80  * The following Python 3 program can be used to generate the contents of the
81  * table:
82  *
83  * from calendar import isleap
84  * from datetime import date
85  * for year in range(2000, 2099+1):
86  * print("%d, " % isleap(year), end='')
87  */
88 static const uint8_t is_leap[] = {
89  1, 0, 0, 0, 1, 0, 0, 0, 1, 0,
90  0, 0, 1, 0, 0, 0, 1, 0, 0, 0,
91  1, 0, 0, 0, 1, 0, 0, 0, 1, 0,
92  0, 0, 1, 0, 0, 0, 1, 0, 0, 0,
93  1, 0, 0, 0, 1, 0, 0, 0, 1, 0,
94  0, 0, 1, 0, 0, 0, 1, 0, 0, 0,
95  1, 0, 0, 0, 1, 0, 0, 0, 1, 0,
96  0, 0, 1, 0, 0, 0, 1, 0, 0, 0,
97  1, 0, 0, 0, 1, 0, 0, 0, 1, 0,
98  0, 0, 1, 0, 0, 0, 1, 0, 0, 0
99 };
100 
101 /*
102  * Pre-computed number of days since the Epoch (January 1st, 1970) for the years
103  * from 2000 to 2099 inclusive. The first element correspondings to the year
104  * 2000, i.e., days_since_epoch[0] returns the number of days from January 1st,
105  * 1970 through December 31st,
106  * 1999.
107  *
108  * The following Python 3 program can be used to generate the contents of the
109  * table:
110  *
111  * from datetime import date epoch = date(1970, 1, 1) for year in range(2000,
112  * 2099+1): diff = date(year, 1, 1) - epoch print("0x%x, " % diff.days, end='')
113  */
114 static const uint16_t days_since_epoch[] = {
115  0x2acd, 0x2c3b, 0x2da8, 0x2f15, 0x3082, 0x31f0, 0x335d, 0x34ca, 0x3637, 0x37a5,
116  0x3912, 0x3a7f, 0x3bec, 0x3d5a, 0x3ec7, 0x4034, 0x41a1, 0x430f, 0x447c, 0x45e9,
117  0x4756, 0x48c4, 0x4a31, 0x4b9e, 0x4d0b, 0x4e79, 0x4fe6, 0x5153, 0x52c0, 0x542e,
118  0x559b, 0x5708, 0x5875, 0x59e3, 0x5b50, 0x5cbd, 0x5e2a, 0x5f98, 0x6105, 0x6272,
119  0x63df, 0x654d, 0x66ba, 0x6827, 0x6994, 0x6b02, 0x6c6f, 0x6ddc, 0x6f49, 0x70b7,
120  0x7224, 0x7391, 0x74fe, 0x766c, 0x77d9, 0x7946, 0x7ab3, 0x7c21, 0x7d8e, 0x7efb,
121  0x8068, 0x81d6, 0x8343, 0x84b0, 0x861d, 0x878b, 0x88f8, 0x8a65, 0x8bd2, 0x8d40,
122  0x8ead, 0x901a, 0x9187, 0x92f5, 0x9462, 0x95cf, 0x973c, 0x98aa, 0x9a17, 0x9b84,
123  0x9cf1, 0x9e5f, 0x9fcc, 0xa139, 0xa2a6, 0xa414, 0xa581, 0xa6ee, 0xa85b, 0xa9c9,
124  0xab36, 0xaca3, 0xae10, 0xaf7e, 0xb0eb, 0xb258, 0xb3c5, 0xb533, 0xb6a0, 0xb80d
125 };
126 
127 
128 /*
129  * The following table can be used to quickly retrieve the number of days since
130  * January 1st until the 1st day of the given month (exclusive). Two variants
131  * are provided for regular and leap years. E.g., days_since_new_year[0][5]
132  * returns the number of days from January 1st through April 30th in a regular
133  * (non-leap) year.
134  */
135 static const uint16_t days_since_new_year[2][12] = {
136  {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334}, // Regular year
137  {0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335} // Leap year
138 };
139 
140 void twr_rtc_init(void)
141 {
142 }
143 
144 uint32_t twr_rtc_datetime_to_timestamp(struct tm *tm)
145 {
146  uint32_t days = 0, seconds = 0;
147  uint16_t i;
148  int year = tm->tm_year + 1900;
149 
150  // Year is below offset year
151  if (year < _TWR_RTC_OFFSET_YEAR)
152  {
153  return 0;
154  }
155  // Days in back years
156  for (i = _TWR_RTC_OFFSET_YEAR; i < year; i++)
157  {
158  days += _TWR_RTC_DAYS_IN_YEAR(i);
159  }
160  // Days in current year
161  days += days_since_new_year[_TWR_RTC_LEAP_YEAR(year)][tm->tm_mon];
162 
163  // Day starts with 1
164  days += tm->tm_mday - 1;
165  seconds = days * _TWR_RTC_SECONDS_PER_DAY;
166  seconds += tm->tm_hour * _TWR_RTC_SECONDS_PER_HOUR;
167  seconds += tm->tm_min * _TWR_RTC_SECONDS_PER_MINUTE;
168  seconds += tm->tm_sec;
169 
170  return seconds;
171 }
172 
173 void twr_rtc_get_datetime(struct tm *tm)
174 {
175  // The value 0xFFFFFFFF is a value that the RTC_DR and RTC_TR registers
176  // should never have. We use that to trigger a full conversion on the first
177  // invocation.
178  //
179  // The RTC does not keep track of daylight saving time, so we set the
180  // corresponding field to -1.
181  static struct {
182  RTC_DR dr;
183  RTC_TR tr;
184  struct tm tm;
185  } mem = { .dr.i = 0xFFFFFFFF, .tr.i = 0xFFFFFFFF, .tm.tm_isdst = -1 };
186 
187  // If RTC_SSR or RTC_TR is read, the value of higher-order date/time
188  // registers is frozen until RTC_DR is read. Make sure to read all these
189  // registers as quickly as possible before doing more.
190  RTC_TR tr = { .i = RTC->TR };
191  RTC_DR dr = { .i = RTC->DR };
192 
193  if (tr.i != mem.tr.i) {
194  mem.tm.tm_sec = 10 * tr.ST + tr.SU;
195  mem.tm.tm_min = 10 * tr.MNT + tr.MNU;
196  mem.tm.tm_hour = 10 * tr.HT + tr.HU;
197  mem.tr = tr;
198  }
199 
200  // Struct tm counts week days from Sunday starting at 0.
201  // RTC counts week days from Mondays starting at 1.
202  // Mo Tu We Th Fr Sa Su
203  // tm 1 2 3 4 5 6 0
204  // rtc 1 2 3 4 5 6 7
205 
206  if (dr.i != mem.dr.i) {
207  int year = 10 * dr.YT + dr.YU;
208  mem.tm.tm_mday = 10 * dr.DT + dr.DU;
209  mem.tm.tm_mon = 10 * dr.MT + dr.MU - 1;
210  mem.tm.tm_year = year + _TWR_RTC_CALENDAR_CENTURY - 1900;
211  mem.tm.tm_wday = dr.WDU == 7 ? 0 : dr.WDU;
212  mem.tm.tm_yday = days_since_new_year[is_leap[year]][mem.tm.tm_mon] + mem.tm.tm_mday - 1;
213  mem.dr = dr;
214  }
215  (*tm) = mem.tm;
216 }
217 
218 void twr_rtc_get_timestamp(struct timespec *tv)
219 {
220  // The value 0xFFFFFFFF is a value that the RTC_DR and RTC_TR registers
221  // should never have. We use that to trigger a full conversion on the first
222  // invocation.
223  static struct {
224  RTC_DR dr;
225  RTC_TR tr;
226  time_t s1;
227  time_t s2;
228  } mem = { .dr.i = 0xFFFFFFFF, .tr.i = 0xFFFFFFFF };
229 
230  // FIXME: ssr can be larger than prediv_s after a shift operation. We may
231  // need to adjust the date/time in that case. See STM32L0X3 reference
232  // manual, section "27.7.10 RTC sub second register"
233  RTC_SSR ssr = { .i = RTC->SSR };
234 
235  // If RTC_SSR or RTC_TR is read, the value of higher-order date/time
236  // registers is frozen until RTC_DR is read. Make sure to read all these
237  // registers as quickly as possible before doing more.
238  RTC_TR tr = { .i = RTC->TR };
239  RTC_DR dr = { .i = RTC->DR };
240 
241  // Note: The code generated by the following expression will only be fast if
242  // TWR_RTC_PREDIV_S is a power of two.
243  tv->tv_nsec = 1000000 * (TWR_RTC_PREDIV_S - 1 - ssr.SS) / TWR_RTC_PREDIV_S;
244 
245  if (tr.i != mem.tr.i) {
246  mem.s1 = (10 * tr.HT + tr.HU) * _TWR_RTC_SECONDS_PER_HOUR
247  + (10 * tr.MNT + tr.MNU) * _TWR_RTC_SECONDS_PER_MINUTE
248  + 10 * tr.ST + tr.SU;
249 
250  mem.tr = tr;
251  }
252 
253  // Struct tm counts week days from Sunday starting at 0.
254  // RTC counts week days from Mondays starting at 1.
255  // Mo Tu We Th Fr Sa Su
256  // tm 1 2 3 4 5 6 0
257  // rtc 1 2 3 4 5 6 7
258 
259  if (dr.i != mem.dr.i) {
260  int year = 10 * dr.YT + dr.YU;
261  mem.s2 = (days_since_epoch[year]
262  + days_since_new_year[is_leap[year]][10 * dr.MT + dr.MU - 1]
263  + 10 * dr.DT + dr.DU - 1) * _TWR_RTC_SECONDS_PER_DAY;
264 
265  mem.dr = dr;
266  }
267  tv->tv_sec = mem.s1 + mem.s2;
268 }
269 
270 int twr_rtc_set_datetime(struct tm *tm, int ms)
271 {
272  int year = tm->tm_year + 1900 - _TWR_RTC_CALENDAR_CENTURY;
273  if (year < 0 || year > 99) return -1;
274 
275  int month = tm->tm_mon + 1;
276 
277  if (ms < 0 || ms > 999) return -2;
278  RTC_SSR ssr = {
279  .SS = TWR_RTC_PREDIV_S - ms * (TWR_RTC_PREDIV_S + 1) / 1000
280  };
281 
282  // Note: The RTC datasheet says that the values of the two reserved bits
283  // must be left set at their reset values. To err on the side of caution, we
284  // fetch those values from the RTC and preserve them when modifying the
285  // register value.
286  RTC_TR old_tr = { .i = RTC->TR };
287 
288  RTC_TR tr = {
289  .SU = tm->tm_sec % 10,
290  .ST = tm->tm_sec / 10,
291  .res1 = old_tr.res1,
292  .MNU = tm->tm_min % 10,
293  .MNT = tm->tm_min / 10,
294  .res2 = old_tr.res2,
295  .HU = tm->tm_hour % 10,
296  .HT = tm->tm_hour / 10,
297  .PM = 0
298  };
299 
300  // Struct tm counts week days from Sunday starting at 0.
301  // RTC counts week days from Mondays starting at 1.
302  // Mo Tu We Th Fr Sa Su
303  // tm 1 2 3 4 5 6 0
304  // rtc 1 2 3 4 5 6 7
305 
306  RTC_DR old_dr = { .i = RTC->DR };
307  RTC_DR dr = {
308  .DU = tm->tm_mday % 10,
309  .DT = tm->tm_mday / 10,
310  .res = old_dr.res,
311  .MU = month % 10,
312  .MT = month / 10,
313  .WDU = tm->tm_wday == 0 ? 7 : tm->tm_wday,
314  .YU = year % 10,
315  .YT = year / 10,
316  };
317 
318  twr_rtc_enable_write();
319  twr_rtc_set_init(true);
320  RTC->SSR = ssr.i;
321  RTC->TR = tr.i;
322  RTC->DR = dr.i;
323  twr_rtc_set_init(false);
324  twr_rtc_disable_write();
325  return 0;
326 }
327 
328 void twr_rtc_set_init(bool state)
329 {
330  if (state) {
331  // Enable initialization mode
332  RTC->ISR |= RTC_ISR_INIT;
333 
334  // Wait for RTC to be in initialization mode...
335  while ((RTC->ISR & RTC_ISR_INITF) == 0)
336  {
337  continue;
338  }
339  } else {
340  // Exit from initialization mode
341  RTC->ISR &= ~RTC_ISR_INIT;
342  }
343 }
uint32_t twr_rtc_datetime_to_timestamp(struct tm *tm)
Convert date and time to UNIX timestamp.
Definition: twr_rtc.c:144
int _twr_rtc_writable_semaphore
Initialize real-time clock.
Definition: twr_rtc.c:72
void twr_rtc_get_timestamp(struct timespec *tv)
Definition: twr_rtc.c:218
void twr_rtc_get_datetime(struct tm *tm)
Definition: twr_rtc.c:173
int twr_rtc_set_datetime(struct tm *tm, int ms)
Definition: twr_rtc.c:270
void twr_rtc_set_init(bool state)
Enable or disable RTC initialization mode.
Definition: twr_rtc.c:328
Definition: twr_rtc.c:44
Definition: twr_rtc.c:21