This is an automated email from the ASF dual-hosted git repository. xiaoxiang781216 pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/nuttx.git
commit 75cbb038273bc6cbd4197418741402c146dc8f40 Author: Max Kriegleder <[email protected]> AuthorDate: Sat Oct 24 18:50:01 2026 +0200 arch/arm/nrf52: Program the tickless RTC alarm relative to the counter. rtc_prepare_alarm() set CC0 to the alarm's absolute position within the current RTC period. Two things went wrong with that: - The position was converted with USEC_TO_COUNTER(), which divides by 31 instead of 30.52 once it exceeds 0x7fffff us (~8.4 s). The target came out 1.6 % of the position too early, 2 s early at 128 s into the period. A target already behind the COUNTER was moved to COUNTER + 2, so from about 64 s into each period on, a 1 s alarm fired after 61 us. The scheduler found nothing expired and set the same alarm again, every 61 us until the deadline. - An alarm that had already passed (clock_timespec_subtract() gives zero) still took its position within its own period. Programmed just after an overflow for a deadline just before it, CC0 ended up near 0xffffff with the COUNTER near 0. The check "CC < COUNTER + 2" compares without regard to the wrap, accepted it, and the alarm fired only when the COUNTER came round again, up to 512 s later. The scheduler stood still until some other interrupt set a new alarm. Program CC0 instead from one COUNTER read: the time left until the alarm, converted to counts exactly and rounded up so that it never fires early, added to that reading. Then read the COUNTER again and, if it is less than two counts short of CC0, where a match is no longer guaranteed, move CC0 to the earliest guaranteed match. Both distances are taken modulo the 24-bit COUNTER, so this holds across an overflow. Checked on the host with the old and new rtc_prepare_alarm() against a modelled COUNTER and CC0, over 2,000,000 random deadlines from 10 ms in the past to 600 s ahead at random points in the period: the old code fired early or up to a period late in 64 % of them. The new code fired every alarm within the period 0-33 us after its deadline (one count is 30.5 us) and every passed one two counts after the call, with CC0 always at least two counts ahead when written. Assisted-by: Claude:claude-opus-5-5 Signed-off-by: Max Kriegleder <[email protected]> --- arch/arm/src/nrf52/nrf52_tickless_rtc.c | 101 +++++++++++++++----------------- 1 file changed, 48 insertions(+), 53 deletions(-) diff --git a/arch/arm/src/nrf52/nrf52_tickless_rtc.c b/arch/arm/src/nrf52/nrf52_tickless_rtc.c index 592d1ca9517..e716a30cf8d 100644 --- a/arch/arm/src/nrf52/nrf52_tickless_rtc.c +++ b/arch/arm/src/nrf52/nrf52_tickless_rtc.c @@ -60,18 +60,13 @@ #endif #define NRF52_RTC_PERIOD (512) -#define NRF52_RTC_MAX (0x00ffffff) -#define NRF52_RTC_MAX_TIME (NRF52_RTC_MAX * 31) - -/* Convert uS to timer count for f = 32768Hz, using more precision - * when possible: - * (1 / 32768) s ~ 30.51 uS ~ 31 uS - * 512 * (1 / 32768) s = 0.015625 s = 15625 uS - * So, instead of always dividing by 31, if t * 512 < (2**32 - 1), we can do: - * (t * 512) / 15625 ~ t / 30.51 +#define NRF52_RTC_FREQ (32768) + +/* A COMPARE event is guaranteed only for a CC value at least two counts + * ahead of the COUNTER (nRF52832_PS_v1.4, p. 245). */ -#define USEC_TO_COUNTER(t) (t > 0x7fffff ? (t / 31) : ((t * 512) / 15625)) +#define NRF52_RTC_MIN_DELAY (2) /* To convert from counter to uS we split the counter into one second worth * of counts (32768) and a fractional part we can safely multiply first @@ -120,70 +115,70 @@ static inline void rtc_counter_to_ts(uint32_t counter, struct timespec *now) now->tv_sec += g_tickless_dev.periods * NRF52_RTC_PERIOD; } +/* Convert a time shorter than one RTC period to counts, rounding up so + * that an alarm never fires early. 32768 / 1000000 = 512 / 15625. + */ + +static inline uint32_t rtc_ts_to_counts(const struct timespec *ts) +{ + uint32_t usec = (ts->tv_nsec + NSEC_PER_USEC - 1) / NSEC_PER_USEC; + + return (uint32_t)ts->tv_sec * NRF52_RTC_FREQ + + (usec * 512 + 15624) / 15625; +} + static void rtc_prepare_alarm(void) { struct timespec now; struct timespec delta; - uint32_t usec; uint32_t counter; - uint32_t target_counter; + uint32_t elapsed; + uint32_t counts; const uint32_t rtc_base = NRF52_RTC_GETBASE(g_tickless_dev.rtc); - /* Get current absolute time */ + /* Time left until the alarm, zero if it has passed */ counter = NRF52_RTC_GETCOUNTER_REG(rtc_base); rtc_counter_to_ts(counter, &now); - - /* Obtain relative time to alarm */ - clock_timespec_subtract(&g_tickless_dev.alarm, &now, &delta); - usec = delta.tv_sec * USEC_PER_SEC + delta.tv_nsec / NSEC_PER_USEC; - /* Check if the alarm is to expire within one RTC period, if so we can set - * the CC. - */ + /* An alarm further away is prepared again on a later overflow */ - if (usec < NRF52_RTC_PERIOD * USEC_PER_SEC) + if (delta.tv_sec >= NRF52_RTC_PERIOD) { - /* Obtain absolute number of microseconds of alarm within current - * RTC period. - */ - - usec = (g_tickless_dev.alarm.tv_sec % NRF52_RTC_PERIOD) * - USEC_PER_SEC + g_tickless_dev.alarm.tv_nsec / NSEC_PER_USEC; - - /* Compute counter value for that point in time */ - - target_counter = USEC_TO_COUNTER(usec); - - /* Enable interrupt. First set CC to distant value to ensure - * no match will be generated. Doing things this way we now that - * once we write a CC value it should be ready to match. - */ + return; + } - NRF52_RTC_SETCC_REG(rtc_base, 0, counter - 1); - NRF52_RTC_ENABLEINT(g_tickless_dev.rtc, NRF52_RTC_EVT_COMPARE0); + counts = rtc_ts_to_counts(&delta); + if (counts < NRF52_RTC_MIN_DELAY) + { + counts = NRF52_RTC_MIN_DELAY; + } - /* Set CC to desired value */ + /* Move CC0 as far ahead as it goes before enabling its interrupt */ - NRF52_RTC_SETCC_REG(rtc_base, 0, target_counter); + NRF52_RTC_SETCC_REG(rtc_base, 0, counter - 1); + NRF52_RTC_ENABLEINT(g_tickless_dev.rtc, NRF52_RTC_EVT_COMPARE0); - /* Ensure counter fires: from nRF52832_PS_v1.4 (p. 245) we know that - * "If the COUNTER is N, writing N+2 to a CC register is - * guaranteed to trigger a COMPARE event at N+2.", so anything - * less than that is not guaranteed and it may not ever match. - * - * To ensure this, we check if CC < N + 2, and if so we set CC = N+2. - * We repeat this until this is satisfied (as the counter may change - * in between calculations). - */ + /* Set CC0 'counts' after 'counter'. The COUNTER has kept running since + * it was read: if it is now less than NRF52_RTC_MIN_DELAY short of CC0, + * move CC0 to the earliest guaranteed match and check again. Both + * distances are taken modulo the 24-bit COUNTER, so this holds across + * an overflow. + */ - while (NRF52_RTC_GETCC_REG(rtc_base, 0) < - NRF52_RTC_GETCOUNTER_REG(rtc_base) + 2) + for (; ; ) + { + NRF52_RTC_SETCC_REG(rtc_base, 0, + (counter + counts) & RTC_COUNTER_MASK); + elapsed = (NRF52_RTC_GETCOUNTER_REG(rtc_base) - counter) & + RTC_COUNTER_MASK; + if (elapsed + NRF52_RTC_MIN_DELAY <= counts) { - NRF52_RTC_SETCC_REG(rtc_base, 0, - NRF52_RTC_GETCOUNTER_REG(rtc_base) + 2); + break; } + + counts = elapsed + NRF52_RTC_MIN_DELAY; } }
