正点原子STM32FX开发板:
《STM32FX开发指南-HAL库版本》- 定时器相关实验
STM32Fxx官方资料:
《STM32Fxx中文参考手册》-第x章 通用定时器
笔记基于正点原子官方视频
视频连接https://www.bilibili.com/video/BV1Wx411d7wT?p=71&spm_id_from=333.1007.top_right_bar_window_history.content.click
如有侵权,联系删除
1、计数器时钟可以由下列时钟源提供
① 内部时钟(CK_INT)
② 外部时钟模式1:外部输入脚(TIx)
③ 外部时钟模式2:外部触发输入(ETR)(仅适用TIM2,3,4)
④ 内部触发输入(ITRx):使用一个定时器作为另一个定时器的预分频器,如可以配置一个定时器Timer1而作为另一个定时器Timer2的预分频器。
2、内部时钟选择
以F429为例,默认调用时钟初始化函数之后:
SYSCLK(系统时钟) =180MHz
PLL 主时钟 =180MHz
AHB 总线时钟(HCLK=SYSCLK/1) =180MHz
APB1 总线时钟(PCLK1=HCLK/4) =45MHz
APB2 总线时钟(PCLK2=HCLK/2) =90MHz
所以APB1的分频系数=AHB/APB1时钟=4
所以,通用定时器时钟CK_INT=2*42M=84M
通用定时器可以向上计数、向下计数、向上向下双向计数模式。
①向上计数模式:计数器从0计数到自动加载值(TIMx_ARR),然后重新从0开始计数并且产生一个计数器溢出事件。
②向下计数模式:计数器从自动装入的值(TIMx_ARR)开始向下计数到0,然后从自动装入的值重新开始,并产生一个计数器向下溢出事件。
③中央对齐模式(向上/向下计数):计数器从0开始计数到自动装入的值-1,产生一个计数器溢出事件,然后向下计数到1并且产生一个计数器溢出事件;然后再从0开始重新计数。
在这个寄存器里面进行数据的向上计数和向下计数。
Stm32fxxx_hal_tim.c
位置:工程文件 - HALLIB - Stm32fxxx_hal_tim.c
Stm32fxxx_hal_tim.h
位置:工程文件 - HALLIB - Stm32fxxx_hal_tim.c - Stm32fxxx_hal_tim.h
在Stm32fxxx_hal_tim.h中有三部分函数体,如下:
时基部分
函数体:HAL_StatusTypeDef HAL_TIM_Base_Init(TIM_HandleTypeDef *htim);
位置:工程文件 - HALLIB - Stm32fxxx_hal_tim.c
typedef struct
{
uint32_t Prescaler; //预分频系数
uint32_t CounterMode; //计数模式:向上/下
uint32_t Period; //自动装载值
uint32_t ClockDivision; //时钟分频因子:定时器时钟与数字滤波器分频比
uint32_t RepetitionCounter; //重复计数次数:高级定时器使用
} TIM_Base_InitTypeDef
函数体:void HAL_TIM_Base_MspInit(TIM_HandleTypeDef *htim);
位置:工程文件 - HALLIB - Stm32fxxx_hal_tim.c
主要用来编写定时器时钟使能,以及中断优先级。
__HAL_RCC_TIM3_CLK_ENABLE(); //定时器3时钟使能
函数体:HAL_StatusTypeDef HAL_TIM_Base_Start(TIM_HandleTypeDef *htim)
函数体:HAL_StatusTypeDef HAL_TIM_Base_Start_IT(TIM_HandleTypeDef *htim)
位置:工程文件 - HALLIB - Stm32fxxx_hal_tim.c - Stm32fxxx_hal_tim.h
此函数同时开启了定时器更新中断
函数体:void HAL_TIM_IRQHandler(TIM_HandleTypeDef *htim);
定义所在位置:工程文件 - HALLIB - Stm32fxxx_hal_tim.c
调用位置:工程文件 - HARDWARE - timer.c
该函数被中断服务函数调用。是定时器中断处理通用入口函数,通过对中断类型进行分析判断,调用对应的回调函数
函数体:void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim);
函数体:void HAL_TIM_OC_DelayElapsedCallback(TIM_HandleTypeDef *htim)
函数体:void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
函数体:void HAL_TIM_PWM_PulseFinishedCallback(TIM_HandleTypeDef *htim)
函数体:void HAL_TIM_TriggerCallback(TIM_HandleTypeDef *htim)
函数体:void HAL_TIM_ErrorCallback(TIM_HandleTypeDef *htim)
① 使能定时器时钟。
__HAL_RCC_TIM3_CLK_ENABLE();
② 初始化定时器,配置ARR,PSC。
HAL_TIM_Base_Init();
③ 开启定时器/中断。
HAL_TIM_Base_Start();
HAL_TIM_Base_Start_IT();
④ 设置中断优先级。
HAL_NVIC_SetPriority(); HAL_NVIC_EnableIRQ();
⑤ 编写中断服务函数。
TIMx_IRQHandler();//中断服务函数
HAL_TIM_IRQHandler();//中断处理入口函数
HAL_TIM_PeriodElapsedCallback();//定时器更新中断回调函数
要求:
通过定时器中断配置,每500ms中断一次,然后中断服务函数中控制LED1实现LED1状态取反(闪烁)。
Tout(溢出时间)=(ARR+1)(PSC+1)/Tclk
本实验以之前小节03.按键输入实验为基础做改进
**注意:**在F429中Tclk的值为90MHz,即90x10^6Hz。
如果要设置溢出时间为0.5s,则可以令ARR+1=5000,PSC+1=9000
即Tout(溢出时间)=(4999+1)(8999+1)/90x10^6=0.5s
新建一个TIM3_Init函数,在里面调用HAL_TIM_Base_Init(TIM_HandleTypeDef *htim);
因为HAL_TIM_Base_Init();里面调用的是一个结构体变量,所以我们要定义一个变量来指向该结构体变量
在主函数中写入以下程序:
TIM_HandleTypeDef TIM3_Handler;
void TIM3_Init(void)
{
HAL_TIM_Base_Init(&TIM3_Handler);
}
配置Init
因为Init为一个结构体变量,我们需要对其子变量进行配置,
这里我们需要配置其中三个变量:
Prescaler 预分频系数(PSC)
CounterMode 计数模式
Period 自动装载值(ARR)
设置如下:
TIM3_Handler.Init.Prescaler = 9000 - 1;
TIM3_Handler.Init.CounterMode = TIM_COUNTERMODE_UP;
TIM3_Handler.Init.Period = 5000 - 1;
即Tout(溢出时间)=(ARR+1)(PSC+1)/Tclk
Tout(溢出时间)=(5000)(9000)/90x10^6=0.5s
至此初始化定时器,配置ARR,PSC,配置完毕,代码如下:
TIM_HandleTypeDef TIM3_Handler;
void TIM3_Init(void)
{
TIM3_Handler.Instance = TIM3;
TIM3_Handler.Init.Prescaler = 9000 - 1;
TIM3_Handler.Init.CounterMode = TIM_COUNTERMODE_UP;
TIM3_Handler.Init.Period = 5000 - 1;
HAL_TIM_Base_Init(&TIM3_Handler);
}
__HAL_RCC_TIM3_CLK_ENABLE();
HAL_TIM_Base_Start();
HAL_TIM_Base_Start_IT();
初始化时基函数,在里面调用使能定时器时钟函数,代码如下
void HAL_TIM_Base_MspInit(TIM_HandleTypeDef *htim)
{
__HAL_RCC_TIM3_CLK_ENABLE();
}
HAL_NVIC_SetPriority(); HAL_NVIC_EnableIRQ();
开启定时器/中断,把HAL_TIM_Base_Start_IT(&TIM3_Handler);函数放在初始化定时器函数体里面
设置中断优先级
复制 工程文件 - SYSTEM - usart.c中的两行代码到主函数
把使能变量改为TIM3_IRQn,设置抢占优先级为1,代码如下
HAL_NVIC_EnableIRQ(TIM3_IRQn); //使能TIM3中断通道
HAL_NVIC_SetPriority(TIM3_IRQn,1,3); //抢占优先级1,子优先级3
TIMx_IRQHandler();//中断服务函数
HAL_TIM_IRQHandler();//中断处理入口函数
HAL_TIM_PeriodElapsedCallback();//定时器更新中断回调函数
在启动文件 位置:工程文件 - CORE - startup_stm32f429xx.s 中找到中断服务函数函数体
编写中断服务函数框架如下
void HAL_TIM_IRQHandler(TIM_HandleTypeDef *htim);
引入TIM3_Handler
HAL_TIM_IRQHandler(&TIM3_Handler); //定时器中断通用处理函数
代码如下:
void TIM3_IRQHandler(void)
{
HAL_TIM_IRQHandler(&TIM3_Handler); //定时器中断通用处理函数
}
__weak void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
因为是个弱函数所以需要对在main.c函数中重写
因为在工程文件 - HARDWARE - lde.c - led.h 中有对输出口(LED0、LED1)进行位操作,所以可以直接调用
代码如下
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) //重写定时器更新中断回调函数
{
if (htim->Instance == TIM3) //判断是否是定时器3发生中断响应
{
LED1!=LED1 ; //进行LED1的反转以实现亮灭
}
}
为了做对比,我在主函数while函数中写入LED0闪烁函数(采用延时方式),闪烁周期不同,以做对比。代码如下:
int main(void)
{
HAL_Init(); //初始化HAL库
Stm32_Clock_Init(360,25,2,8); //设置时钟,180Mhz
delay_init(180); //初始化延时函数
uart_init(115200); //初始化USART
LED_Init(); //初始化LED
KEY_Init(); //初始化按键
TIM3_Init(); //调用定时器中断函数
while(1)
{
LED0 =! LED0; //延时法闪烁LED0,与LED1做对比
delay_ms(200);
}
}
至此所有函数编辑完毕,main.c代码如下:
#include "sys.h"
#include "delay.h"
#include "usart.h"
#include "led.h"
#include "key.h"
TIM_HandleTypeDef TIM3_Handler;
void TIM3_Init(void)
{
TIM3_Handler.Instance = TIM3; //选定定时器3
TIM3_Handler.Init.Prescaler = 9000 - 1; //预分频系数(PSC)
TIM3_Handler.Init.CounterMode = TIM_COUNTERMODE_UP; //计数模式,向上计数
TIM3_Handler.Init.Period = 5000 - 1; //自动装载值(ARR)
HAL_TIM_Base_Init(&TIM3_Handler);
HAL_TIM_Base_Start_IT(&TIM3_Handler); //使能定时器3,并开启更新中断
}
void HAL_TIM_Base_MspInit(TIM_HandleTypeDef *htim)
{
__HAL_RCC_TIM3_CLK_ENABLE(); //初始化时基函数
HAL_NVIC_EnableIRQ(TIM3_IRQn); //使能TIM3中断通道
HAL_NVIC_SetPriority(TIM3_IRQn,1,3); //抢占优先级1,子优先级3
}
void TIM3_IRQHandler(void)
{
HAL_TIM_IRQHandler(&TIM3_Handler); //定时器中断通用处理函数
}
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) //重写定时器更新中断回调函数
{
if (htim->Instance == TIM3) //判断是否是定时器3发生中断响应
{
LED1 =! LED1 ; //进行LED1的反转以实现亮灭
}
}
int main(void)
{
HAL_Init(); //初始化HAL库
Stm32_Clock_Init(360,25,2,8); //设置时钟,180Mhz
delay_init(180); //初始化延时函数
uart_init(115200); //初始化USART
LED_Init(); //初始化LED
KEY_Init(); //初始化按键
TIM3_Init(); //调用定时器中断函数
while(1)
{
LED0 =! LED0; //延时法闪烁LED0,与LED1做对比
delay_ms(200);
}
}