Skip to content

Commit aa176ee

Browse files
authored
Merge pull request #5585 from SiliconLabs/bugfix/timer_precision
Fix issue with timer timebase on EFR32
2 parents 1498a80 + c95728f commit aa176ee

File tree

1 file changed

+54
-58
lines changed

1 file changed

+54
-58
lines changed

targets/TARGET_Silicon_Labs/TARGET_EFM32/us_ticker.c

Lines changed: 54 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -38,21 +38,27 @@
3838
* the upper 16 bits are implemented in software.
3939
*/
4040

41-
static uint8_t us_ticker_inited = 0; // Is ticker initialized yet
41+
static uint8_t us_ticker_inited = 0; // Is ticker initialized yet
4242

43-
static volatile uint32_t ticker_cnt = 0; //Internal overflow count, used to extend internal 16-bit counter to (MHz * 32-bit)
44-
static volatile uint32_t ticker_int_cnt = 0; //Amount of overflows until user interrupt
45-
static volatile uint8_t ticker_freq_mhz = 0; //Frequency of timer in MHz
46-
static volatile uint32_t ticker_top_us = 0; //Amount of us corresponding to the top value of the timer
43+
static volatile uint32_t ticker_cnt = 0; // Internal overflow count, used to extend internal 16-bit counter to (MHz * 32-bit)
44+
static volatile uint32_t ticker_int_cnt = 0; // Amount of overflows until user interrupt
45+
static volatile uint32_t ticker_freq_khz = 0; // Frequency of timer in MHz
46+
static volatile uint32_t ticker_top_ms = 0; // Amount of ms corresponding to the top value of the timer
47+
static volatile uint32_t soft_timer_top = 0; // When to wrap the software counter
4748

4849
void us_ticker_irq_handler_internal(void)
4950
{
50-
/* Handle timer overflow */
51-
if (TIMER_IntGet(US_TICKER_TIMER) & TIMER_IF_OF) {
52-
ticker_cnt++;
53-
if(ticker_cnt >= ((uint32_t)ticker_freq_mhz << 16)) ticker_cnt = 0;
54-
TIMER_IntClear(US_TICKER_TIMER, TIMER_IF_OF);
55-
}
51+
/* Handle timer overflow */
52+
if (TIMER_IntGet(US_TICKER_TIMER) & TIMER_IF_OF) {
53+
ticker_cnt++;
54+
55+
/* Wrap ticker_cnt when we've gone over 32-bit us value */
56+
if (ticker_cnt >= soft_timer_top) {
57+
ticker_cnt = 0;
58+
}
59+
60+
TIMER_IntClear(US_TICKER_TIMER, TIMER_IF_OF);
61+
}
5662

5763
/* Check for user interrupt expiration */
5864
if (TIMER_IntGet(US_TICKER_TIMER) & TIMER_IF_CC0) {
@@ -78,28 +84,28 @@ void us_ticker_init(void)
7884
/* Clear TIMER counter value */
7985
TIMER_CounterSet(US_TICKER_TIMER, 0);
8086

81-
/* Get frequency of clock in MHz for scaling ticks to microseconds */
82-
ticker_freq_mhz = (REFERENCE_FREQUENCY / 1000000);
83-
MBED_ASSERT(ticker_freq_mhz > 0);
87+
/* Get frequency of clock in kHz for scaling ticks to microseconds */
88+
ticker_freq_khz = (REFERENCE_FREQUENCY / 1000);
89+
MBED_ASSERT(ticker_freq_khz > 0);
8490

8591
/*
86-
* Calculate maximum prescaler that gives at least 1 MHz frequency, while keeping clock as an integer multiple of 1 MHz.
87-
* Example: 14 MHz => prescaler = 1 (i.e. DIV2), ticker_freq_mhz = 7;
88-
* 24 MHz => prescaler = 3 (i.e. DIV8), ticker_freq_mhz = 3;
89-
* 48 MHz => prescaler = 4 (i.e. DIV16), ticker_freq_mhz = 3;
92+
* Calculate maximum prescaler that gives at least 1 MHz frequency, giving us 1us resolution.
9093
* Limit prescaling to maximum prescaler value, which is 10 (DIV1024).
9194
*/
9295
uint32_t prescaler = 0;
93-
while((ticker_freq_mhz & 1) == 0 && prescaler <= 10) {
94-
ticker_freq_mhz = ticker_freq_mhz >> 1;
96+
while((ticker_freq_khz >= 2000) && prescaler <= 10) {
97+
ticker_freq_khz = ticker_freq_khz >> 1;
9598
prescaler++;
9699
}
97100

98101
/* Set prescaler */
99102
US_TICKER_TIMER->CTRL = (US_TICKER_TIMER->CTRL & ~_TIMER_CTRL_PRESC_MASK) | (prescaler << _TIMER_CTRL_PRESC_SHIFT);
100103

101-
/* calculate top value */
102-
ticker_top_us = (uint32_t) 0x10000 / ticker_freq_mhz;
104+
/* calculate top value.*/
105+
ticker_top_ms = (uint32_t) 0x10000 / ticker_freq_khz;
106+
107+
/* calculate software timer overflow */
108+
soft_timer_top = ((0xFFFFFFFFUL / 1000UL) / ticker_top_ms) + 1;
103109

104110
/* Select Compare Channel parameters */
105111
TIMER_InitCC_TypeDef timerCCInit = TIMER_INITCC_DEFAULT;
@@ -114,7 +120,7 @@ void us_ticker_init(void)
114120
NVIC_EnableIRQ(US_TICKER_TIMER_IRQ);
115121

116122
/* Set top value */
117-
TIMER_TopSet(US_TICKER_TIMER, (ticker_top_us * ticker_freq_mhz) - 1);
123+
TIMER_TopSet(US_TICKER_TIMER, (ticker_top_ms * ticker_freq_khz) - 1);
118124

119125
/* Start TIMER */
120126
TIMER_Enable(US_TICKER_TIMER, true);
@@ -123,7 +129,7 @@ void us_ticker_init(void)
123129
uint32_t us_ticker_read()
124130
{
125131
uint32_t countH_old, countH;
126-
uint16_t countL;
132+
uint32_t countL;
127133

128134
if (!us_ticker_inited) {
129135
us_ticker_init();
@@ -145,12 +151,12 @@ uint32_t us_ticker_read()
145151
/* Timer count value needs to be div'ed by the frequency to get to 1MHz ticks.
146152
* For the software-extended part, the amount of us in one overflow is constant.
147153
*/
148-
return (countL / ticker_freq_mhz) + (countH * ticker_top_us);
154+
return ((countL * 1000UL) / ticker_freq_khz) + (countH * ticker_top_ms * 1000);
149155
}
150156

151157
void us_ticker_set_interrupt(timestamp_t timestamp)
152158
{
153-
uint64_t goal = timestamp;
159+
uint32_t goal = timestamp;
154160
uint32_t trigger;
155161

156162
if((US_TICKER_TIMER->IEN & TIMER_IEN_CC0) == 0) {
@@ -160,52 +166,42 @@ void us_ticker_set_interrupt(timestamp_t timestamp)
160166
TIMER_IntDisable(US_TICKER_TIMER, TIMER_IEN_CC0);
161167

162168
/* convert us delta value back to timer ticks */
163-
goal -= us_ticker_read();
169+
trigger = us_ticker_read();
170+
if (trigger < goal) {
171+
goal -= trigger;
172+
} else {
173+
goal = (0xFFFFFFFFUL - (trigger - goal));
174+
}
164175
trigger = US_TICKER_TIMER->CNT;
165176

166177
/* Catch "Going back in time" */
167-
if(goal < (50 / (REFERENCE_FREQUENCY / 1000000)) ||
178+
if(goal < 10 ||
168179
goal >= 0xFFFFFF00UL) {
169180
TIMER_IntClear(US_TICKER_TIMER, TIMER_IFC_CC0);
170-
TIMER_CompareSet(US_TICKER_TIMER, 0, (US_TICKER_TIMER->CNT + 3 > US_TICKER_TIMER->TOP ? 3 : US_TICKER_TIMER->CNT + 3));
181+
TIMER_CompareSet(US_TICKER_TIMER, 0, (US_TICKER_TIMER->CNT + 3 >= US_TICKER_TIMER->TOP ? 3 : US_TICKER_TIMER->CNT + 3));
171182
TIMER_IntEnable(US_TICKER_TIMER, TIMER_IEN_CC0);
172183
return;
173184
}
174185

175-
/* Cap at 32 bit */
176-
goal &= 0xFFFFFFFFUL;
177-
/* Convert to ticker timebase */
178-
goal *= ticker_freq_mhz;
179-
180-
/* Note: we should actually populate the following fields by the division and remainder
181-
* of goal / ticks_per_overflow, but since we're keeping the frequency as low
182-
* as possible, and ticks_per_overflow as close to FFFF as possible, we can
183-
* get away with ditching the division here and saving cycles.
184-
*
185-
* "exact" implementation:
186-
* ticker_int_cnt = goal / TIMER_TopGet(US_TICKER_TIMER);
187-
* ticker_int_rem = goal % TIMER_TopGet(US_TICKER_TIMER);
188-
*/
189-
ticker_int_cnt = (goal >> 16) & 0xFFFFFFFF;
186+
uint32_t timer_top = TIMER_TopGet(US_TICKER_TIMER);
187+
uint32_t top_us = 1000 * ticker_top_ms;
190188

191-
/* Set compare channel 0 to (current position + lower 16 bits of target).
192-
* When lower 16 bits match, run complete cycles with ticker_int_rem as trigger value
193-
* for ticker_int_cnt times. */
194-
TIMER_IntClear(US_TICKER_TIMER, TIMER_IFC_CC0);
189+
/* Amount of times we expect to overflow: us offset / us period of timer */
190+
ticker_int_cnt = goal / top_us;
195191

196-
/* Take top of timer into account so that we don't end up missing a cycle */
197-
/* Set trigger point by adding delta to current time */
198-
if((goal & 0xFFFF) >= TIMER_TopGet(US_TICKER_TIMER)) {
199-
trigger += (goal & 0xFFFF) - TIMER_TopGet(US_TICKER_TIMER);
200-
ticker_int_cnt++;
201-
} else {
202-
trigger += (goal & 0xFFFF);
203-
}
192+
/* Leftover microseconds need to be converted to timer timebase */
193+
trigger += (((goal % top_us) * ticker_freq_khz) / 1000);
204194

205-
if(trigger >= TIMER_TopGet(US_TICKER_TIMER)) {
206-
trigger -= TIMER_TopGet(US_TICKER_TIMER);
195+
/* Cap compare value to timer top */
196+
if (trigger >= timer_top) {
197+
trigger -= timer_top;
207198
}
208199

200+
/* Set compare channel 0 to (current position + lower 16 bits of target).
201+
* When lower 16 bits match, run complete cycles with ticker_int_rem as trigger value
202+
* for ticker_int_cnt times. */
203+
TIMER_IntClear(US_TICKER_TIMER, TIMER_IEN_CC0);
204+
209205
TIMER_CompareSet(US_TICKER_TIMER, 0, trigger);
210206

211207
TIMER_IntEnable(US_TICKER_TIMER, TIMER_IEN_CC0);

0 commit comments

Comments
 (0)