Skip to content

Commit c95728f

Browse files
committed
Fix issue with timer timebase on EFR32
Timer code was written based on integer multiple HF clock frequencies. EFR32 doesn't conform to that (38.4), and so the timestamp ticks were off by 1%. Enough to trip up some CI tests on TB_SENSE_12 (#5496)
1 parent 4e22295 commit c95728f

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)