Skip to content

Commit

Permalink
STM32 16bits tickers: consider all corner cases in us_ticker_set_inte…
Browse files Browse the repository at this point in the history
…rrupt

The present commit comes from monkiineko mbed contributor.
The comments in code explains in details all the possible case and
how they are handled.
  • Loading branch information
LMESTM committed Jun 5, 2017
1 parent fbfbb5e commit ea2cc1d
Showing 1 changed file with 102 additions and 18 deletions.
120 changes: 102 additions & 18 deletions targets/TARGET_STM/us_ticker_16b.c
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,6 @@ volatile uint32_t oc_int_part = 0;

static int us_ticker_inited = 0;

void set_compare(uint16_t count)
{
TimMasterHandle.Instance = TIM_MST;
// Set new output compare value
__HAL_TIM_SET_COMPARE(&TimMasterHandle, TIM_CHANNEL_1, count);
// Enable IT
__HAL_TIM_ENABLE_IT(&TimMasterHandle, TIM_IT_CC1);
}

void us_ticker_init(void)
{
if (us_ticker_inited) return;
Expand Down Expand Up @@ -80,25 +71,118 @@ uint32_t us_ticker_read()

void us_ticker_set_interrupt(timestamp_t timestamp)
{
// NOTE: This function must be called with interrupts disabled to keep our
// timer interrupt setup atomic
TimMasterHandle.Instance = TIM_MST;
// Set new output compare value
__HAL_TIM_SET_COMPARE(&TimMasterHandle, TIM_CHANNEL_1, timestamp & 0xFFFF);
// Ensure the compare event starts clear
__HAL_TIM_CLEAR_FLAG(&TimMasterHandle, TIM_FLAG_CC1);
// Enable IT
__HAL_TIM_ENABLE_IT(&TimMasterHandle, TIM_IT_CC1);

int current_time = us_ticker_read();
int delta = (int)(timestamp - current_time);

if (delta <= 0) { // This event was in the past
/* Force the event to be handled in next interrupt context
* This prevents calling interrupt handlers in loops as
* us_ticker_set_interrupt might called again from the
/* Immediately set the compare event to cause the event to be handled in
* the next interrupt context. This prevents calling interrupt handlers
* recursively as us_ticker_set_interrupt might be called again from the
* application handler
*/
oc_int_part = 0;
TimMasterHandle.Instance = TIM_MST;
HAL_TIM_GenerateEvent(&TimMasterHandle, TIM_EVENTSOURCE_CC1);
} else {
/* set the comparator at the timestamp lower 16 bits
* and count the number of wrap-around loops to do with
* the upper 16 bits
/* Set the number of timer wrap-around loops before the actual timestamp
* is reached. If the calculated delta time is more than halfway to the
* next compare event, check to see if a compare event has already been
* set, and if so, add one to the wrap-around count. This is done to
* ensure the correct wrap count is used in the corner cases where the
* 16 bit counter passes the compare value during the process of
* configuring this interrupt.
*
* Assumption: The time to execute this function is less than 32ms
* (otherwise incorrect behaviour could result)
*
* Consider the following corner cases:
* 1) timestamp is 1 us in the future:
* oc_int_part = 0 initially
* oc_int_part left at 0 because ((delta - 1) & 0xFFFF) < 0x8000
* Compare event should happen in 1 us and us_ticker_irq_handler()
* called
* 2) timestamp is 0x8000 us in the future:
* oc_int_part = 0 initially
* oc_int_part left at 0 because ((delta - 1) & 0xFFFF) < 0x8000
* There should be no possibility of the CC1 flag being set yet
* (see assumption above). When the compare event does occur in
* 32768 us, us_ticker_irq_handler() will be called
* 3) timestamp is 0x8001 us in the future:
* oc_int_part = 0 initially
* ((delta - 1) & 0xFFFF) >= 0x8000 but there should be no
* possibility of the CC1 flag being set yet (see assumption above),
* so oc_int_part will be left at 0, and when the compare event
* does occur in 32769 us, us_ticker_irq_handler() will be called
* 4) timestamp is 0x10000 us in the future:
* oc_int_part = 0 initially
* ((delta - 1) & 0xFFFF) >= 0x8000
* There are two subcases:
* a) The timer counter has not incremented past the compare
* value while setting up the interrupt. In this case, the
* CC1 flag will not be set, so oc_int_part will be
* left at 0, and when the compare event occurs in 65536 us,
* us_ticker_irq_handler() will be called
* b) The timer counter has JUST incremented past the compare
* value. In this case, the CC1 flag will be set, so
* oc_int_part will be incremented to 1, and the interrupt will
* occur immediately after this function returns, where
* oc_int_part will decrement to 0 without calling
* us_ticker_irq_handler(). Then about 65536 us later, the
* compare event will occur again, and us_ticker_irq_handler()
* will be called
* 5) timestamp is 0x10001 us in the future:
* oc_int_part = 1 initially
* oc_int_part left at 1 because ((delta - 1) & 0xFFFF) < 0x8000
* CC1 flag will not be set (see assumption above). In 1 us the
* compare event will cause an interrupt, where oc_int_part will be
* decremented to 0 without calling us_ticker_irq_handler(). Then
* about 65536 us later, the compare event will occur again, and
* us_ticker_irq_handler() will be called
* 6) timestamp is 0x18000 us in the future:
* oc_int_part = 1 initially
* oc_int_part left at 1 because ((delta - 1) & 0xFFFF) < 0x8000
* There should be no possibility of the CC1 flag being set yet
* (see assumption above). When the compare event does occur in
* 32768 us, oc_int_part will be decremented to 0 without calling
* us_ticker_irq_handler(). Then about 65536 us later, the
* compare event will occur again, and us_ticker_irq_handler() will
* be called
* 7) timestamp is 0x18001 us in the future:
* oc_int_part = 1 initially
* ((delta - 1) & 0xFFFF) >= 0x8000 but there should be no
* possibility of the CC1 flag being set yet (see assumption above),
* so oc_int_part will be left at 1, and when the compare event
* does occur in 32769 us, oc_int_part will be decremented to 0
* without calling us_ticker_irq_handler(). Then about 65536 us
* later, the compare event will occur again, and
* us_ticker_irq_handler() will be called
*
* delta - 1 is used because the timer compare event happens on the
* counter incrementing to match the compare value, and it won't occur
* immediately when the compare value is set to the current counter
* value.
*/
oc_int_part = (uint32_t)(delta >> 16);
set_compare(timestamp & 0xFFFF);
oc_int_part = ((uint32_t)delta - 1) >> 16;
if ( ((delta - 1) & 0xFFFF) >= 0x8000 &&
__HAL_TIM_GET_FLAG(&TimMasterHandle, TIM_FLAG_CC1) == SET ) {
++oc_int_part;
/* NOTE: Instead of incrementing oc_int_part here, we could clear
* the CC1 flag, but then you'd have to wait to ensure the
* interrupt is knocked down before returning and reenabling
* interrupts. Since this is a rare case, it's not worth it
* to try and optimize it, and it keeps the code simpler and
* safer to just do this increment instead.
*/
}
}
}

Expand Down

0 comments on commit ea2cc1d

Please sign in to comment.