本文主要介绍STM32的定时器的作用和原理,并在掌握理论知识的基础上,尝试利用定时器实现定时串口通信和LED的周期性闪烁。
目录
一、任务要求
之前练习中的延时功能都是通过循环、delay/Hal_delay函数等实现,本次练习通过定时器Timer方式实现时间的精准控制,相当于给CPU上了一个闹钟,CPU平时处理其它任务,当定时时间到了以后,处理定时相关的任务。请设置一个5秒的定时器,每隔5秒从串口发送“hello windows!”;同时设置一个2秒的定时器,让LED等周期性地闪烁。
首先我们先来了解一下STM32的定时器。
二、STM32定时器
1、定时器简介
一般来说,定时器 就是用来定时的机器,是存在于STM32单片机中的一个外设。其本质就是计数器,只不过 计数器 记录的是STM32的外部情况,所接收的也是外部脉冲,而 定时器 则是由STM32自身提供的一个非常稳定的计数器,这个稳定的计数器就是STM32上连接的晶振部件。
2、定时器分类
STM32F1 系列中,除了互联型的产品,共有 8 个定时器,分为 基本定时器,通用定时器 和 高级定时器 。
- 基本定时器:
TIM6 和 TIM7 是一个 16 位的只能向上计数的定时器,只能定时,没有外部 IO。 - 通用定时器:
TIM2 TIM3 TIM4 TIM5 是一个 16 位的可以向上/下计数的定时器,可以定时,可以输出比较,可以输入捕捉,每个定时器有四个外部 IO。 - 高级定时器:
TIM1 和 TIM8 是一个 16 位的可以向上/下计数的定时器,可以定时,可以输出比较,可以输入捕捉,还可以有三相电机互补输出信号,每个定时器有 8 个外部 IO。
具体如下图所示:
这三种定时器的区别如下:
- 高级定时器具有捕获/比较通道和互补输出,通用定时器只有捕获/比较通道,基本定时器没有以上两者。
下面来具体介绍一下这三种定时器:
3、基本定时器(TIM6/TIM7)
- 16位自动重装载累加计数器
- 16位可编程(可实时修改)预分频器,用于对输入的时钟按系数为1~65536之间的任意数值分频
- 触发DAC的同步电路
- 在更新事件(计数器溢出)时产生中断/DMA请求
功能框图:
- 1、时钟源
定时器时钟 TIMxCLK,即内部时钟 CK_INT,经 APB1 预分频器后分频提供,如果
APB1 预分频系数等于 1,则频率不变,否则频率乘以 2,库函数中 APB1 预分频的系
数是 2,即 PCLK1=36M,所以定时器时钟 TIMxCLK=36*2=72M。 - 2、计数器时钟
定时器时钟经过 PSC 预分频器之后,即 CK_CNT,用来驱动计数器计数。PSC 是一个
16 位的预分频器,可以对定时器时钟 TIMxCLK 进行 1~65536 之间的任何一个数进行分频。
具体计算方式为:CK_CNT=TIMxCLK/(PSC+1)。 - 3、计数器
计数器 CNT 是一个 16 位的计数器,只能往上计数,最大计数值为 65535。当计数达
到自动重装载寄存器的时候产生更新事件,并清零从头开始计数。 - 4、自动重装载寄存器
自动重装载寄存器 ARR 是一个 16 位的寄存器,这里面装着计数器能计数的最大数
值。当计数到这个值的时候,如果使能了中断的话,定时器就产生溢出中断。 - 5、定时时间的计算
定时器的定时时间等于 计数器的中断周期乘以中断的次数。计数器在 CK_CNT 的驱动下,计一个数的时间则是 CK_CLK 的倒数,等于:1/(TIMxCLK/(PSC+1)),产生一次中断的时间则等于: 1/(CK_CLK*ARR 。如果在中断服务程序里面设置一个变量 time ,用来记录中断的次数,那么就可以计算出我们需要的定时时间等于: 1/CK_CLK * (ARR+1) * time。
4、通用定时器(TIM2/TIM3/TIM4/TIM5)
STM32的众多定时器中我们使用最多的是高级定时器和通用定时器,而高级定时器一般也是用作通用定时器的功能。
通用定时器的功能和特点:
-
位于低速的APB1总线上(APB1)
-
16 位向上、向下、向上/向下(中心对齐)计数模式,自动装载计数器(TIMx_CNT)
-
16 位可编程(可以实时修改)预分频器(TIMx_PSC),计数器时钟频率的分频系数 为 1~65535 之间的任意数值
-
4 个独立通道(TIMx_CH1~4),这些通道可以用来作为:
① 输入捕获
② 输出比较
③ PWM 生成(边缘或中间对齐模式)
④ 单脉冲模式输出 -
可使用外部信号(TIMx_ETR)控制定时器和定时器互连(可以用 1 个定时器控制另外一个定时器)的同步电路
-
如下事件发生时产生中断/DMA(6个独立的IRQ/DMA请求生成器):
①更新:计数器向上溢出/向下溢出,计数器初始化(通过软件或者内部/外部触发)
②触发事件(计数器启动、停止、初始化或者由内部/外部触发计数)
③输入捕获
④输出比较
⑤支持针对定位的增量(正交)编码器和霍尔传感器电路
⑥触发输入作为外部时钟或者按周期的电流管理
STM32 的通用定时器可以被用于:测量输入信号的脉冲长度(输入捕获)或者产生输出波形(输出比较和 PWM)等。
使用定时器预分频器和 RCC 时钟控制器预分频器,脉冲长度和波形周期可以在几个微秒到几个毫秒间调整。STM32 的每个通用定时器都是完全独立的,没有互相共享的任何资源。
功能框图:
5、高级定时器(TIM1/TIM8)
高级控制定时器(TIM1 和 TIM8)和通用定时器在基本定时器的基础上引入了外部引脚,可以实现输入捕获和输出比较功能。高级控制定时器比通用定时器增加了可编程死区互补输出、重复计数器、带刹车(断路)功能,这些功能都是针对工业电机控制方面。
高级控制定时器时基单元包含一个 16 位自动重装载寄存器 ARR,一个 16 位的计数器CNT,可向上/下计数,一个 16位可编程预分频器PSC,预分频器时钟源有多种可选,有内部的时钟、外部时钟。还有一个 8 位的重复计数器 RCR,这样最高可实现 40 位的可编程定时。
功能:
- 16位向上、向下、向上/下自动装载计数器
- 16位可编程(可以实时修改)预分频器,计数器时钟频率的分频系数为1~65535之间的任意数值
- 4个独立通道:
①输入捕获
③输出比较
③PWM生成(边缘或中间对齐模式)
④单脉冲模式输出 - 死区时间可编程的互补输出
- 使用外部信号控制定时器和定时器互联的同步电路
- 允许在指定数目的计数器周期之后更新定时器寄存器的重复计数器
- 刹车输入信号可以将定时器输出信号置于复位状态或者一个已知状态
- 如下事件发生时产生中断/DMA:
①更新:计数器向上溢出/向下溢出,计数器初始化(通过软件或者内部/外部触发)
②触发事件(计数器启动、停止、初始化或者由内部/外部触发计数)
③输入捕获
④输出比较
⑤刹车信号输入 - 支持针对定位的增量(正交)编码器和霍尔传感器电路
- 触发输入作为外部时钟或者按周期的电流管理高级控制定时器(TIM1和TIM8)
功能框图:
6、计数器模式
通用定时器可以向上计数、向下计数、向上向下双向计数模式。
- 向上计数模式:计数器从0计数到自动加载值(TIMx_ARR),然后重新从0开始计数并且产生一个计数器溢出事件。
- 向下计数模式:计数器从自动装入的值(TIMx_ARR)开始向下计数到0,然后从自动装入的值重新开始,并产生一个计数器向下溢出事件。
- 中央对齐模式(向上/向下计数):计数器从0开始计数到自动装入的值-1,产生一个计数器溢出事件,然后向下计数到1并且产生一个计数器溢出事件;然后再从0开始重新计数。
7、定时器工作原理
定时器大致可以分为四个大部分,分别是:
①时钟产生器部分
②时基单元部分
③输入捕获部分
④输出比较部分
1、时钟产生器部分
在第一部分时钟选择上,STM32定时器有四种时钟源选择,分别是:
①内部时钟(CK_INT)
②外部时钟模式:外部触发输入(ETR)
③内部触发输入(ITRx):使用一个定时器作为另一个定时器的预分频器,如可以配置一个定时 Timer1 而作为另一个定时器Timer2的预分频器。
④外部时钟模式:外部输入脚(TIx)
2、时基单元
时基单元就是定时器框图的第二部分,它包括三个寄存器,分别是:计数器寄存器(TIMx_CNT)、预分频器寄存器(TIMx_PSC)和自动装载寄存器(TIMx_ARR) 。
-
计数器寄存器(TIMx_CNT):
向上计数、向下计数或者中心对齐计数; -
预分频器寄存器(TIMx_PSC):
可将时钟频率按1到65535之间的任意值进行分频,可在运行时改变其设置值; -
自动装载寄存器(TIMx_ARR):
如果TIMx_CR1寄存器中的ARPE位为0,ARR寄存器的内容将直接写入影子寄存器;如果ARPE为1,ARR寄存器的那日同将在每次的更新时间UEV发生时,传送到影子寄存器;如果TIM1_CR1中的UDIS位为0,当计数器产生溢出条件时,产生更新事件。
3、输入捕获通道
IC1、2和IC3、4可以分别通过软件设置将其映射到TI1、TI2和TI3、TI4;4个16位捕捉比较寄存器可以编程用于存放检测到对应的每一次输入捕捉时计数器的值;当产生一次捕捉,相应的CCxIF标志位被置1;同时如果中断或DMA请求使能,则产生中断或DMA请求。如果当CCxIF标志位已经为1,当又产生一个捕捉,则捕捉溢出标志位CCxOF将被置1。
4、输出比较通道
比较输出功能:定时器通过对预设的比较值与定时器的值做匹配比较之后,并依据相应的输出模式从而实现各类输出。如 PWM输出、电平翻转、单脉冲模式、强制输出 等。
8、工作过程
在选定的时钟源(可以是内部的也可以是外部的)和预分频器 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)作为计数器的时钟源,可以用来统计脉冲,实现脉冲频率采集功能
STM32F103 系列的单片机一共有11个定时器,其中包括 2个高级定时器、4个普通定时器、2个基本定时器、2个看门狗定时器、1个系统嘀嗒定时器。
- 看门狗定时器:
看门狗定时器(WDT,Watch Dog Timer)是单片机的一个组成部分,它实际上是一个计数器,一般给看门狗一个数字,程序开始运行后看门狗开始计数。如果程序运行正常,过一段时间CPU应发出指令让看门狗置零,重新开始计数。如果看门狗增加到设定值就认为程序没有正常工作,强制整个系统复位。 - 系统嘀嗒定时器:
SysTick定时器 被捆绑在NVIC中,用于产生SYSTICK异常。他的作用是为各个不同任务许以不同数目的时间片,确保没有一个任务能霸占系统;或者把每个定时器周期 的某个时间范围赐予特定的任务等,还有操作系统提供的各种定时功能,都与这个滴答定时器有关。
三、创建工程
(1)打开 STMCubeMX 主界面,点击 ACCEE TO MCU SELECTOR
(2)选择 STM32F103C8Tx,点击 start project
(3)打开外部时钟,点击 System Core,选择RCC,在右侧弹出的菜单栏中选择Crystal/Ceramic Resonator
(4)选择调试接口,点击 System Core,选择SYS,在右侧弹出的菜单栏中选 Serial Wire
(5)配置IO口,选择 PA5 作为 LED 灯的阴极输入,将其设置为 GPIO-Output
(6)配置定时器,我选择定时器2和定时器3来实现定时的功能。选中 TIM2,将定时器2的时钟源设置为内部时钟;设置分频系数为71,向上计数模式,计数周期为50000。
(7)选择TIM3,设置到和 TIM2一样
这里将分频系数设置为71,系统处理的时候会自动加1,所以此处进行的是72分频。由于时钟设置为为72MHZ,所以72分频后得到1MHZ的时钟;1MHZ的时钟,计数50000次,得到时间50000/1000000=0.05秒;每隔0.05秒,定时器2和定时器3产生一次定时中断。
(8)配置中断与USART1,允许定时器2和定时器3的中断
(9)选择 Connectivity,点开 USART1,Mode 选择异步通信 Asynchronous
(10)配置时钟,将HCLK修改为 72MHz
(11)设置项目名称、路径和编译环境
(12)生成项目
(13)打开工程,进行代码编写
四、代码编写
(1)在 main.c 文件的主函数里面添加定时器启动代码
HAL_TIM_Base_Start_IT(&htim2); //打开定时器TIM2
HAL_TIM_Base_Start_IT(&htim3); //打开定时器TIM3
(2)定义一个数组来存储发送的信息
uint8_t hello[]="hello windows!\r\n"; //定义一个数组来存储发送的信息
(3)编写定时器中断回调函数
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
static uint32_t time_cnt1 =0; //记录中断次数
static uint32_t time_cnt2 =0;
if(htim->Instance == TIM2)
{
if(++time_cnt1 >= 40) //判断是否已经达到两秒
{
time_cnt1 =0; //点灯用的中断次数归零
HAL_GPIO_TogglePin(GPIOA,GPIO_PIN_5); //改变LED所接引脚的电平
}
}
if(htim->Instance == TIM3)
{
if(++time_cnt2 >= 100) //判断是否以及达到五秒
{
time_cnt2 =0; //串口通信的中断次数归零
HAL_UART_Transmit(&huart1,hello,17,100000); //发送信息
}
}
}
该函数为定时器的中断回调函数,当产生定时中断的时候,会自动调用这个函数。其中的 tim_cnt1和tim_cnt2 用来记录定时器中断了多少次了。当它 time_cnt1 大于等于 40 的时候,即过两秒后,LED连接的引脚PA5的状态会发生一次翻转;对于 time_cnt2,当它大于等于100的时候,即5秒,才会让串口发一次消息。
五、编译
(1)点击 Options for Target…,在 Output 下勾选 Create HEX File
(2)在 Debug 下勾选 Use Simulator,将 Dialog DLL下的输入框改为 DARMSTM.DLL,Parameter 输入框改为 -pSTM32F103C8
(3)在 Target 选择使用 V5编译器,V5编译器的安装可以参考我另一篇博客:嵌入式系统开发10——STM32串口通信 第三部分的第三小节
(4)点击 Rebuild进行编译
六、烧录运行
1、硬件连接
通信模块和供电模块连接方法:
USB转TTL | STM32F103C8T6 |
---|---|
GND | G |
3V3 | 3V3 |
RXD | PA9 |
TXD | PA10 |
注意将核心板上的BOOT0设置为1,BOOT1设置为0
LED模块连接方法:
STM32F103C8T6 | LED |
---|---|
3.3V | 阳极 |
PA5 | 阴极 |
2、烧录
打开 FlyMcu 烧录助手,选择刚刚生成的 HEX文件,点击 开始编程 进行烧录
3、运行效果
(1)LED灯每两秒闪烁一次
STM32定时器实现LED闪烁
(2)使用SSCOM串口调试助手观察串口输出,发现STM32每五秒向串口发送一次 “hello windows!”
STM32定时器实现定时串口通信
七、总结
这篇博客主要介绍STM32的定时器的作用和原理,并在掌握理论知识的基础上,尝试利用定时器实现定时串口通信和LED的周期性闪烁。通过本次的练习,让我更加深入的理解了STM32的定时器功能。
定时器本质上就是一个计数器,不过相较于计数器而言,定时器计数的是石英晶振提供的频率恒定、周期不变的脉冲序列,当计数溢出时就会产生中断。设置好最大计数次数时,就可以通过对时间脉冲序列的计数实现定时器的功能。
嵌入式学习不能只知道学习理论知识而不动手实践,也不能天天敲代码而原理一点也不懂,两者要兼得。只有同时掌握理论知识和实践方法,才能学好嵌入式。希望大家在学习嵌入式的过程中,也要做到理论与实践并重。能够做到理论指导实践,利用实践操作来加深对理论知识的理解,二者相辅相成。
最后就是当我们碰到解决不了的问题时,要多查阅资料,多参考他人的成功方法。当然这里不是去“Crtrl+C/V”,而是去学习解决问题的方法,在理解思路的过程中提升自我。
参考列表:
1.STM32CUBEMX_定时器控制LED闪烁
2.STM32F103C8T6核心开发板下,采用定时器Timer方式实现串口定时输出及LED灯周期闪烁
3.STM32之三种定时器的不同功能
4.STM32-定时器详解