采用STM32CubeMX和HAL库的定时器应用实例
目录
STM32的通用定时器配置流程
定时器应用的硬件设计
定时器应用的软件设计
1. 通过STM32CubeMX新建工程 通过STM32CubeMX新建工程的步骤如下:
2. 通过Keil MDK实现工程 通过Keil MDK实现工程的步骤如下:
STM32的通用定时器配置流程
通用定时器具有多种功能,但其原理大致相同,但其流程有所区别,以使用中断方式为例,主要包括三部分,即NVIC设置、TIM中断配置、定时器中断服务程序。 对每个步骤通过库函数的实现方式描述。首先要提到是,定时器相关的库函数主要中在HAL库文件stm32f1xx_hal_tim.h 和stm32f1xx_hal_tim.c文件中。 定时器配置步骤如下:
(1)TIM3时钟使能。 HAL中定时器使能是通过宏定义标识符实现对相关寄存器操作的,方法如下:
__HAL_RCC_TIM3_CLK_ENABLE(); //使能TIM3时钟
(2)初始化定时器参数,设置自动重装值,分频系数,计数方式等。 在HAL库中,定时器的初始化参数是通过定时器初始化函数HAL_TIM_Base_Init 实现的: HAL_StatusTypeDef HAL_TIM_Base_Init(TIM_HandleTypeDef *htim); 该函数只有一个入口参数,就是TIM_HandleTypeDef类型结构体指针。
(3)使能定时器更新中断,使能定时器。 HAL库中,使能定时器更新中断和使能定时器两个操作可以在函数HAL_TIM_Base_Start_IT()中一次完成的,该函数声明如下:
HAL_StatusTypeDef HAL_TIM_Base_Start_IT(TIM_HandleTypeDef *htim); 该函数非常好理解,只有一个入口参数。调用该定时器之后,会首先调用__HAL_TIM_ENABLE_IT宏定义使能更新中断,然后调用宏定义__HAL_TIM_ENABLE 使能相应的定时器。
这里分别列出单独使能/关闭定时器中断和使能/关闭定时器方法:
__HAL_TIM_ENABLE_IT(htim, TIM_IT_UPDATE);//使能句柄指定的定时器更新中断 __HAL_TIM_DISABLE_IT (htim, TIM_IT_UPDATE);//关闭句柄指定的定时器更新中断 _HAL_TIM_ENABLE(htim);//使能句柄 htim 指定的定时器
__HAL_TIM_DISABLE(htim);//关闭句柄 htim 指定的定时器
(4)TIM3 中断优先级设置。 在定时器中断使能之后,因为要产生中断,必不可少的要设置 NVIC 相关寄存器,设置中断优先级。之前多次讲解到中断优先级的设置,这里就不重复讲解。 和串口等其他外设一样,HAL 库为定时器初始化定义了回调函数 HAL_TIM_Base_MspInit。 一般情况下,与MCU有关的时钟使能,以及中断优先级配置都会放在该回调函数内部。 函数声明如下: void HAL_TIM_Base_MspInit(TIM_HandleTypeDef *htim); 对于回调函数,这里不做过多讲解,只需要重写这个函数即可。
(5)编写中断服务函数。 在最后,还是要编写定时器中断服务函数,通过该函数处理定时器产生的相关中断。通常情况下,在中断产生后,通过状态寄存器的值判断此次产生的中断属于什么类型。然后执行相关的操作,这里使用的是更新(溢出)中断,所以在状态寄存器SR的最低位。在处理完中断之后应该向TIM3_SR的最低位写 0,清除该中断标志。
定时器应用的硬件设计
本实例利用基本定时器TIM6/7定时1s,1s时间到LED翻转一次。基本定时器是单片机内部的资源,没有外部IO,不需要接外部电路,只需要一个LED灯即可。
定时器应用的软件设计
在HAL库函数头文件stm32f1xx_hal_tim.h中对定时器外设建立了四个初始化结构体,基本定时器只用到其中一个即TIM_TimeBaseInitTypeDef,其实现如下:
typedef struct {
uint32_t Prescaler; // 预分频器
uint32_t CounterMode; // 计数模式
uint32_t Period; // 定时器周期
uint32_t ClockDivision; // 时钟分频
uint32_t RepetitionCounter; // 重复计算器
uint32_t AutoReloadPreload; // 自动预装载
} TIM_TimeBaseInitTypeDef;
这些结构体成员说明如下,其中括号内的文字是对应参数在STM32 HAL库中定义的宏:
(1)Prescaler:定时器预分频器设置,时钟源经该预分频器才是定时器时钟,它设定TIMx_PSC寄存器的值。可设置范围为0至65535,实现1至65536分频。
(2)CounterMode:定时器计数方式,可设置为向上计数、向下计数以及中心对齐模式。基本定时器只能是向上计数,即TIMx_CNT只能从0开始递增,并且无须初始化。
(3)Period:定时器周期,实际就是设定自动重载寄存器的值,在事件生成时更新到影子寄存器。可设置范围为0至65535。
(4)ClockDivision:时钟分频,设置定时器时钟CK_INT频率与数字滤波器采样时钟频率分频比,基本定时器没有此功能,不用设置。
(5)RepetitionCounter:重复计数器,属于高级控制寄存器专用寄存器位,利用它可以非常容易控制输出PWM的个数。这里不用设置。
(6)AutoReloadPreload:计数器在计满一个周期之后会自动重新计数,也就是默认会连续运行。连续运行过程中如果修改了Period,那么根据当前状态的不同有可能发生超出预料的过程。如果使能了AutoReloadPreload,那么对Period的修改将会在完成当前计数周期后才更新。这里不用设置。
下面讲述如何通过STM32CubeMX新建工程、如何通过Keil MDK实现工程。
1. 通过STM32CubeMX新建工程 通过STM32CubeMX新建工程的步骤如下:
(1)新建文件夹 Demo目录下新建文件夹TIMER,这是保存本章新建工程的文件夹。
(2)新建STM32CubeMX工程 在STM32CubeMX开发环境中新建工程。
(3)选择MCU或开发板 Commercial Part Number和MCUs/MPUs List选择STM32F103ZET6,选择Start Project启动工程。
(4)保存STM32Cube MX工程 使用STM32CubeMX菜单File→Save Project,保存工程。
(5)生成报告 使用STM32CubeMX菜单File→Generate Report生成当前工程的报告文件。
(6)配置MCU时钟树 STM32CubeMX Pinout & Configuration子页面下,选择System Core→RCC,High Speed Clock(HSE)根据开发板实际情况,选择Crystal/Ceramic Resonator(晶体/陶瓷晶振)。 STM32CubeMX切换到Clock Configuration子页面下,根据开发板外设情况配置总线时钟。此处配置PLL Source Mux为HSE,PLLMul为9倍频72MHz,System Clock Mux为PLLCLK,APB1 Prescaler为X2,其余默认设置即可。
(7)配置MCU外设 根据LED电路,整理出MCU连接的GPIO引脚的输入/输出配置
再根据表进行GPIO引脚配置,具体步骤如下。 STM32CubeMX Pinout & Configuration子页面下选择System Core→GPIO,对使用的GPIO口进行设置。LED输出端口LED1_RED(PB5)
STM32CubeMX Pinout & Configuration子页面下选择Timers→TIM6,对TIM6进行设置。Mode选择Activated,定时器预分频系数71,时钟频率为1MHz;BASIC_TIMx_PERIOD为1000,定时器计数周期为1ms
切换到STM32CubeMX Pinout & Configuration子页面下选择System Core→NVIC,修改Priority Group为2 bits for pre-emption priority(2位抢占优先级),Enabled栏勾选TIM3 global interrupt,修改Preemption Priority(抢占优先级)为0,Sub Priority(子优先级)为3
Code Generation页面Select for init sequence ordering栏勾选TIM6 global interrupt
(8)配置工程 STM32CubeMX Project Manager子页面Project栏下Toolchain/IDE选择MDK-Arm,Min Version选择V5,可生成Keil MDK工程;选择STM32CubeIDE,可生成STM32CubeIDE工程。
(9)生成C代码工程 STM32CubeMX主页面,单击GENERATE CODE按钮生成C代码工程。
2. 通过Keil MDK实现工程 通过Keil MDK实现工程的步骤如下:
(1)打开工程 打开TIMER\MDK-Arm文件夹下的工程文件。
(2)编译STM32CubeMX自动生成的MDK工程 在MDK开发环境中通过菜单Project→Rebuild all target files或工具栏 Rebuild按钮编译工程。
(3)STM32CubeMX自动生成的MDK工程 main.c文件中函数main()依次调用了HAL_Init()函数用于复位所有外设,初始化Flash接口和Systick定时器。SystemClock_Config()函数用于配置各种时钟信号频率。MX_GPIO_Init()函数初始化GPIO引脚。 文件gpio.c包含了函数MX_GPIO_Init()的实现代码,如下。
void MX_GPIO_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
/* GPIO Ports Clock Enable */
__HAL_RCC_GPIOB_CLK_ENABLE();
/*Configure GPIO pin Output Level */
HAL_GPIO_WritePin(LED1_RED_GPIO_Port, LED1_RED_Pin, GPIO_PIN_SET);
/*Configure GPIO pin : PtPin */
GPIO_InitStruct.Pin = LED1_RED_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_PULLUP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(LED1_RED_GPIO_Port, &GPIO_InitStruct);
}
main()函数外设初始化函数MX_TIM6_Init(),它是TIM6的初始化函数。MX_TIM6_Init()是在文件time.c中定义的函数,它的代码里调用了函数HAL_TIM_Base_Init()实现STM32CubeMX配置的定时器设置。MX_TIM6_Init()实现的代码如下。
void MX_TIM6_Init(void)
{
TIM_MasterConfigTypeDef sMasterConfig = {0};
htim6.Instance = TIM6;
htim6.Init.Prescaler = 71;
htim6.Init.CounterMode = TIM_COUNTERMODE_UP;
htim6.Init.Period = 1000;
htim6.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
if (HAL_TIM_Base_Init(&htim6) != HAL_OK)
{
Error_Handler();
}
sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
if (HAL_TIMEx_MasterConfigSynchronization(&htim6, &sMasterConfig) != HAL_OK)
{
Error_Handler();
}
}
函数MX_NVIC_Init()实现中断的初始化,代码如下
static void MX_NVIC_Init(void)
{
/* TIM6_IRQn interrupt configuration */
HAL_NVIC_SetPriority(TIM6_IRQn, 0, 3);
HAL_NVIC_EnableIRQ(TIM6_IRQn);
}
(4)新建用户文件 在TIMER\Core\Src下新建bsp_led.c,在TIMER\Core\Inc下新建bsp_led.h。将bsp_led.c添加到工程Application/User/Core文件夹下。
(5)编写用户代码 bsp_led.h和bsp_led.c文件实现LED操作的宏定义和LED初始化。 timer.c文件MX_TIM6_Init()函数使能TIM6和更新中断。 /* USER CODE BEGIN TIM6_Init 2 */ HAL_TIM_Base_Start_IT(&htim6); /* USER CODE END TIM6_Init 2 */ timer.c文件添加中断回调函数HAL_TIM_PeriodElapsedCallback(),对计数器time加1。
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if(htim==(&htim6))
time++;
}
main.c文件添加对用户自定义头文件的引用。
/* Private includes ---------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "bsp_led.h"
/* USER CODE END Includes */
main.c文件添加计数器time的定义。
/* USER CODE BEGIN PV */
volatile uint32_t time = 0; // ms 计时变量
/* USER CODE END PV */
main.c文件添加对LED1的取反操作
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
if ( time == 1000 ) /* 1000 * 1 ms = 1s 时间到 */
{
time = 0;
/* LED1 取反 */
LED1_TOGGLE;
}
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
(6)重新编译工程 重新编译添加代码后的工程。
(7)配置工程仿真与下载项 在MDK开发环境中通过菜单Project→Options for Target或工具栏 配置工程。
打开Debug选项卡,选择使用的仿真下载器ST-Link Debugger。Flash Download下勾选Reset and Run选项。单击确定。
(8)下载工程 连接好仿真下载器,开发板上电。 在MDK开发环境中通过菜单Flash→Download或工具栏 下载工程。 工程下载完成后,可以看到LED1以1s的频率闪烁一次