STM32 F4 General-purpose Timers for Periodic Interrupts

Goal: generating periodic interrupts using the 16-bit TIM3 in the output compare mode (SysTick alternative).

TIM2 - TIM7 are driven by the APB1 timer clock. Refer to this post for more clock-related info.




#include <stm32f4xx.h>
#include "other_stuff.h"

#define   APB1_FREQ        42000000                           // Clock driving TIM3
#define   CNT_FREQ         21000000                           // TIM3 counter clock (prescaled APB1)
#define   IT_PER_SEC       2000                               // Interrupts per second
#define   TIM3_PULSE       ((CNT_FREQ) / (IT_PER_SEC))        // Output compare reg value
#define   TIM_PRESCALER    (((APB1_FREQ) / (CNT_FREQ))-1)     // APB1 prescaler

uint16_t current_count = 0;                                   // To sample the counter

static void TIM3_Config(void);

int main()
{
  TIM_TimeBaseInitTypeDef TIM3_TimeBase;                      // Time base structure
  TIM_OCInitTypeDef       TIM3_OC;                            // Output Compare structure
 
  TIM3_Config();

  TIM3_TimeBase.TIM_ClockDivision = 0;                        // Not dividing
  TIM3_TimeBase.TIM_CounterMode   = TIM_CounterMode_Up;       // Upcounting configuration
  TIM3_TimeBase.TIM_Period        = 65535;                    // Autoreload value (ARR) 
  TIM3_TimeBase.TIM_Prescaler     = TIM_PRESCALER;            // Dividing APB1 by 2
  TIM_TimeBaseInit(TIM3, &TIM3_TimeBase);                     // Initializing Time Base structure

  TIM3_OC.TIM_OCMode      = TIM_OCMode_Toggle;                // Output compare toggling mode
  TIM3_OC.TIM_OutputState = TIM_OutputState_Enable;           // Enabling the Output Compare state
  TIM3_OC.TIM_OCPolarity  = TIM_OCPolarity_Low;               // Reverse polarity
  TIM3_OC.TIM_Pulse       = TIM3_PULSE;                       // Output Compare 1 reg value
  TIM_OC1Init(TIM3, &TIM3_OC);                                // Initializing Output Compare 1 structure
  TIM_OC1PreloadConfig(TIM3, TIM_OCPreload_Disable);          // Disabling Ch.1 Output Compare preload

  TIM_Cmd(TIM3, ENABLE);                                      // Ready, Set, Go!
  TIM_ITConfig(TIM3, TIM_IT_CC1, ENABLE);                     // Enabling TIM3 Ch.1 interrupts
 
  while(1)
  {
    //os_sys_init(init_task);                                 // Starting the RTX kernel
  }

}


static void TIM3_Config(void)
{
  GPIO_InitTypeDef gpio_C;                                    // GPIOC structure
  NVIC_InitTypeDef NVIC_TIM3;                                 // NVIC structure
  
  RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOC, ENABLE);       // Clocking GPIOC (AHB1 = 84MHz)
  RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);        // Clocking TIM3  (APB1 = 42MHz)
   
  gpio_C.GPIO_Pin   = GPIO_Pin_6;                             // Ch.1 (PC6)
  gpio_C.GPIO_Mode  = GPIO_Mode_AF;                           // Alternative function
  gpio_C.GPIO_Speed = GPIO_Fast_Speed;                        // 50MHz
  gpio_C.GPIO_OType = GPIO_OType_PP;                          // Push-pull
  gpio_C.GPIO_PuPd  = GPIO_PuPd_UP ;                          // Pulling-up
  GPIO_Init(GPIOC, &gpio_C);                                  // Initializing GPIOC structure
  GPIO_PinAFConfig(GPIOC, GPIO_PinSource6, GPIO_AF_TIM3);     // Routing TIM3 output to PC6

  NVIC_TIM3.NVIC_IRQChannel                   = TIM3_IRQn;    // Specifying the channel (stm32f4xx.h)
  NVIC_TIM3.NVIC_IRQChannelPreemptionPriority = 0;            // Only matters for multiple interrupts
  NVIC_TIM3.NVIC_IRQChannelSubPriority        = 0;            // Only matters for multiple interrupts
  NVIC_TIM3.NVIC_IRQChannelCmd                = ENABLE;       // Enabling global interrupt
  NVIC_Init(&NVIC_TIM3);                                      // Initializing NVIC structure
}



void TIM3_IRQHandler(void)
{
  if (TIM_GetITStatus(TIM3, TIM_IT_CC1) != RESET)             // Just a precaution (RESET = 0) 
  {
    TIM_ClearITPendingBit(TIM3, TIM_IT_CC1);                  // Clear TIM3 Ch.1 flag
    current_count = TIM_GetCapture1(TIM3);                    // Get current counter value
    TIM_SetCompare1(TIM3, current_count + TIM3_PULSE);        // Set Output Compare 1 to the new value
  }
}

Note that TIM3_IRQHandler() updates the Output Compare 1 Register value every interrupt.


11 comments:

  1. Very clear
    thank you

    ReplyDelete
  2. where is this other_stuff.h file? what should be in it?

    ReplyDelete
    Replies
    1. For this application it should be transparent since there are no special defines that require a header anywhere in this application.

      Delete
  3. can you modify the program to toggle led13 and led12 i tried with tim4 and it just won't work

    ReplyDelete
    Replies
    1. You can try this code, it worked for me. Sorry, it is not properly ordered, but it works

      Delete

    2. #define green_led GPIO_Pin_12
      #define orange_led GPIO_Pin_13
      #define red_led GPIO_Pin_14
      #define blue_led GPIO_Pin_15


      uint16_t PrescalerValue = 0;

      uint16_t current_count = 0; // To sample the counter
      uint16_t CCR1_Val = 500;


      static void TIM4_Config(void);

      int main()
      {
      TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
      TIM_OCInitTypeDef TIM_OCInitStructure;

      PrescalerValue = (uint16_t) ((SystemCoreClock /2) / 2000) - 1;


      TIM4_Config();

      TIM_TimeBaseStructure.TIM_ClockDivision = 0;
      TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
      TIM_TimeBaseStructure.TIM_Period = 1000;
      TIM_TimeBaseStructure.TIM_Prescaler = PrescalerValue;
      TIM_TimeBaseInit(TIM4, &TIM_TimeBaseStructure);

      TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_Toggle;
      TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
      TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_Low;
      TIM_OCInitStructure.TIM_Pulse = CCR1_Val;
      TIM_OC1Init(TIM4, &TIM_OCInitStructure);

      TIM_OC1PreloadConfig(TIM4, TIM_OCPreload_Enable);

      TIM_Cmd(TIM4, ENABLE);
      TIM_ITConfig(TIM4, TIM_IT_CC1, ENABLE);

      while(1)
      {
      //os_sys_init(init_task);
      }

      }


      static void TIM4_Config(void)
      {
      GPIO_InitTypeDef GPIO_InitStructure;
      NVIC_InitTypeDef NVIC_InitStruct;

      RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOD, ENABLE);
      RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE);

      GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12;
      GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
      GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
      GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
      GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP ;
      GPIO_Init(GPIOD, &GPIO_InitStructure);
      GPIO_PinAFConfig(GPIOD, GPIO_PinSource12, GPIO_AF_TIM4);


      GPIO_InitStructure.GPIO_Pin = red_led;
      GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
      GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
      GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
      GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP ;
      GPIO_Init(GPIOD, &GPIO_InitStructure);



      NVIC_InitStruct.NVIC_IRQChannel = TIM4_IRQn;
      NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 0;
      NVIC_InitStruct.NVIC_IRQChannelSubPriority = 0;
      NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
      NVIC_Init(&NVIC_InitStruct);
      }



      void TIM4_IRQHandler(void)
      {
      if (TIM_GetITStatus(TIM4, TIM_IT_CC1) != RESET)
      {
      TIM_ClearITPendingBit(TIM4, TIM_IT_CC1);
      //current_count = TIM_GetCapture1(TIM4);
      //TIM_SetCompare1(TIM4, current_count + CCR1_Val);
      GPIO_ToggleBits(GPIOD, red_led);
      }
      }

      Delete
  4. Hi,
    Can you please explain me when will the GPIO out will be at high state and when will it change to low state.
    Is it because every time the compare register becomes equal to timer counter, GPIO state will change based on TIM_OCMode value?



    Thanks
    Abin

    ReplyDelete
    Replies
    1. Hi,

      Check out section "18.3.8 Output compare mode" in the STM32F4 reference manual. They explain in detail how it works :)

      To make a long story short (at least somewhat short), the pin (PC6) toggles (changes its state) when CNT register (counter driven by the 21MHz clock (follow the blue line in the diagram)) matches the value in the "compare 1 register" which is set to 10500 initially. The value of "compare 1 register" is updated (incremented by 10500) each interrupt :)

      The reason it toggles it because of this line of code:
      TIM3_OC.TIM_OCMode = TIM_OCMode_Toggle;

      Here's an example:

      1. Initially CNT is 0 and GPIO is low.
      2. CNT starts counting up, 0,1,2,3,4... at 21MHz
      3. CNT reaches the value of "compare 1 register" which is 10500 and triggers an interrupt
      4. GPIO is high
      5. Interrupt code updates the value of "compare 1 register" to 10500 + 10500 = 21000
      6. CNT keeps countint up 10500, 10501, 10502, ... at 21MHz
      7. CNT reaches the new value of "compare 1 register" which is 21000 and triggers an interrupt
      8. GPIO is low
      9. Interrupt code updates the value of "compare 1 register" to 21000 + 10500 = 31500
      10. CNT keeps counting ...
      and so on and so forth :)

      Best regards,
      Sergey

      Delete
  5. ok I will post it there roo thanks, I am new to this embedded system world and I'm really trying to make sense of the code.
    1 -question since TIM 6 is defined as a basic timer will I still be able to get a square wave by changing the TIM from 3 to 6?

    2-could you point out the maths behind the square wave frequency/ I want to modify it to get out 151 HZ, how do I change te prameters?

    3-and I am using other pins rather than 6 like pin 9, is there any consideration to take when choosing pins?

    ReplyDelete
    Replies
    1. 1 - Yes, you will still be able to get a square wave.

      2 - The math behind the square wave frequency requires understanding of the clock tree a little bit.

      You start with a crystal oscillator from which all other clocks are derived as follows:
      Crystal oscillator --> PLL --> System clock --> APB Prescaler --> APB1 clock --> Prescaler --> Timer clock

      Clock tree can be found here http://00xnor.blogspot.com/2014/01/1-stm32-f4-clock-configuration.html.

      Now that you have a timer clock (in my code it is 21MHz), the math is 21MHz / 10500 = 2000 interrupts per second. 10500 is the value of the compare register (it is shown in the diagram above). The PC6 pin is toggled each interrupt (2000 times per second) which results in the 1000Hz square waveform. In your case, you want 151*2 = 302 interrupts per second.

      Please note that sometimes it's not possible to get exact frequencies because prescaling is discrete.

      3 - For low frequencies (e.g. 151Hz) - no. For high frequencies (e.g. 8MHz) - yes.

      Delete