cancel
Showing results for 
Search instead for 
Did you mean: 

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

arnold_w
Senior

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;
        }
    }
}

2 REPLIES 2
arnold_w
Senior

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
Senior

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();
}