Skip to main content
arnold_w
Senior II
Feb 4, 2021 3:44 PM UTC
Question

Why does my multi-timer generate interrupts at the wrong time?

I am working with the STM32F769 microcontroller and I need several timers. I think it's too risky to use the hardware timers extensively so I decided to use ONE hardware timer in its simplest form and then do more processing in software. However, there is something wrong with my implementation, I can't get it to work properly, does anybody know what's wrong? My code consists of 3 parts, a low-level timer driver, a timer middle-layer and a linked list implementation. None of the timers can be prescaled down to 1kHz (I realize that it would make it a little easier) so I have chosen 2 kHz instead.

Low-level timer driver:

#define TIMER_TICK_FREQ_kHz (2)
#define TIMx_ARR (0xFFFF) ///< The value where the timer wraps around
 
#define GET_NUM_ELAPSED_TICKS(__TIMx_CNT__, __START_TIME_TICK__) ((uint16_t)((uint16_t)(__TIMx_CNT__) - (uint16_t)(__START_TIME_TICK__)))
 
#define DISABLE_ALL_INTS_IF_NECESSARY() uint32_t old_primask; \
 old_primask = __get_PRIMASK(); \
 __disable_irq()
 
#define ENABLE_ALL_INTS_IF_THEY_WERE_ENABLED() if (!old_primask) \
 { \
 __enable_irq(); \
 }
 
static uint16_t getPrescalar(uint32_t timerFreqHz, uint16_t desiredTickFreqHz);
static void setPreScalarAndPeriod(uint16_t preScalar, uint16_t timerPeriod);
 
void LL_superTimerInit() {
 __HAL_RCC_TIM14_CLK_ENABLE();
 __TIM14_FORCE_RESET();
 __TIM14_RELEASE_RESET();
 uint32_t timerFreqHz = getTimerFreq(TIM14);
 uint16_t prescalar = getPrescalar(timerFreqHz, 1000 * TIMER_TICK_FREQ_kHz);
 setPreScalarAndPeriod(prescalar, TIMx_ARR);
 NVIC_SetPriority(TIM8_TRG_COM_TIM14_IRQn, SPI_INT_PRIORITY);
 NVIC_EnableIRQ(TIM8_TRG_COM_TIM14_IRQn);
}
 
static void setPreScalarAndPeriod(uint16_t preScalar, uint16_t timerPeriod) {
 if (((uint16_t) TIM14->PSC) != preScalar) {
 TIM14->PSC = preScalar;
 TIM14->EGR = TIM_EGR_UG;
 TIM14->SR = 0xFFFFFFFE; // Clear update interrupt flag
 }
 TIM14->ARR = timerPeriod;
}
 
static uint16_t getPrescalar(uint32_t timerFreqHz, uint16_t desiredTickFreqHz) {
 uint32_t timerFreqHzDiv = timerFreqHz/desiredTickFreqHz;
 uint32_t divisor = timerFreqHzDiv / (uint32_t)(TIMx_ARR + 1);
 uint16_t prescalar = (uint16_t)(timerFreqHzDiv/(divisor + 1) - 1);
 return prescalar;
}
 
uint8_t LL_scheduleSuperTimerInt(uint16_t delayMsMax32767, EventCallback_t callback, uint32_t callbackArg1, uint32_t callbackArg2) {
 DISABLE_ALL_INTS_IF_NECESSARY();
 uint16_t desiredDelayTicks = TIMER_TICK_FREQ_kHz * delayMsMax32767;
 uint16_t TIMx_CNT = TIM14->CNT;
 uint8_t handle = LL_insertElement(
 TIMx_CNT, // startTickTime
 desiredDelayTicks, // desiredDelayTicks
 (uint16_t)(TIMx_CNT + desiredDelayTicks), // timerThresholdForInt
 callback, // timerIntCallback
 callbackArg1, // callbackArgs1
 callbackArg2); // callbackArgs2
 struct timerIntStates_s* firstElement = LL_getFirstElement();
 TIM14->CCR1 = firstElement->timerThresholdForInt;
 TIM14->CR1 = 1; // Start timer
 TIM14->DIER = 2; // Enable CCxIE (Capture/Compare interrupt enable)
 if ((desiredDelayTicks == 0) || (firstElement->desiredDelayTicks <= GET_NUM_ELAPSED_TICKS(TIM14->CNT, TIMx_CNT))) {
 TIM14->EGR = 1; // Generate Capture/Compare interrupt from software immediately
 }
 ENABLE_ALL_INTS_IF_THEY_WERE_ENABLED();
 return handle;
}
 
void LL_deleteScheduledSuperTimerInt(uint8_t handle) {
 DISABLE_ALL_INTS_IF_NECESSARY();
 LL_deleteElement_(handle);
 if (LL_atLeastOneScheduledTimerInt()) {
 struct timerIntStates_s* firstElement = LL_getFirstElement();
 TIM14->CCR1 = firstElement->timerThresholdForInt;
 if (firstElement->desiredDelayTicks <= GET_NUM_ELAPSED_TICKS(TIM14->CNT, firstElement->startTickTime)) {
 TIM14->EGR = 1; // Generate Capture/Compare interrupt from software immediately
 }
 }
 ENABLE_ALL_INTS_IF_THEY_WERE_ENABLED();
}
 
void TIM8_TRG_COM_TIM14_IRQHandler(void) {
 TIM14->SR = 0; // Clear all interrupt flags
 while (TRUE) {
 if (!LL_atLeastOneScheduledTimerInt()) {
 TIM14->DIER = 0; // Disable Capture/Compare 1 and Update interrupt enable interrupts
 return;
 }
 
 struct timerIntStates_s* firstElement = LL_getFirstElement();
 uint16_t numTicksElapsed = GET_NUM_ELAPSED_TICKS(TIM14->CNT, firstElement->startTickTime);
 if (firstElement->desiredDelayTicks <= numTicksElapsed) {
 struct timerIntStates_s* secondElement = LL_getSecondElement();
 if (secondElement != NULL) {
 TIM14->CCR1 = secondElement->timerThresholdForInt; // Update the value at which the timer will generate an interrupt
 }
 EventCallback_t timerIntCallbackCopy = firstElement->timerIntCallback;
 uint32_t callbackArg1 = firstElement->callbackArg1;
 uint32_t callbackArg2 = firstElement->callbackArg2;
 LL_deleteElement(firstElement);
 if (timerIntCallbackCopy != NULL) {
 timerIntCallbackCopy(callbackArg1, callbackArg2);
 }
 } else {
 break;
 }
 }
}

This topic has been closed for replies.

2 replies

arnold_w
arnold_wAuthor
Senior II
Feb 4, 2021 3:48 PM UTC

Timer middle-layer:

#define MAX_NUM_SCHEDULED_INTS (4) ///< Maximum number of scheduled interrupts
 
/// Super timer selector
typedef enum {
#if 1 <= MAX_NUM_SCHEDULED_INTS
 SUPERTIMER_0 = 0,
#endif
#if 2 <= MAX_NUM_SCHEDULED_INTS
 SUPERTIMER_1 = 1,
#endif
#if 3 <= MAX_NUM_SCHEDULED_INTS
 SUPERTIMER_2 = 2,
#endif
#if 4 <= MAX_NUM_SCHEDULED_INTS
 SUPERTIMER_3 = 3,
#endif
} superTimerSelector_e;
 
/// Timer states
struct timerState_s {
 volatile uint8_t handle; ///< Handle
 volatile Bool_t timerIntIsScheduled; ///< Timer interrupt is scheduled
 volatile uint32_t numHiddenInts; ///< Number of interrupts from the low-level timer driver that we will need to reach the specified period
 volatile uint16_t timerSubDelayLastIntTicks; ///< Timer sub delay last interrupt in ticks
 volatile EventCallback_t callback; ///< Callback function
 volatile uint32_t callbackArg1; ///< Callback argument 1
 volatile uint32_t callbackArg2; ///< Callback argument 2
};
 
static struct timerState_s timerStates[] = {
#if 1 <= MAX_NUM_SCHEDULED_INTS
 {0xFF, FALSE, 0, 0, NULL, 0, 0},
#endif
#if 2 <= MAX_NUM_SCHEDULED_INTS
 {0xFF, FALSE, 0, 0, NULL, 0, 0},
#endif
#if 3 <= MAX_NUM_SCHEDULED_INTS
 {0xFF, FALSE, 0, 0, NULL, 0, 0},
#endif
#if 4 <= MAX_NUM_SCHEDULED_INTS
 {0xFF, FALSE, 0, 0, NULL, 0, 0},
#endif
};
 
void ML_superTimerInit() {
 LL_superTimerInit();
}
 
 
void ML_scheduleSuperTimerInts(superTimerSelector_e superTimerSelector, uint32_t periodMs, EventCallback_t callback, uint32_t callbackArg1, uint32_t callbackArg2) {
 DISABLE_ALL_INTS_IF_NECESSARY();
 struct timerState_s* t = &timerStates[superTimerSelector];
 if (t->timerIntIsScheduled) {
 LL_deleteScheduledSuperTimerInt(t->handle);
 t->timerIntIsScheduled = FALSE;
 }
 
 t->numHiddenInts = (uint16_t)((TIMER_TICK_FREQ_kHz * periodMs) / TIMx_ARR) + 1;
 t->timerSubDelayLastIntTicks = (uint16_t)((TIMER_TICK_FREQ_kHz * periodMs) % TIMx_ARR);
 
 t->callback = callback;
 t->callbackArg1 = callbackArg1;
 t->callbackArg2 = callbackArg2;
 
 uint16_t firstDelayTicks = (t->numHiddenInts == 1) ? t->timerSubDelayLastIntTicks : TIMx_ARR;
 t->handle = LL_scheduleSuperTimerInt(firstDelayTicks/TIMER_TICK_FREQ_kHz, timerInt, (uint32_t)superTimerSelector, 0);
 t->timerIntIsScheduled = TRUE;
 ENABLE_ALL_INTS_IF_THEY_WERE_ENABLED();
}
 
void ML_deleteScheduledSuperTimerInts(superTimerSelector_e superTimerSelector) {
 DISABLE_ALL_INTS_IF_NECESSARY();
 struct timerState_s* t = &timerStates[superTimerSelector];
 if (t->timerIntIsScheduled) {
 LL_deleteScheduledSuperTimerInt(t->handle);
 t->timerIntIsScheduled = FALSE;
 }
 ENABLE_ALL_INTS_IF_THEY_WERE_ENABLED();
}
 
static uint32_t timerInt(uint32_t superTimerSelectorUint, uint32_t intCount) {
 struct timerState_s* t = &timerStates[superTimerSelectorUint];
 intCount++;
 if (intCount == t->numHiddenInts) {
 t->timerIntIsScheduled = FALSE;
 if (t->callback != NULL) {
 t->callback(t->callbackArg1, t->callbackArg2);
 }
 } else if ((intCount + 1) == t->numHiddenInts) {
 // This was the interrupt right before the interrupt at which we will call the callback
 t->handle = LL_scheduleSuperTimerInt(t->timerSubDelayLastIntTicks/TIMER_TICK_FREQ_kHz, timerInt, superTimerSelectorUint, intCount);
 } else {
 t->handle = LL_scheduleSuperTimerInt(TIMx_ARR/TIMER_TICK_FREQ_kHz, timerInt, superTimerSelectorUint, intCount);
 }
 return DONT_CARE;
}

arnold_w
arnold_wAuthor
Senior II
Feb 4, 2021 3:50 PM UTC

Linked list:

struct timerIntStates_s {
 volatile uint16_t startTickTime; ///< Start tick time
 volatile uint16_t desiredDelayTicks; ///< Desired delay in timer ticks
 volatile uint16_t timerThresholdForInt; ///< Timer threshold for interrupt
 volatile Bool_t inUse; ///< In use
 volatile EventCallback_t timerIntCallback; ///< Timer interrupt callback function
 volatile uint32_t callbackArg1; ///< Callback argument 1
 volatile uint32_t callbackArg2; ///< Callback argument 2
 struct timerIntStates_s* prevElement; ///< Previous element
 struct timerIntStates_s* nextElement; ///< Next Element
};
 
static struct timerIntStates_s linkedList[] = {
#if 1 <= MAX_NUM_SCHEDULED_INTS
 {0, 0, 0, FALSE, NULL, 0, 0, NULL, NULL},
#endif
#if 2 <= MAX_NUM_SCHEDULED_INTS
 {0, 0, 0, FALSE, NULL, 0, 0, NULL, NULL},
#endif
#if 3 <= MAX_NUM_SCHEDULED_INTS
 {0, 0, 0, FALSE, NULL, 0, 0, NULL, NULL},
#endif
#if 4 <= MAX_NUM_SCHEDULED_INTS
 {0, 0, 0, FALSE, NULL, 0, 0, NULL, NULL},
#endif
};
 
static volatile struct timerIntStates_s* firstElement = &linkedList[0];
 
inline static uint8_t getFirstUnusedElementIndex() {
 struct timerIntStates_s* element = linkedList;
 uint8_t i;
 for (i = 0; i < (sizeof(linkedList)/sizeof(linkedList[0])); i++) {
 if (!element->inUse) {
 return i;
 }
 element++;
 }
 logCriticalErrorNullTermString(" \r\nNo unused element found in function getFirstUnusedElementIndex!! ");
 return 0xFF;
}
 
struct timerIntStates_s* LL_getFirstElement() {
 return (firstElement->inUse) ? (struct timerIntStates_s*)firstElement : NULL;
}
 
struct timerIntStates_s* LL_getSecondElement() {
 return (firstElement->inUse) ? (struct timerIntStates_s*)firstElement->nextElement : NULL;
}
 
Bool_t LL_atLeastOneScheduledTimerInt() {
 return firstElement->inUse;
}
 
uint8_t LL_insertElement(uint16_t startTickTime,
 uint16_t desiredDelayTicks,
 uint16_t timerThresholdForInt,
 EventCallback_t timerIntCallback,
 uint32_t callbackArg1,
 uint32_t callbackArg2) {
 DISABLE_ALL_INTS_IF_NECESSARY();
 if (!firstElement->inUse) {
 firstElement->startTickTime = startTickTime;
 firstElement->desiredDelayTicks = desiredDelayTicks;
 firstElement->timerThresholdForInt = timerThresholdForInt;
 firstElement->inUse = TRUE;
 firstElement->timerIntCallback = timerIntCallback;
 firstElement->callbackArg1 = callbackArg1;
 firstElement->callbackArg2 = callbackArg2;
 firstElement->prevElement = NULL;
 firstElement->nextElement = NULL;
 ENABLE_ALL_INTS_IF_THEY_WERE_ENABLED();
 return 0;
 }
 
 uint8_t linkedListArrayIndex = getFirstUnusedElementIndex();
 struct timerIntStates_s* runningElement = (struct timerIntStates_s*)firstElement;
 struct timerIntStates_s* newElement = &linkedList[linkedListArrayIndex];
 newElement->startTickTime = startTickTime;
 newElement->desiredDelayTicks = desiredDelayTicks;
 newElement->timerThresholdForInt = timerThresholdForInt;
 newElement->inUse = TRUE;
 newElement->timerIntCallback = timerIntCallback;
 newElement->callbackArg1 = callbackArg1;
 newElement->callbackArg2 = callbackArg2;
 
 while (TRUE) {
 uint16_t ticksUntilTimerInt = runningElement->timerThresholdForInt - startTickTime;
 if (desiredDelayTicks < ticksUntilTimerInt) {
 if (runningElement->prevElement == NULL) {
 firstElement = newElement;
 } else {
 runningElement->prevElement->nextElement = newElement;
 }
 newElement->prevElement = runningElement->prevElement;
 newElement->nextElement = runningElement;
 runningElement->prevElement = newElement;
 ENABLE_ALL_INTS_IF_THEY_WERE_ENABLED();
 return linkedListArrayIndex;
 } else {
 if (runningElement->nextElement == NULL) {
 newElement->prevElement = runningElement;
 newElement->nextElement = NULL;
 runningElement->nextElement = newElement;
 ENABLE_ALL_INTS_IF_THEY_WERE_ENABLED();
 return linkedListArrayIndex;
 } else {
 runningElement = runningElement->nextElement;
 }
 }
 }
 ENABLE_ALL_INTS_IF_THEY_WERE_ENABLED();
 return 0xFF;
}
 
void LL_deleteElement(struct timerIntStates_s* elementToDelete) {
 if (elementToDelete == NULL) {
 logCriticalErrorNullTermString(" \r\nAttempting to delete a NULL-element in function LL_deleteElement!! ");
 return;
 } else if (!elementToDelete->inUse) {
 logCriticalErrorNullTermString(" \r\nAttempting to delete an unused element in function LL_deleteElement!! ");
 return;
 }
 
 DISABLE_ALL_INTS_IF_NECESSARY();
 if ((elementToDelete->prevElement == NULL) && (elementToDelete->nextElement == NULL)) {
 // This must be the only element in the list
 elementToDelete->inUse = FALSE;
 } else if (elementToDelete->prevElement == NULL) {
 // This must be the first element
 elementToDelete->nextElement->prevElement = NULL;
 firstElement = elementToDelete->nextElement;
 elementToDelete->nextElement = NULL;
 elementToDelete->inUse = FALSE;
 } else if (elementToDelete->nextElement == NULL) {
 // This must be the last element
 elementToDelete->prevElement->nextElement = NULL;
 elementToDelete->prevElement = NULL;
 elementToDelete->inUse = FALSE;
 } else {
 // The element is somewhere in the middle
 elementToDelete->prevElement->nextElement = elementToDelete->nextElement;
 elementToDelete->nextElement->prevElement = elementToDelete->prevElement;
 elementToDelete->prevElement = NULL;
 elementToDelete->nextElement = NULL;
 elementToDelete->inUse = FALSE;
 }
 ENABLE_ALL_INTS_IF_THEY_WERE_ENABLED();
}
 
void LL_deleteElement_(uint8_t handle) {
 if ((sizeof(linkedList)/sizeof(linkedList[0])) <= handle) {
 logCriticalErrorNullTermString(" \r\nBad handle in function LL_deleteElement_: 0x");
 logCriticalErrorHexNumber(handle, 2);
 logCriticalErrorNullTermString("!! ");
 }
 LL_deleteElement(&linkedList[handle]);
}
 
void LL_clearLinkedList() {
 DISABLE_ALL_INTS_IF_NECESSARY();
 struct timerIntStates_s* element = linkedList;
 uint8_t i;
 for (i = 0; i < (sizeof(linkedList)/sizeof(linkedList[0])); i++) {
 element->inUse = FALSE;
 element++;
 }
 firstElement = linkedList;
 ENABLE_ALL_INTS_IF_THEY_WERE_ENABLED();
}