STM32F0 Tutorial 4: Timer and Counter

STM32F0 Tutorial 4: Timer and Counter

In this post, we will explore the Timer and Counter of STM32F0 using CubeMX. 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 and functions among the other peripherals. If you have worked with Arduino before, you might realize the limitations of the arduino’s timers. The STM32’s timers give you way more functions that will be very useful for many of your applications that you may not think of before. For STM32F051, it has a total of 9 main timers including seven 16-bit timers, one 24-bit systick timer, and one 32-bit timer. Some of them have 6 channels advanced-control PWM output, deadtime generation, etc. It also has an independent timer and a watchdog timer for dealing with program hanging. This tutorial will cover the following function of the timer:

  • Timebase interrupt function
  • Counter with external input
  • Input capture
  • PWM generation

Time Base Interrupt

STM32F051 has several timers for you to play with including TIM1, TIM2, TIM3, TIM6, TIM14, TIM15, TIM16, and TIM17. Basically, the timer and counter are just different from the input clock signal. For the timer, the clock source is an internal clock that is generated from the 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 a 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 a range from 0 to 65535. In the video, I set it to 47999 (corrected from 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 the Auto-reload register (Counter period) and then generate an Overflow interrupt and start all over again from 0. For count-down: the counter will count from the value stored in the Auto-reload register (Counter period) down to 0 and then generate Underflow interrupt and start all over again from the 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 it to 499, which means that the counter will count from 0 to 499 with a 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 shared among all timers interrupt. If you are using more than one Timebase 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
      }
}

External Input Counter

In the last tutorial on “External interrupt“, I have shown the example of using an 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 difference between the two methods? While the external interrupt needs to jump into the interrupt routine to do the increment or decrement of a variable, the counter can handle the job nicely without jumping anywhere. Therefore, it will be obviously useful when your program has many types of interrupts 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 the input clock. The first source is the “Internal clock”, which we discovered in the first part of this tutorial. Now, we’re gonna look at the second source, which is the “external input pin (TIx)”. With this source of the clock, the timer will apparently become a counter. The other two sources are out of the scope of this tutorial that you can discover by yourself later. So which pin is compatible with the counter mode? You can notice these pins with functions like “TIMx_CH1” and “TIMx_CH2” in which x represents the timer number (1,2,3….).

channel

The 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 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 “HAL_TIM_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 */

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.

source: electronics-tutorials

Similar to the external input counter function, the input capture function also uses these pins with the name TIMx_CH1 to 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 the 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 two 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 the 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 */

PWM Generation

As you may hear about this function somewhere before, PWM (Pulse-Width Modulation) is mostly used for controlling the speed of a DC motor or changing the brightness of an 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 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 2 elements: Pulse width and Period. From these 2 elements, we can determine the Duty Cycle (%) of the PWM signal. A low-duty cycle will result in low brightness (LED) or low speed (DC motor) and vice versa.

Source: protostack
Source: protostack

Similar to the Input Capture function, PWM generation uses the same Capture/Compare register. The only difference is that one captures the pulse width and stores it into the register while the other compares the register with the counter to trigger the output pin. PWM also uses pins that have TIMx_CH1 to CH4 function. It means that one Timer can generate a maximum of 4 PWM outputs and the STM32F051 we are using here can generate a maximum of 16 PWM signals.

pwm

The following video shows 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 the 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+1)) = 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 based 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 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 making the Green LED fade in and out similarly to the Blue LED. Hope you enjoy the tutorial.

The next tutorial will be about UART.