STM32F0 Timer, Counter, Input Capture and PWM - Tutorial 4

As said before, I will explore the Timer and Counter of STM32F0 using CubeMX in this post. In this STM32F0 timer tutorial, I will try to cover as many functions of the STM32F0’s Timer as possible because this peripheral may have the greatest features, functions among the other peripherals. If you have worked with Arduino before, you might realize the limitation in function of those arduino’s timers. However , the STM32’s timers will give you way more functions that will be very useful for many of your applications that you may not think about it before. For STM32F051, it has totally 9 main timers including 7 16-bit timers, 1 24-bit systick timer and 1 32-bit timer. Some of them have 6 channels advanced-control PWM output, deadtime generation… It also has independent and watchdog timer for dealing with program hanging. This tutorial will cover the following function of the timer:

  • Time base interrupt function
  • Counter with external input
  • Input capture
  • PWM generation

1. Time Base Interrupt

STM32F051 has several timer for you to play with including: TIM1, TIM2, TIM3, TIM6, TIM14, TIM15, TIM16, TIM17. Basically, timer and counter are just different on the input clock signal. For timer, the clock source is internal clock which is generated from external crystal internal RC circuit of the STM32F0 Discovery. The following video will show you how to config a timer in CubeMX in order to generate time base interrupt for blinking the green Led (PC9).

Noticed the configurations that I have entered in the timer setting window of TIM3. There are some definitions that need to be mentioned here:

tim1

  • Prescaler: can be understood as a frequency divider for the counter, with range from 0 to 65535. In the video I set to 47999 (not 48000) so it means that the frequency supply for the counter will be equal to 48MHz/(47999+1) = 1000Hz = 1ms.
  • Counter mode: can be count-up or count-down. For count-up: the counter will count from 0 to the value stored in Auto-reload register (Counter period) and then generate Overflow interrupt and start all over again from 0. For count-down: the counter will count from the value stored in Auto-reload register (Counter period) down to 0 and then generate Underflow interrupt and start all over again from stored value.
  • Counter period: the value that the counter will count to or count from (depend on count-up or count-down). In the video I set to 499, it means that the counter will count from 0 to 499 with 1ms cycle, so totally the interrupt time will be 500*1ms =500ms.

Speaking of the interrupt handler, the HAL library helps us to handle all the checking and clearing status flag bits so we don’t have to worry about them again, just use the following function as an interrupt handler routine. Check the code in the video again:

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)  
{
    if (htim->Instance==TIM3) //check if the interrupt comes from TIM3
        {
        HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_9);
        }
}

This Callback function is sharing among all timers interrupt. If you are using more than one Time base interrupt, you need to check the source of the interrupt before executing any command. For example, if you use TIM3 and TIM6 time base interrupt, the Callback function should be like this:

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)  
{
   if (htim->Instance==TIM3)
      {
      //do something here
      }
   if (htim->Instance==TIM6)
      {
      //do something else here
      }
}

2. External Input Counter

Last tutorial on “External interrupt“, I have shown the example of using external interrupt to count the input signal from the button. In this post, I will introduce another method to count the external input signal using Counter. So what is the different between two methods ? While external interrupt needs to jump into the interrupt routine to do the increment or decrement of a variable, counter can handle the job nicely without jumping anywhere. Therefore, it will be obviously useful when your program has many types of interrupt running. Take a look at the F0’s reference manual, timer section:

clk source We can easily see that the timer has many sources for input clock. The first source is “Internal clock”, which we have discovered in the first part of this tutorial. Now, we’re gonna look at the second source, which is “external input pin (TIx)”. With this source of clock, the timer will apparently become a counter. The other two sources are out of the scope of this tutorial that you can discover it by yourself later. So which pin is compatible for the counter mode ? You can notice these pins with function like “TIMx_CH1” and “TIMx_CH2” which x represents the timer number (1,2,3….).

channel

Picture above is an example of pin PA0, which has the “TIM2_CH1” function. It means that you can use this pin as an input signal for Counter TIM2. Watch the video below and follow yourself. Basically, I will set up the Timer 2 as a counter to count the number of times that I press the blue button on the Discovery kit with CubeMX. I also used STMStudio to monitor the counter value.

Please make sure that you need to Start the timer before doing anything else. The code in the video:

/* USER CODE BEGIN 2 */
 HAL_TIM_Base_Start_IT(&htim3);
 HAL_TIM_Base_Start(&htim2);    //Start TIM2 without interrupt
/* USER CODE END 2 */

The macro “HALTIM_GetCounter(timer)” is used to retrieve the timer counter.

/* USER CODE BEGIN WHILE */
  while (1)
  {
  count = __HAL_TIM_GetCounter(&htim2);    //read TIM2 counter value
/* USER CODE END WHILE */

 3. Input Capture

Another function of the Timer is to identify the width of input signals by using Input capture. It will record a timestamp in memory when an input signal is received. It will also set a flag indicating that an input has been captured so that you can read out the capture value easily through interrupt or event polling.

So what can it be used for? See the picture below, what if you want to determine the cycle period or the pulse width of the input pulse? Input capture will help you with that.

soure: electronics-tutorials

Similar to external input counter function, the input capture function also uses these pins with name “TIMx_CH1″,…,”TIMx_CH4”. Each time the input capture is triggered, it will latch the counter value into the Capture/Compare register of the respective channel as seen in the picture below:

ic

The video tutorial shows you how to configure a timer to be used with input capture function. Here, I used TIM2_CH1 as the input capture channel because it is connected to the User button and I want to capture the time between to 2 pushes (1 cycle).

The code in the video: There is a small modification in the code (thanks to Nathan to spot it out) as

input_capture= __HAL_TIM_GetCompare(&htim2, TIM_CHANNEL_1);  

is placed into HAL_TIM_IC_CaptureCallback instead of being in main loop.

-Timer Input capture callback:

void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)  
{
 if (htim->Instance==TIM2)
  {
  input_capture= __HAL_TIM_GetCompare(&htim2, TIM_CHANNEL_1);    //read TIM2 channel 1 capture value
  __HAL_TIM_SetCounter(&htim2, 0);    //reset counter after input capture interrupt occurs
  }
}

-Start timer 2 in input capture interrupt mode, channel 1:

HAL_TIM_IC_Start_IT(&htim2, TIM_CHANNEL_1);  

-Main loop

/* USER CODE BEGIN WHILE */
while (1)  
 {
 count = __HAL_TIM_GetCounter(&htim2);    //read TIM2 counter value
/* USER CODE END WHILE */

/* USER CODE BEGIN 3 */
 }
/* USER CODE END 3 */

4. PWM Generation

As you may hear about this function somewhere before, PWM (Pulse-Width Modulation) is mostly used for controlling the speed of DC motor or changing the brightness of LED or even mixing colors for RGB LED. In this part, I’m going to show you how to initialize the PWM function to control the brightness of the Blue LED on the STM32 Discovery kit.

Digital control is used to create a square wave, a signal switched between on and off. This on-off pattern can simulate voltages in between full on (3.3 Volts) and off (0 Volts) by changing the portion of the time the signal spends on versus the time that the signal spends off. The duration of “on time” is called the pulse width. So basically, when talking about PWM, we need to know immediately of 2 elements: Pulse width and Period. From these 2 elements, we can determine the Duty Cycle (%) of PWM signal. Low duty cycle will result in low brightness (LED) or low speed (DC motor) and vice versa.

Source: protostack Source: protostack

Similarly to Input Capture function, PWM generation uses the same Capture/Compare register. The only difference is that one capture the pulse width and store into the register while the other compare the register with the counter to trigger the output pin. PWM also uses pins which has TIMx_CH1 to CH4 function. It means that one Timer can generate maximum 4 PWM outputs and the STM32F051 we are using here can generate maximum 16 PWM signals.

pwm

Please watch the following video to know how to setup in CubeMX:

How to calculate the PWM pulse width and period ?

As you may see in the video, I set the Timer prescaler at 24 and Counter period (Autoreload register) at 200. What do these numbers mean ? The STM32F051 chip currently runs at 48MHz then the clock frequency supplies for Timer 3 is: 48MHz/(24+1) = 1.92MHz ~ 0.5us. From that, Timer3 will take (0.5us * 200) = 100us to finish one cycle counting ~ 10kHz. As a result, PWM Period relies on both Prescaler and Counter Period (Autoreload register). Besides, Pulse (Capture/Compare register) will determine the Pulse width.

To make it simple, just use the following steps to calculate prescaler and counter period base on PWM resolution and frequency:

  • Determine the desired PWM resolution (for example: 100 steps, 200 steps, 1000…)
  • Determine the desired PWM frequency (for example: 1kHz, 10kHz, 40kHz, …)
  • Set the Counter Period at the same value as the resolution
  • Set the Prescaler = 48000000/(PWM frequency*PWM resolution) – 1 (thanks to Ruben and Avinash to spot out the mistake)
  • Set the Pulse equal to the desired Pulse width where the value varies from 0 to Counter Period.

The code in the video:

– Start PWM function:

/* USER CODE BEGIN 2 */
HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_3);  
/* USER CODE END 2 */

– LED fading code:

/* USER CODE BEGIN WHILE */
while (1)  
 {
 for (pwm=0;pwm<=200;pwm++)  //darkest to brightest: 0-100% duty cycle
  {
  __HAL_TIM_SetCompare(&htim3, TIM_CHANNEL_3, pwm); //update pwm value
  HAL_Delay(10);
  }
 HAL_Delay(400);  //hold for 400ms
 for (pwm=200;pwm>=0;pwm--)  //brightest to darkest: 100-0% duty cycle
  {
  __HAL_TIM_SetCompare(&htim3, TIM_CHANNEL_3, pwm);
  HAL_Delay(10);
  }
 HAL_Delay(400);   //hold for 400ms

 /* USER CODE END WHILE */
 /* USER CODE BEGIN 3 */
 }

Now, you can try yourself making the Green LED fade in and out similarly to the Blue LED. Hope you enjoy the tutorial.

Next tutorial will be about UART.