Skip to content

Commit ea2cc1d

Browse files
committed
STM32 16bits tickers: consider all corner cases in us_ticker_set_interrupt
The present commit comes from monkiineko mbed contributor. The comments in code explains in details all the possible case and how they are handled.
1 parent fbfbb5e commit ea2cc1d

File tree

1 file changed

+102
-18
lines changed

1 file changed

+102
-18
lines changed

targets/TARGET_STM/us_ticker_16b.c

Lines changed: 102 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -28,15 +28,6 @@ volatile uint32_t oc_int_part = 0;
2828

2929
static int us_ticker_inited = 0;
3030

31-
void set_compare(uint16_t count)
32-
{
33-
TimMasterHandle.Instance = TIM_MST;
34-
// Set new output compare value
35-
__HAL_TIM_SET_COMPARE(&TimMasterHandle, TIM_CHANNEL_1, count);
36-
// Enable IT
37-
__HAL_TIM_ENABLE_IT(&TimMasterHandle, TIM_IT_CC1);
38-
}
39-
4031
void us_ticker_init(void)
4132
{
4233
if (us_ticker_inited) return;
@@ -80,25 +71,118 @@ uint32_t us_ticker_read()
8071

8172
void us_ticker_set_interrupt(timestamp_t timestamp)
8273
{
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);
83+
8384
int current_time = us_ticker_read();
8485
int delta = (int)(timestamp - current_time);
8586

8687
if (delta <= 0) { // This event was in the past
87-
/* Force the event to be handled in next interrupt context
88-
* This prevents calling interrupt handlers in loops as
89-
* us_ticker_set_interrupt might called again from the
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
9091
* application handler
9192
*/
9293
oc_int_part = 0;
93-
TimMasterHandle.Instance = TIM_MST;
9494
HAL_TIM_GenerateEvent(&TimMasterHandle, TIM_EVENTSOURCE_CC1);
9595
} else {
96-
/* set the comparator at the timestamp lower 16 bits
97-
* and count the number of wrap-around loops to do with
98-
* the upper 16 bits
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.
99173
*/
100-
oc_int_part = (uint32_t)(delta >> 16);
101-
set_compare(timestamp & 0xFFFF);
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+
*/
185+
}
102186
}
103187
}
104188

0 commit comments

Comments
 (0)