From 372306411adf223b61961b639a0d79e2d1d19494 Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Fri, 25 Mar 2022 11:17:15 -0500 Subject: [PATCH] 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() ``` --- ports/atmel-samd/supervisor/port.c | 46 +++++++++++------------------- 1 file changed, 17 insertions(+), 29 deletions(-) diff --git a/ports/atmel-samd/supervisor/port.c b/ports/atmel-samd/supervisor/port.c index cccef8be530b..704d17c68def 100644 --- a/ports/atmel-samd/supervisor/port.c +++ b/ports/atmel-samd/supervisor/port.c @@ -247,7 +247,6 @@ static void rtc_init(void) { RTC_MODE0_CTRLA_COUNTSYNC; #endif - RTC->MODE0.INTENSET.reg = RTC_MODE0_INTENSET_OVF; // Set all peripheral interrupt priorities to the lowest priority by default. 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 // is enabled. static volatile uint64_t overflowed_ticks = 0; +static uint32_t rtc_old_count; static uint32_t _get_count(uint64_t *overflow_count) { - while (1) { - // Disable interrupts so we can grab the count and the overflow atomically. - 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(); + #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. - if (!overflow_pending) { - return count; - } + 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; - // Try again if overflow hasn't been processed yet. + if (overflow_count != NULL) { + *overflow_count = overflowed_ticks; } + + return count; } volatile bool _woken_up; void RTC_Handler(void) { 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 if (intflag & RTC_MODE0_INTFLAG_PER2) { RTC->MODE0.INTFLAG.reg = RTC_MODE0_INTFLAG_PER2;