[nrf52][tick] improve RTC use for LK tick
Using the RTC1 module for the LK tick (instead of arm systick) Use the nrf_hal library for peripheral manipulation instead of direct register access. Fixed race condition in current_time_hires which would result in non monotonic readings. Tested with clock_tests on: nrf52-pca10040-test (nrf52832) nrf52-pca10056-test (nrf52840)
This commit is contained in:
@@ -12,40 +12,76 @@
|
||||
#include <platform/timer.h>
|
||||
#include <platform/clock.h>
|
||||
#include <nrfx_clock.h>
|
||||
#include <hal/nrf_rtc.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
// TODO - integrate nrfx rtc driver instead of direct register manipulation here.
|
||||
/*
|
||||
* This uses the RTC1 module for the LK system tick instead of using the ARM
|
||||
* systick since systick is not available in all low power states. This is also
|
||||
* not utilizing the nrfx rtc driver as it adds unneeded functionality and thus
|
||||
* overhead to the irq processing. It is however using convenience functions
|
||||
* in the nrf hal library (nrf hal should not be confused with nrfx driver library)
|
||||
*/
|
||||
|
||||
//base counter is total number of clock cycles elapsed
|
||||
// compare channel used by our timer.
|
||||
#define LK_RTC_CC_CHAN (0)
|
||||
|
||||
#define MULT_BY_32768(x) ((x) << 15)
|
||||
#define DIV_BY_32768(x) ((x) >> 15)
|
||||
|
||||
/*
|
||||
* base_counter is total number of ms elapsed in 49.15 format. By choosing this
|
||||
* format we take advantage of the fact our oscillator is 32768Hz, allowing the
|
||||
* recurring math to be done with shift by 15. This preserves some precision in
|
||||
* the base_counter for use in current_time_hires.
|
||||
*
|
||||
* base_counter will overflow every 17,851 years. Plan accordingly.
|
||||
*/
|
||||
static volatile uint64_t base_counter = 0;
|
||||
|
||||
static uint32_t clock_rate = 0;
|
||||
|
||||
//cycles of our clock per tick interval
|
||||
static uint32_t cycles_per_tick = 0;
|
||||
static uint32_t rtc_counts_per_tick = 0;
|
||||
static volatile uint32_t last_cc = 0;
|
||||
|
||||
//delta to be added to base counter (ms in 49.15 format) each lk tick
|
||||
static uint32_t ms_scale = 0;
|
||||
|
||||
static platform_timer_callback cb;
|
||||
static void *cb_args;
|
||||
|
||||
typedef enum handler_return (*platform_timer_callback)(void *arg, lk_time_t now);
|
||||
|
||||
status_t platform_set_periodic_timer(platform_timer_callback callback, void *arg, lk_time_t interval) {
|
||||
ASSERT(clock_rate > 0);
|
||||
ASSERT(nrfx_clock_lfclk_is_running());
|
||||
cb = callback;
|
||||
cb_args = arg;
|
||||
// RTC clock frequency is always 32768Hz, so we take advantage of shifts vs. multiplicaiton
|
||||
rtc_counts_per_tick = MULT_BY_32768(interval) / 1000 ;
|
||||
ms_scale = rtc_counts_per_tick * 1000;
|
||||
|
||||
cycles_per_tick = clock_rate * interval / 1000 ;
|
||||
// Stop the counter in case it was already running
|
||||
nrf_rtc_task_trigger(NRF_RTC1, NRF_RTC_TASK_STOP);
|
||||
|
||||
NRF_RTC1->CC[0] = 0x00ffffff & (base_counter + cycles_per_tick);
|
||||
// Clear the counter
|
||||
nrf_rtc_task_trigger(NRF_RTC1, NRF_RTC_TASK_CLEAR);
|
||||
|
||||
NRF_RTC1->PRESCALER = 0;
|
||||
// Set the first timer interval
|
||||
nrf_rtc_cc_set(NRF_RTC1, LK_RTC_CC_CHAN, rtc_counts_per_tick);
|
||||
|
||||
NRF_RTC1->INTENSET = RTC_INTENSET_COMPARE0_Enabled << RTC_INTENSET_COMPARE0_Pos;
|
||||
// Run at full 32768Hz rate, no prescale
|
||||
nrf_rtc_prescaler_set(NRF_RTC1, 0);
|
||||
|
||||
// Allow the CC channel to create interrupts when flagged
|
||||
nrf_rtc_int_enable(NRF_RTC1, RTC_CHANNEL_INT_MASK(LK_RTC_CC_CHAN));
|
||||
|
||||
// In order for this event to wake the device from low power/sleep state, the
|
||||
// event must be enabled to do so.
|
||||
nrf_rtc_event_enable(NRF_RTC1, RTC_CHANNEL_INT_MASK(LK_RTC_CC_CHAN));
|
||||
|
||||
// Clear the event state prior to enabling the irq to prevent spurious irq
|
||||
nrf_rtc_event_clear(NRF_RTC1, nrf_rtc_compare_event_get(LK_RTC_CC_CHAN));
|
||||
|
||||
// Start the RTC counter.
|
||||
nrf_rtc_task_trigger(NRF_RTC1, NRF_RTC_TASK_START);
|
||||
|
||||
NRF_RTC1->EVENTS_TICK = 0;
|
||||
NRF_RTC1->EVENTS_COMPARE[0] = 0;
|
||||
NRF_RTC1->TASKS_START = 1;
|
||||
NVIC_EnableIRQ(RTC1_IRQn);
|
||||
|
||||
return NO_ERROR;
|
||||
@@ -59,36 +95,37 @@ lk_time_t current_time(void) {
|
||||
t = base_counter;
|
||||
} while (base_counter != t);
|
||||
|
||||
return t * 1000 / clock_rate;
|
||||
return DIV_BY_32768(t);
|
||||
}
|
||||
|
||||
// time in usec
|
||||
lk_bigtime_t current_time_hires(void) {
|
||||
uint64_t t;
|
||||
volatile uint64_t t;
|
||||
volatile uint32_t delta;
|
||||
|
||||
do {
|
||||
// order of operations below is critical to catch if timer is updated
|
||||
// during the calculations.
|
||||
t = base_counter;
|
||||
} while (base_counter != t);
|
||||
delta = (nrf_rtc_counter_get(NRF_RTC1) - last_cc);
|
||||
} while ((t != base_counter));
|
||||
|
||||
// base_counter only gets updated once every tick, regain extra
|
||||
// precision by adding in elapsed counts since last tick interrupt
|
||||
// Note: counter is only 24 bits
|
||||
t = t + ((NRF_RTC1->COUNTER - (t & 0x00ffffff)) & 0x00ffffff);
|
||||
return (t * 1000000 / clock_rate);
|
||||
return DIV_BY_32768((t + delta * 1000) * 1000);
|
||||
}
|
||||
|
||||
void nrf52_RTC1_IRQ(void) {
|
||||
// update to this point in time
|
||||
base_counter += cycles_per_tick;
|
||||
base_counter += ms_scale;
|
||||
arm_cm_irq_entry();
|
||||
|
||||
NRF_RTC1->EVENTS_COMPARE[0] = 0;
|
||||
nrf_rtc_event_clear(NRF_RTC1, nrf_rtc_compare_event_get(LK_RTC_CC_CHAN));
|
||||
// calculate time of next interrupt
|
||||
NRF_RTC1->CC[0] = 0x00ffffff & (base_counter + cycles_per_tick);
|
||||
last_cc = nrf_rtc_cc_get(NRF_RTC1, LK_RTC_CC_CHAN);
|
||||
nrf_rtc_cc_set(NRF_RTC1, LK_RTC_CC_CHAN, last_cc + rtc_counts_per_tick);
|
||||
|
||||
bool resched = false;
|
||||
if (cb) {
|
||||
lk_time_t now = current_time();
|
||||
lk_time_t now = DIV_BY_32768(base_counter);
|
||||
if (cb(cb_args, now) == INT_RESCHEDULE)
|
||||
resched = true;
|
||||
}
|
||||
@@ -97,8 +134,11 @@ void nrf52_RTC1_IRQ(void) {
|
||||
|
||||
void arm_cm_systick_init(uint32_t hz) {
|
||||
ASSERT(hz == 32768);
|
||||
clock_rate = hz;
|
||||
//Enable the LF xtal oscillator
|
||||
nrf52_clock_lfclk_enable(NRF_CLOCK_LFCLK_Xtal);
|
||||
//Try to enable the LF xtal oscillator
|
||||
status_t status = nrf52_clock_lfclk_enable(NRF_CLOCK_LFCLK_Xtal);
|
||||
if (status != NO_ERROR) {
|
||||
// If xtal won't start (might not be present on target) then
|
||||
// use the internal RC oscillator which is always present.
|
||||
nrf52_clock_lfclk_enable(NRF_CLOCK_LFCLK_RC);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user