Smart Lock System Based on STM32: A Classic Introduction and Timer Review

Now we have reached the configuration of the timer and the review of the interrupt demonstration, preparing adequately for the subsequent project.Question:The timer is provided with a clock frequency of 84MHz,please configure a 100KHz PWM waveform with a duty cycle of 30%. How should the timer be configured? Think about it first.First, from the information given above, we know the frequency of the PWM waveform, so the period of the PWM waveform is 10us.

100KHz means a frequency of 100,000 times per second, so the period is 0.00001 seconds, which converts to 10us.

There is no mistake in the above.Now that we know the PWM period, we know thatPWM period = ARR count value * counting frequency, the counting frequency is the clock frequency provided, but we need to perform prescaling. Since we calculated the PWM period to be 10us, at the microsecond level.So we will set the prescaler to 84, making the frequency after prescaling 1MHz, which means counting once every 1us.According to the formulaPWM period = ARR count value * counting frequency, the ARR count value is 10, so a 30% PWM waveform can be configured.Next, we move on to the program code section,

#include "main.h"

void timer5_delay_ms(u16 ms){
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM5,ENABLE);
    TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct = {0};
    TIM_TimeBaseInitStruct.TIM_CounterMode = TIM_CounterMode_Up;    // Count up
    TIM_TimeBaseInitStruct.TIM_Period = ms * 10 - 1;
    TIM_TimeBaseInitStruct.TIM_Prescaler = 8400-1;
    TIM_TimeBaseInit(TIM5,&TIM_TimeBaseInitStruct);
    TIM_ClearFlag(TIM5,TIM_FLAG_Update);
    TIM_Cmd(TIM5,ENABLE);
    while(!(TIM_GetFlagStatus(TIM5,TIM_FLAG_Update)));
    TIM_Cmd(TIM5,DISABLE);
}

void timer5_delay_us(u16 us){
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM5,ENABLE);
    TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct = {0};
    TIM_TimeBaseInitStruct.TIM_CounterMode = TIM_CounterMode_Up;    // Count up
    TIM_TimeBaseInitStruct.TIM_Period = us * 2 - 1;
    TIM_TimeBaseInitStruct.TIM_Prescaler = 42-1;
    TIM_TimeBaseInit(TIM5,&TIM_TimeBaseInitStruct);
    TIM_ClearFlag(TIM5,TIM_FLAG_Update);
    TIM_Cmd(TIM5,ENABLE);
    while(!(TIM_GetFlagStatus(TIM5,TIM_FLAG_Update)));
    TIM_Cmd(TIM5,DISABLE);
}

void Tim9_IT_ms(u16 ms){
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM9,ENABLE);
    TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct = {0};
    TIM_TimeBaseInitStruct.TIM_CounterMode = TIM_CounterMode_Up;    // Count up
    TIM_TimeBaseInitStruct.TIM_Period = ms * 10 - 1;
    TIM_TimeBaseInitStruct.TIM_Prescaler = 8400-1;
    TIM_TimeBaseInit(TIM9,&TIM_TimeBaseInitStruct);
    TIM_ClearITPendingBit(TIM9,TIM_IT_Update);
    NVIC_InitTypeDef NVIC_InitStruct = {0};
    NVIC_InitStruct.NVIC_IRQChannel = TIM1_BRK_TIM9_IRQn;
    NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
    NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 0;
    NVIC_InitStruct.NVIC_IRQChannelSubPriority = 2;
    NVIC_Init(&NVIC_InitStruct);
    TIM_ITConfig(TIM9,TIM_IT_Update,ENABLE);
    TIM_Cmd(TIM9,ENABLE);
}

1. Implementation of Timer Delay Functions

In the code above, we used <span>TIM5</span> to implement two delay functions:

void timer5_delay_ms(u16 ms);
void timer5_delay_us(u16 us);

They achieve “delay waiting” by configuring the prescaler and auto-reload register (ARR) of TIM5, allowing the timer to generate an update flag upon overflow.

1. Millisecond Level Delay (<span><span>timer5_delay_ms</span></span>)

TIM_TimeBaseInitStruct.TIM_Period = ms * 10 - 1;
TIM_TimeBaseInitStruct.TIM_Prescaler = 8400 - 1;
  • The system clock frequency is 84MHz.

  • After prescaling by <span>8400</span>, the timer counting frequency is:84MHz ÷ 8400 = 10kHz (i.e., counting once every 0.1ms).

  • If counting <span>ms × 10</span> times, it corresponds to <span>ms</span> milliseconds.

In the end: a simple while polling of the <span>Update</span> flag can achieve blocking delay.

2. Microsecond Level Delay (<span><span>timer5_delay_us</span></span>)

TIM_TimeBaseInitStruct.TIM_Period = us * 2 - 1;
TIM_TimeBaseInitStruct.TIM_Prescaler = 42 - 1;
  • 84MHz ÷ 42 = 2MHz (i.e., counting once every 0.5µs);

  • Counting <span>us × 2</span> times corresponds to <span>us</span> microseconds.

The implementation ideas for both delay functions are consistent, only differing in the prescaler.

2. Using Timer Interrupts to Achieve “Non-blocking Delay”

Using <span>while()</span><span> in the delay functions will block the main loop, affecting system concurrency.</span><span> Therefore, the code for </span><code><span>TIM9_IT_ms()</span> uses timer interrupts to implement system timing.

void Tim9_IT_ms(u16 ms);

Configuration steps:

  1. Enable the clock

    RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM9, ENABLE);
    
  2. Configure basic timer parameters

TIM_TimeBaseInitStruct.TIM_Period = ms * 10 - 1;
TIM_TimeBaseInitStruct.TIM_Prescaler = 8400 - 1;
  1. Clear interrupt flags

    TIM_ClearITPendingBit(TIM9, TIM_IT_Update);
    
  2. Configure NVIC interrupt priority

    NVIC_InitStruct.NVIC_IRQChannel = TIM1_BRK_TIM9_IRQn;
    NVIC_Init(&amp;NVIC_InitStruct);
    
  3. Enable update interrupt & start the timer

    TIM_ITConfig(TIM9, TIM_IT_Update, ENABLE);
    TIM_Cmd(TIM9, ENABLE);
    

Thus, the interrupt service function <span><span>TIM1_BRK_TIM9_IRQHandler()</span></span> will be called periodically:

void TIM1_BRK_TIM9_IRQHandler(void){  if(TIM_GetITStatus(TIM9, TIM_IT_Update))    {        TIM_ClearITPendingBit(TIM9, TIM_IT_Update);        tim9_cont[0]--;        tim9_cont[1]++;    }}

The “delay” achieved in this way is actually based on interrupt-based time slice counting, which is non-blocking and efficient.

Leave a Comment