samd: Don't rely on RTC interrupt

I instrumented RTC_Handler and determined that on SAMD51 it was possible
for the interrupt to be delivered well before the actual overflow of the
RTC COUNT register (e.g., a value as small as 0xffff_fffd could be seen
at the time of overflow)

Rather than depending on the overflow interrupt coming in at the same time
as COUNT overflows (exactly), rely only on observed values of COUNT in
_get_count, overflowing when it wraps around from a high value to a low
one.

With this change, PLUS a second change so that it is possible to warp
the RTC counter close to an overflow and test in 20ms instead of 3 days,
there was no problem detected over 20000+ overflows. Before, a substantial
fraction (much greater than 10%) of overflows failed.

Fixes #5985

Change to common-hal/rtc/RTC.c for time warping (plus make rtc_old_count non-static):
```patch
 void common_hal_rtc_set_calibration(int calibration) {
+
+    common_hal_mcu_disable_interrupts();
+
+        RTC->MODE0.COUNT.reg = 0xffffff00;
+        rtc_old_count = 0;
+        do {
+        while ((RTC->MODE0.SYNCBUSY.reg & (RTC_MODE0_SYNCBUSY_COUNTSYNC | RTC_MODE0_SYNCBUSY_COUNT)) != 0) { }
+    }
+    while(RTC->MODE0.COUNT.reg < 0xffffff00);
+    common_hal_mcu_enable_interrupts();
+
+    mp_printf(&mp_plat_print, "Warping RTC in calibration setter count=%08x rtc_old_count=%08x\n", RTC->MODE0.COUNT.reg, rtc_old_count);
```

Test program:
```python
import time
from rtc import RTC

i = 0
while True:
    RTC().calibration = 1 # Warps to ~16ms before overflow, with patch to RTC code
    t0 = time.monotonic_ns()
    et = t0 + 20_000_000 # 20ms
    while (t1 := time.monotonic_ns()) < et: pass
    i += 1
    print(f"{i:6d}: duration {t1-t0}")
    if t1-t0 > 200_000_000: break
    print()
```
This commit is contained in:
Jeff Epler 2022-03-25 11:17:15 -05:00
parent fcde108d03
commit 372306411a
No known key found for this signature in database
GPG Key ID: D5BF15AB975AB4DE

View File

@ -247,7 +247,6 @@ static void rtc_init(void) {
RTC_MODE0_CTRLA_COUNTSYNC; RTC_MODE0_CTRLA_COUNTSYNC;
#endif #endif
RTC->MODE0.INTENSET.reg = RTC_MODE0_INTENSET_OVF;
// Set all peripheral interrupt priorities to the lowest priority by default. // Set all peripheral interrupt priorities to the lowest priority by default.
for (uint16_t i = 0; i < PERIPH_COUNT_IRQn; i++) { for (uint16_t i = 0; i < PERIPH_COUNT_IRQn; i++) {
@ -501,45 +500,34 @@ uint32_t port_get_saved_word(void) {
// TODO: Move this to an RTC backup register so we can preserve it when only the BACKUP power domain // TODO: Move this to an RTC backup register so we can preserve it when only the BACKUP power domain
// is enabled. // is enabled.
static volatile uint64_t overflowed_ticks = 0; static volatile uint64_t overflowed_ticks = 0;
static uint32_t rtc_old_count;
static uint32_t _get_count(uint64_t *overflow_count) { static uint32_t _get_count(uint64_t *overflow_count) {
while (1) { #ifdef SAM_D5X_E5X
// Disable interrupts so we can grab the count and the overflow atomically. while ((RTC->MODE0.SYNCBUSY.reg & (RTC_MODE0_SYNCBUSY_COUNTSYNC | RTC_MODE0_SYNCBUSY_COUNT)) != 0) {
common_hal_mcu_disable_interrupts();
#ifdef SAM_D5X_E5X
while ((RTC->MODE0.SYNCBUSY.reg & (RTC_MODE0_SYNCBUSY_COUNTSYNC | RTC_MODE0_SYNCBUSY_COUNT)) != 0) {
}
#endif
// SAMD21 does continuous sync so we don't need to wait here.
uint32_t count = RTC->MODE0.COUNT.reg;
if (overflow_count != NULL) {
*overflow_count = overflowed_ticks;
}
bool overflow_pending = RTC->MODE0.INTFLAG.bit.OVF;
common_hal_mcu_enable_interrupts();
if (!overflow_pending) {
return count;
}
// Try again if overflow hasn't been processed yet.
} }
#endif
// SAMD21 does continuous sync so we don't need to wait here.
uint32_t count = RTC->MODE0.COUNT.reg;
if (count < rtc_old_count) {
// Our RTC is 32 bits and we're clocking it at 16.384khz which is 16 (2 ** 4) subticks per
// tick.
overflowed_ticks += (1L << (32 - 4));
}
rtc_old_count = count;
if (overflow_count != NULL) {
*overflow_count = overflowed_ticks;
}
return count;
} }
volatile bool _woken_up; volatile bool _woken_up;
void RTC_Handler(void) { void RTC_Handler(void) {
uint32_t intflag = RTC->MODE0.INTFLAG.reg; uint32_t intflag = RTC->MODE0.INTFLAG.reg;
if (intflag & RTC_MODE0_INTFLAG_OVF) {
RTC->MODE0.INTFLAG.reg = RTC_MODE0_INTFLAG_OVF;
// Our RTC is 32 bits and we're clocking it at 16.384khz which is 16 (2 ** 4) subticks per
// tick.
overflowed_ticks += (1L << (32 - 4));
}
#ifdef SAM_D5X_E5X #ifdef SAM_D5X_E5X
if (intflag & RTC_MODE0_INTFLAG_PER2) { if (intflag & RTC_MODE0_INTFLAG_PER2) {
RTC->MODE0.INTFLAG.reg = RTC_MODE0_INTFLAG_PER2; RTC->MODE0.INTFLAG.reg = RTC_MODE0_INTFLAG_PER2;