From d86b8275ec441cb8bbce2e1dae41269de416b1a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Th=C3=A9baudeau?= Date: Wed, 4 Dec 2013 16:17:14 +0100 Subject: [PATCH] cc2538: clock: Fix time drift occurring with PM1/2 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The clock adjustments made when waking up from PM1/2 were very inaccurate. If relying on ContikiMAC's rtimer to sleep, this led to Contiki's software clock time, seconds and etimers to be 2.5 s slower after each min, i.e. 1 hour slower after each day, which is a show stopper issue for most real-life applications. This was caused by a lack of accuracy in several pieces of code during sleep entry and wake-up: - It was difficult to synchronize the calls to RTIMER_NOW() before and after sleep with the deactivation and activation of the SysTick peripheral caused by PM1/2. This caused an inaccuracy in the corrective number of ticks passed to clock_adjust(). - The value passed to clock_adjust() was truncated from an rtimer_clock_t value, but the accumulated error caused by these truncated bits was ignored. - The SysTick peripheral had to be stopped during the call to clock_adjust(). Rather than creating even more complicated clock adjustment mechanisms that would probably still have mixed results as to accuracy, this change simply uses the Sleep Timer counter as a base value for Contiki's clock and seconds counters. The tick from the Systick peripheral is still used as the interrupt source to update Contiki's clocks and timers. When running, the SysTick peripheral and the Sleep Timer are synchronized, so combining both is not an issue, and this allows not to alter the rtimer interrupt mechanism using the Sleep Timer. The purpose of the Sleep Timer is to be an RTC, so it is the perfect fit for the clock module, all the more it can not be disturbed by PM1/2. If the 32-kHz XOSC is used, the Sleep Timer is also very accurate. If the 32-kHZ RCOSC is used, it is calibrated from the 32-MHz XOSC, so it is also accurate, and the 32753-Hz vs. 32768-Hz systematic error in that case is negligible, all the more one would use the 32-kHz XOSC for better accuracy. Besides fixing this time drift issue, this change has several benefits: - clock_time(), clock_seconds() and RTIMER_NOW() start synchronized, and they change at the same source pace. - If clock_set_seconds() is called, then clock_seconds() indicates one more second almost exactly one second later, then exactly each second. Before this change, clock_seconds() was not synchronized with clock_set_seconds(), so the value returned by the former could be incremented immediately after the call to the latter in some cases. - The code tied to the clock module is simpler and more robust. Signed-off-by: Benoît Thébaudeau --- cpu/cc2538/clock.c | 114 ++++++++++++++++++++++++++------------------- cpu/cc2538/lpm.c | 24 ++++------ 2 files changed, 74 insertions(+), 64 deletions(-) diff --git a/cpu/cc2538/clock.c b/cpu/cc2538/clock.c index 51f0c7990..98abdf2e3 100644 --- a/cpu/cc2538/clock.c +++ b/cpu/cc2538/clock.c @@ -38,7 +38,11 @@ * * To implement the clock functionality, we use the SysTick peripheral on the * cortex-M3. We run the system clock at 16 MHz and we set the SysTick to give - * us 128 interrupts / sec + * us 128 interrupts / sec. However, the Sleep Timer counter value is used for + * the number of elapsed ticks in order to avoid a significant time drift caused + * by PM1/2. Contrary to the Sleep Timer, the SysTick peripheral is indeed + * frozen during PM1/2, so adjusting upon wake-up a tick counter based on this + * peripheral would hardly be accurate. * @{ * * \file @@ -52,14 +56,15 @@ #include "dev/sys-ctrl.h" #include "sys/energest.h" +#include "sys/etimer.h" +#include "sys/rtimer.h" #include /*---------------------------------------------------------------------------*/ +#define RTIMER_CLOCK_TICK_RATIO (RTIMER_SECOND / CLOCK_SECOND) #define RELOAD_VALUE (125000 - 1) /** Fire 128 times / sec */ -static volatile clock_time_t count; -static volatile unsigned long secs = 0; -static volatile uint8_t second_countdown = CLOCK_SECOND; +static volatile uint64_t rt_ticks_startup = 0, rt_ticks_epoch = 0; /*---------------------------------------------------------------------------*/ /** * \brief Arch-specific implementation of clock_init for the cc2538 @@ -75,8 +80,6 @@ static volatile uint8_t second_countdown = CLOCK_SECOND; void clock_init(void) { - count = 0; - REG(SYSTICK_STRELOAD) = RELOAD_VALUE; /* System clock source, Enable */ @@ -109,19 +112,19 @@ clock_init(void) CCIF clock_time_t clock_time(void) { - return count; + return rt_ticks_startup / RTIMER_CLOCK_TICK_RATIO; } /*---------------------------------------------------------------------------*/ void clock_set_seconds(unsigned long sec) { - secs = sec; + rt_ticks_epoch = (uint64_t)sec * RTIMER_SECOND; } /*---------------------------------------------------------------------------*/ CCIF unsigned long clock_seconds(void) { - return secs; + return rt_ticks_epoch / RTIMER_SECOND; } /*---------------------------------------------------------------------------*/ void @@ -160,43 +163,40 @@ clock_delay(unsigned int i) } /*---------------------------------------------------------------------------*/ /** - * \brief Adjust the clock by moving it forward by a number of ticks - * \param ticks The number of ticks + * \brief Update the software clock ticks and seconds * - * This function is useful when coming out of PM1/2, during which the system - * clock is stopped. We adjust the clock by moving it forward by a number of - * ticks equal to the deep sleep duration. We update the seconds counter if - * we have to and we also do some housekeeping so that the next second will - * increment when it is meant to. + * This function is used to update the software tick counters whenever the + * system clock might have changed, which can occur upon a SysTick ISR or upon + * wake-up from PM1/2. * - * \note This function is only meant to be used by lpm_exit(). Applications - * should really avoid calling this + * For the software clock ticks counter, the Sleep Timer counter value is used + * as the base tick value, and extended to a 64-bit value thanks to a detection + * of wraparounds. + * + * For the seconds counter, the changes of the Sleep Timer counter value are + * added to the reference time, which is either the startup time or the value + * passed to clock_set_seconds(). + * + * This function polls the etimer process if an etimer has expired. */ -void -clock_adjust(clock_time_t ticks) +static void +update_ticks(void) { - /* Halt the SysTick while adjusting */ - REG(SYSTICK_STCTRL) &= ~SYSTICK_STCTRL_ENABLE; + rtimer_clock_t now; + uint64_t prev_rt_ticks_startup, cur_rt_ticks_startup; + uint32_t cur_rt_ticks_startup_hi; - /* Moving forward by more than a second? */ - secs += ticks >> 7; + now = RTIMER_NOW(); + prev_rt_ticks_startup = rt_ticks_startup; - /* Increment tick count */ - count += ticks; - - /* - * Update internal second countdown so that next second change will actually - * happen when it's meant to happen. - */ - second_countdown -= ticks & 127; - - if(second_countdown == 0 || second_countdown > 128) { - secs++; - second_countdown -= 128; + cur_rt_ticks_startup_hi = prev_rt_ticks_startup >> 32; + if(now < (rtimer_clock_t)prev_rt_ticks_startup) { + cur_rt_ticks_startup_hi++; } + cur_rt_ticks_startup = (uint64_t)cur_rt_ticks_startup_hi << 32 | now; + rt_ticks_startup = cur_rt_ticks_startup; - /* Re-Start the SysTick */ - REG(SYSTICK_STCTRL) |= SYSTICK_STCTRL_ENABLE; + rt_ticks_epoch += cur_rt_ticks_startup - prev_rt_ticks_startup; /* * Inform the etimer library that the system clock has changed and that an @@ -208,23 +208,39 @@ clock_adjust(clock_time_t ticks) } /*---------------------------------------------------------------------------*/ /** - * \brief The clock Interrupt Service Routine. It polls the etimer process - * if an etimer has expired. It also updates the software clock tick and - * seconds counter since reset. + * \brief Adjust the clock following missed SysTick ISRs + * + * This function is useful when coming out of PM1/2, during which the system + * clock is stopped. We adjust the clock counters like after any SysTick ISR. + * + * \note This function is only meant to be used by lpm_exit(). Applications + * should really avoid calling this + */ +void +clock_adjust(void) +{ + /* Halt the SysTick while adjusting */ + REG(SYSTICK_STCTRL) &= ~SYSTICK_STCTRL_ENABLE; + + update_ticks(); + + /* Re-Start the SysTick */ + REG(SYSTICK_STCTRL) |= SYSTICK_STCTRL_ENABLE; +} +/*---------------------------------------------------------------------------*/ +/** + * \brief The clock Interrupt Service Routine + * + * It polls the etimer process if an etimer has expired. It also updates the + * software clock tick and seconds counter. */ void clock_isr(void) { ENERGEST_ON(ENERGEST_TYPE_IRQ); - count++; - if(etimer_pending()) { - etimer_request_poll(); - } - if(--second_countdown == 0) { - secs++; - second_countdown = CLOCK_SECOND; - } + update_ticks(); + ENERGEST_OFF(ENERGEST_TYPE_IRQ); } /*---------------------------------------------------------------------------*/ diff --git a/cpu/cc2538/lpm.c b/cpu/cc2538/lpm.c index 80b536998..0c017f371 100644 --- a/cpu/cc2538/lpm.c +++ b/cpu/cc2538/lpm.c @@ -86,14 +86,11 @@ rtimer_clock_t lpm_stats[3]; /*---------------------------------------------------------------------------*/ /* * Remembers what time it was when went to deep sleep - * This is used when coming out of PM1/2 to adjust the system clock, which - * stops ticking while in those PMs + * This is used when coming out of PM0/1/2 to keep stats */ static rtimer_clock_t sleep_enter_time; -#define RTIMER_CLOCK_TICK_RATIO (RTIMER_SECOND / CLOCK_SECOND) - -void clock_adjust(clock_time_t ticks); +void clock_adjust(void); /*---------------------------------------------------------------------------*/ /* Stores the currently specified MAX allowed PM */ static uint8_t max_pm; @@ -135,10 +132,7 @@ enter_pm0(void) /* We are only interested in IRQ energest while idle or in LPM */ ENERGEST_IRQ_RESTORE(irq_energest); - /* - * After PM0 we don't need to adjust the system clock. Thus, saving the time - * we enter Deep Sleep is only required if we are keeping stats. - */ + /* Remember the current time so we can keep stats when we wake up */ if(LPM_CONF_STATS) { sleep_enter_time = RTIMER_NOW(); } @@ -213,10 +207,8 @@ lpm_exit() RTIMER_NOW() - sleep_enter_time); /* Adjust the system clock, since it was not counting while we were sleeping - * We need to convert sleep duration from rtimer ticks to clock ticks and - * this will cost us some accuracy */ - clock_adjust((clock_time_t) - ((RTIMER_NOW() - sleep_enter_time) / RTIMER_CLOCK_TICK_RATIO)); + * We need to convert sleep duration from rtimer ticks to clock ticks */ + clock_adjust(); /* Restore system clock to the 32 MHz XOSC */ select_32_mhz_xosc(); @@ -304,8 +296,10 @@ lpm_enter() ENERGEST_OFF(ENERGEST_TYPE_CPU); ENERGEST_ON(ENERGEST_TYPE_LPM); - /* Remember the current time so we can adjust the clock when we wake up */ - sleep_enter_time = RTIMER_NOW(); + /* Remember the current time so we can keep stats when we wake up */ + if(LPM_CONF_STATS) { + sleep_enter_time = RTIMER_NOW(); + } /* * Last chance to abort entering Deep Sleep.