Clarification on Timers in Microcontrollers

Thread Starter

Vihaan@123

Joined Oct 7, 2025
220
I have basic doubt on usage of timers in micro controllers, generally all microcontrollers have many timers let us say for example 5 timers, if i configure the timers to 1ms with interrupts enabled, then how is it possible that all the timers generate 1ms exactly? Can we assume that some of the timers will not generate an accurate 1ms (but with some ns delay) and for most of the applications it may not be significant?
 

WBahn

Joined Mar 31, 2012
32,707
There's a number of factors involved here. Whether ANY of the timers can generate 1 ms "exactly" depends on what YOUR definition of "exactly" is. You following sentence implies that you are talking about 1 ms within a small number of nanoseconds.

First off, it may not be possible for the timer to even be set for exactly one millisecond. That depends on the resolution of the clock and whether 1 ms represents an integer number of clock cycles. For instance, a standard watch crystal oscillates at 32.768 kHz. So one millisecond is 32.768 clock cycles. Hence, assuming your system can count at that full rate, the closest you can set it for is 0.9766 ms or 1.0071 ms. So right off the bat you are talking about thousands, if not tens of thousands, of nanoseconds of error. Then there's the fact that no oscillator is going to be EXACTLY accurate, so there is uncertainly around the nominal period of the timer. Since watch crystals are intended for timekeeping applications, they are very accurate relative to many other oscillator types, but even here they come in different grades. The most common grade is 20 ppm, which amounts to an error of slightly under two seconds a day. Since 20 ppm translates into an uncertainty of 20 ns over 1 ms, that means that the actual clock period of your timer, even if you could nominally set it for exactly one millisecond, could be off by as much as 20 ns either way just due to the accuracy of the crystal used in the oscillator, and that's under the specified test conditions, usually room temperature. Oscillators are generally temperature sensitive (in fact, that very sensitivity can be used as the basis for making temperature measurements), as well as sensitive to changes in voltage and load conditions.

All of this is before you get to the issues associated with using timer interrupts. Now you have to consider the latency between when an interrupt is triggered and when it is serviced. That can depend on a lot of factors, but many of them can be taken into account in the software design to mitigate them. One of the harder ones to mitigate is when a timer-interrupt occurs while another interrupt is being serviced.

As in any design, you first need to determine what you actually NEED. Terms like "exactly" have virtually no place in engineering design.
 

BobTPH

Joined Jun 5, 2013
11,466
Why would you want more than 1 timer interrupting at the same rate? Typically, one uses 1 timer to set up a system clock tick, then base all timing off multiples of that clock tick. If there are other timings that need more resolution, one could still use another of the timers for it.
 
Last edited:

WBahn

Joined Mar 31, 2012
32,707
Why would you want more than 1 timer interrupting at the same rate? Typically, one uses 1 timer to set up a system clock tick, than base all timing off multiples of that clock tick. If there are other timings that need more resolution, one could still use another of the timers for it.
I think the TS is trying to understand a more general concept and is just using multiple timers all set for the same rate as an example. Reading between the lines, I think they are primarily concerned with how a timer interrupt can be counted on (no pun intended) to be accurate (within its resolution and tolerance) if other timers are also generating interrupts that have to be serviced. There are a number of strategies for dealing with this, but much depends on the specific capabilities of the hardware being used and, as always, what is actually important and what is not in a system design that is "good enough" for the application.
 

Thread Starter

Vihaan@123

Joined Oct 7, 2025
220
If you observe the below screen shot from ST micro

1775583192707.png
there are number of timers i can understand the HRTIM and Low power timers but remaining all seem to be same. What could be the main reason for such large number of timers? Apart from this there is also Systick timer. If i take the case of PIC etc similar strategy is followed. Even if i use all timers the timing cannot be maintained since if i am servicing one timer interrupt the other timer interrupts need to wait which is going to add delay.
 

BobTPH

Joined Jun 5, 2013
11,466
Yes, that is a problem you have to deal with. The general rule is that an interrupt handler should be as short as possible. Typically, they just increment a counter, or set a flag so that you are ready to receive the next interrupt as soon as possible.
 

MrChips

Joined Oct 2, 2009
34,630
You are not limited to using interrupts with hardware timers. Timer modules can be configured to generate hardware events without software. For example, a timer event can be used to capture an ADC sample and store it into memory without using software. Similarly, a timer can be used to output a waveform from memory to a DAC at precise time intervals.
 

WBahn

Joined Mar 31, 2012
32,707
If you observe the below screen shot from ST micro

View attachment 365697
there are number of timers i can understand the HRTIM and Low power timers but remaining all seem to be same. What could be the main reason for such large number of timers? Apart from this there is also Systick timer. If i take the case of PIC etc similar strategy is followed. Even if i use all timers the timing cannot be maintained since if i am servicing one timer interrupt the other timer interrupts need to wait which is going to add delay.
To understand the differences you are going to have to carefully read the fine details of each timer module.

As I said previously, there are several strategies to mitigate the interactions you are talking about. Depending on the hardware, some timers can be configured to perform certain tasks and reset themselves without program interaction at all. This is your best option, if it's available and will do what you need. If you need it badly enough, then you might have to choose a different MCU or even design a customer peripheral. As has already been pointed out, you can also minimum the impact by keeping your interrupt service routines as short as possible and, if available, carefully prioritizing your interrupts.

At the end of the day, how you should/could go about doing it can only be rationally evaluated once you determine what is actually needed for your application.
 

Ian0

Joined Aug 7, 2020
13,097
If a timer is used to output a pulse, or other rectangular waveform, then its accuracy is only dependent on the accuracy of the crystal timing the master clock.
It is only when timers trigger interrupts which have latency in their interrupt service routines that random errors can occur, especially if several timer interrupts are used on a processor such as an ARM which has to finish one service routine before it can start another,
The reason that there are so many timers, is that they can do so many jobs:
Capture can measure the time between events, such as measuring the frequency of a waveform.
But you can't generally use the same timer to capture events as to generate a waveform.
A waveform generating timer has to run continuously, but most timers have the facility to be started and stopped by external inputs.
 

BobTPH

Joined Jun 5, 2013
11,466
Timers are also used as the time base for PWM hardware, without the need for interrupts. They can also be used to calculate the time interval between two triggering events. In a complex application, you could easily find that 5 is not enough. Interrupting at an interval is only one of their many uses.
 

Thread Starter

Vihaan@123

Joined Oct 7, 2025
220
Thank you all for the support, the recommendation that the interrupt routine shall be as minimal as possible, most of the time i feel it is only for beginners like me since i have seen experts (ST for example since i am following some of their code) do not either follow this rule or i don't understand it completely, for example as below
Code:
/**
  * @brief  This function handles TIMx global interrupt request for M1 Speed Sensor.
  * @param  None
  */
void SPD_HALL_TIM_M1_IRQHandler(void)
{
  /* USER CODE BEGIN SPD_TIM_M1_IRQn 0 */

  /* USER CODE END SPD_TIM_M1_IRQn 0 */

  /* HALL Timer Update IT always enabled, no need to check enable UPDATE state */
  if (0U == LL_TIM_IsActiveFlag_UPDATE(HALL_M1.TIMx))
  {
    /* Nothing to do */
  }
  else
  {
    LL_TIM_ClearFlag_UPDATE(HALL_M1.TIMx);
    (void)HALL_TIMx_UP_IRQHandler(&HALL_M1);

    /* USER CODE BEGIN M1 HALL_Update */

    /* USER CODE END M1 HALL_Update   */
  }

  /* HALL Timer CC1 IT always enabled, no need to check enable CC1 state */
  if (LL_TIM_IsActiveFlag_CC1 (HALL_M1.TIMx) != 0U)
  {
    LL_TIM_ClearFlag_CC1(HALL_M1.TIMx);
    (void)HALL_TIMx_CC_IRQHandler(&HALL_M1);

    /* USER CODE BEGIN M1 HALL_CC1 */

    /* USER CODE END M1 HALL_CC1 */
  }
  else
  {
    /* Nothing to do */
  }

  /* USER CODE BEGIN SPD_TIM_M1_IRQn 1 */

  /* USER CODE END SPD_TIM_M1_IRQn 1 */
}

#if defined (CCMRAM)
#if defined (__ICCARM__)
#pragma location = ".ccmram"
#elif defined (__CC_ARM) || defined(__GNUC__)
__attribute__((section (".ccmram")))
#endif
#endif
and the subroutines inside the functions
Code:
/**
  * @brief  Example of private method of the class HALL to implement an MC IRQ function
  *         to be called when TIMx update event occurs
  * @param  pHandle: handler of the current instance of the hall_speed_pos_fdbk component
  */
__weak void *HALL_TIMx_UP_IRQHandler(void *pHandleVoid)
{
  HALL_Handle_t *pHandle = (HALL_Handle_t *)pHandleVoid; //cstat !MISRAC2012-Rule-11.5
  TIM_TypeDef *TIMx = pHandle->TIMx;

  if (pHandle->SensorIsReliable)
  {
    uint16_t hMaxTimerOverflow;
    /* an update event occured for this interrupt request generation */
    pHandle->OVFCounter++;

    hMaxTimerOverflow = (uint16_t)(((uint32_t)pHandle->HallTimeout * pHandle->OvfFreq)
                                 / ((LL_TIM_GetPrescaler(TIMx) + 1U) * 1000U));
    if (pHandle->OVFCounter >= hMaxTimerOverflow)
    {
      /* Set rotor speed to zero */
      pHandle->_Super.hElSpeedDpp = 0;

      /* Reset the electrical angle according the hall sensor configuration */
      HALL_Init_Electrical_Angle(pHandle);

      /* Reset the overflow counter */
      pHandle->OVFCounter = 0U;

      /* Reset first capture flag */
      pHandle->FirstCapt = 0U;

      /* Reset the SensorSpeed buffer*/
      uint8_t bIndex;
      for (bIndex = 0U; bIndex < pHandle->SpeedBufferSize; bIndex++)
      {
        pHandle->SensorPeriod[bIndex]  = (int32_t)pHandle->MaxPeriod;
      }
      pHandle->BufferFilled = 0U ;
      pHandle->AvrElSpeedDpp = 0;
      pHandle->SpeedFIFOIdx = 0U;
      uint32_t tempReg = pHandle->MaxPeriod * pHandle->SpeedBufferSize;
      pHandle->ElPeriodSum = (int32_t)tempReg;
    }
  }
  return (MC_NULL);
}
with so many things like #pragma etc, etc, the coding seems to be at different level. What it takes to be at that level of coding?
 

atferrari

Joined Jan 6, 2004
5,001
Sorry to say but you seem trying to navigate different waters at the same time what will add to an eventual initial confussion. Now you are asking about interrupts...!

If you are in condition of doing it, implement a minimal basic hardware test for each timer.

For that, read the description of each type in the manual and see if you can grasp it supported by the testing.
Read and reread the manual to solve doubts.
 
Last edited:

nsaspook

Joined Aug 27, 2009
16,255
Thank you all for the support, the recommendation that the interrupt routine shall be as minimal as possible, most of the time i feel it is only for beginners like me since i have seen experts (ST for example since i am following some of their code) do not either follow this rule or i don't understand it completely, for example as below
Code:
/**
  * @brief  This function handles TIMx global interrupt request for M1 Speed Sensor.
  * @param  None
  */
void SPD_HALL_TIM_M1_IRQHandler(void)
{
  /* USER CODE BEGIN SPD_TIM_M1_IRQn 0 */

  /* USER CODE END SPD_TIM_M1_IRQn 0 */

  /* HALL Timer Update IT always enabled, no need to check enable UPDATE state */
  if (0U == LL_TIM_IsActiveFlag_UPDATE(HALL_M1.TIMx))
  {
    /* Nothing to do */
  }
  else
  {
    LL_TIM_ClearFlag_UPDATE(HALL_M1.TIMx);
    (void)HALL_TIMx_UP_IRQHandler(&HALL_M1);

    /* USER CODE BEGIN M1 HALL_Update */

    /* USER CODE END M1 HALL_Update   */
  }

  /* HALL Timer CC1 IT always enabled, no need to check enable CC1 state */
  if (LL_TIM_IsActiveFlag_CC1 (HALL_M1.TIMx) != 0U)
  {
    LL_TIM_ClearFlag_CC1(HALL_M1.TIMx);
    (void)HALL_TIMx_CC_IRQHandler(&HALL_M1);

    /* USER CODE BEGIN M1 HALL_CC1 */

    /* USER CODE END M1 HALL_CC1 */
  }
  else
  {
    /* Nothing to do */
  }

  /* USER CODE BEGIN SPD_TIM_M1_IRQn 1 */

  /* USER CODE END SPD_TIM_M1_IRQn 1 */
}

#if defined (CCMRAM)
#if defined (__ICCARM__)
#pragma location = ".ccmram"
#elif defined (__CC_ARM) || defined(__GNUC__)
__attribute__((section (".ccmram")))
#endif
#endif
and the subroutines inside the functions
Code:
/**
  * @brief  Example of private method of the class HALL to implement an MC IRQ function
  *         to be called when TIMx update event occurs
  * @param  pHandle: handler of the current instance of the hall_speed_pos_fdbk component
  */
__weak void *HALL_TIMx_UP_IRQHandler(void *pHandleVoid)
{
  HALL_Handle_t *pHandle = (HALL_Handle_t *)pHandleVoid; //cstat !MISRAC2012-Rule-11.5
  TIM_TypeDef *TIMx = pHandle->TIMx;

  if (pHandle->SensorIsReliable)
  {
    uint16_t hMaxTimerOverflow;
    /* an update event occured for this interrupt request generation */
    pHandle->OVFCounter++;

    hMaxTimerOverflow = (uint16_t)(((uint32_t)pHandle->HallTimeout * pHandle->OvfFreq)
                                 / ((LL_TIM_GetPrescaler(TIMx) + 1U) * 1000U));
    if (pHandle->OVFCounter >= hMaxTimerOverflow)
    {
      /* Set rotor speed to zero */
      pHandle->_Super.hElSpeedDpp = 0;

      /* Reset the electrical angle according the hall sensor configuration */
      HALL_Init_Electrical_Angle(pHandle);

      /* Reset the overflow counter */
      pHandle->OVFCounter = 0U;

      /* Reset first capture flag */
      pHandle->FirstCapt = 0U;

      /* Reset the SensorSpeed buffer*/
      uint8_t bIndex;
      for (bIndex = 0U; bIndex < pHandle->SpeedBufferSize; bIndex++)
      {
        pHandle->SensorPeriod[bIndex]  = (int32_t)pHandle->MaxPeriod;
      }
      pHandle->BufferFilled = 0U ;
      pHandle->AvrElSpeedDpp = 0;
      pHandle->SpeedFIFOIdx = 0U;
      uint32_t tempReg = pHandle->MaxPeriod * pHandle->SpeedBufferSize;
      pHandle->ElPeriodSum = (int32_t)tempReg;
    }
  }
  return (MC_NULL);
}
with so many things like #pragma etc, etc, the coding seems to be at different level. What it takes to be at that level of coding?
"minimal as possible" doesn't mean short, it means doing the critical function needed for each type of interrupt function without wasting time. A timer interrupt that flashes a LED should optimize the code needed for that function. An interrupt that updates hardware using compute power to calculate those register updates at fixed points in time (from sensor data that changes over time) might be more complicated but the "minimal as possible" rule still applies if there are critical time restrictions per each type of interrupt. There's lots of ways to do complex functions with interrupt bound code, it's just not always simple to make it "minimal as possible" using only software.

Read the controller interrupt section in the hardware manuals and study the principles of multitasking and processing to see and understand how the hardware and software can be optimized. You need to take the time to understand basic principles before you can understand complicated examples.
 
Top