Merge pull request #472 from ADVANSEE/cc2538-clock-adjust-etimer-poll

cc2538: clock: Fix clock / timer issues with PM1/2
This commit is contained in:
George Oikonomou 2014-04-13 16:35:17 +01:00
commit 602f834caf
2 changed files with 76 additions and 58 deletions

View file

@ -38,7 +38,11 @@
* *
* To implement the clock functionality, we use the SysTick peripheral on the * 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 * 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 * \file
@ -52,14 +56,15 @@
#include "dev/sys-ctrl.h" #include "dev/sys-ctrl.h"
#include "sys/energest.h" #include "sys/energest.h"
#include "sys/etimer.h"
#include "sys/rtimer.h"
#include <stdint.h> #include <stdint.h>
/*---------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------*/
#define RTIMER_CLOCK_TICK_RATIO (RTIMER_SECOND / CLOCK_SECOND)
#define RELOAD_VALUE (125000 - 1) /** Fire 128 times / sec */ #define RELOAD_VALUE (125000 - 1) /** Fire 128 times / sec */
static volatile clock_time_t count; static volatile uint64_t rt_ticks_startup = 0, rt_ticks_epoch = 0;
static volatile unsigned long secs = 0;
static volatile uint8_t second_countdown = CLOCK_SECOND;
/*---------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------*/
/** /**
* \brief Arch-specific implementation of clock_init for the cc2538 * \brief Arch-specific implementation of clock_init for the cc2538
@ -75,8 +80,6 @@ static volatile uint8_t second_countdown = CLOCK_SECOND;
void void
clock_init(void) clock_init(void)
{ {
count = 0;
REG(SYSTICK_STRELOAD) = RELOAD_VALUE; REG(SYSTICK_STRELOAD) = RELOAD_VALUE;
/* System clock source, Enable */ /* System clock source, Enable */
@ -109,19 +112,19 @@ clock_init(void)
CCIF clock_time_t CCIF clock_time_t
clock_time(void) clock_time(void)
{ {
return count; return rt_ticks_startup / RTIMER_CLOCK_TICK_RATIO;
} }
/*---------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------*/
void void
clock_set_seconds(unsigned long sec) clock_set_seconds(unsigned long sec)
{ {
secs = sec; rt_ticks_epoch = (uint64_t)sec * RTIMER_SECOND;
} }
/*---------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------*/
CCIF unsigned long CCIF unsigned long
clock_seconds(void) clock_seconds(void)
{ {
return secs; return rt_ticks_epoch / RTIMER_SECOND;
} }
/*---------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------*/
void void
@ -160,63 +163,84 @@ clock_delay(unsigned int i)
} }
/*---------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------*/
/** /**
* \brief Adjust the clock by moving it forward by a number of ticks * \brief Update the software clock ticks and seconds
* \param ticks The number of ticks *
* 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.
*
* 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.
*/
static void
update_ticks(void)
{
rtimer_clock_t now;
uint64_t prev_rt_ticks_startup, cur_rt_ticks_startup;
uint32_t cur_rt_ticks_startup_hi;
now = RTIMER_NOW();
prev_rt_ticks_startup = rt_ticks_startup;
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;
rt_ticks_epoch += cur_rt_ticks_startup - prev_rt_ticks_startup;
/*
* Inform the etimer library that the system clock has changed and that an
* etimer might have expired.
*/
if(etimer_pending()) {
etimer_request_poll();
}
}
/*---------------------------------------------------------------------------*/
/**
* \brief Adjust the clock following missed SysTick ISRs
* *
* This function is useful when coming out of PM1/2, during which the system * 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 * clock is stopped. We adjust the clock counters like after any SysTick ISR.
* 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.
* *
* \note This function is only meant to be used by lpm_exit(). Applications * \note This function is only meant to be used by lpm_exit(). Applications
* should really avoid calling this * should really avoid calling this
*/ */
void void
clock_adjust(clock_time_t ticks) clock_adjust(void)
{ {
/* Halt the SysTick while adjusting */ /* Halt the SysTick while adjusting */
REG(SYSTICK_STCTRL) &= ~SYSTICK_STCTRL_ENABLE; REG(SYSTICK_STCTRL) &= ~SYSTICK_STCTRL_ENABLE;
/* Moving forward by more than a second? */ update_ticks();
secs += ticks >> 7;
/* 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;
if(second_countdown == 0 || second_countdown > 128) {
secs++;
second_countdown -= 128;
}
/* Re-Start the SysTick */ /* Re-Start the SysTick */
REG(SYSTICK_STCTRL) |= SYSTICK_STCTRL_ENABLE; REG(SYSTICK_STCTRL) |= SYSTICK_STCTRL_ENABLE;
} }
/*---------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------*/
/** /**
* \brief The clock Interrupt Service Routine. It polls the etimer process * \brief The clock Interrupt Service Routine
* if an etimer has expired. It also updates the software clock tick and *
* seconds counter since reset. * It polls the etimer process if an etimer has expired. It also updates the
* software clock tick and seconds counter.
*/ */
void void
clock_isr(void) clock_isr(void)
{ {
ENERGEST_ON(ENERGEST_TYPE_IRQ); ENERGEST_ON(ENERGEST_TYPE_IRQ);
count++;
if(etimer_pending()) {
etimer_request_poll();
}
if(--second_countdown == 0) { update_ticks();
secs++;
second_countdown = CLOCK_SECOND;
}
ENERGEST_OFF(ENERGEST_TYPE_IRQ); ENERGEST_OFF(ENERGEST_TYPE_IRQ);
} }
/*---------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------*/

View file

@ -88,14 +88,11 @@ rtimer_clock_t lpm_stats[3];
/*---------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------*/
/* /*
* Remembers what time it was when went to deep sleep * 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 * This is used when coming out of PM0/1/2 to keep stats
* stops ticking while in those PMs
*/ */
static rtimer_clock_t sleep_enter_time; static rtimer_clock_t sleep_enter_time;
#define RTIMER_CLOCK_TICK_RATIO (RTIMER_SECOND / CLOCK_SECOND) void clock_adjust(void);
void clock_adjust(clock_time_t ticks);
/*---------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------*/
/* Stores the currently specified MAX allowed PM */ /* Stores the currently specified MAX allowed PM */
static uint8_t max_pm; static uint8_t max_pm;
@ -137,10 +134,7 @@ enter_pm0(void)
/* We are only interested in IRQ energest while idle or in LPM */ /* We are only interested in IRQ energest while idle or in LPM */
ENERGEST_IRQ_RESTORE(irq_energest); ENERGEST_IRQ_RESTORE(irq_energest);
/* /* Remember the current time so we can keep stats when we wake up */
* 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.
*/
if(LPM_CONF_STATS) { if(LPM_CONF_STATS) {
sleep_enter_time = RTIMER_NOW(); sleep_enter_time = RTIMER_NOW();
} }
@ -215,10 +209,8 @@ lpm_exit()
RTIMER_NOW() - sleep_enter_time); RTIMER_NOW() - sleep_enter_time);
/* Adjust the system clock, since it was not counting while we were sleeping /* 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 * We need to convert sleep duration from rtimer ticks to clock ticks */
* this will cost us some accuracy */ clock_adjust();
clock_adjust((clock_time_t)
((RTIMER_NOW() - sleep_enter_time) / RTIMER_CLOCK_TICK_RATIO));
/* Restore system clock to the 32 MHz XOSC */ /* Restore system clock to the 32 MHz XOSC */
select_32_mhz_xosc(); select_32_mhz_xosc();
@ -306,8 +298,10 @@ lpm_enter()
ENERGEST_OFF(ENERGEST_TYPE_CPU); ENERGEST_OFF(ENERGEST_TYPE_CPU);
ENERGEST_ON(ENERGEST_TYPE_LPM); ENERGEST_ON(ENERGEST_TYPE_LPM);
/* Remember the current time so we can adjust the clock when we wake up */ /* Remember the current time so we can keep stats when we wake up */
sleep_enter_time = RTIMER_NOW(); if(LPM_CONF_STATS) {
sleep_enter_time = RTIMER_NOW();
}
/* /*
* Last chance to abort entering Deep Sleep. * Last chance to abort entering Deep Sleep.