Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[EFM32] Microsecond ticker optimization #2666

Merged
merged 1 commit into from
Sep 16, 2016
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
119 changes: 68 additions & 51 deletions hal/targets/hal/TARGET_Silicon_Labs/TARGET_EFM32/us_ticker.c
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
* @file us_ticker.c
*******************************************************************************
* @section License
* <b>(C) Copyright 2015 Silicon Labs, http://www.silabs.com</b>
* <b>(C) Copyright 2016 Silicon Labs, http://www.silabs.com</b>
*******************************************************************************
*
* SPDX-License-Identifier: Apache-2.0
Expand Down Expand Up @@ -42,33 +42,29 @@

static uint8_t us_ticker_inited = 0; // Is ticker initialized yet

static volatile uint32_t ticker_cnt = 0x2ff00; //Internal overflow count, used to extend internal 16-bit counter to (MHz * 32-bit)
static volatile uint16_t ticker_int_rem = 0; //Timer match value for user interrupt
static volatile uint32_t ticker_cnt = 0; //Internal overflow count, used to extend internal 16-bit counter to (MHz * 32-bit)
static volatile uint32_t ticker_int_cnt = 0; //Amount of overflows until user interrupt
static volatile uint8_t ticker_freq_mhz = 0; //Frequency of timer in MHz
static volatile uint32_t ticker_top_us = 0; //Amount of us corresponding to the top value of the timer

void us_ticker_irq_handler_internal(void)
{
/* Handle timer overflow */
if (TIMER_IntGet(US_TICKER_TIMER) & TIMER_IF_OF) {
ticker_cnt++;
if(ticker_cnt >= ((uint32_t)ticker_freq_mhz << 16)) ticker_cnt = 0;
TIMER_IntClear(US_TICKER_TIMER, TIMER_IF_OF);
}

/* Check for user interrupt expiration */
if (TIMER_IntGet(US_TICKER_TIMER) & TIMER_IF_CC0) {
if (ticker_int_rem > 0) {
TIMER_CompareSet(US_TICKER_TIMER, 0, ticker_int_rem);
ticker_int_rem = 0;
TIMER_IntClear(US_TICKER_TIMER, TIMER_IF_CC0);
} else if (ticker_int_cnt > 0) {
if (ticker_int_cnt > 0) {
ticker_int_cnt--;
TIMER_IntClear(US_TICKER_TIMER, TIMER_IF_CC0);
} else {
us_ticker_irq_handler();
}
}

/* Handle timer overflow */
if (TIMER_IntGet(US_TICKER_TIMER) & TIMER_IF_OF) {
ticker_cnt++;
if(ticker_cnt >= (((uint32_t)ticker_freq_mhz) << 16)) ticker_cnt = 0;
TIMER_IntClear(US_TICKER_TIMER, TIMER_IF_OF);
}
}

void us_ticker_init(void)
Expand Down Expand Up @@ -104,6 +100,9 @@ void us_ticker_init(void)
/* Set prescaler */
US_TICKER_TIMER->CTRL = (US_TICKER_TIMER->CTRL & ~_TIMER_CTRL_PRESC_MASK) | (prescaler << _TIMER_CTRL_PRESC_SHIFT);

/* calculate top value */
ticker_top_us = (uint32_t) 0x10000 / ticker_freq_mhz;

/* Select Compare Channel parameters */
TIMER_InitCC_TypeDef timerCCInit = TIMER_INITCC_DEFAULT;
timerCCInit.mode = timerCCModeCompare;
Expand All @@ -117,15 +116,16 @@ void us_ticker_init(void)
NVIC_EnableIRQ(US_TICKER_TIMER_IRQ);

/* Set top value */
TIMER_TopSet(US_TICKER_TIMER, 0xFFFF);
TIMER_TopSet(US_TICKER_TIMER, (ticker_top_us * ticker_freq_mhz) - 1);

/* Start TIMER */
TIMER_Enable(US_TICKER_TIMER, true);
}

uint32_t us_ticker_read()
{
uint32_t volatile countH_old, countH, countL;
uint32_t countH_old, countH;
uint16_t countL;

if (!us_ticker_inited) {
us_ticker_init();
Expand All @@ -134,65 +134,82 @@ uint32_t us_ticker_read()
/* Avoid jumping in time by reading high bits twice */
do {
countH_old = ticker_cnt;
/* If the counter overflowed while in the IRQ handler for the CC0 interrupt,
* it hasn't had time to update ticker_cnt yet. Take this into account here. */
if (TIMER_IntGet(US_TICKER_TIMER) & TIMER_IF_OF) {
countH_old += 1;
countH_old++;
}
countL = US_TICKER_TIMER->CNT;
countH = ticker_cnt;
/* If the counter overflowed while in the IRQ handler for the CC0 interrupt,
* it hasn't had time to update ticker_cnt yet. Take this into account here. */
if (TIMER_IntGet(US_TICKER_TIMER) & TIMER_IF_OF) {
countH += 1;
countH++;
}
} while (countH_old != countH);

/* Merge upper (mhz * 16-bit) and lower 16-bit into 64bit */
uint64_t count = ((uint64_t)countH << 16) | (uint64_t)countL;
/* Divide by ticker_freq_mhz to get 32-bit 1MHz timestamp */
return (count / ticker_freq_mhz);
/* Timer count value needs to be div'ed by the frequency to get to 1MHz ticks.
* For the software-extended part, the amount of us in one overflow is constant.
*/
return (countL / ticker_freq_mhz) + (countH * ticker_top_us);
}

void us_ticker_set_interrupt(timestamp_t timestamp)
{
int32_t delta = 0, ts = timestamp, time = us_ticker_read();
uint64_t goal = timestamp;
uint32_t trigger;

if((US_TICKER_TIMER->IEN & TIMER_IEN_CC0) == 0) {
//Timer was disabled, but is going to be enabled. Set sleep mode.
blockSleepMode(TIMER_LEAST_ACTIVE_SLEEPMODE);
}
TIMER_IntDisable(US_TICKER_TIMER, TIMER_IEN_CC0);

delta = ts - time;
if(delta <= ticker_freq_mhz) {
delta = ticker_freq_mhz;
timestamp = us_ticker_read() + 0x100;
}
/* convert us delta value back to timer ticks */
goal -= us_ticker_read();
trigger = US_TICKER_TIMER->CNT;

/* Multiply by ticker_freq_mhz to get clock ticks */
delta *= ticker_freq_mhz;
/* Overflowing this doesn't matter, since we only need the lower 16 bits */
ts *= ticker_freq_mhz;
/* Catch "Going back in time" */
if(goal < (50 / (REFERENCE_FREQUENCY / 1000000)) ||
goal >= 0xFFFFFF00UL) {
TIMER_IntClear(US_TICKER_TIMER, TIMER_IFC_CC0);
TIMER_CompareSet(US_TICKER_TIMER, 0, (US_TICKER_TIMER->CNT + 3 > US_TICKER_TIMER->TOP ? 3 : US_TICKER_TIMER->CNT + 3));
TIMER_IntEnable(US_TICKER_TIMER, TIMER_IEN_CC0);
return;
}

/* Split delta between timers */
ticker_int_cnt = (((uint64_t)delta) >> 16) & 0xFFFFFFFF;
ticker_int_rem = ts & 0xFFFF;
/* Cap at 32 bit */
goal &= 0xFFFFFFFFUL;
/* Convert to ticker timebase */
goal *= ticker_freq_mhz;

/* Set compare channel 0 to (current position + lower 16 bits of delta).
* If lower 16 bits is a small number, we a do one compare of (current + lower 16 + 0x8000)
* and then one of (current + lower 16). Else, we simply use (current + lower 16).
/* Note: we should actually populate the following fields by the division and remainder
* of goal / ticks_per_overflow, but since we're keeping the frequency as low
* as possible, and ticks_per_overflow as close to FFFF as possible, we can
* get away with ditching the division here and saving cycles.
*
* When time from lower 16 bits have elapsed, run complete cycles with ticker_int_rem as
* reference ticker_int_cnt times. */
if ((delta & 0xFFFF) < 0x8000 && ticker_int_cnt > 0) {
TIMER_CompareSet(US_TICKER_TIMER, 0, ticker_int_rem + 0x8000);
ticker_int_cnt--;
* "exact" implementation:
* ticker_int_cnt = goal / TIMER_TopGet(US_TICKER_TIMER);
* ticker_int_rem = goal % TIMER_TopGet(US_TICKER_TIMER);
*/
ticker_int_cnt = (goal >> 16) & 0xFFFFFFFF;

/* Set compare channel 0 to (current position + lower 16 bits of target).
* When lower 16 bits match, run complete cycles with ticker_int_rem as trigger value
* for ticker_int_cnt times. */
TIMER_IntClear(US_TICKER_TIMER, TIMER_IFC_CC0);

/* Take top of timer into account so that we don't end up missing a cycle */
/* Set trigger point by adding delta to current time */
if((goal & 0xFFFF) >= TIMER_TopGet(US_TICKER_TIMER)) {
trigger += (goal & 0xFFFF) - TIMER_TopGet(US_TICKER_TIMER);
ticker_int_cnt++;
} else {
TIMER_CompareSet(US_TICKER_TIMER, 0, ticker_int_rem);
ticker_int_rem = 0;
trigger += (goal & 0xFFFF);
}
TIMER_IntClear(US_TICKER_TIMER, TIMER_IFC_CC0);

if(trigger >= TIMER_TopGet(US_TICKER_TIMER)) {
trigger -= TIMER_TopGet(US_TICKER_TIMER);
}

TIMER_CompareSet(US_TICKER_TIMER, 0, trigger);

TIMER_IntEnable(US_TICKER_TIMER, TIMER_IEN_CC0);
}

Expand Down