1、准备材料
开发板(STM32F407G-DISC1)
ST-LINK/V2驱动
STM32CubeMX软件(Version 6.10.0)
keil µVision5 IDE(MDK-Arm)
逻辑分析仪nanoDLA
2、实验目标
使用STM32CubeMX软件配置STM32F407开发板使用基本定时器TIM6实现每500ms控制绿灯状态变化一次,基本定时器TIM7实现每1s控制红灯状态变化一次
3、定时器概述
STM32F407拥有2个基础定时器、10个通用定时器和2个高级定时器,14个定时器全部挂载在APB1和APB2时钟总线上,APB2时钟总线时钟频率最高可达84MHz,APB1时钟总线时钟频率最高可达42MHz,除TIM2和TIM5为32位外,其余定时器全部为16位,其结构框图如下图所示(注释1)
不同的定时器具有不同的特性,有些定时器的计数器长度为16位,有些则为32位;有些定时器可以递增、递减或递增/递减计数,但有些定时器只能递增计数;有些定时器可以产生DMA请求,有些则不可以;另外定时器捕获/比较通道数量也不一样;具体特性区别请看下表(注释2)
4、实验流程
4.0、前提知识
基本定时器由TIM6和TIM7组成,计数器为16位,内部结构较为简单,只有定时器的基本功能,可以做定时或驱动DAC,本实验暂不讨论DAC,只用定时功能,如下图所示为基本定时器框架(注释3),基本定时器的时钟来源为APB1 Timer clocks,当通过控制器启动基本定时器TIM6/7时,时钟信号经过PSC预分频器将时钟分频,然后以分频后的时钟频率增加计数器的值,当计数器达到自动重载寄存器设置的值之后,产生溢出
4.1、CubeMX相关配置
请先阅读“STM32CubeMX 工程建立”实验3.4.1小节配置RCC和SYS
4.1.1、时钟树配置
基本定时器涉及到定时时间的问题,而TIM6/7的时钟来源自APB1 Timer clocks,因此需要先设置时钟树,知道APB1 Timer clocks的频率,才可以计算基本定时器的溢出时间
如下图所示,时钟树上所有总线频率均设置为了STM32F4能达到的最高频率,此时APB1 Timer clocks=84MHz
4.1.2、外设参数配置
在Pinout & Configuration页面左侧功能分类栏目中点开Timers栏目,单击栏目下的TIM6和TIM7
在页面中间TIM6/7 Mode and Configuration 中勾选Activated激活基本定时器,One Pulse Mode为单次定时模式,勾选该模式则定时器只触发一次,默认定时器为连续触发,触发完一次后自动重载ARR中设置的值重新计数
在页面中间Configuration栏中可以设置基本定时器的参数,包括预分频器系数、计数模式、ARR寄存器的值和预装载值自动重载,通过这些参数的设置可以决定基本定时器的溢出时间,APB1 Timer clocks=84MHz,PSC=8399,ARR=4999,此时可计算溢出时间为(PSC+1)(ARR+1)/APB1 Timer clocks=0.5秒=500毫秒,则每500ms定时器产生一次溢出,ARR设置为9999则定时器1s溢出一次
参数auto re-load preload可以选择使能或不使能,如果不使能该参数,则在使用__HAL_TIM_SET_AUTORELOAD()函数动态修改基本定时器ARR参数值时,修改的值会立马生效;而如果使能该参数,则修改的值会在当前计数溢出之后下次得到修改
Trigger Output (TRGO) Parameters一般是用来设置用作其他外设的触发源的,比如将Trigger Event Selection选择为Update Event,然后在其他外设比如ADC中配置外部触发源时选择该定时器的触发事件(如果可以的话),这样在定时器产生Update Event时就可以启动外设,实现用定时器来控制外设启动的功能
上述配置如下图所示
4.1.3、外设中断配置
基本定时器的触发有三种模式①轮询方式②中断方式③DMA方式,这里只介绍前两种方式
①对于轮询方式,当前设置已经足够,只需要在生成的程序中使用HAL_TIM_Base_Start(&htim6)启动基本定时器,然后不断轮询计数值或UEV事件标志来判断是否发生了计数溢出
②中断方式是基本定时器最常用的方式,在Pinout & Configuration页面左侧功能分类栏目中点开NVIC栏目,然后选择合适的中断优先级并勾选基本定时器6和7的中断使能
4.2、生成代码
请先阅读“STM32CubeMX 工程建立”实验3.4.3小节配置Project Manager
单击页面右上角GENERATE CODE生成工程
4.2.1、外设初始化调用流程
在工程代码主函数main()中调用MX_TIM6_Init()函数对基本定时器TIM6参数进行了配置
在该MX_TIM6_Init()函数中调用了HAL_TIM_Base_Init()对定时器进行了初始化
然后在HAL_TIM_Base_Init()函数中调用了HAL_TIM_Base_MspInit()函数对TIM6时钟和中断设置/使能
TIM7初始化流程类似,具体定时器TIM6初始化流程如下图所示
4.2.2、外设中断调用流程
激活了基本定时器并启动TIM6/7全局中断之后,会在stm32f4xx_it.c中新增TIM6/7的中断服务函数TIM6_DAC_IRQHandler()和TIM7_IRQHandler()
该函数均调用HAL库的定时器中断统一处理函数HAL_TIM_IRQHandler(),该函数通过一系列的判断最终得出基本定时器目的为周期回调(注释4),因此最终调用周期回调函数HAL_TIM_PeriodElapsedCallback(),该函数为虚函数
TIM7中断调用流程类似,具体定时器TIM6中断调用流程如下图所示
4.2.3、添加其他必要代码
重新在tim.c中实现周期回调函数HAL_TIM_PeriodElapsedCallback(),当定时器TIM6溢出则翻转GREEN_LED引脚状态,当定时器TIM7溢出则翻转RED_LED引脚状态,具体代码如下图所示
源代码如下
/*基本定时器周期回调函数*/
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if(htim == &htim6)
{
HAL_GPIO_TogglePin(GREEN_LED_GPIO_Port, GREEN_LED_Pin) ;
}
if(htim == &htim7)
{
HAL_GPIO_TogglePin(RED_LED_GPIO_Port, RED_LED_Pin) ;
}
}
在主函数中以中断方式启动基本定时器TIM6/7,具体代码如下图所示
5、常用函数
/*以轮询工作方式启动定时器*/
HAL_StatusTypeDef HAL_TIM_Base_Start(TIM_HandleTypeDef *htim)
/*停止轮询工作方式的定时器*/
HAL_StatusTypeDef HAL_TIM_Base_Stop(TIM_HandleTypeDef *htim)
/*以中断工作方式启动定时器*/
HAL_StatusTypeDef HAL_TIM_Base_Start_IT(TIM_HandleTypeDef *htim)
/*停止中断工作方式的定时器*/
HAL_StatusTypeDef HAL_TIM_Base_Stop_IT(TIM_HandleTypeDef *htim)
/*定时器周期回调子函数*/
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
6、烧录验证
6.1、具体步骤
“激活基本定时器TIM6/7 -> 配置合适参数实现500ms/1s定时时间 -> 勾选TIM6/7全局中断 -> 在生成的代码中重新实现周期回调函数HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) -> 在该回调函数中判断定时器溢出来源 -> 根据来源编程对应响应 -> 在主函数主循环前使用HAL_TIM_Base_Start_IT(&htim6/7)以中断方式启动定时器”
6.2、实验现象
烧录程序,观察现象为绿灯每隔500ms状态改变一次,红灯每隔1s状态改变一次
使用逻辑分析仪监测PD12/14引脚状态,可以看出TIM6每500ms翻转一次PD12引脚状态,TIM7每1000ms翻转一次PD14引脚状态
7、注释详解
注释1:图片来源STM32F407VGT6 Datasheet DS8626
注释2:图片来源STM32 CubeMX 学习:003-定时器(其原表有错误)
注释3:图片来源STM32F4xx中文参考手册
注释4:具体过程请参看 HAL_TIM_IRQHandler(TIM_HandleTypeDef *htim) 函数详解
更多内容请浏览 OSnotes的CSDN博客