ES1 PRJ2, PRJV & PRJD: STM32F05x microcontroller

Lesson 4: Timers/Counter & PWM

By Hugo Arends

© 2018 HAN University of Applied Sciences, version 1.0


Delays

In previous lessons we have used the following function to create a delay:

delay(SystemCoreClock/8);

We have seen that the compiler creates assembler instructions that take 8 cycles in total, so the function call above creates a delay of approximately 1 second.

Creating a delay like this has two major disadvantages:

Let’s see how Timer/Counter peripherals of the STM32F05x can be used to create delays instead of using the Cortex-M0 core.

Timers

The STM32F05x has 8 peripherals that are meant to be used as a timer: TIM1, TIM2, TIM3, TIM6, TIM14, TIM15, TIM16 and TIM17. There is also a timer peripheral in the Cortex-M0 core called the SysTick timer. Although the features of timers may be different, basic operation of a general purpose timer is described with the following block diagram:

Copyright STMicroelectronics (2012). Retrieved from RM0091.

Four registers form the ‘base’ of every timer:

Timers can be used in different modes of operation. Let’s discuss several modes of operation and see how these modes can be used in an application. We will use TIM3, which has a some more features then the timer described before as can be seen in the block diagram:

Copyright STMicroelectronics (2012). Retrieved from RM0091.

Basically it’s the same diagram as seen before, except that it has more options with respect to the Trigger Controller (both internal and external clock sources) and not one but four Capture/Compare registers with an associated input capture/output compare channel.

We will use the STM32F05x standard peripheral driver library to setup and use the timer. The comments in the library file stm32f0xx_tim.c tells us how to use the driver in a specific mode of operation.

Timebase mode

In Timebase mode interrupts are generated on a regular time base. When up counting, the counter counts from 0 to the autoreload value (content of the TIM3_ARR register), then restarts from 0 and generates a counter update event. The following figure shows this relationship:

The time between TIM_IT_Update interrupts is determined by 3 variables:

The counter TIM3_CNT is clocked with a frequency:

(1)

So the prescaler is calculated with:

(2)

The time between TIM_IT_Update interrupts is:

(3)

So the autoreload value is calculated by:

(4)

Let's set these values if we want the time between TIM_IT_Update interrupts to be 1 second. We will make TIM3_CNT count with a frequency of 1 kHz and then make it count for 1.000 steps.

Remember that this is a 16-bit timer, so all calculated values should not exceed 65.535!

We will use the peripheral driver library to implement this timebase. The file stm32f0xx_tim.c tells us how to use the driver in TimeBase mode:

/** @defgroup TIM_Group1 TimeBase management functions

 *  @brief   TimeBase management functions

 *

@verbatim

 ===============================================================================

                 ##### TimeBase management functions #####

 ===============================================================================

 

        *** TIM Driver: how to use it in Timing(Time base) Mode ***

 ===============================================================================

    [..] To use the Timer in Timing(Time base) mode, the following steps are

         mandatory:

         (#) Enable TIM clock using

             RCC_APBxPeriphClockCmd(RCC_APBxPeriph_TIMx, ENABLE) function.

         (#) Fill the TIM_TimeBaseInitStruct with the desired parameters.

         (#) Call TIM_TimeBaseInit(TIMx, &TIM_TimeBaseInitStruct) to configure

             the Time Base unit with the corresponding configuration.

         (#) Enable the NVIC if you need to generate the update interrupt.

         (#) Enable the corresponding interrupt using the function

             TIM_ITConfig(TIMx, TIM_IT_Update).

         (#) Call the TIM_Cmd(ENABLE) function to enable the TIM counter.

    [..]

        (@) All other functions can be used seperatly to modify, if needed,

            a specific feature of the Timer.

@endverbatim

  * @{

  */

Open the project associated with this lesson. This project has multiple targets defined, each with a separate main application source file. Make sure the target called ‘Timebase’ is selected:

In the file main1.c you’ll find the following implementation of the main routine:

int main(void)

{

  TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;

  NVIC_InitTypeDef        NVIC_InitStructure;

  // Configure LED3 and LED4 on STM32F0-Discovery

  STM_EVAL_LEDInit(LED3);

  STM_EVAL_LEDInit(LED4);

 

  // Initialize User Button on STM32F0-Discovery

  STM_EVAL_PBInit(BUTTON_USER, BUTTON_MODE_GPIO);

 

  //[..] To use the Timer in Timing(Time base) mode, the following steps are

  //     mandatory:

 

  //(#) Enable TIM clock using

  //    RCC_APBxPeriphClockCmd(RCC_APBxPeriph_TIMx, ENABLE) function.

  RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);

 

  //(#) Fill the TIM_TimeBaseInitStruct with the desired parameters.

  TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;

  TIM_TimeBaseStructure.TIM_Period = 1000-1;

  TIM_TimeBaseStructure.TIM_Prescaler = (uint16_t)((SystemCoreClock / 1000) - 1);

 

  //(#) Call TIM_TimeBaseInit(TIMx, &TIM_TimeBaseInitStruct) to configure

  //    the Time Base unit with the corresponding configuration.

  TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);

 

  //(#) Enable the NVIC if you need to generate the update interrupt.

  //    Enable the TIM3 gloabal Interrupt

  NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn;

  NVIC_InitStructure.NVIC_IRQChannelPriority = 0;

  NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;

  NVIC_Init(&NVIC_InitStructure);

 

  //(#) Enable the corresponding interrupt using the function

  //    TIM_ITConfig(TIMx, TIM_IT_Update).

  TIM_ITConfig(TIM3, TIM_IT_Update, ENABLE);

 

  //(#) Call the TIM_Cmd(ENABLE) function to enable the TIM counter.

  TIM_Cmd(TIM3, ENABLE);

 

  while(1)

  {

    // Button pressed?

    if(STM_EVAL_PBGetState(BUTTON_USER) == Bit_SET)

    {

      STM_EVAL_LEDOn(LED3);

    }

    else

    {

      STM_EVAL_LEDOff(LED3);

    }

  }

}

A Timebase- and NVIC initialisation structure is used. These structures are filled with the appropriate settings as discussed before and used as an argument in the initialisation function.

Every second the TIM_IT_Update interrupt will be triggered. The corresponding interrupt handler in the file stm32f0xx_it.c is implementeren like this:

void TIM3_IRQHandler(void)

{

  if(TIM_GetITStatus(TIM3, TIM_IT_Update) != RESET)

  {

    TIM_ClearITPendingBit(TIM3, TIM_IT_Update);

    STM_EVAL_LEDToggle(LED4);

  }

}

The first thing to do is verify if TIM_IT_Update triggered the handler. Remember that all TIM3 related interrupts trigger the same handler routine. Then the interrupt bit is cleared and LED4 is toggled.

Assignment

If we wish to generate an interrupt every 1 second with TIM3, then it would be obvious to set the prescaler to SysCoreClock and the autoreload value to 0. Why is this not possible in TIM3?

What is the maximum time between TIM_IT_Update interrupts that can be realized with TIM3 when SystemCoreClock equals 48 MHz? And for TIM2?

Output Compare mode

This mode is used to control an output waveform directly on an I/O-pin. This means the peripheral hardware does all the work, there is no code to execute for the Cortex-M0 core. Not even an interrupt handler!

Compared to the Timebase mode, one additional register is used: Capture/Compare register TIM3_CCRx. In Output Compare mode, when TIM3_CNT matches TIM3_CCRx, a corresponding output pin can keep it’s level, be set active, be set inactive or can toggle.

The time between OC_Matches is determined by 4 variables:

The first three are calculated in exactly the same way as discussed in Timebase mode. The Capture/Compare Register needs a value between 0 and TIM3_ARR. If not, there will never be a match and hence no triggering of the I/O-pin.

Let’s see how to setup TIM3 to toggle an I/O-pin each second with use of Output Compare mode. To make the waveform visible, we will use channel 3. This channel is connected to LED4 on the STM32F0Discovery board[1].

Again, the comment in the file stm32f0xx_tim.c helps us in how to use the timer peripheral driver library.

/** @defgroup TIM_Group3 Output Compare management functions

 *  @brief    Output Compare management functions

 *

@verbatim

 ===============================================================================

                ##### Output Compare management functions #####

 ===============================================================================

        *** TIM Driver: how to use it in Output Compare Mode ***

 ===============================================================================

    [..] To use the Timer in Output Compare mode, the following steps are mandatory:

         (#) Enable TIM clock using

             RCC_APBxPeriphClockCmd(RCC_APBxPeriph_TIMx, ENABLE) function.

         (#) Configure the TIM pins by configuring the corresponding GPIO pins

         (#) Configure the Time base unit as described in the first part of this

             driver, if needed, else the Timer will run with the default

             configuration:

             (++) Autoreload value = 0xFFFF.

             (++) Prescaler value = 0x0000.

             (++) Counter mode = Up counting.

             (++) Clock Division = TIM_CKD_DIV1.

         (#) Fill the TIM_OCInitStruct with the desired parameters including:

             (++) The TIM Output Compare mode: TIM_OCMode.

             (++) TIM Output State: TIM_OutputState.

             (++) TIM Pulse value: TIM_Pulse.

             (++) TIM Output Compare Polarity : TIM_OCPolarity.

         (#) Call TIM_OCxInit(TIMx, &TIM_OCInitStruct) to configure the desired

             channel with the corresponding configuration.

         (#) Call the TIM_Cmd(ENABLE) function to enable the TIM counter.

    [..]

        (@) All other functions can be used separately to modify, if needed,

            a specific feature of the Timer.

        (@) In case of PWM mode, this function is mandatory:

            TIM_OCxPreloadConfig(TIMx, TIM_OCPreload_ENABLE).

        (@) If the corresponding interrupt or DMA request are needed,

            the user should:

            (#@) Enable the NVIC (or the DMA) to use the TIM interrupts (or DMA

                 requests).

            (#@) Enable the corresponding interrupt (or DMA request) using the

                 function TIM_ITConfig(TIMx, TIM_IT_CCx) (or TIM_DMA_Cmd(TIMx,

                 TIM_DMA_CCx)).

@endverbatim

  * @{

  */

Open the project associated with this lesson. This project has multiple targets defined, each with a separate main application source file. Make sure the target called ‘Output Compare’ is selected and rebuild all target files ().

In the file main2.c you’ll find the following implementation of the main routine:

int main(void)

{

  GPIO_InitTypeDef        GPIO_InitStructure;  

  TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;

  TIM_OCInitTypeDef       TIM_OCInitStructure;

 

  // Initialize User Button on STM32F0-Discovery

  STM_EVAL_PBInit(BUTTON_USER, BUTTON_MODE_GPIO);

  //[..] To use the Timer in Output Compare mode, the following steps are

  //     mandatory:

  //(#) Enable TIM clock using

  //    RCC_APBxPeriphClockCmd(RCC_APBxPeriph_TIMx, ENABLE) function.

  RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);

 

  //(#) Configure the TIM pins by configuring the corresponding GPIO pins

  //    This is LED4 on STM32F0-Discovery

  RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOC, ENABLE);

  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;

  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8;

  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;

  GPIO_Init(GPIOC, &GPIO_InitStructure);

  GPIO_PinAFConfig(GPIOC, GPIO_PinSource8, GPIO_AF_1);

  //(#) Configure the Time base unit as described in the first part of this

  //    driver, if needed, else the Timer will run with the default

  //    configuration:

  //    (++) Autoreload value = 0xFFFF.

  //    (++) Prescaler value = 0x0000.

  //    (++) Counter mode = Up counting.

  //    (++) Clock Division = TIM_CKD_DIV1.

  TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;

  TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;

  TIM_TimeBaseStructure.TIM_Period = 1000 - 1;

  TIM_TimeBaseStructure.TIM_Prescaler = (uint16_t)((SystemCoreClock / 1000) - 1);

  TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);

 

  //(#) Fill the TIM_OCInitStruct with the desired parameters including:

  //    (++) The TIM Output Compare mode: TIM_OCMode.

  //    (++) TIM Output State: TIM_OutputState.

  //    (++) TIM Pulse value: TIM_Pulse.

  //    (++) TIM Output Compare Polarity : TIM_OCPolarity.

  TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_Toggle;

  TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;

  TIM_OCInitStructure.TIM_Pulse = 500;

  TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;

 

  //(#) Call TIM_OCxInit(TIMx, &TIM_OCInitStruct) to configure the desired

  //    channel with the corresponding configuration.

  TIM_OC3Init(TIM3, &TIM_OCInitStructure);

  //(#) Call the TIM_Cmd(ENABLE) function to enable the TIM counter.

  TIM_Cmd(TIM3, ENABLE);

  while(1)

  {

    ;

  }

}

First thing to notice is that the GPIOC pin 8 needs to be set to it’s alternate function 1.

Then the timebase is set with the same parameters as in the Timebase example.

Then the output compare options are set. We make the I/O-pin toggle on each match and the Compare register is set to 500. So as soon as the TIM3_CNT starts counting, 0,5 seconds later a compare match will occur and LED4 will toggle.

The final thing to notice is that the NVIC has not been initialized. No interrupts need to be used in order to generate the waveform!

Assignment

This timer has four channels that can be connected to an I/O-pin. GPIOC I/O-pin 9 (LED3) is connected to channel 4 of TIM3. The following figure shows two waveforms. PC8/LED4 has been implemented. Now also implement PC9/LED3 by additionally implementing channel 4.

Tips:

PWM mode

Pulse width modulation mode allows you to generate a signal with a frequency determined by the value of the TIMx_ARR register and a duty cycle determined by the value of the TIMx_CCRx register. The mode of operation is almost the same as with Output Compare operation, except that when TIMx_CNT reaches 0 the I/O-pin is set (or reset, this is programmable by software). The I/O-pin will stay set as long as TIMx_CNT < TIMx_CCRx. In a figure:

The figure shows that the PWM period is a fixed value and depends on the timer clock frequency and the value of the autoreload register. By changing the value of the Capture/Compare Register the width, also called duty cycle, can be changed on the fly.

 

Lets see how to setup TIM3 when we want to generate a PWM signal of 50Hz and control the duty cycle in 100 steps.

A 50 Hz PWM signal means a PWM period of:

(5)

If we want 100 steps to control the duty cycle, then

Minus 1, because 0 until 99 is 100 steps.

Then the frequency of TIM3_CNT is calculated by:

(6)

This means the following prescaler is needed:

(7)

When SystemCoreClock equals 48MHz, the prescaler equals 9.599.

Both these values (autoreload and prescaler) fit within a 16 bit register!

Open the project associated with this lesson. This project has multiple targets defined, each with a separate main application source file. Make sure the target called ‘PWM’ is selected and rebuild all target files ().

In the file main3.c you’ll find the following implementation of the main routine:

int main(void)

{

  GPIO_InitTypeDef        GPIO_InitStructure;  

  TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;

  TIM_OCInitTypeDef       TIM_OCInitStructure;

 

  uint32_t compare=0;

 

  // Initialize User Button on STM32F0-Discovery

  STM_EVAL_PBInit(BUTTON_USER, BUTTON_MODE_GPIO);

  //[..] To use the Timer in Output Compare mode, the following steps are

  //     mandatory:

 

  //(#) Enable TIM clock using

  //    RCC_APBxPeriphClockCmd(RCC_APBxPeriph_TIMx, ENABLE) function.

  RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);

 

  //(#) Configure the TIM pins by configuring the corresponding GPIO pins

  //    This is LED3 on STM32F0-Discovery

  RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOC, ENABLE);

  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;

  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;

  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;

  GPIO_Init(GPIOC, &GPIO_InitStructure);

  GPIO_PinAFConfig(GPIOC, GPIO_PinSource9, GPIO_AF_1);

 

  //(#) Configure the Time base unit as described in the first part of this

  //    driver, if needed, else the Timer will run with the default

  //    configuration:

  //    (++) Autoreload value = 0xFFFF.

  //    (++) Prescaler value = 0x0000.

  //    (++) Counter mode = Up counting.

  //    (++) Clock Division = TIM_CKD_DIV1.

  TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;

  TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;

  TIM_TimeBaseStructure.TIM_Period = 100 - 1;

  TIM_TimeBaseStructure.TIM_Prescaler = (uint16_t)((SystemCoreClock / 5000) - 1);

  TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);

 

  //(#) Fill the TIM_OCInitStruct with the desired parameters including:

  //    (++) The TIM Output Compare mode: TIM_OCMode.

  //    (++) TIM Output State: TIM_OutputState.

  //    (++) TIM Pulse value: TIM_Pulse.

  //    (++) TIM Output Compare Polarity : TIM_OCPolarity.

  TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;

  TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;

  TIM_OCInitStructure.TIM_Pulse = compare;

  TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;

 

  //(#) Call TIM_OCxInit(TIMx, &TIM_OCInitStruct) to configure the desired

  //    channel with the corresponding configuration.

  TIM_OC4Init(TIM3, &TIM_OCInitStructure);

  //(#) Call the TIM_Cmd(ENABLE) function to enable the TIM counter.

  TIM_Cmd(TIM3, ENABLE);

  while(1)

  {

    // Delay ~ 0.01 sec.

    delay(SystemCoreClock/8/100);

   

    // Update compare value

    if(++compare > 99){ compare=0;}

    TIM_SetCompare4(TIM3, compare);

  }

}

The implementation is very similar to output Compare mode. There are however differences related to timing and the OC_Mode is set to TIM_OCMode_PWM1. Again, there is no use of interrupts, so the TIM3 peripheral hardware takes care of generating the PWM waveform.

The channel 4 Capture/Compare Register value is continually updated with the variable called ‘compare’. By using a simple delay routine in the main application program, this compare value is updated every 0.01 sec. So in one second a duty cycle from 0 until 99 (=100 steps) will be made visible on LED3.

Assignment

Change the PWM example to generate a PWM frequency of 100 Hz and control the duty cycle in 1.000 steps.

Assignment

Setup TIM3 to create a PWM signal suitable for servo motors on channel 3. A servo motor is controlled with a 50 Hz PWM frequency and the duty cycle typically has a value between 1 and 2 ms, where 1 ms means the shaft rotates to the outer left position and 2 ms means the outer right position.

Setup the timer and implement the function using the following defines and function prototype:

#define SERVO_MOVETO_LEFT  ((uint32_t)(0x0))

#define SERVO_MOVETO_RIGHT ((uint32_t)(0x1))

void SERVO_MoveTo(uint32_t moveTo);

SysTick timer

Besides the eight timer peripherals in the STM32F05x, there is also a timer called SysTick available in the Cortex-M0 core. The main purpose of this 24-bit timer is the use of a task switch scheduler in an real-time operating system. If you do not use an operating system, the SysTick timer could also be used as a standard down counter. It features:

Using the SysTick timer to generate a periodic interrupt is very easy. Let’s see how to setup the SysTick timer with the CMSIS compliant functions to generate an interrupt every 5 ms, as depicted in the following figure:

The CMSIS functions are also very well documented. The file core_cm0.h describes how to configure the SysTick with the function SysTick_Config():

SysTick_Config(uint32_t ticks);

/** \brief  System Tick Configuration

    This function initialises the system tick timer and its

    interrupt and start the system tick timer.

    Counter is in free running mode to generate periodical

    interrupts.

    \param [in]  ticks  Number of ticks between two interrupts

    \return          0  Function succeeded

    \return          1  Function failed

 */

So this function performs all initialisation, we only need to supply the number of ticks between two interrupts and the function returns 0 if this succeeded.

If we want the time between interrupts to be 5 ms and the core clock runs at SystemCoreClock Hz, then the number of ticks is calculated by:

(8)

Open the project associated with this lesson. This project has multiple targets defined, each with a separate main application source file. Make sure the target called ‘SysTick’ is selected and rebuild all target files ().

In the file main4.c you’ll find the following implementation of the main routine:

int main(void)

{

  // Configure LED3 and LED4 on STM32F0-Discovery

  STM_EVAL_LEDInit(LED3);

 

  // Setup SysTick Timer for 5 msec interrupts

  if(SysTick_Config((SystemCoreClock * 0.005)-1))

  {

    // Error!

    // Do nothing, LED3 will stay off indicating an erroneous situation.

    while(1){;}

  }

  while(1)

  {

    ;

  }

}

Also take a look at the SysTick_Handler in the file stm32f0xx_it.c. It looks like this:

/**

  * @brief  This function handles SysTick Handler.

  * @param  None

  * @retval None

  */

void SysTick_Handler(void)

{

  static uint32_t ticks=0;

  if(ticks++ ==   0){GPIOC->BSRR = 0x0200;} // Green LED on

  if(ticks   ==  15){GPIOC->BRR  = 0x0200;} // Green LED off

  if(ticks   ==  30){GPIOC->BSRR = 0x0200;} // Green LED on

  if(ticks   ==  45){GPIOC->BRR  = 0x0200;} // Green LED off

  if(ticks   == 300){ticks=0;}

}

This handler implements the blinking sequence. This sequence is repeated every .

See how this handler also uses the CMSIS compliant way of setting (BSRR) and resetting (BRR) the GPIOC I/O-pin 9. This is just an example to show that we can always use the CMSIS compliant interface, although the I/O-pin has been initialized with a standard peripheral driver library function.

Assignment

Adapt the SysTick project so that the green LED blinks with a frequency of 1 Hz: 0,5 sec. on and 0,5 sec off.

Now use timer 3 to make the blue LED blink with a frequency of 10 Hz: 0,05 sec. on and 0,05 sec. off. Decide for yourself what mode of operation to use.

What is the maximum time between SysTick interrupts that can be realized when SystemCoreClock equals 48 MHz?

hugo.arends@han.nl More lessons? Click here!


[1] Ref.: STMicroelectronics (2012), User Manual UM1525, [Online publication]