2013-01-12 22:44:42 +00:00
|
|
|
/*
|
|
|
|
* Copyright (c) 2013, Texas Instruments Incorporated - http://www.ti.com/
|
|
|
|
* All rights reserved.
|
|
|
|
*
|
|
|
|
* Redistribution and use in source and binary forms, with or without
|
|
|
|
* modification, are permitted provided that the following conditions
|
|
|
|
* are met:
|
|
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
|
|
* notice, this list of conditions and the following disclaimer.
|
|
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
|
|
* documentation and/or other materials provided with the distribution.
|
|
|
|
*
|
|
|
|
* 3. Neither the name of the copyright holder nor the names of its
|
|
|
|
* contributors may be used to endorse or promote products derived
|
|
|
|
* from this software without specific prior written permission.
|
|
|
|
*
|
|
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
|
|
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
|
|
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
|
|
|
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
|
|
|
* COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
|
|
|
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
|
|
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
|
|
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
|
|
|
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
|
|
|
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
|
|
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
|
|
|
|
* OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
|
|
*/
|
|
|
|
/**
|
|
|
|
* \addtogroup cc2538-lpm
|
|
|
|
* @{
|
|
|
|
*
|
|
|
|
* \file
|
|
|
|
* Implementation of low power modes ofr the cc2538
|
|
|
|
*/
|
|
|
|
#include "contiki-conf.h"
|
|
|
|
#include "sys/energest.h"
|
|
|
|
#include "sys/process.h"
|
|
|
|
#include "dev/sys-ctrl.h"
|
|
|
|
#include "dev/scb.h"
|
|
|
|
#include "dev/rfcore-xreg.h"
|
|
|
|
#include "rtimer-arch.h"
|
2013-11-15 15:21:34 +00:00
|
|
|
#include "lpm.h"
|
2013-01-12 22:44:42 +00:00
|
|
|
#include "reg.h"
|
|
|
|
|
2013-11-15 15:21:34 +00:00
|
|
|
#include <stdbool.h>
|
2013-01-12 22:44:42 +00:00
|
|
|
#include <stdint.h>
|
|
|
|
#include <string.h>
|
2014-01-28 19:10:51 +00:00
|
|
|
|
|
|
|
#if LPM_CONF_ENABLE != 0
|
2013-01-12 22:44:42 +00:00
|
|
|
/*---------------------------------------------------------------------------*/
|
|
|
|
#if ENERGEST_CONF_ON
|
|
|
|
static unsigned long irq_energest = 0;
|
|
|
|
|
|
|
|
#define ENERGEST_IRQ_SAVE(a) do { \
|
|
|
|
a = energest_type_time(ENERGEST_TYPE_IRQ); } while(0)
|
|
|
|
#define ENERGEST_IRQ_RESTORE(a) do { \
|
|
|
|
energest_type_set(ENERGEST_TYPE_IRQ, a); } while(0)
|
|
|
|
#else
|
|
|
|
#define ENERGEST_IRQ_SAVE(a) do {} while(0)
|
|
|
|
#define ENERGEST_IRQ_RESTORE(a) do {} while(0)
|
|
|
|
#endif
|
|
|
|
/*---------------------------------------------------------------------------*/
|
|
|
|
/*
|
|
|
|
* Deep Sleep thresholds in rtimer ticks (~30.5 usec)
|
|
|
|
*
|
|
|
|
* If Deep Sleep duration < DEEP_SLEEP_PM1_THRESHOLD, simply enter PM0
|
|
|
|
* If duration < DEEP_SLEEP_PM2_THRESHOLD drop to PM1
|
|
|
|
* else PM2.
|
|
|
|
*/
|
|
|
|
#define DEEP_SLEEP_PM1_THRESHOLD 10
|
|
|
|
#define DEEP_SLEEP_PM2_THRESHOLD 100
|
|
|
|
/*---------------------------------------------------------------------------*/
|
|
|
|
#define assert_wfi() do { asm("wfi"::); } while(0)
|
|
|
|
/*---------------------------------------------------------------------------*/
|
|
|
|
#if LPM_CONF_STATS
|
|
|
|
rtimer_clock_t lpm_stats[3];
|
|
|
|
|
2015-05-12 12:06:40 +00:00
|
|
|
#define LPM_STATS_INIT() \
|
|
|
|
do { memset(lpm_stats, 0, sizeof(lpm_stats)); } while(0)
|
2013-01-12 22:44:42 +00:00
|
|
|
#define LPM_STATS_ADD(pm, val) do { lpm_stats[pm] += val; } while(0)
|
|
|
|
#else
|
|
|
|
#define LPM_STATS_INIT()
|
|
|
|
#define LPM_STATS_ADD(stat, val)
|
|
|
|
#endif
|
|
|
|
/*---------------------------------------------------------------------------*/
|
|
|
|
/*
|
|
|
|
* Remembers what time it was when went to deep sleep
|
cc2538: clock: Fix time drift occurring with PM1/2
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 <benoit.thebaudeau@advansee.com>
2013-12-04 15:17:14 +00:00
|
|
|
* This is used when coming out of PM0/1/2 to keep stats
|
2013-01-12 22:44:42 +00:00
|
|
|
*/
|
|
|
|
static rtimer_clock_t sleep_enter_time;
|
|
|
|
|
cc2538: clock: Fix time drift occurring with PM1/2
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 <benoit.thebaudeau@advansee.com>
2013-12-04 15:17:14 +00:00
|
|
|
void clock_adjust(void);
|
2013-01-12 22:44:42 +00:00
|
|
|
/*---------------------------------------------------------------------------*/
|
|
|
|
/* Stores the currently specified MAX allowed PM */
|
|
|
|
static uint8_t max_pm;
|
|
|
|
/*---------------------------------------------------------------------------*/
|
2013-11-15 15:21:34 +00:00
|
|
|
/* Buffer to store peripheral PM1+ permission FPs */
|
|
|
|
#ifdef LPM_CONF_PERIPH_PERMIT_PM1_FUNCS_MAX
|
|
|
|
#define LPM_PERIPH_PERMIT_PM1_FUNCS_MAX LPM_CONF_PERIPH_PERMIT_PM1_FUNCS_MAX
|
|
|
|
#else
|
2015-12-13 01:33:31 +00:00
|
|
|
#define LPM_PERIPH_PERMIT_PM1_FUNCS_MAX 5
|
2013-11-15 15:21:34 +00:00
|
|
|
#endif
|
|
|
|
|
2013-11-18 10:57:59 +00:00
|
|
|
static lpm_periph_permit_pm1_func_t
|
2013-11-15 15:21:34 +00:00
|
|
|
periph_permit_pm1_funcs[LPM_PERIPH_PERMIT_PM1_FUNCS_MAX];
|
|
|
|
/*---------------------------------------------------------------------------*/
|
|
|
|
static bool
|
|
|
|
periph_permit_pm1(void)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
|
|
|
|
for(i = 0; i < LPM_PERIPH_PERMIT_PM1_FUNCS_MAX &&
|
|
|
|
periph_permit_pm1_funcs[i] != NULL; i++) {
|
|
|
|
if(!periph_permit_pm1_funcs[i]()) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
/*---------------------------------------------------------------------------*/
|
2013-01-12 22:44:42 +00:00
|
|
|
/*
|
|
|
|
* Routine to put is in PM0. We also need to do some housekeeping if the stats
|
|
|
|
* or the energest module is enabled
|
|
|
|
*/
|
|
|
|
static void
|
|
|
|
enter_pm0(void)
|
|
|
|
{
|
2015-09-03 14:06:46 +00:00
|
|
|
ENERGEST_SWITCH(ENERGEST_TYPE_CPU, ENERGEST_TYPE_LPM);
|
2013-01-12 22:44:42 +00:00
|
|
|
|
|
|
|
/* We are only interested in IRQ energest while idle or in LPM */
|
|
|
|
ENERGEST_IRQ_RESTORE(irq_energest);
|
|
|
|
|
cc2538: clock: Fix time drift occurring with PM1/2
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 <benoit.thebaudeau@advansee.com>
2013-12-04 15:17:14 +00:00
|
|
|
/* Remember the current time so we can keep stats when we wake up */
|
2013-01-12 22:44:42 +00:00
|
|
|
if(LPM_CONF_STATS) {
|
|
|
|
sleep_enter_time = RTIMER_NOW();
|
|
|
|
}
|
|
|
|
|
|
|
|
assert_wfi();
|
|
|
|
|
|
|
|
/* We reach here when the interrupt context that woke us up has returned */
|
|
|
|
LPM_STATS_ADD(0, RTIMER_NOW() - sleep_enter_time);
|
|
|
|
|
|
|
|
/* Remember IRQ energest for next pass */
|
|
|
|
ENERGEST_IRQ_SAVE(irq_energest);
|
|
|
|
|
2015-09-03 14:06:46 +00:00
|
|
|
ENERGEST_SWITCH(ENERGEST_TYPE_LPM, ENERGEST_TYPE_CPU);
|
2013-01-12 22:44:42 +00:00
|
|
|
}
|
|
|
|
/*---------------------------------------------------------------------------*/
|
|
|
|
static void
|
|
|
|
select_32_mhz_xosc(void)
|
|
|
|
{
|
2015-05-12 12:06:40 +00:00
|
|
|
/* First, make sure there is no ongoing clock source change */
|
2013-12-03 17:51:39 +00:00
|
|
|
while((REG(SYS_CTRL_CLOCK_STA) & SYS_CTRL_CLOCK_STA_SOURCE_CHANGE) != 0);
|
|
|
|
|
2013-01-12 22:44:42 +00:00
|
|
|
/* Turn on the 32 MHz XOSC and source the system clock on it. */
|
|
|
|
REG(SYS_CTRL_CLOCK_CTRL) &= ~SYS_CTRL_CLOCK_CTRL_OSC;
|
|
|
|
|
|
|
|
/* Wait for the switch to take place */
|
|
|
|
while((REG(SYS_CTRL_CLOCK_STA) & SYS_CTRL_CLOCK_STA_OSC) != 0);
|
2013-12-03 18:03:00 +00:00
|
|
|
|
2015-05-12 12:06:40 +00:00
|
|
|
/* Power down the unused oscillator and restore divisors (silicon errata) */
|
|
|
|
REG(SYS_CTRL_CLOCK_CTRL) = (REG(SYS_CTRL_CLOCK_CTRL)
|
|
|
|
#if SYS_CTRL_SYS_DIV == SYS_CTRL_CLOCK_CTRL_SYS_DIV_32MHZ
|
|
|
|
& ~SYS_CTRL_CLOCK_CTRL_SYS_DIV
|
|
|
|
#endif
|
|
|
|
#if SYS_CTRL_IO_DIV == SYS_CTRL_CLOCK_CTRL_IO_DIV_32MHZ
|
|
|
|
& ~SYS_CTRL_CLOCK_CTRL_IO_DIV
|
|
|
|
#endif
|
|
|
|
) | SYS_CTRL_CLOCK_CTRL_OSC_PD;
|
2013-01-12 22:44:42 +00:00
|
|
|
}
|
|
|
|
/*---------------------------------------------------------------------------*/
|
|
|
|
static void
|
|
|
|
select_16_mhz_rcosc(void)
|
|
|
|
{
|
2013-12-03 18:03:00 +00:00
|
|
|
/*
|
|
|
|
* Power up both oscillators in order to speed up the transition to the 32-MHz
|
2015-05-12 12:06:40 +00:00
|
|
|
* XOSC after wake up. In addition, consider CC2538 silicon errata:
|
|
|
|
* "Possible Incorrect Value of Clock Dividers after PM2 and PM3" and
|
|
|
|
* set system clock divisor / I/O clock divisor to 16 MHz in case they run
|
|
|
|
* at full speed (=32 MHz)
|
2013-12-03 18:03:00 +00:00
|
|
|
*/
|
2015-05-12 12:06:40 +00:00
|
|
|
REG(SYS_CTRL_CLOCK_CTRL) = (REG(SYS_CTRL_CLOCK_CTRL)
|
|
|
|
#if SYS_CTRL_SYS_DIV == SYS_CTRL_CLOCK_CTRL_SYS_DIV_32MHZ
|
|
|
|
| SYS_CTRL_CLOCK_CTRL_SYS_DIV_16MHZ
|
|
|
|
#endif
|
|
|
|
#if SYS_CTRL_IO_DIV == SYS_CTRL_CLOCK_CTRL_IO_DIV_32MHZ
|
|
|
|
| SYS_CTRL_CLOCK_CTRL_IO_DIV_16MHZ
|
|
|
|
#endif
|
|
|
|
) & ~SYS_CTRL_CLOCK_CTRL_OSC_PD;
|
2013-12-03 18:03:00 +00:00
|
|
|
|
2013-01-12 22:44:42 +00:00
|
|
|
/*First, make sure there is no ongoing clock source change */
|
|
|
|
while((REG(SYS_CTRL_CLOCK_STA) & SYS_CTRL_CLOCK_STA_SOURCE_CHANGE) != 0);
|
|
|
|
|
|
|
|
/* Set the System Clock to use the 16MHz RC OSC */
|
|
|
|
REG(SYS_CTRL_CLOCK_CTRL) |= SYS_CTRL_CLOCK_CTRL_OSC;
|
|
|
|
|
|
|
|
/* Wait till it's happened */
|
|
|
|
while((REG(SYS_CTRL_CLOCK_STA) & SYS_CTRL_CLOCK_STA_OSC) == 0);
|
|
|
|
}
|
|
|
|
/*---------------------------------------------------------------------------*/
|
|
|
|
void
|
|
|
|
lpm_exit()
|
|
|
|
{
|
|
|
|
if((REG(SYS_CTRL_PMCTL) & SYS_CTRL_PMCTL_PM3) == SYS_CTRL_PMCTL_PM0) {
|
|
|
|
/* We either just exited PM0 or we were not sleeping in the first place.
|
|
|
|
* We don't need to do anything clever */
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2013-12-03 18:16:24 +00:00
|
|
|
/*
|
|
|
|
* When returning from PM1/2, the sleep timer value (used by RTIMER_NOW()) is
|
|
|
|
* not up-to-date until a positive edge on the 32-kHz clock has been detected
|
|
|
|
* after the system clock restarted. To ensure an updated value is read, wait
|
|
|
|
* for a positive transition on the 32-kHz clock by polling the
|
|
|
|
* SYS_CTRL_CLOCK_STA.SYNC_32K bit, before reading the sleep timer value.
|
|
|
|
*/
|
|
|
|
while(REG(SYS_CTRL_CLOCK_STA) & SYS_CTRL_CLOCK_STA_SYNC_32K);
|
|
|
|
while(!(REG(SYS_CTRL_CLOCK_STA) & SYS_CTRL_CLOCK_STA_SYNC_32K));
|
|
|
|
|
2013-01-12 22:44:42 +00:00
|
|
|
LPM_STATS_ADD(REG(SYS_CTRL_PMCTL) & SYS_CTRL_PMCTL_PM3,
|
|
|
|
RTIMER_NOW() - sleep_enter_time);
|
|
|
|
|
|
|
|
/* Adjust the system clock, since it was not counting while we were sleeping
|
cc2538: clock: Fix time drift occurring with PM1/2
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 <benoit.thebaudeau@advansee.com>
2013-12-04 15:17:14 +00:00
|
|
|
* We need to convert sleep duration from rtimer ticks to clock ticks */
|
|
|
|
clock_adjust();
|
2013-01-12 22:44:42 +00:00
|
|
|
|
|
|
|
/* Restore system clock to the 32 MHz XOSC */
|
|
|
|
select_32_mhz_xosc();
|
|
|
|
|
|
|
|
/* Restore PMCTL to PM0 for next pass */
|
|
|
|
REG(SYS_CTRL_PMCTL) = SYS_CTRL_PMCTL_PM0;
|
|
|
|
|
|
|
|
/* Remember IRQ energest for next pass */
|
|
|
|
ENERGEST_IRQ_SAVE(irq_energest);
|
|
|
|
|
2015-09-03 14:06:46 +00:00
|
|
|
ENERGEST_SWITCH(ENERGEST_TYPE_LPM, ENERGEST_TYPE_CPU);
|
2013-01-12 22:44:42 +00:00
|
|
|
}
|
|
|
|
/*---------------------------------------------------------------------------*/
|
|
|
|
void
|
|
|
|
lpm_enter()
|
|
|
|
{
|
|
|
|
rtimer_clock_t lpm_exit_time;
|
|
|
|
rtimer_clock_t duration;
|
|
|
|
|
|
|
|
/*
|
2013-11-15 16:14:45 +00:00
|
|
|
* If either the RF or the registered peripherals are on, dropping to PM1/2
|
|
|
|
* would equal pulling the rug (32MHz XOSC) from under their feet. Thus, we
|
|
|
|
* only drop to PM0. PM0 is also used if max_pm==0.
|
2013-01-12 22:44:42 +00:00
|
|
|
*/
|
|
|
|
if((REG(RFCORE_XREG_FSMSTAT0) & RFCORE_XREG_FSMSTAT0_FSM_FFCTRL_STATE) != 0
|
2013-11-15 16:14:45 +00:00
|
|
|
|| !periph_permit_pm1() || max_pm == 0) {
|
2013-01-12 22:44:42 +00:00
|
|
|
enter_pm0();
|
|
|
|
|
|
|
|
/* We reach here when the interrupt context that woke us up has returned */
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
2013-11-15 16:14:45 +00:00
|
|
|
* Registered peripherals were off. Radio was off: Some Duty Cycling in place.
|
2013-01-12 22:44:42 +00:00
|
|
|
* rtimers run on the Sleep Timer. Thus, if we have a scheduled rtimer
|
|
|
|
* task, a Sleep Timer interrupt will fire and will wake us up.
|
|
|
|
* Choose the most suitable PM based on anticipated deep sleep duration
|
|
|
|
*/
|
|
|
|
lpm_exit_time = rtimer_arch_next_trigger();
|
|
|
|
duration = lpm_exit_time - RTIMER_NOW();
|
|
|
|
|
|
|
|
if(duration < DEEP_SLEEP_PM1_THRESHOLD || lpm_exit_time == 0) {
|
|
|
|
/* Anticipated duration too short or no scheduled rtimer task. Use PM0 */
|
|
|
|
enter_pm0();
|
|
|
|
|
|
|
|
/* We reach here when the interrupt context that woke us up has returned */
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* If we reach here, we -may- (but may as well not) be dropping to PM1+. We
|
2013-11-15 16:14:45 +00:00
|
|
|
* know the registered peripherals and RF are off so we can switch to the
|
2013-11-15 15:21:34 +00:00
|
|
|
* 16MHz RCOSC. */
|
2013-01-12 22:44:42 +00:00
|
|
|
select_16_mhz_rcosc();
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Switching the System Clock from the 32MHz XOSC to the 16MHz RC OSC may
|
|
|
|
* have taken a while. Re-estimate sleep duration.
|
|
|
|
*/
|
|
|
|
duration = lpm_exit_time - RTIMER_NOW();
|
|
|
|
|
|
|
|
if(duration < DEEP_SLEEP_PM1_THRESHOLD) {
|
|
|
|
/*
|
|
|
|
* oops... The clock switch took some time and now the remaining sleep
|
|
|
|
* duration is too short. Restore the clock source to the 32MHz XOSC and
|
|
|
|
* abort the LPM attempt altogether. We can't drop to PM0,
|
|
|
|
* we need to yield to main() since we may have events to service now.
|
|
|
|
*/
|
|
|
|
select_32_mhz_xosc();
|
|
|
|
|
|
|
|
return;
|
|
|
|
} else if(duration >= DEEP_SLEEP_PM2_THRESHOLD && max_pm == 2) {
|
|
|
|
/* Long sleep duration and PM2 is allowed. Use it */
|
|
|
|
REG(SYS_CTRL_PMCTL) = SYS_CTRL_PMCTL_PM2;
|
|
|
|
} else {
|
|
|
|
/*
|
|
|
|
* Anticipated duration too short for PM2 but long enough for PM1 and we
|
|
|
|
* are allowed to use PM1
|
|
|
|
*/
|
|
|
|
REG(SYS_CTRL_PMCTL) = SYS_CTRL_PMCTL_PM1;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* We are only interested in IRQ energest while idle or in LPM */
|
|
|
|
ENERGEST_IRQ_RESTORE(irq_energest);
|
2015-09-03 14:06:46 +00:00
|
|
|
ENERGEST_SWITCH(ENERGEST_TYPE_CPU, ENERGEST_TYPE_LPM);
|
2013-01-12 22:44:42 +00:00
|
|
|
|
cc2538: clock: Fix time drift occurring with PM1/2
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 <benoit.thebaudeau@advansee.com>
2013-12-04 15:17:14 +00:00
|
|
|
/* Remember the current time so we can keep stats when we wake up */
|
|
|
|
if(LPM_CONF_STATS) {
|
|
|
|
sleep_enter_time = RTIMER_NOW();
|
|
|
|
}
|
2013-01-12 22:44:42 +00:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Last chance to abort entering Deep Sleep.
|
|
|
|
*
|
|
|
|
* - There is the slight off-chance that a SysTick interrupt fired while we
|
|
|
|
* were trying to make up our mind. This may have raised an event.
|
|
|
|
* - The Sleep Timer may have fired
|
|
|
|
*
|
|
|
|
* Check if there is still a scheduled rtimer task and check for pending
|
|
|
|
* events before going to Deep Sleep
|
|
|
|
*/
|
|
|
|
if(process_nevents() || rtimer_arch_next_trigger() == 0) {
|
|
|
|
/* Event flag raised or rtimer inactive.
|
|
|
|
* Turn on the 32MHz XOSC, restore PMCTL and abort */
|
|
|
|
select_32_mhz_xosc();
|
|
|
|
|
|
|
|
REG(SYS_CTRL_PMCTL) = SYS_CTRL_PMCTL_PM0;
|
2013-12-03 17:44:01 +00:00
|
|
|
|
|
|
|
/* Remember IRQ energest for next pass */
|
|
|
|
ENERGEST_IRQ_SAVE(irq_energest);
|
2015-09-03 14:06:46 +00:00
|
|
|
ENERGEST_SWITCH(ENERGEST_TYPE_LPM, ENERGEST_TYPE_CPU);
|
2013-01-12 22:44:42 +00:00
|
|
|
} else {
|
|
|
|
/* All clear. Assert WFI and drop to PM1/2. This is now un-interruptible */
|
|
|
|
assert_wfi();
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* We reach here after coming back from PM1/2. The interrupt context that
|
|
|
|
* woke us up has returned. lpm_exit() has run, it has switched the system
|
|
|
|
* clock source back to the 32MHz XOSC, it has adjusted the system clock,
|
|
|
|
* it has restored PMCTL and it has done energest housekeeping
|
|
|
|
*/
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
/*---------------------------------------------------------------------------*/
|
|
|
|
void
|
|
|
|
lpm_set_max_pm(uint8_t pm)
|
|
|
|
{
|
|
|
|
max_pm = pm > LPM_CONF_MAX_PM ? LPM_CONF_MAX_PM : pm;
|
|
|
|
}
|
|
|
|
/*---------------------------------------------------------------------------*/
|
|
|
|
void
|
2013-11-15 15:21:34 +00:00
|
|
|
lpm_register_peripheral(lpm_periph_permit_pm1_func_t permit_pm1_func)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
|
|
|
|
for(i = 0; i < LPM_PERIPH_PERMIT_PM1_FUNCS_MAX; i++) {
|
|
|
|
if(periph_permit_pm1_funcs[i] == permit_pm1_func) {
|
|
|
|
break;
|
|
|
|
} else if(periph_permit_pm1_funcs[i] == NULL) {
|
|
|
|
periph_permit_pm1_funcs[i] = permit_pm1_func;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
/*---------------------------------------------------------------------------*/
|
|
|
|
void
|
2013-01-12 22:44:42 +00:00
|
|
|
lpm_init()
|
|
|
|
{
|
|
|
|
/*
|
|
|
|
* The main loop calls lpm_enter() when we have no more events to service.
|
|
|
|
* By default, we will enter PM0 unless lpm_enter() decides otherwise
|
|
|
|
*/
|
|
|
|
REG(SYS_CTRL_PMCTL) = SYS_CTRL_PMCTL_PM0;
|
|
|
|
REG(SCB_SYSCTRL) |= SCB_SYSCTRL_SLEEPDEEP;
|
|
|
|
|
|
|
|
max_pm = LPM_CONF_MAX_PM;
|
|
|
|
|
|
|
|
LPM_STATS_INIT();
|
|
|
|
}
|
|
|
|
/*---------------------------------------------------------------------------*/
|
2014-01-28 19:10:51 +00:00
|
|
|
#endif /* LPM_CONF_ENABLE != 0 */
|
2013-01-12 22:44:42 +00:00
|
|
|
/** @} */
|