From e01cb0ae84b8c1cefa1cac3a637147ac9bc2ee43 Mon Sep 17 00:00:00 2001 From: Rohit Grover Date: Thu, 5 Mar 2015 10:30:53 +0000 Subject: [PATCH] use a virtual 64-bit timestamp to avoid wrap-around issues. gosh! --- .../TARGET_MCU_NRF51822/us_ticker.c | 50 ++++++++++++++++++- 1 file changed, 49 insertions(+), 1 deletion(-) diff --git a/libraries/mbed/targets/hal/TARGET_NORDIC/TARGET_MCU_NRF51822/us_ticker.c b/libraries/mbed/targets/hal/TARGET_NORDIC/TARGET_MCU_NRF51822/us_ticker.c index f62c9d246c..3f05ef0a17 100644 --- a/libraries/mbed/targets/hal/TARGET_NORDIC/TARGET_MCU_NRF51822/us_ticker.c +++ b/libraries/mbed/targets/hal/TARGET_NORDIC/TARGET_MCU_NRF51822/us_ticker.c @@ -189,6 +189,11 @@ uint32_t us_ticker_read() * @Note: If this function is used to setup an interrupt which is immediately * pending--such as for 'now' or a time in the past,--then the callback is * invoked a few ticks later. + * + * @suggestion: Avoid studying this code unless you're forced to. And please + * don't prove me wrong--I've spent too much time trying to get this code to + * behave correctly; currently it pretends to do so, and I don't want to learn + * anything different unless I'm given an easy way to reproduce the problem. */ void us_ticker_set_interrupt(timestamp_t timestamp) { @@ -196,7 +201,50 @@ void us_ticker_set_interrupt(timestamp_t timestamp) us_ticker_init(); } - uint32_t newCallbackTime = MICROSECONDS_TO_RTC_UNITS(timestamp); + /* + * Alert: Get yourself a beverage. + * + * In comes a harmless, merry-looking 32-bit timestamp in units of micro- + * seconds from the generic us_ticker_api. On most platforms with 32-bit + * hardware timers running at micro-second precision this poses no serious + * challenge. But on the nRF51, we use an RTC timer running at 32kHz to + * implement a low-power us-ticker. This brings with a problem that's rooted + * in the fact that 1000000 is not a multiple of 32768. + * + * Going from a micro-second based timestamp to a 32kHz based RTC-time is a + * linear mapping; but then when the 32-bit micro-second timestamp wraps + * around, unfortunately the underlying RTC counter doesn't. The result is + * that timestamp expiry checks on micro-second timestamps don't yield the + * same result when applied on the corresponding RTC timestamp values. + * You'll really need to whip up some numbers to see the problem. + * + * As far as I understand--and this could just be me turning old and rusty, + * --there is no clean solution for this problem at the wrap-around + * boundary. I'd be interested to be shown how. My solution is to translate + * the incoming 32-bit timestamp into a virtual 64-bit timestamp based on + * the knowledge of system-uptime, and then use this 64-bit value to do a + * linear mapping to RTC time. 64-bit timestamp values shouldn't wrap around + * in a few thousand years. This may be pulling down the theoretical upper + * boundary for the shelf life of nRF products when used with mbed; please + * accept my two to the power 56 apologies for that. But then life often + * forces one to live within constraints. What's the meaning of all this + * anyway? + * + * System uptime on an nRF is maintained using RTC's counter. We track the + * overflow count to extend the 24-bit hardware counter by an additional 32 + * bits. This then forms the basis for system time. + * RTC_UNITS_TO_MICROSECONDS() converts this into microsecond units (in + * 64-bits). We then shave off the lower 32-bits of it and add in the + * incoming timestamp to get a mapping into a virtual 64-bit timestamp. + * There's one additional check to handle the case of wraparound for the + * 32-bit timestamp. + */ + uint64_t timestamp64 = (RTC_UNITS_TO_MICROSECONDS(rtc1_getCounter()) & ~(uint64_t)0xFFFFFFFF) + timestamp; + if ((us_ticker_read() > 0xF0000000) && (timestamp < 0x10000000)) { + timestamp64 += (uint64_t)0x100000000; + } + + uint32_t newCallbackTime = MICROSECONDS_TO_RTC_UNITS(timestamp64); /* Check for repeat setup of an existing callback. This is actually not * important; the following code should work even without this check. */