目录
2.2 通用定时器 TIM2到TIM5、TIM9到TIM14
1. 什么是定时器?
定时器 顾名思义就是一个定时的器件。给定定时器一个初值,当定时器达到给定的初值时,会通知CPU或者其他处理器去做他们该做的事。可以理解为我们日常的闹钟,设置一个时间,该事件到达后,闹钟会提醒我们去做该做的事。
定时器 可以认为是一个计数器;给定计数器一个初值,每当计数一次,就会走过一个固定的时间(等同于我们的钟表,固定的进行扫描),当达到我们给定的初值时,该定时器就完成了自己的使命,产生定时器中断,执行中断函数中的程序命令(等同于闹钟响了,提醒我们该做更重要的事是一个意思)。
2. STM32定时器简介
STM32F4 板载了高级控制定时器(TIM1和TIM8)、通用定时器(TIM2到TIM5)、通用定时器(TIM9到TIM14)以及基本定时器(TIM6和TIM7)等,总共达14个定时器之多。
三种定时器的区别:
2.1 高级控制定时器 TIM1和TIM8
2.1.1 TIM1和TIM8简介
高级控制定时器TIM1和TIM8包含一个16位的自动重载计数器(重载的意思是该计数器板载了重载寄存器,当对重载寄存器写入相关命令后,可将重载寄存器的值写入到计数器中),该计数器由可编程预分频器驱动。
此类定时器可以用于测量输入信号的脉冲宽度(输入信号的脉冲宽度:通过对51的学习,脉冲宽度会影响PWM,在对某些外设的调速中起重要作用),或者生成输出波形。
2.1.2 时基单元
可编程高级控制定时器的主要模块是一个16位计数器及其相关的自动重载寄存器。计数器可递增计数、递减计数、交替进行递增递减计数。计数器的时钟可通过预分频器进行分频。
时基单元包括:计数器寄存器(TIMx_CNT)、预分频器寄存器(TIMx_PSC)、自动重载寄存器(TIMx_ARR)、重复计数器寄存器(TIMx_RCR)
自动重载寄存器是预装载的。对自动重载寄存器执行写入或读取操作时会访问预装载寄存器。计数器由预分频器输出CK_CNT提供时钟,仅当TIMx_CR1寄存器中的计数器启动位(CEN)置1时,才会启动计数器。
注意:计数器将在TIMx_CR1寄存器的CEN位置1时刻的一个时钟周期后开始计数。
预分频器:预分频器可对计数器时钟频率进行分频,分频系数介于1和65536之间。该预分频器基于预分频寄存器TIMx_PSC的16位寄存器所控制的16位计数器。
2.1.3 计数器模式
递增计数模式:
在递增计数模式下,计数器从0计数到自动重载值(TIMx_ARR寄存器的内容),然后重新从0开始计数并生成计数器上溢事件。
如果使用重复计数器,则当递增计数的重复次数达到重复计数器寄存器中编程的次数加一次(TIMx_RCR+1)后,将升成更新时间UEV。否则将在每次计数器上溢时产生更新事件。
将TIMx_EGR寄存器的UG位置1时,也会产生更新事件;通过软件将TIMx_CR1寄存器中的UDIS位置1可禁止UEV事件。
发生更新事件时,将更新所有寄存器且将更新标志(TIMx_SR状态寄存器中的UIF位)置1。
递减计数模式:
在递减计数模式下,计数器从自动重载值(TIMx_ARR重载寄存器的内容)开始递减计数到0,然后重新开始从自动重载值开始计数并生成计数器下溢事件。
如果使用重复计数器,则当递减计数的重复次数达到重复计数器寄存器中编程的次数加一次(TIMx_RCR+1) 后,将生成更新事件 (UEV)。否则,将在每次计数器下溢时产生更新事件。
将 TIMx_EGR 寄存器的 UG 位置 1(通过软件或使用从模式控制器)时,也将产生更新事件。通过软件将 TIMx_CR1 寄存器中的 UDIS 位置 1 可禁止 UEV 更新事件。
发生更新事件时,将更新所有寄存器且将更新标志(TIMx_SR 寄存器中的 UIF 位)置 1。
中心对齐模式(递增/递减计数):
在中心对齐模式下,计数器从0开始计数到自动重载值(TIMx_ARR寄存器的内容) - 1,生成计数器上溢事件;然后从自动重载值开始向下计数到1生成计数器下溢事件。之后从0开始重新计数。
每次发生计数器上溢和下溢时都会生成更新事件,或将 TIMx_EGR 寄存器中的 UG 位置 1 (通过软件或使用从模式控制器)也可以生成更新事件。这种情况下,计数器以及预分频器计数器将重新从 0 开始计数。通过软件将 TIMx_CR1 寄存器中的 UDIS 位置 1 可禁止 UEV 更新事件。
2.1.4 重复计数器
当重复寄存器TIMx_RCR达到0时,才会生成更新事件。这在生成PWM信号时很有用。
每当发生N+1个计数器上溢或下溢(其中,N是TIMx_RCR重复计数器寄存器中的值),数据就从预装载寄存器转移到影子寄存器(TIMx_ARR 自动重载寄存器、 TIMx_PSC 预分频器寄存器以及比较模式下的 TIMx_CCRx 捕获/比较寄存器);
重复计数器在下列情况下递减:
递增计数模式下的每个计数器上溢;
递减计数模式下的每个计数器下溢;
中心对齐模式下的每个计数器上溢或者计数器下溢;
2.1.5 时钟选择
计数器时钟可由下列时钟源提供:
内部时钟 CK_INT
外部时钟模式1:外部输入引脚
外部时钟模式2:外部触发输入ETR
外部触发输入(ITRx):使用一个定时器作为另一个定时器的预分频值。
①如果禁止从模式控制器(SMS=000),则CEN位、DIR位(TIMx_CR1寄存器中)和UG位(TIMx_EGR寄存器)为实际控制位,并且只能通过软件进行更改(UG除外,仍保持自动清0)。当对CEN位写入1时,预分频器的时钟就由内部时钟CK_INT提供。
②当 TIMx_SMCR 寄存器中的 SMS=111 时,可选择外部时钟模式1。计数器可在选定的输入信号上出现上升沿或下降沿时计数。
③通过在 TIMx_SMCR 寄存器中写入 ECE=1 可选择外部时钟模式2。计数器可在外部触发输入 ETR 出现上升沿或下降沿时计数。
2.1.6 捕获/比较通道
每个捕获/比较通道均围绕一个捕获/比较寄存器(包括一个影子寄存器)、一个捕获输入阶段(数字滤波、多路复用和预分频器)和一个输出阶段(比较器和输出控制)构建而成。
捕获/比较模块由一个预装载寄存器和一个影子寄存器组成。始终可通过读写操作访问预装载寄存器。
在捕获模式下,捕获实际发生在影子寄存器中,然后将影子寄存器的内容复制到预装载寄存器中。
在比较模式下,预装载寄存器的内容将被复制到影子寄存器中,然后将影子寄存器的内容与计数器进行比较。
2.1.7 输入捕获模式
在输入捕获模式下,当相应的 ICx 信号检测到跳变沿后,将使用捕获/比较寄存器 (TIMx_CCRx) 来锁存计数器的值。
发生捕获事件时,会将相应的 CCXIF 标志(TIMx_SR 寄存器)置 1, 并可发送中断或 DMA 请求(如果已使能)。
如果发生捕获事件时 CCxIF 标志已处于高位, 则会将重复捕获标志 CCxOF(TIMx_SR 寄存器)置 1 。可通过软件向 CCxIF 写入 0 来给 CCxIF 清零,或读取存储在 TIMx_CCRx 寄存器中的已捕获数据。向 CCxOF 写入 “0” 后 会将其清零。
2.1.8 其他功能
PWM输入模式、强制输出模式、输出比较模式、PWM模式、互补输出和死区插入、使用断路功能、发生外部事件时清除、生成6步PWM、单脉冲模式、编码器接口模式、定时器输入异或功能、连接霍尔传感器、TIMx与外部触发同步、定时器同步、调试模式。
2.2 通用定时器 TIM2到TIM5、TIM9到TIM14
通用定时器包含一个16位或32位自动重载计数器CNT,该计数器由可编程预分频器PSC驱动。
通用定时器可用于测量输入信号的脉冲宽度(输入捕获)或生成输出波形(输出比较和PWM)。使用定时器预分频器和RCC时钟控制器预分频器,可将脉冲宽度和波形周期从几微秒调制到几毫秒。STM32F4的每个通用定时器都是完全独立的,没有互相共享任何资源。
STM32的通用TIMx(TIM2~TIM5和TIM9~TIM14)定时器功能包括:
1. 16位/32位(仅TIM2和TIM5)递增、递减、递增/递减自动装载计数器(TIMx_CNT),注意:TIM9~TIM14只支持递增计数模式。
2. 16位可编程(可以实时进行修改)预分频器TIMx_PSC,计数器时钟频率的分频系数为1~65535之间的任意数值。
3. 4个独立通道(TIMx_CH1~4,TIM9~TIM14最多两个通道),这些通道可以用来作为:
输入捕获 输出比较 PWM生成(边缘或中间对齐模式),注意:TIM9~TIM14不支持中间对齐模式 单脉冲模式输出
4. 可使用外部信号TIMx_ETR控制定时器和定时器互连(可以用一个定时器控制另外一个定时器)的同步电路。
5. 如下事件发生时,产生中断或者DMA(TIM9~TIM14不支持DMA)
A 更新:计数器向上溢出/向下溢出,计数器初始化(通过软件或者内部/外部触发)
B 触发事件(计时器启动、停止、初始化或者由内部/外部触发计数)
C 输入捕获
D 输出比较
E 支持针对定位的增量(正交)编码器和霍尔传感器电路(TIM9~TIM14不支持)
F 触发输入作为外部时钟或者按周期的电流管理(TIM9~TIM14不支持)
2.2.1 相关重要寄存器
控制寄存器1:TIMx_CR1
DMA/中断使能寄存器:TIMx_DIER
预分频寄存器:TIMx_PSC
计数器:TIMx_CNT寄存器
该寄存器是定时器的计数器,该寄存器存储了当前定时器的计数值。
自动重装载寄存器:TIMx_ARR
该寄存器在物理上实际对应着2个寄存器
一个是程序员可以直接操作的,另外一个是程序员看不到的。这个看不到的寄存器称为影子寄存器。真正上起作用的也是影子寄存器。
控制1寄存器TIMx_CR1中的ARPE位:
ARPE=0时,预装载寄存器的内容可以随时传送到影子寄存器,此时二者是连同的。
ARPE=1时,在每一次更新事件(UEV)时,才把预装载寄存器(ARR)的内容传送到影子寄存器。
状态寄存器 :TIMx_SR
该寄存器用来标记当前与定时器相关的各种事件/中断是否发生。
3. 库函数配置定时器中断
本次操作以通用定时器TIM3为例:
1. TIM3时钟使能:
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE); //使能TIM3时钟
2. 初始化定时器参数,设置自动重装值,分频系数,计数方式等
voidTIM_TimeBaseInit(TIM_TypeDef*TIMx, TIM_TimeBaseInitTypeDef* TIM_TimeBaseInitStruct); //定时器初始化参数
第一个参数是:确定那个定时器 第二个参数是:定时器初始化参数结构体指针
结构体定义: 注意:通用定时器只有前四个参数有用,最后一个参数是针对高级定时器的;
typedef struct
{
uint16_t TIM_Prescaler; //设置分频系数
uint16_t TIM_CounterMode; //设置计数方式/向上计数/向下计数/中央对齐计数
//也就是递增/递减/递增和递减 TIM_CounterMode_Up/TIM_CounterMode_Down
uint16_t TIM_Period; //设置自动重载计数周期值
uint16_t TIM_ClockDivision; //设置时钟的分频因子
uint8_t TIM_RepetitionCounter;
} TIM_TimeBaseInitTypeDef;
例如:
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_TimeBaseStructure.TIM_Period = 5000; //自动重载计数器周期值
TIM_TimeBaseStructure.TIM_Prescaler =7199; //设置分频系数
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;//时钟分频因子 TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //计数模式为向上计数
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);
3. 设置TIM3_DIER允许更新中断
void TIM_ITConfig(TIM_TypeDef* TIMx, uint16_t TIM_IT, FunctionalState NewState); //定时器中断使能
第一个参数:选择定时器号; ag. TIM1~TIM17;
第二个参数:指明使能定时器中断的类型,ag. 更新中断TIM_IT_Update 触发中断TIM_IT_Trigger 以及输入捕获中断
第三个参数:使能 ;ENABLE;
例如: TIM_ITConfig(TIM3,TIM_IT_Update,ENABLE );
4. TIM3中断优先级设置;
NVIC_Init();//初始化中断优先级
5. 允许TIM3工作,也就是使能TIM3
void TIM_Cmd(TIM_TypeDef* TIMx, FunctionalState NewState) //使能中断
ag. TIM_Cmd(TIM3, ENABLE); //使能TIMx外设
6. 编写中断服务函数
编写中断服务函数,通过该函数处理定时器产生的相关中断。中断产生后,通过状态寄存器的值来判断产生的中断属于什么类型。处理完中断以后,向TIM3_SR的最低位写0,来清除该中断标志。 void TIM3_IRQHandler(void) ;
ITStatus TIM_GetITStatus(TIM_TypeDef* TIMx, uint16_t) //判断定时器TIMx的中断类型TIM_IT是否发生中断。
例如:if (TIM_GetITStatus(TIM3, TIM_IT_Update) != RESET){}
void TIM_ClearITPendingBit(TIM_TypeDef* TIMx, uint16_t TIM_IT) //清除定时器TIMx的中断TIM_IT标志位
TIM_ClearITPendingBit(TIM3, TIM_IT_Update );
4. 实验程序:
本实验程序将实现TIM3定时器中断控制LED1闪烁,也就是中断产生,LED1闪烁,LED0的翻转来判断程序是否正在运行。
4.1 程序中通用定时器时钟计算?
4.2 程序代码
4.2.1 main.c
#include "stm32f4xx.h"
#include "delay.h"
#include "LED.h"
#include "BEEP.h"
#include "Key.h"
#include "usart.h"
#include "exti.h"
#include "iwdg.h"
#include "wwdg.h"
#include "Timer.h"
//AutomaticReload:自动重装值 PreporitySendCount:时钟预分频数
//定时器溢出时间计算方法:Tout=((AutomaticReload+1)*(PreporitySendCount+1))/Ft us
//Ft:定时器工作频率 MHz
int main(void)
{
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//设置中断优先级分组为2
LED_Init();//LED初始化
delay_init(168);//延迟函数初始化
TIM3_Int_Init(5000-1,8400-1);//TIM3初始化
//定时器时钟经APB1分频后为42M/2=84M,分频系数8400,自动重装载值也就是计数器计数值为5000
//计数频率为8400/84M=10KHz,计数5000次需要500ms
while(1)
{
LED0=!LED0;//LED0翻转
delay_ms(200);//延迟200ms
}
}
4.2.2 Timer.c
#include "stm32f4xx.h"
#include "LED.h"
//AutomaticReload:自动重装值 PreporitySendCount:时钟预分频数
//定时器溢出时间计算方法:Tout=((AutomaticReload+1)*(PreporitySendCount+1))/Ft us
//Ft:定时器工作频率 MHz
void TIM3_Int_Init(unsigned int AutomaticReload,unsigned int PreporitySendCount)
{
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);//TIM3时钟使能
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;//定时器结构体变量
TIM_TimeBaseInitStructure.TIM_Prescaler=PreporitySendCount;//定时器分频数
TIM_TimeBaseInitStructure.TIM_CounterMode=TIM_CounterMode_Up;//向上计数模式/递增
TIM_TimeBaseInitStructure.TIM_Period=AutomaticReload;//自动重装载值
TIM_TimeBaseInitStructure.TIM_ClockDivision=TIM_CKD_DIV1;//时钟分频因子
TIM_TimeBaseInit(TIM3,&TIM_TimeBaseInitStructure);//初始化定时器TIM3
TIM_ITConfig(TIM3,TIM_IT_Update,ENABLE);//允许定时器3更新中断
NVIC_InitTypeDef NVIC_InitStructure;//设置NVIC结构体变量
NVIC_InitStructure.NVIC_IRQChannel=TIM3_IRQn;//定时器3中断
NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;//中断使能
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=0x01;//抢占优先级1
NVIC_InitStructure.NVIC_IRQChannelSubPriority=0x03;//响应优先级3
NVIC_Init(&NVIC_InitStructure);//初始化NVIC中断优先级
TIM_Cmd(TIM3,ENABLE);//使能定时器3
}
void TIM3_IRQHandler(void)//定时器3中断服务函数
{
if(TIM_GetITStatus(TIM3,TIM_IT_Update)==SET)//获取状态寄存器的0位,0位如果是SET,也就是1,溢出中断
{
LED1=!LED1;//LED1闪烁
}
TIM_ClearITPendingBit(TIM3,TIM_IT_Update);//清除中断标志位
}
4.2.3 Timer.h
#ifndef _TIMER__H_
#define _TIMER__H_
void TIM3_Int_Init(unsigned int AutomaticReload,unsigned int PreporitySendCount);
void TIM3_IRQHandler(void);
#endif