当前位置:   article > 正文

细说MCU定时器中断的实现方法

细说MCU定时器中断的实现方法

目录

一、硬件及工程 

二、STM32G4系列MCU的定时器

三、定时器中断的实现过程

1、配置新工程.ioc

2、代码修改

(1)时钟初始化函数MX_TIM3_Init()

(2)使能定时器中断

(3)定时器中断服务函数

(4)重定义定时器回调函数

3、下载并运行 

4、修改定时器参数


一、硬件及工程 

        文章依赖的硬件及工程配置同本文作者的其他文章:细说ARM MCU的串口接收数据的实现过程-CSDN博客 https://wenchm.blog.csdn.net/article/details/139541112

二、STM32G4系列MCU的定时器

         STM32G4系列MCU的定时器功能比较强大,有下面几种定时器:

  • 1个高精度定时器(high-resolution timer);
  • 3个高级控制定时器(advanced-control timer);
  • 7个通用定时器(general-purpose timer);
  • 2个基本定时器(basic timer);
  • 2个看门狗定时器(watch-dog timer);
  • 1个SysTick定时器。

  

定时器

计数器
精度

计数器类型

预分频因子

DMA
请求产生

捕捉/

比较通道

互补输出

高精度
定时器

HRTIM

16

Up

1/2/4(x2,x4,
x8,x16,x32,
DLL)

12

高级
控制

TIM1TIM8TIM20

16

UpDown
Up/Down

165536
之间的整数

4

4

通用

TIM2TIM5

32

UpDown
Up/Down

165536
之间的整数

4

通用

TIM3TIM4

16

UpDown
Up/Down

165536
之间的整数

4

通用

TIM15

16

Up

165536
之间的整数

2

1

通用

TIM16TIM17

16

Up

165536
之间的整数

1

1

基本

TIM6TIM7

16

Up

165536
之间的整数

0

        定时器最基本的功能是起到定时的作用,其中有一个关键模块:计数器(counter)。该计数器可以循环往复计数,计数的模式有三种类型:升、降和升/降。Up模式是从0到最大值递增计数,计到最大值后再从0重新开始计。

        除了TIM2和TIM5以外,其余的定时器中,计数器都是16位,相应的计数最大值为65535。除了计数器的参数以外,定时器中的另一个比较重要的参数是预分频因子(prescaler factor),这个参数关系到两次计数之间的计时间隔(具体数值,还要结合定时器的时钟频率来计算)。此外,定时器还可用于输入捕捉,以及产生PWM波形(互补)输出;当然对这两个功能,不同的定时器是有差别的。

三、定时器中断的实现过程

        本文利用 STM32G474RE上的通用定时器,以定时器中断的方式控制NUCLEO - G474RE板上的发光二极管LD2以不同的频率闪烁(该功能也可通过延时函数的方式实现)。

1、配置新工程.ioc

  • 配置GPIO:配置PA5为输出,用PA5控制开发板上的LD2。Level=High,mode=PP,Pull_up,speed=High,Label=LED;
  • 外部时钟,Serial Wire;
  • 配置定时器:打开TIM3的配置界面,在模式(Mode)区,将时钟源(Clock Source)选择为Internal Clock;然后,在配置区中,将参数设置(Parameter Settings)选项卡中的预分频因子(Prescaler)和计数器周期(Counter Period)分别设置为999和16999。这两个参数从0开始计数,分频因子为999,实际为分频999+1倍;计数器周期的计算与此相同。这里的计数周期实际就是计数器计数时的最大值,在时钟频率确定的情况下,预分频因子决定着两次计数之间的时间间隔。所以,根据这两个参数以及定时器的时钟频率,就可以计算出定时器计数的周期。此外,把计数模式(Counter Mode)设置为升模式(Up),并使能自动重载(auto-reload preload)。
  • 配置中断:使能TIM3的全局中断。将它的抢占式优先级设为1,响应优先级设为0。
  • 配置系统时钟:将系统时钟(SYSCLK)频率配置为170 MHz。定时器的时钟来自高级外设总线(APB,Advanced Peripheral Bus),APB的时钟也有自己的预分频因子,如果该因子为1,则定时器的时钟频率就与APB时钟相同,也与系统时钟相同,都是170 MHz。

2、代码修改

        至此,硬件配置便完成了。保存,启动代码生成过程,系统会将刚才配置硬件的信息自动转换成代码。

(1)时钟初始化函数MX_TIM3_Init()

         该函数自动生成。

  1. /**
  2. * @brief TIM3 Initialization Function
  3. * @param None
  4. * @retval None
  5. */
  6. static void MX_TIM3_Init(void)
  7. {
  8. /* USER CODE BEGIN TIM3_Init 0 */
  9. /* USER CODE END TIM3_Init 0 */
  10. TIM_ClockConfigTypeDef sClockSourceConfig = {0};
  11. TIM_MasterConfigTypeDef sMasterConfig = {0};
  12. /* USER CODE BEGIN TIM3_Init 1 */
  13. /* USER CODE END TIM3_Init 1 */
  14. htim3.Instance = TIM3;
  15. htim3.Init.Prescaler = 999;
  16. htim3.Init.CounterMode = TIM_COUNTERMODE_UP;
  17. htim3.Init.Period = 16999;
  18. htim3.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
  19. htim3.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;
  20. if (HAL_TIM_Base_Init(&htim3) != HAL_OK)
  21. {
  22. Error_Handler();
  23. }
  24. sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
  25. if (HAL_TIM_ConfigClockSource(&htim3, &sClockSourceConfig) != HAL_OK)
  26. {
  27. Error_Handler();
  28. }
  29. sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
  30. sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
  31. if (HAL_TIMEx_MasterConfigSynchronization(&htim3, &sMasterConfig) != HAL_OK)
  32. {
  33. Error_Handler();
  34. }
  35. /* USER CODE BEGIN TIM3_Init 2 */
  36. /* USER CODE END TIM3_Init 2 */
  37. }

        MX_TIM3_Init()函数主要完成对TIM3的模式和参数配置,如预分频因子、计数模式、计数周期等参数。在MX_TIM3_Init()函数的定义中,用到了一个结构体变量htim3,该结构体变量也被称为定时器句柄。这个变量是在自动代码生成过程中自动生成的,位于main.c文件的最前面:

TIM_HandleTypeDef htim3;

        在MX_TIM3_Init()函数的定义中,把设置的参数赋给了结构体变量htim3。其中一条if语句,在其条件表达式中调用了一个函数:HAL_TIM_Base_Init(&htim3);结构体变量htim3通过调用HAL_ TIM_Base_Init(&htim3)实现与实际硬件关联的。该函数只有一个参数。调用时,把刚配置的结构体变量htim3传递了过来。实际上,真正与硬件关联的,还不是HAL_TIM_Base_Init()函数,而是在HAL_TIM_Base_Init()函数中调用的TIM_Base_SetConfig()函数。正是通过TIM_Base_SetConfig()函数,才真正地把设置的参数传递给了相关寄存器。在库函数文件stm32g4xx_hal_tim.c中有对TIM_Base_Set-Config()函数的定义。

(2)使能定时器中断

        虽然配置了TIM3的中断功能,但在默认情况下,中断不是开启的。所以,在使用时,还要开启该中断。开启定时器中断可以使用如下库函数:

HAL_TIM_Base_Start_IT(TIM_HandleTypeDef *htim);

        该函数也只有一个参数,并且该参数也是一个结构体变量。对于TIM3来说,其实就可以用前面提到的htim3。开启定时器中断可以使用如下代码:

  1. /*USER CODE BEGIN 2 */
  2. HAL_TIM_Base_Start_IT(&htim3); //手动添加
  3. /*USER CODE END 2*/

        上述代码位于while(1)循环前面的注释对中。不过,需要将它放到TIM3初始化函数MX_TIM3_ Init()的后面。

(3)定时器中断服务函数

        开启TIM3的中断后,当条件满足时,就会执行定时器中断服务函数TIM3_IRQHandler()。该函数是自动生成的,位置在stm32g4xx_it.c文件中有该函数的定义:

  1. /**
  2. * @brief This function handles TIM3 global interrupt.
  3. */
  4. void TIM3_IRQHandler(void)
  5. {
  6. /* USER CODE BEGIN TIM3_IRQn 0 */
  7. /* USER CODE END TIM3_IRQn 0 */
  8. HAL_TIM_IRQHandler(&htim3);
  9. /* USER CODE BEGIN TIM3_IRQn 1 */
  10. /* USER CODE END TIM3_IRQn 1 */
  11. }

        TIM3_IRQHandler()函数的定义中又调用了HAL_TIM_IRQHandler()函数,此函数的定义在stm32g4xxhal_tim.c中。实际上,在HAL_TIM_IRQHandler()函数中,还会调用TIM中断的回调函数HAL_TIM_PeriodElapsedCallback()。这个函数的定义也是在stm32g4xxhal_tim.c中,不过,被定义为一个弱函数。这种方式与串口中断接收过程是类似的。就像对串口接收中断回调函数的处理一样,在定时器中断的使用中,需要做的是在main.c中重新定义TIM中断的回调函数。

(4)重定义定时器回调函数

        在main.c中重新定义回调函数HAL_TIM_PeriodElapsedCallback()。在其中让PA5的输出状态翻转。具体实现如下:

  1. /*USER CODE BEGIN 4 */
  2. void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
  3. {
  4. HAL_GPIO_TogglePin(LED_GPIO_Port,LED_Pin);
  5. }
  6. /*USER CODE END 4*/

        本例中,定时器的预分频因子(Prescaler)和计数器周期(Counter Period)分别置为999和16999,定时器的时钟频率为170 MHz,最终TIM3中断的周期为

999+1)/(170×10⁶)×(16999+1)=0.1(s),即频率为10 Hz。

3、下载并运行 

        编译工程并下载到硬件中运行,会看到LD2灯以5 Hz的频率闪烁。为什么是5 Hz因为控制PA5用的是Toggle。

4、修改定时器参数

        修改定时器的预分频因子(Prescaler)和计数器周期(CounPeriod),改变LD2灯的闪烁频率为1 Hz、0.5 Hz等。

  1. 1999+1)/(170×10⁶)×(1699+1)=2(s),即频率为2 Hz。
  2. 999+1)/(170×10⁶)×(1699+1)=1(s),即频率为1 Hz。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/weixin_40725706/article/detail/712663
推荐阅读
相关标签
  

闽ICP备14008679号