STM32配置TIM定时器计数
坚持就是胜利
一、定时器Timer介绍
01 TIM简介
02 定时器类型
STM32F103C8T6定时器资源只有TIM1、TIM2、TIM3、TIM4
类型 | 编号 | 总线 | 功能 |
---|---|---|---|
高级定时器 | TIM1 TIM8 | APB2 | 拥有通用定时器全部功能,并额外具有重复计数器、死区生成、互补输出、刹车输入等功能 |
通用定时器 | TIM2 TIM3 TIM4 TIM5 | APB1 | 拥有基本定时器全部功能,并额外具有内外时钟源选择、输入捕获、输出比较、编码器接口、主从触发模式等功能 |
基本定时器 | TIM6 TIM7 | APB1 | 拥有定时中断、主模式触发DAC的功能 |
03 定时器基本结构
可以查阅STM32 F10xxx中文参考手册,理解三种定时器不同的寄存器结构
-
高级定时器
-
通用定时器
-
基本定时器
04定时器中断基本结构
05 定时器工作过程
第一部分首先看到psc预分频器需要接收时钟源,而时钟源共有四种来源,分别如下:
RCC寄存器中的APB1外设时钟使能寄存器,经过倍频之后输出时钟源,这是因为该寄存器的位0到位5分别表示的是定时器2到定时器7的使能位。
外部触发引脚TIMx_ETR的外部触发输入ETR,对应的引脚可以通过查数据手册得到。ETR经过分频得到ETRP,在经过滤波得到ETRF作为时钟信号。
内部触发输入(ITRx),来自其他的定时器的时钟,即将,经过后面的选择器,进入到触发控制器。
外部输入引脚Tix,这个主要来自于TIMx_CHx (四个通道)。
第二部分为时基单元,包括PSC预分频器、自动重装载寄存器和CNT计数器。首先由第一部分产生时钟源,进入PSC预分频器进行分频处理,得到新的时钟信号CK_CNT,使得CNT计数器加1或者减1,此时在自动重装载寄存器中有一个预先设定的装载值,当计数器的值达到装载值的时候,会产生溢出事件,然后触发中断。
第三部分为输入捕获,TIMx_CH1——TIMx_CH4 这四个通道,在芯片中都有对应的引脚,当脉冲从通道口进入时,经过输入滤波器(抗干扰的作用),然后经过边沿检测器检测到上升沿(下降沿),经过分频器,输入到捕获寄存器中,然后捕获寄存器记录此刻CNT计数器的值,当下一次下降沿(上升沿)过来时,也记录下CNT计数器的值,这样就可以计算出输入脉冲的宽度。
第四部分为输出比较(注意输入捕获和输出比较不可以同时进行),比如在比较寄存器中预先设定一个值,计数器从初始值到装载值之间计数时,当正好等于比较寄存器中的预设值时,控制TIMx_CH1——TIMx_CH4通道输出低电平或者高电平,这样随着计数器不断的计数,就可以获得一个脉冲,通过调整预设值,就可以调整脉冲宽度,调整初始值和装载值就可以调整周期。
二、HAL库小试定时器
01 题目要求
之前的延时功能都是通过循环、delay/Hal_delay函数等实现,本次通过定时器Timer方式实现时间的精准控制,相当于给CPU上了一个闹钟,CPU平时处理其它任务,当定时时间到了以后,处理定时相关的任务。请设置一个5秒的定时器,每隔5秒从串口发送“hello windows!”;同时设置一个2秒的定时器,让LED等周期性地闪烁。
02 配置CubeMX
-
配置RCC为高速时钟,在菜单栏中选
Crystal/Ceramic Resonator
-
配置SYS,菜单栏中选
Serial Wire
-
配置端口输出,选择PA1作为LED灯的输出
-
配置定时器TIM2和TIM3
其次
注意:分频系数虽然是71,但系统处理的时候会自动加上1,所以实际进行的是72分频。由于时钟一般会配置为72MHZ,所以72分频后得到1MHZ的时钟;1MHZ的时钟,计数5000次,得到时间5000/1000000=0.005秒;也就是每隔0.005秒定时器2会产生一次定时中断
-
配置定时器中断
配置中断优先级
-
配置USART1,异步通信
Asynchronous
-
配置时钟树
-
生成项目
03 配置Keil
-
启动定时器的代码,“h”表示HAL库,“tim2”表示定时器2。所以这行代码的意思就是启动定时器2和定时器3。
-
添加串口输出变量数组为全局变量,方便在后续回调函数中输出
-
配置定时器中断回调。该函数为定时器的中断回调函数,当产生定时中断的时候,会自动调用这个函数。在函数内部定义了定时器的一个静态变量:time_cnt与定时器3 的time_cnt3。
回调函数如下:
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
static uint32_t time_cnt =0;
static uint32_t time_cnt3 =0;
if(htim->Instance == TIM2)
{
if(++time_cnt >= 400)
{
time_cnt =0;
HAL_GPIO_TogglePin(GPIOA,GPIO_PIN_1);
}
}
if(htim->Instance == TIM3)
{
if(++time_cnt3 >= 1000)
{
time_cnt3 =0;
HAL_UART_Transmit(&huart1,hello,20,100000);
}
}
}
例如time_cnt,当它大于等于100的时候,才会执行if里面的代码。也就是说需要发生400次中断,才会让LED的状态翻转。前面已经算过了,一次定时中断的时间是0.005秒,所以400次中断的时间是0.005400=2秒。也就是说每隔2秒,LED的状态翻转一次。
例如time_cnt3,当它大于等于1000的时候,才会执行if里面的代码。也就是说需要发生1000次中断,才会让串口发一次消息。0.0051000=5秒,符合题目要求。
04 结果展示
2s转换灯的状态
5s串口通信展示
三、库函数实现定时器计数
笔者使用B站UP主江科大自化协实现一秒计时的功能,但是没有配置定时器中断,而在主函数循环中对计数的变量进行判断,但是无法完成间隔五秒串口发送信息。具体函数如下。
while (1)
{
//OLED_ShowNum(1, 5, Num, 5);OLED输出Num的值
if(Num % 2 == 0)
LED1_ON();
if(Num % 4 == 0)
LED1_OFF();
if(Num % 5 == 0)
Flag = 1;
while(Flag == 1 ){
Flag2 = 0;
}
}
Num在中断函数中执行Num++
操作,笔者在和队友讨论之后认为是硬件在实现过程中在主函数多次循环,当Num由5变为6的时候,主函数执行了很多次串口通信的程序,所以定时器中断是不能没有的。这就是硬件编程和软件编程的很大区别。
四、总结
完成定时器中断学习了库函数和HAL库两种操作,这部分的内容有点繁杂,需要我们在之后中多多练习使用,才能加深理解,记得更牢固。如果没有自己动手操作,那么我们学到的知识也就会更少。多学多练才能完成更好。
非常感谢同学的解答和B站的优秀教程,给了我很大的帮助