文章目录
一. 定时器介绍
1.定时器概念
1.能够对内部时钟信号或外部输入信号进行计数,数值达到设定要求时,向CPU发起中断请求,完成外部程序的运行。
2.本质就是进行计数,选择内部时钟脉冲,作为计数器时,技术信号的来源选择非周期脉冲信号。
STM32中定时器可分为高级定时器、通用定时器、基本定时器三类,他们都是由一个可编程的16位预分频器(TIMX_PSC)驱动的16位。
在大容量的 STM32F103xx增强型系列产品包含最多2个高级控制定时器、4个普通定时器和2个基本定时器,以及2个看门狗定时器和1个系统嘀嗒定时器,本次的目标芯片(STM32F103C8T6)也是大容量系列,所以具备以上所述的定时器种类
2.STM32定时器分类
- 定时器可分为3类:
1、基本定时器:功能最少,只能充当基本的时基,甚至都没有外部引脚
2、通用定时器:拥有基本定时器的全部功能,同时有输入捕获模式,用以接收外部的PWM,脉冲之类的信息
3、高级定时器:又有通用定时器的全部功能,又有互补输出模式,功能最为强大
通常我们使用的都是通用定时器
- 通用定时器特点:
1.位于ABP1低速总线上
2.16位向下,向上/向下(中心对齐模式)计数模式,自动重装载计数器(TIMx_CNT)
3.16位可编程(可以实现修改)预分频器(TIMx_PSC),计数器时钟频率的分频系数为1~65535任意数值
4.四个独立通道(TIMx_CH1~4),通道用来支持:
①输入捕获
②输出比较
③PWM生成
④单脉冲模式输出
5.可使用外部信号(TIM_ETR)控制定时器和定时器互连的同步电路
3.计数器模式
向上计数模式
:计数器从0计数到自动加载值(TIMx_ARR),然后重新从0开始计数并且产生一个计数器溢出事件。向下计数模式
:计数器从自动装入的值(TIMx_ARR)开始向下计数到0,然后从自动装入的值重新开始,并产生一个计数器向下溢出事件。中央对齐模式(向上/向下计数)
:计数器从0开始计数到自动装入的值-1,产生一个计数器溢出事件,然后向下计数到1并且产生一个计数器溢出事件;然后再从0开始重新计数。
4.定时时钟计算方法
Tout = ((arr+1)(psc+1))/Tclk
其中:
Tclk:定时器的输入时钟频率(单位MHZ)
Tout:定时器溢出时间(单位为us)
arr: 计数装载值
psc: 时钟分频系数
5.工作过程
在选定的时钟源(可以是内部的也可以是外部的)和预分频器TIMX_PSC的驱动下,根据设置的计数模式(向上、向下、中央对齐)自动。
装载计数器TIMX_CNT开始计数;如果使能了相应的事件(更新事件、触发事件、输入捕获、输出比较)则会产生相应的中断。
- 如果没有开启输入和输出,只使能了计数器计数溢出后自动装载,可以做为一个简单定时器使用,计数器自己开始周期计数
- 如果开启了通道输入捕获,当检测到ICx信号上相应的边沿后,计数器(CNT)的当前值被锁存到捕获/比较寄存器(TIMx_CCRx)中,通过中断的方式可以读取出来假设为
n1,然后更改输入捕获的信号级性(上升沿或下降沿),当再次检测到ICx信号上相应的边沿后,计数器(CNT)的当前值再次被锁存到捕获/比较寄存器(TIMx_CCRx)中假设为
n2;n2 -n1节可算出电平的持续时间 - 如果开启了输出控制,可以产生一个由TIMx_ARR寄存器确定频率、由TIMx_CCRx寄存器确定占空比的PWM信号。
- 如果选择外部的同步时钟信号(TI1F_ED、TI1FP1、TI2FP2)作为计数器的时钟源,可以用来统计脉冲,实现脉冲频率采集功能
二.工程建立
这里笔者选择采用hal库完成所有实验要求,这样比较简单也易于理解,关于STM32CubeMX软件在笔者之前博客有详细讲述过了。
1.题目要求
之前博客中的延时功能都是通过循环delay/Hal_delay函数等实现,本次作业通过定时器Timer方式实现时间的精准控制,相当于给CPU上了一个闹钟,CPU平时处理其它任务,当定时时间到了以后,处理定时相关的任务。请设置一个5秒的定时器,每隔5秒从串口发送“hello windows!”;同时设置一个2秒的定时器,让LED等周期性地闪烁。
2.工程建立
(1)创建新项目
在STMCubeMX主界面,创建新项目,点击ACCEE TO MCU SELECTOR
(2)芯片选择
在part name
里选择自己的芯片(一般选择直接搜索所需芯片),本文采用STM32F103C8T6
点击信息栏中的具体芯片信息选中,点击start project
(3)配置RCC
点System Cor
,选择RCC
,在右侧弹出的菜单栏中选Crystal/Ceramic Resonator
(4)配置SYS
选择调试接口,点System Cor
,选择SYS
。,在右侧弹出的菜单栏中选Serial Wire
。
(5)配置IO口输出
这里笔者选择PA1
作为LED灯的输出,将其选为GPIO-OUT
,这里我们只使用一个灯,做演示用。
(6)配置定时器2和定时器3
这里我们使用定时器2和定时器3来实现定时的功能。如图所示,依次点击位置1,选中定时器2;位置2,配置定时器2的时钟源为内部时钟;位置3,分频系数为71,向上计数模式,计数周期为5000,使能自动重载模式。
- 注意;分频系数那里虽然写的是71,但系统处理的时候会自动加上1,所以实际进行的是72分频。由于时钟我们一般会配置为72MHZ,所以72分频后得到1MHZ的时钟。1MHZ的时钟,计数5000次,得到时间5000/1000000=0.005秒。也就是每隔0.005秒定时器2会产生一次定时中断。
(7)配置中断
如下图所示,开启定时器2和定时器3的中断。
如下图所示,生成定时器2和定时器3中断优先级配置代码。
(8)配置USART
选择Connectivity
,点开USART1
,Mode选择异步通信Asynchronous
波特率为115200,1位停止位,无校验位(这里不需要改,默认就是这样)
(9)时钟配置
如图所示更改配置即可。
(10)配置项目设置
(11)生成项目
三.代码编写
1.启动定时器
HAL_TIM_Base_Start_IT(&htim2);
HAL_TIM_Base_Start_IT(&htim3);
该函数表示启动相应的定时器,“h”表示HAL库,“tim2”表示定时器2。所以这行代码的意思就是启动定时器2和定时器3。
2.串口通信
2.1在main.c中定义STM32需要给上位机发送的消息
uint8_t hello[20]="hello windows!\r\n";
2.2进行串口通信
将其放入后续的定时器中断回调函数
HAL_UART_Transmit(&huart1,hello,20,100000);
3.定时器中断回调函数
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与定时器3 的time_cnt3。
例如time_cnt,当它大于等于100的时候,才会执行if里面的代码。也就是说需要发生400次中断,才会让LED的状态翻转。前面已经算过了,一次定时中断的时间是0.005秒,所以400次中断的时间是0.005400=2秒。也就是说每隔2秒,LED的状态翻转一次。
例如time_cnt3,当它大于等于1000的时候,才会执行if里面的代码。也就是说需要发生1000次中断,才会让串口发一次消息。0.0051000=5秒,符合题目要求。
四.结果演示
LED灯(定时器)
串口通信(定时器)
五.总结
之前我们总是通过中断来实现我们想要的功能,实则既没那么精确又浪费了资源。而定时器可以很好地满足我们的需求,此次试验代码只有些许参考,需要我们自己将之前学习的串口通信与此次学习的定时器进行一个较好的连接建立。在初期也许或有所迷茫,但在听老师讲解完定时器及网上搜索查询资料学习后,思路自然出现。通过本次实验笔者也更加地意识到了HAL库的好用,将来也会更为深入地研究与使用,也要多学会与思考一个工程去实现多个要求,做到将之前所学融合贯通。笔者也在学习过程中,欢迎大家交流讨论,如有错误请多指教。
参考