Skip to content

Commit 4d7c045

Browse files
authored
Merge pull request #4424 from LMESTM/STM32_16bits_tickers
Fix corner cases in STM32 16bit tickers
2 parents 28f590f + ea2cc1d commit 4d7c045

File tree

2 files changed

+130
-64
lines changed

2 files changed

+130
-64
lines changed

targets/TARGET_STM/hal_tick_16b.c

Lines changed: 11 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -24,31 +24,24 @@ extern TIM_HandleTypeDef TimMasterHandle;
2424

2525
extern volatile uint32_t SlaveCounter;
2626
extern volatile uint32_t oc_int_part;
27-
extern volatile uint16_t oc_rem_part;
28-
extern volatile uint8_t tim_it_update;
29-
extern volatile uint32_t tim_it_counter;
3027

3128
volatile uint32_t PreviousVal = 0;
3229

3330
void us_ticker_irq_handler(void);
34-
void set_compare(uint16_t count);
3531

3632
#if defined(TARGET_STM32F0)
3733
void timer_update_irq_handler(void) {
3834
#else
3935
void timer_irq_handler(void)
4036
{
4137
#endif
42-
uint16_t cnt_val = TIM_MST->CNT;
4338
TimMasterHandle.Instance = TIM_MST;
4439

4540
// Clear Update interrupt flag
4641
if (__HAL_TIM_GET_FLAG(&TimMasterHandle, TIM_FLAG_UPDATE) == SET) {
4742
if (__HAL_TIM_GET_IT_SOURCE(&TimMasterHandle, TIM_IT_UPDATE) == SET) {
4843
__HAL_TIM_CLEAR_IT(&TimMasterHandle, TIM_IT_UPDATE);
4944
SlaveCounter++;
50-
tim_it_counter = cnt_val + (uint32_t)(SlaveCounter << 16);
51-
tim_it_update = 1;
5245
}
5346
}
5447

@@ -57,31 +50,25 @@ void timer_irq_handler(void)
5750
// Used for mbed timeout (channel 1) and HAL tick (channel 2)
5851
void timer_oc_irq_handler(void)
5952
{
60-
uint16_t cnt_val = TIM_MST->CNT;
6153
TimMasterHandle.Instance = TIM_MST;
6254
#endif
6355

6456
// Channel 1 for mbed timeout
6557
if (__HAL_TIM_GET_FLAG(&TimMasterHandle, TIM_FLAG_CC1) == SET) {
6658
if (__HAL_TIM_GET_IT_SOURCE(&TimMasterHandle, TIM_IT_CC1) == SET) {
6759
__HAL_TIM_CLEAR_IT(&TimMasterHandle, TIM_IT_CC1);
68-
if (oc_rem_part > 0) {
69-
set_compare(oc_rem_part); // Finish the remaining time left
70-
oc_rem_part = 0;
71-
} else {
60+
7261
if (oc_int_part > 0) {
73-
set_compare(0xFFFF);
74-
oc_rem_part = cnt_val; // To finish the counter loop the next time
7562
oc_int_part--;
7663
} else {
77-
us_ticker_irq_handler();
64+
us_ticker_irq_handler();
7865
}
79-
}
8066
}
8167
}
8268

8369
// Channel 2 for HAL tick
8470
if (__HAL_TIM_GET_FLAG(&TimMasterHandle, TIM_FLAG_CC2) == SET) {
71+
8572
if (__HAL_TIM_GET_IT_SOURCE(&TimMasterHandle, TIM_IT_CC2) == SET) {
8673
__HAL_TIM_CLEAR_IT(&TimMasterHandle, TIM_IT_CC2);
8774
uint32_t val = __HAL_TIM_GET_COUNTER(&TimMasterHandle);
@@ -118,11 +105,16 @@ HAL_StatusTypeDef HAL_InitTick(uint32_t TickPriority)
118105
TimMasterHandle.Init.Prescaler = (uint32_t)(SystemCoreClock / 1000000) - 1; // 1 us tick
119106
TimMasterHandle.Init.ClockDivision = 0;
120107
TimMasterHandle.Init.CounterMode = TIM_COUNTERMODE_UP;
121-
#ifdef TARGET_STM32F0
108+
#if !defined(TARGET_STM32L0)
109+
TimMasterHandle.Init.RepetitionCounter = 0;
110+
#endif
111+
#ifdef TIM_AUTORELOAD_PRELOAD_DISABLE
122112
TimMasterHandle.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
123113
#endif
124114
HAL_TIM_Base_Init(&TimMasterHandle);
125115

116+
//LL_TIM_EnableUpdateEvent(TimMasterHandle.Instance);
117+
126118
// Configure output compare channel 1 for mbed timeout (enabled later when used)
127119
HAL_TIM_OC_Start(&TimMasterHandle, TIM_CHANNEL_1);
128120

@@ -131,6 +123,8 @@ HAL_StatusTypeDef HAL_InitTick(uint32_t TickPriority)
131123
PreviousVal = __HAL_TIM_GET_COUNTER(&TimMasterHandle);
132124
__HAL_TIM_SET_COMPARE(&TimMasterHandle, TIM_CHANNEL_2, PreviousVal + HAL_TICK_DELAY);
133125

126+
127+
134128
// Configure interrupts
135129
// Update interrupt used for 32-bit counter
136130
// Output compare channel 1 interrupt for mbed timeout

targets/TARGET_STM/us_ticker_16b.c

Lines changed: 119 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -25,21 +25,9 @@ TIM_HandleTypeDef TimMasterHandle;
2525

2626
volatile uint32_t SlaveCounter = 0;
2727
volatile uint32_t oc_int_part = 0;
28-
volatile uint16_t oc_rem_part = 0;
29-
volatile uint8_t tim_it_update; // TIM_IT_UPDATE event flag set in timer_irq_handler()
30-
volatile uint32_t tim_it_counter = 0; // Time stamp to be updated by timer_irq_handler()
3128

3229
static int us_ticker_inited = 0;
3330

34-
void set_compare(uint16_t count)
35-
{
36-
TimMasterHandle.Instance = TIM_MST;
37-
// Set new output compare value
38-
__HAL_TIM_SET_COMPARE(&TimMasterHandle, TIM_CHANNEL_1, count);
39-
// Enable IT
40-
__HAL_TIM_ENABLE_IT(&TimMasterHandle, TIM_IT_CC1);
41-
}
42-
4331
void us_ticker_init(void)
4432
{
4533
if (us_ticker_inited) return;
@@ -52,22 +40,22 @@ void us_ticker_init(void)
5240

5341
uint32_t us_ticker_read()
5442
{
55-
uint32_t counter;
56-
57-
TimMasterHandle.Instance = TIM_MST;
58-
5943
if (!us_ticker_inited) us_ticker_init();
6044

61-
#if defined(TARGET_STM32L0)
6245
uint16_t cntH_old, cntH, cntL;
6346
do {
64-
// For some reason on L0xx series we need to read and clear the
65-
// overflow flag which give extra time to propelry handle possible
66-
// hiccup after ~60s
67-
if (__HAL_TIM_GET_FLAG(&TimMasterHandle, TIM_FLAG_CC1OF) == SET) {
68-
__HAL_TIM_CLEAR_FLAG(&TimMasterHandle, TIM_FLAG_CC1OF);
69-
}
7047
cntH_old = SlaveCounter;
48+
/* SlaveCounter needs to be checked before AND after we read the
49+
* current counter TIM_MST->CNT, in case it wraps around.
50+
* there are 2 possible cases of wrap around
51+
* 1) in case this function is interrupted by timer_irq_handler and
52+
* the SlaveCounter is updated. In that case we will loop again.
53+
* 2) in case this function is called from interrupt context during
54+
* wrap-around condtion. That would prevent/delay the timer_irq_handler
55+
* from being called so we need to locally check the FLAG_UPDATE and
56+
* update the cntH accordingly. The SlaveCounter variable itself will
57+
* be updated in the interrupt handler just after ...
58+
*/
7159
if (__HAL_TIM_GET_FLAG(&TimMasterHandle, TIM_FLAG_UPDATE) == SET) {
7260
cntH_old += 1;
7361
}
@@ -79,35 +67,121 @@ uint32_t us_ticker_read()
7967
} while(cntH_old != cntH);
8068
// Glue the upper and lower part together to get a 32 bit timer
8169
return (uint32_t)(cntH << 16 | cntL);
82-
#else
83-
tim_it_update = 0; // Clear TIM_IT_UPDATE event flag
84-
counter = TIM_MST->CNT + (uint32_t)(SlaveCounter << 16); // Calculate new time stamp
85-
if (tim_it_update == 1) {
86-
return tim_it_counter; // In case of TIM_IT_UPDATE return the time stamp that was calculated in timer_irq_handler()
87-
}
88-
else {
89-
return counter; // Otherwise return the time stamp calculated here
90-
}
91-
#endif
9270
}
9371

9472
void us_ticker_set_interrupt(timestamp_t timestamp)
9573
{
96-
int delta = (int)((uint32_t)timestamp - us_ticker_read());
74+
// NOTE: This function must be called with interrupts disabled to keep our
75+
// timer interrupt setup atomic
76+
TimMasterHandle.Instance = TIM_MST;
77+
// Set new output compare value
78+
__HAL_TIM_SET_COMPARE(&TimMasterHandle, TIM_CHANNEL_1, timestamp & 0xFFFF);
79+
// Ensure the compare event starts clear
80+
__HAL_TIM_CLEAR_FLAG(&TimMasterHandle, TIM_FLAG_CC1);
81+
// Enable IT
82+
__HAL_TIM_ENABLE_IT(&TimMasterHandle, TIM_IT_CC1);
9783

98-
uint16_t cval = TIM_MST->CNT;
84+
int current_time = us_ticker_read();
85+
int delta = (int)(timestamp - current_time);
9986

10087
if (delta <= 0) { // This event was in the past
101-
us_ticker_irq_handler();
88+
/* Immediately set the compare event to cause the event to be handled in
89+
* the next interrupt context. This prevents calling interrupt handlers
90+
* recursively as us_ticker_set_interrupt might be called again from the
91+
* application handler
92+
*/
93+
oc_int_part = 0;
94+
HAL_TIM_GenerateEvent(&TimMasterHandle, TIM_EVENTSOURCE_CC1);
10295
} else {
103-
oc_int_part = (uint32_t)(delta >> 16);
104-
oc_rem_part = (uint16_t)(delta & 0xFFFF);
105-
if (oc_rem_part <= (0xFFFF - cval)) {
106-
set_compare(cval + oc_rem_part);
107-
oc_rem_part = 0;
108-
} else {
109-
set_compare(0xFFFF);
110-
oc_rem_part = oc_rem_part - (0xFFFF - cval);
96+
/* Set the number of timer wrap-around loops before the actual timestamp
97+
* is reached. If the calculated delta time is more than halfway to the
98+
* next compare event, check to see if a compare event has already been
99+
* set, and if so, add one to the wrap-around count. This is done to
100+
* ensure the correct wrap count is used in the corner cases where the
101+
* 16 bit counter passes the compare value during the process of
102+
* configuring this interrupt.
103+
*
104+
* Assumption: The time to execute this function is less than 32ms
105+
* (otherwise incorrect behaviour could result)
106+
*
107+
* Consider the following corner cases:
108+
* 1) timestamp is 1 us in the future:
109+
* oc_int_part = 0 initially
110+
* oc_int_part left at 0 because ((delta - 1) & 0xFFFF) < 0x8000
111+
* Compare event should happen in 1 us and us_ticker_irq_handler()
112+
* called
113+
* 2) timestamp is 0x8000 us in the future:
114+
* oc_int_part = 0 initially
115+
* oc_int_part left at 0 because ((delta - 1) & 0xFFFF) < 0x8000
116+
* There should be no possibility of the CC1 flag being set yet
117+
* (see assumption above). When the compare event does occur in
118+
* 32768 us, us_ticker_irq_handler() will be called
119+
* 3) timestamp is 0x8001 us in the future:
120+
* oc_int_part = 0 initially
121+
* ((delta - 1) & 0xFFFF) >= 0x8000 but there should be no
122+
* possibility of the CC1 flag being set yet (see assumption above),
123+
* so oc_int_part will be left at 0, and when the compare event
124+
* does occur in 32769 us, us_ticker_irq_handler() will be called
125+
* 4) timestamp is 0x10000 us in the future:
126+
* oc_int_part = 0 initially
127+
* ((delta - 1) & 0xFFFF) >= 0x8000
128+
* There are two subcases:
129+
* a) The timer counter has not incremented past the compare
130+
* value while setting up the interrupt. In this case, the
131+
* CC1 flag will not be set, so oc_int_part will be
132+
* left at 0, and when the compare event occurs in 65536 us,
133+
* us_ticker_irq_handler() will be called
134+
* b) The timer counter has JUST incremented past the compare
135+
* value. In this case, the CC1 flag will be set, so
136+
* oc_int_part will be incremented to 1, and the interrupt will
137+
* occur immediately after this function returns, where
138+
* oc_int_part will decrement to 0 without calling
139+
* us_ticker_irq_handler(). Then about 65536 us later, the
140+
* compare event will occur again, and us_ticker_irq_handler()
141+
* will be called
142+
* 5) timestamp is 0x10001 us in the future:
143+
* oc_int_part = 1 initially
144+
* oc_int_part left at 1 because ((delta - 1) & 0xFFFF) < 0x8000
145+
* CC1 flag will not be set (see assumption above). In 1 us the
146+
* compare event will cause an interrupt, where oc_int_part will be
147+
* decremented to 0 without calling us_ticker_irq_handler(). Then
148+
* about 65536 us later, the compare event will occur again, and
149+
* us_ticker_irq_handler() will be called
150+
* 6) timestamp is 0x18000 us in the future:
151+
* oc_int_part = 1 initially
152+
* oc_int_part left at 1 because ((delta - 1) & 0xFFFF) < 0x8000
153+
* There should be no possibility of the CC1 flag being set yet
154+
* (see assumption above). When the compare event does occur in
155+
* 32768 us, oc_int_part will be decremented to 0 without calling
156+
* us_ticker_irq_handler(). Then about 65536 us later, the
157+
* compare event will occur again, and us_ticker_irq_handler() will
158+
* be called
159+
* 7) timestamp is 0x18001 us in the future:
160+
* oc_int_part = 1 initially
161+
* ((delta - 1) & 0xFFFF) >= 0x8000 but there should be no
162+
* possibility of the CC1 flag being set yet (see assumption above),
163+
* so oc_int_part will be left at 1, and when the compare event
164+
* does occur in 32769 us, oc_int_part will be decremented to 0
165+
* without calling us_ticker_irq_handler(). Then about 65536 us
166+
* later, the compare event will occur again, and
167+
* us_ticker_irq_handler() will be called
168+
*
169+
* delta - 1 is used because the timer compare event happens on the
170+
* counter incrementing to match the compare value, and it won't occur
171+
* immediately when the compare value is set to the current counter
172+
* value.
173+
*/
174+
oc_int_part = ((uint32_t)delta - 1) >> 16;
175+
if ( ((delta - 1) & 0xFFFF) >= 0x8000 &&
176+
__HAL_TIM_GET_FLAG(&TimMasterHandle, TIM_FLAG_CC1) == SET ) {
177+
++oc_int_part;
178+
/* NOTE: Instead of incrementing oc_int_part here, we could clear
179+
* the CC1 flag, but then you'd have to wait to ensure the
180+
* interrupt is knocked down before returning and reenabling
181+
* interrupts. Since this is a rare case, it's not worth it
182+
* to try and optimize it, and it keeps the code simpler and
183+
* safer to just do this increment instead.
184+
*/
111185
}
112186
}
113187
}
@@ -121,9 +195,7 @@ void us_ticker_disable_interrupt(void)
121195
void us_ticker_clear_interrupt(void)
122196
{
123197
TimMasterHandle.Instance = TIM_MST;
124-
if (__HAL_TIM_GET_FLAG(&TimMasterHandle, TIM_FLAG_CC1) == SET) {
125-
__HAL_TIM_CLEAR_FLAG(&TimMasterHandle, TIM_FLAG_CC1);
126-
}
198+
__HAL_TIM_CLEAR_FLAG(&TimMasterHandle, TIM_FLAG_CC1);
127199
}
128200

129201
#endif // TIM_MST_16BIT

0 commit comments

Comments
 (0)