-
-
Notifications
You must be signed in to change notification settings - Fork 356
/
Copy pathrtc.cpp
495 lines (427 loc) · 17.6 KB
/
rtc.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
/*
* Copyright (C) EdgeTX
*
* Based on code named
* opentx - https://github.com/opentx/opentx
* th9x - http://code.google.com/p/th9x
* er9x - http://code.google.com/p/er9x
* gruvin9x - http://code.google.com/p/gruvin9x
*
* License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include <limits.h>
#include "opentx.h"
extern void rtcdriver_settime(struct gtm * t);
#define LEAP_SECONDS_POSSIBLE 0
/* Shift A right by B bits portably, by dividing A by 2**B and
truncating towards minus infinity. A and B should be free of side
effects, and B should be in the range 0 <= B <= INT_BITS - 2, where
INT_BITS is the number of useful bits in an int. GNU code can
assume that INT_BITS is at least 32.
ISO C99 says that A >> B is implementation-defined if A < 0. Some
implementations (e.g., UNICOS 9.0 on a Cray Y-MP EL) don't shift
right in the usual way when A < 0, so SHR falls back on division if
ordinary A >> B doesn't seem to be the usual signed shift. */
#define SHR(a, b) (-1 >> 1 == -1 ? (a) >> (b) : (a) / (1 << (b)) - ((a) % (1 << (b)) < 0))
/* The extra casts in the following macros work around compiler bugs,
e.g., in Cray C 5.0.3.0. */
/* True if the arithmetic type T is an integer type. bool counts as
an integer. */
#define TYPE_IS_INTEGER(t) ((t) 1.5 == 1)
/* True if negative values of the signed integer type T use two's
complement, ones' complement, or signed magnitude representation,
respectively. Much GNU code assumes two's complement, but some
people like to be portable to all possible C hosts. */
#define TYPE_TWOS_COMPLEMENT(t) ((t) ~ (t) 0 == (t) -1)
#define TYPE_ONES_COMPLEMENT(t) ((t) ~ (t) 0 == 0)
#define TYPE_SIGNED_MAGNITUDE(t) ((t) ~ (t) 0 < (t) -1)
/* True if the arithmetic type T is signed. */
#define TYPE_SIGNED(t) (! ((t) 0 < (t) -1))
/* The maximum and minimum values for the integer type T. These
macros have undefined behavior if T is signed and has padding bits.
If this is a problem for you, please let us know how to fix it for
your host. */
#define TYPE_MINIMUM(t) \
((t) (! TYPE_SIGNED (t) \
? (t) 0 \
: TYPE_SIGNED_MAGNITUDE (t) \
? ~ (t) 0 \
: ~ (t) 0 << (sizeof (t) * CHAR_BIT - 1)))
#define TYPE_MAXIMUM(t) \
((t) (! TYPE_SIGNED (t) \
? (t) -1 \
: ~ (~ (t) 0 << (sizeof (t) * CHAR_BIT - 1))))
#ifndef TIME_T_MIN
# define TIME_T_MIN TYPE_MINIMUM (gtime_t)
#endif
#ifndef TIME_T_MAX
# define TIME_T_MAX TYPE_MAXIMUM (gtime_t)
#endif
#define TIME_T_MIDPOINT (SHR (TIME_T_MIN + TIME_T_MAX, 1) + 1)
static_assert(TYPE_IS_INTEGER(gtime_t), "gtime_t is not integer");
static_assert(TYPE_TWOS_COMPLEMENT(int), "twos complement arithmetic");
/* The code also assumes that signed integer overflow silently wraps
around, but this assumption can't be stated without causing a
diagnostic on some hosts. */
#define EPOCH_YEAR 1970
static_assert(TM_YEAR_BASE % 100 == 0, "base year is not a multiple of 100");
/* Return 1 if YEAR + TM_YEAR_BASE is a leap year. */
static inline int leapyear(long int year)
{
/* Don't add YEAR to TM_YEAR_BASE, as that might overflow.
Also, work even if YEAR is negative. */
return ((year & 3) == 0
&& (year % 100 != 0
|| ((year / 100) & 3) == (-(TM_YEAR_BASE / 100) & 3)));
}
const unsigned short int __mon_yday[2][13] = {
/* Normal years. */
{ 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365 },
/* Leap years. */
{ 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366 }
};
/* Compute the `struct tm' representation of *T,
offset OFFSET seconds east of UTC,
and store year, yday, mon, mday, wday, hour, min, sec into *TP.
Return nonzero if successful. */
int __offtime(const gtime_t * t, long int offset, struct gtm * tp)
{
long int days, rem, y;
const unsigned short int * ip;
days = *t / SECS_PER_DAY;
rem = *t % SECS_PER_DAY;
rem += offset;
while (rem < 0) {
rem += SECS_PER_DAY;
--days;
}
while (rem >= (long int)SECS_PER_DAY) {
rem -= SECS_PER_DAY;
++days;
}
tp->tm_hour = rem / SECS_PER_HOUR;
rem %= SECS_PER_HOUR;
tp->tm_min = rem / 60;
tp->tm_sec = rem % 60;
/* January 1, 1970 was a Thursday. */
tp->tm_wday = (4 + days) % 7;
if (tp->tm_wday < 0)
tp->tm_wday += 7;
y = 1970;
#define DIV(a, b) ((a) / (b) - ((a) % (b) < 0))
#define LEAPS_THRU_END_OF(y) (DIV(y, 4) - DIV(y, 100) + DIV(y, 400))
while (days < 0 || days >= (leapyear(y) ? 366 : 365)) {
/* Guess a corrected year, assuming 365 days per year. */
long int yg = y + days / 365 - (days % 365 < 0);
/* Adjust DAYS and Y to match the guessed year. */
days -= ((yg - y) * 365 + LEAPS_THRU_END_OF(yg - 1) - LEAPS_THRU_END_OF(y - 1));
y = yg;
}
tp->tm_year = y - TM_YEAR_BASE;
if (tp->tm_year != y - TM_YEAR_BASE) {
/* The year cannot be represented due to overflow. */
// __set_errno (EOVERFLOW);
return 0;
}
tp->tm_yday = days;
ip = __mon_yday[leapyear(y)];
for (y = 11; days < (long int)ip[y]; --y)
continue;
days -= ip[y];
tp->tm_mon = y;
tp->tm_mday = days + 1;
return 1;
}
/* time_r function implementations */
// G: No time zones in our implementation so just do the converion from gtime_t to struct tm
struct gtm * __localtime_r(const gtime_t * t, struct gtm * tp)
{
__offtime(t, 0, tp);
return tp;
}
/* Return an integer value measuring (YEAR1-YDAY1 HOUR1:MIN1:SEC1) -
(YEAR0-YDAY0 HOUR0:MIN0:SEC0) in seconds, assuming that the clocks
were not adjusted between the time stamps.
The YEAR values uses the same numbering as TP->tm_year. Values
need not be in the usual range. However, YEAR1 must not be less
than 2 * INT_MIN or greater than 2 * INT_MAX.
The result may overflow. It is the caller's responsibility to
detect overflow. */
static inline gtime_t ydhms_diff(long int year1, long int yday1, int hour1, int min1, int sec1,
int year0, int yday0, int hour0, int min0, int sec0)
{
static_assert(-1 / 2 == 0, "no C99 integer division");
static_assert(INT_MAX <= LONG_MAX / 2 || TIME_T_MAX <= INT_MAX, "long int year and yday are not wide enough");
/* Compute intervening leap days correctly even if year is negative.
Take care to avoid integer overflow here. */
int a4 = SHR(year1, 2) + SHR(TM_YEAR_BASE, 2) - !(year1 & 3);
int b4 = SHR(year0, 2) + SHR(TM_YEAR_BASE, 2) - !(year0 & 3);
int a100 = a4 / 25 - (a4 % 25 < 0);
int b100 = b4 / 25 - (b4 % 25 < 0);
int a400 = SHR(a100, 2);
int b400 = SHR(b100, 2);
int intervening_leap_days = (a4 - b4) - (a100 - b100) + (a400 - b400);
/* Compute the desired time in gtime_t precision. Overflow might
occur here. */
gtime_t tyear1 = year1;
gtime_t years = tyear1 - year0;
gtime_t days = 365 * years + yday1 - yday0 + intervening_leap_days;
gtime_t hours = 24 * days + hour1 - hour0;
gtime_t minutes = 60 * hours + min1 - min0;
gtime_t seconds = 60 * minutes + sec1 - sec0;
return seconds;
}
/* Return a gtime_t value corresponding to (YEAR-YDAY HOUR:MIN:SEC),
assuming that *T corresponds to *TP and that no clock adjustments
occurred between *TP and the desired time.
If TP is null, return a value not equal to *T; this avoids false matches.
If overflow occurs, yield the minimal or maximal value, except do not
yield a value equal to *T. */
static gtime_t guess_time_tm(long int year, long int yday, int hour, int min, int sec,
gtime_t * t, struct gtm * tp)
{
if (tp) {
gtime_t d = ydhms_diff(year, yday, hour, min, sec,
tp->tm_year, tp->tm_yday,
tp->tm_hour, tp->tm_min, tp->tm_sec);
gtime_t t1 = *t + d;
if ((t1 < *t) == (TYPE_SIGNED(gtime_t) ? d < 0 : TIME_T_MAX / 2 < d))
return t1;
}
/* Overflow occurred one way or another. Return the nearest result
that is actually in range, except don't report a zero difference
if the actual difference is nonzero, as that would cause a false
match; and don't oscillate between two values, as that would
confuse the spring-forward gap detector. */
return (*t < TIME_T_MIDPOINT
? (*t <= TIME_T_MIN + 1 ? *t + 1 : TIME_T_MIN)
: (TIME_T_MAX - 1 <= *t ? *t - 1 : TIME_T_MAX));
}
/* Use CONVERT to convert *T to a broken down time in *TP.
If *T is out of range for conversion, adjust it so that
it is the nearest in-range value and then convert that. */
static struct gtm * ranged_convert(struct gtm *(*convert)(const gtime_t *, struct gtm *), gtime_t * t, struct gtm * tp)
{
struct gtm * r = convert(t, tp);
if (!r && *t) {
gtime_t bad = *t;
gtime_t ok = 0;
/* BAD is a known unconvertible gtime_t, and OK is a known good one.
Use binary search to narrow the range between BAD and OK until
they differ by 1. */
while (bad != ok + (bad < 0 ? -1 : 1)) {
gtime_t mid = *t = (bad < 0 ? bad + ((ok - bad) >> 1) : ok + ((bad - ok) >> 1));
r = convert(t, tp);
if (r) ok = mid;
else bad = mid;
}
if (!r && ok) {
/* The last conversion attempt failed;
revert to the most recent successful attempt. */
*t = ok;
r = convert(t, tp);
}
}
return r;
}
/* Convert *TP to a gtime_t value, inverting
the monotonic and mostly-unit-linear conversion function CONVERT.
Use *OFFSET to keep track of a guess at the offset of the result,
compared to what the result would be for UTC without leap seconds.
If *OFFSET's guess is correct, only one CONVERT call is needed.
This function is external because it is used also by timegm.c. */
gtime_t __mktime_internal(struct gtm * tp,
struct gtm *(*convert)(const gtime_t *, struct gtm *),
gtime_t * offset)
{
gtime_t t, gt, t0, t1, t2;
struct gtm tm;
/* The maximum number of probes (calls to CONVERT) should be enough
to handle any combinations of time zone rule changes, solar time,
leap seconds, and oscillations around a spring-forward gap.
POSIX.1 prohibits leap seconds, but some hosts have them anyway. */
int remaining_probes = 6;
/* Time requested. Copy it in case CONVERT modifies *TP; this can
occur if TP is localtime's returned value and CONVERT is localtime. */
int sec = tp->tm_sec;
int min = tp->tm_min;
int hour = tp->tm_hour;
int mday = tp->tm_mday;
int mon = tp->tm_mon;
int year_requested = tp->tm_year;
/* Ensure that mon is in range, and set year accordingly. */
int mon_remainder = mon % 12;
int negative_mon_remainder = mon_remainder < 0;
int mon_years = mon / 12 - negative_mon_remainder;
long int lyear_requested = year_requested;
long int year = lyear_requested + mon_years;
/* The other values need not be in range:
the remaining code handles minor overflows correctly,
assuming int and gtime_t arithmetic wraps around.
Major overflows are caught at the end. */
/* Calculate day of year from year, month, and day of month.
The result need not be in range. */
int mon_yday = ((__mon_yday[leapyear(year)][mon_remainder + 12 * negative_mon_remainder]) - 1);
long int lmday = mday;
long int yday = mon_yday + lmday;
gtime_t guessed_offset = *offset;
int sec_requested = sec;
/*
if (LEAP_SECONDS_POSSIBLE)
{
// Handle out-of-range seconds specially,
// since ydhms_tm_diff assumes every minute has 60 seconds.
if (sec < 0)
sec = 0;
if (59 < sec)
sec = 59;
}
*/
/* Invert CONVERT by probing. First assume the same offset as last
time. */
t0 = ydhms_diff(year, yday, hour, min, sec, EPOCH_YEAR - TM_YEAR_BASE, 0, 0, 0, -guessed_offset);
if (TIME_T_MAX / INT_MAX / 366 / 24 / 60 / 60 < 3) {
/* gtime_t isn't large enough to rule out overflows, so check
for major overflows. A gross check suffices, since if t0
has overflowed, it is off by a multiple of TIME_T_MAX -
TIME_T_MIN + 1. So ignore any component of the difference
that is bounded by a small value. */
/* Approximate log base 2 of the number of time units per
biennium. A biennium is 2 years; use this unit instead of
years to avoid integer overflow. For example, 2 average
Gregorian years are 2 * 365.2425 * 24 * 60 * 60 seconds,
which is 63113904 seconds, and rint (log2 (63113904)) is
26. */
int ALOG2_SECONDS_PER_BIENNIUM = 26;
int ALOG2_MINUTES_PER_BIENNIUM = 20;
int ALOG2_HOURS_PER_BIENNIUM = 14;
int ALOG2_DAYS_PER_BIENNIUM = 10;
int LOG2_YEARS_PER_BIENNIUM = 1;
int approx_requested_biennia =
(SHR(year_requested, LOG2_YEARS_PER_BIENNIUM)
- SHR(EPOCH_YEAR - TM_YEAR_BASE, LOG2_YEARS_PER_BIENNIUM)
+ SHR(mday, ALOG2_DAYS_PER_BIENNIUM)
+ SHR(hour, ALOG2_HOURS_PER_BIENNIUM)
+ SHR(min, ALOG2_MINUTES_PER_BIENNIUM)
+ (LEAP_SECONDS_POSSIBLE
? 0
: SHR(sec, ALOG2_SECONDS_PER_BIENNIUM)));
int approx_biennia = SHR(t0, ALOG2_SECONDS_PER_BIENNIUM);
int diff = approx_biennia - approx_requested_biennia;
int abs_diff = diff < 0 ? -diff : diff;
/* IRIX 4.0.5 cc miscalculates TIME_T_MIN / 3: it erroneously
gives a positive value of 715827882. Setting a variable
first then doing math on it seems to work.
(ghazi@caip.rutgers.edu) */
gtime_t time_t_max = TIME_T_MAX;
gtime_t time_t_min = TIME_T_MIN;
gtime_t overflow_threshold = (time_t_max / 3 - time_t_min / 3) >> ALOG2_SECONDS_PER_BIENNIUM;
if (overflow_threshold < abs_diff) {
/* Overflow occurred. Try repairing it; this might work if
the time zone offset is enough to undo the overflow. */
gtime_t repaired_t0 = -1 - t0;
approx_biennia = SHR(repaired_t0, ALOG2_SECONDS_PER_BIENNIUM);
diff = approx_biennia - approx_requested_biennia;
abs_diff = diff < 0 ? -diff : diff;
if (overflow_threshold < abs_diff)
return -1;
guessed_offset += repaired_t0 - t0;
t0 = repaired_t0;
}
}
/* Repeatedly use the error to improve the guess. */
for (t = t1 = t2 = t0;
(gt = guess_time_tm(year, yday, hour, min, sec, &t, ranged_convert(convert, &t, &tm)), t != gt);
t1 = t2, t2 = t, t = gt) {
if (t == t1 && t != t2)
goto offset_found;
else if (--remaining_probes == 0)
return -1;
}
offset_found:
*offset = guessed_offset + t - t0;
if (LEAP_SECONDS_POSSIBLE && sec_requested != tm.tm_sec) {
/* Adjust time to reflect the tm_sec requested, not the normalized value.
Also, repair any damage from a false match due to a leap second. */
int sec_adjustment = (sec == 0 && tm.tm_sec == 60) - sec;
t1 = t + sec_requested;
t2 = t1 + sec_adjustment;
if (((t1 < t) != (sec_requested < 0))
| ((t2 < t1) != (sec_adjustment < 0))
| !convert(&t2, &tm))
return -1;
t = t2;
}
*tp = tm;
return t;
}
/* Convert *TP to a gtime_t value. */
gtime_t gmktime(struct gtm * tp)
{
// no time zone stuff. Just do the math ;)
static gtime_t localtime_offset;
return __mktime_internal(tp, __localtime_r, &localtime_offset);
}
/* Fill a (struct tm) TP* from a given gtime_t time stamp */
gtime_t filltm(const gtime_t * t, struct gtm * tp)
{
return __offtime(t, 0, tp);
}
gtime_t g_rtcTime;
uint8_t g_ms100 = 0; // global to allow time set function to reset to zero
void gettime(struct gtm * tm)
{
filltm(&g_rtcTime, tm); // create a struct tm date/time structure from global unix time stamp
}
void rtcGetTime(struct gtm * t);
#define RTC_ADJUST_PERIOD 60 // how often RTC is checked for accuracy [seconds]
#define RTC_ADJUST_TRESHOLD 20 // how much clock must differ before adjustment is made [seconds]
/*
Changes RTC date/time to the given UTC date/time if:
* RTC_ADJUST_PERIOD seconds have elapsed from the last time this function adjusted the RTC clock
* AND if actual RTC clock differs from the given clock by more than RTC_ADJUST_TRESHOLD seconds
* Function does nothing for a minute around midnight, where date change could produce erroneous result
*/
uint8_t rtcAdjust(uint16_t year, uint8_t mon, uint8_t day, uint8_t hour, uint8_t min, uint8_t sec)
{
static tmr10ms_t lastRtcAdjust = 0;
if ((get_tmr10ms() - lastRtcAdjust) > (RTC_ADJUST_PERIOD * 100)) {
lastRtcAdjust = get_tmr10ms();
if (year == 0 || (hour == 0 && min == 0) || (hour == 23 && min == 59)) return 0;
// convert given UTC time to local time (to seconds) and compare it with RTC
struct gtm t;
t.tm_year = year - TM_YEAR_BASE;
t.tm_mon = mon - 1;
t.tm_mday = day;
t.tm_hour = hour;
t.tm_min = min;
t.tm_sec = sec;
gtime_t newTime = gmktime(&t) + g_eeGeneral.timezone * 3600;
gtime_t diff = (g_rtcTime > newTime) ? (g_rtcTime - newTime) : (newTime - g_rtcTime);
#if defined(DEBUG) && defined (PCBTARANIS)
struct gtm utm;
rtcGetTime(&utm);
gtime_t rtcTime = gmktime(&utm);
TRACE("rtc: %d, grtc: %d, gps: %d, diff: %d, ", rtcTime, g_rtcTime, newTime, diff);
#endif
if (diff > RTC_ADJUST_TRESHOLD) {
// convert newTime to struct gtm and set RTC clock
filltm(&newTime, &t);
g_rtcTime = gmktime(&t); // update local timestamp and get wday calculated
rtcSetTime(&t);
TRACE("RTC clock adjusted to %04d-%02d-%02d %02d:%02d:%02d", year, mon, day, hour, min, sec);
// TODO perhaps some kind of audio notification ???
return 1;
}
}
return 0;
}