STM32—定时器知多少
定时器的分类
定时器的分类:
定时器的类型可以查看STM32F1XX中文参考手册的第14,15,16章节,里面对定时器的知识有详细的介绍。
STM32F1系列的开发板一共有8个定时器,按照功能的不同可以分为:
高级定时器(TIM1、TIM8)
通用定时器(TIM2、TIM3、TIM4、TIM5)
基本定时器(TIM6、TIM7)
看门狗定时器
SysTick定时器
他们之间的区别情况见下表:

定时器的工作原理
1 高级定时器简介
TIM1 和 TIM8 简介
高级控制定时器(TIM1 和 TIM8)包含一个 16 位自动重载计数器,该计数器由可编程预分频器驱动。
此类定时器可用于各种用途,包括测量输入信号的脉冲宽度(输入捕获),或者生成输出波形(输出比较、PWM 和带死区插入的互补 PWM)。
使用定时器预分频器和 RCC 时钟控制器预分频器,可将脉冲宽度和波形周期从几微秒调制到几毫秒。
高级控制定时器(TIM1 和 TIM8)和通用 (TIMx) 定时器彼此完全独立,不共享任何资源。
14.2 TIM1 和 TIM8 主要特性
TIM1 和 TIM8 定时器具有以下特性:
● 16 位递增、递减、递增/递减自动重载计数器。
● 16 位可编程预分频器,用于对计数器时钟频率进行分频(即运行时修改),分频系数介于 1 到 65536 之间。
● 多达 4 个独立通道,可用于:
— 输入捕获
— 输出比较
— PWM 生成(边沿和中心对齐模式)
— 单脉冲模式输出
● 带可编程死区的互补输出。
● 使用外部信号控制定时器且可实现多个定时器互连的同步电路。
● 重复计数器,用于仅在给定数目的计数器周期后更新定时器寄存器。
● 用于将定时器的输出信号置于复位状态或已知状态的断路输入。
● 发生如下事件时生成中断/DMA 请求:
— 更新:计数器上溢/下溢、计数器初始化(通过软件或内部/外部触发)
— 触发事件(计数器启动、停止、初始化或通过内部/外部触发计数)
— 输入捕获
— 输出比较
— 断路输入
● 支持定位用增量(正交)编码器和霍尔传感器电路。
● 外部时钟触发输入或逐周期电流管理。
STM32F103中有TIM1,TIM8两个高级定时器,每一定时器都有
一个16位向上、向下、向上/下自动装载计数器
一个16位预分频器和四个独立从输入输出通道
每一个通道都可用于输入捕获、输出比较、PWM和单脉冲模式(除了基本定时器,高级定时器和通用定时器都能产生PWM)
单片机中没有时间的概念,因此定时器本质是一个以单位时间为准的计数器,计数值可以从0开始累加,也可以从一个设定值(ARR)的值递减,每隔一个固定的时间(由psc和时钟周期控制)计数器的值+1或-1,加到或减到头时会产生一个溢出信号,此时计数器的计数值清零或补充成初值,重新开始计数。
高级定时器框图

1 时钟源
由四个时钟来源,分别是
内部时钟源 CK_INT
外部时钟模式 1:外部输入引脚 TIx(x=1,2,3,4)
外部时钟模式 2:外部触发输入 ETR
内部触发输入(ITRx)
我们一般只用内部时钟源CK_INT,对于高级定时器,其时钟是由APB2总线时钟决定的

其他类型的定时器同理可以参考参考手册的RCC部分的时钟框图。
2 控制器
用来控制和发送命令的相关的寄存器有
CR1(control register 1 )控制寄存器1
CR2(control register 2 )控制寄存器2
SMCR(slave mode control register)从模式控制寄存器
CCER(capture/compare enable register)捕获比较寄存器
3 时基单元Time-base unit

时基单元的组成:
(1)1-16位的预分频器 PSC
对控制计数器的时钟进行分频,分配系数可以为1~65536。
(2)16位的计数器CNT
每经过一个计数器时钟周期这里变化1(+1或-1)。
(3)4-16位的自动重装载寄存器ARR
向上计数时,但计数器的值CNT从0计数到ARR的值的时候,会产生溢出中断(也叫更新中断,update interrupt),然后会清零重新计数向下计数时,当计数器的值CNT从ARR的值计数到0的时候会产生溢出中断,然后会重新填充ARR的值。
(4)8位的重复计数器RCR(高级定时器独有的,基本和基础定时器都没有)
向上计数时,我们把REO的值设置成10,如果有设置当计数器溢出的时候,那么此时不会产生中断,而是RCR的值+1,当加到10的时候才产生中断,向下 计数原理类似,只不过是从10减到0。
4 输入捕获

输入捕获可以对输入的信号的上升沿,下降沿或者双边沿进行捕获,常用的有测量输入信号的脉宽和测量 PWM 输入信号的频率和占空比这两种。
输入捕获的大概原理:
输入引脚检测到电平跳变时(上升沿或下降沿),把计数器CNT的值锁存到捕获寄存器CCRx中,把前后两次捕获到的 CCR x寄存器中的值相减,就可以算出脉宽或者频率。
(1)输入通道引脚
需要测量的信号从TIMx_CH1~4进入
(2)输入滤波器和边沿检测器
信号受到干扰时,滤波器可以对输入信号进行滤波,即进行重新采样。
滤波器的配置由 CR1 寄存器的位 CKD[1:0]和 CCMR1/2 的位 ICxF[3:0]控制。
边沿检测器用来设置信号在捕获的时候是什么边沿有效,可以是上升沿,下降沿,或者是双边沿,由 CCER 寄存器的位 CCxP 和 CCxNP 决定。

(3)捕获通道
捕获通道就是图中的 IC1/2/3/4,每个捕获/比较通道都有相对应的捕获寄存器 CCR1/2/3/4,当发生捕获的时候,计数器 CNT 的值就会被锁存到捕获寄存器中。

(4)预分频器
ICx 的输出信号会经过一个预分频器,用于决定发生多少个事件时进行一次捕获。具体的由寄存器 CCMRx 的位 ICxPSC 配置。
如果希望捕获信号的每一个边沿,则不分频。

(5)捕获寄存器
经过预分频器的信号 ICxPS 是最终被捕获的信号。
当发生捕获时(第一次),计数器CNT 的值会被锁存到捕获寄存器 CCR 中,还会产生 CCxI 中断,相应的中断位 CCxIF(在SR 寄存器中)会被置位,通过软件或者读取 CCR 中的值可以将 CCxIF 清 0。
如果发生第二次捕获(即重复捕获: CCR 寄存器中已捕获到计数器值且 CCxIF 标志已置 1),则捕获溢出标志位 CCxOF(在 SR 寄存器中)会被置位, CCxOF 只能通过软件清零。
5.输出比较

通过定时器的外部引脚对外输出控制信号。
有八种模式,主要使用的是其PWM1和PWM2模式。
具体使用哪种模式由寄存器 CCMRx 的位 OCxM[2:0]配置。

(1)比较寄存器
比较寄存器与捕获寄存器是同一个寄存器。
计数器 CNT 的值跟比较寄存器 CCR 的值相等的时候,输出参考信号 OCxREF 的信号的极性就会改变( OCxREF=1(高电平)称之为有效电平, OCxREF=0(低电平)称之为无效电平)。会产生比较中断 CCxI,相应的标志位 CCxIF(SR 寄存器中(状态寄存器,里面有各种状态或中断标记))会置位。
(2)死区发生器
在生成的参考波形 OCxREF 的基础上,可以插入死区时间,用于生成两路互补的输出信号 OCx 和 OCxN(OCxN的波形与OCx的相反)。

死区时间的大小具体由 BDTR 寄存器的位 DTG[7:0]配置。

这里的T_ DTS是由内部时钟T_CK_INT经过分频得到的,由CR1寄存器上的CKD[9:8]位控制的。

(3)输出控制
(4)输出引脚
输出比较的输出信号最终是通过定时器的外部 IO 来输出的,分别为 CH1/2/3/4,其中
前面三个通道还有互补的输出通道 CH1/2/3N。

2 STM32的通用定时器
通用定时器功能特点描述
STM32的通用定时器是由一个可编程预分频器(PSC)驱动的16位自动重装载计数器(CNT)构成,可用于测量输入脉冲长度(输入捕获)或者产生输出波形(输出比较和PWM)等。
STM32的通用TIMx(TIM2、TIM3、TIM4 和 TIM5)定时器功能特点包括:
位于低速的APB1总线上(注意:高级定时器是在高速的APB2总线上);
16位向上、向下、向上/向下(中心对齐)计数模式,自动装载计数器(TIMx_CNT);
16位可编程(可以实时修改)预分频器(TIMx_PSC),计数器时钟频率的分频系数 为 1~65535 之间的任意数值;
4 个独立通道(TIMx_CH1~4),这些通道可以用来作为:
输入捕获
输出比较
PWM生成(边缘或中间对齐模式)
单脉冲模式输出
可使用外部信号**(TIMx_ETR)控制定时器**和定时器互连(可以用 1 个定时器控制另外一个定时器)的同步电路。
如下事件发生时产生中断/DMA(6个独立的IRQ/DMA请求生成器):
1 更新:计数器向上溢出/向下溢出,计数器初始化(通过软件或者内部/外部触发)
2 触发事件(计数器启动、停止、初始化或者由内部/外部触发计数)
3 输入捕获
4 输出比较
5 支持针对定位的增量(正交)编码器和霍尔传感器电路
6 触发输入作为外部时钟或者按周期的电流管理
STM32 的通用定时器可以被用于:测量输入信号的脉冲长度(输入捕获)或者产生输出波形(输出比较和 PWM)等。
使用定时器预分频器和 RCC 时钟控制器预分频器,脉冲长度和波形周期可以在几个微秒到几个毫秒间调整。
STM32 的每个通用定时器都是完全独立的,没有互相共享的任何资源。
计数器模式
通用定时器可以向上计数、向下计数、向上向下双向计数模式。
向上计数模式:计数器从0计数到自动加载值(TIMx_ARR),然后重新从0开始计数并且产生一个计数器溢出事件。
向下计数模式:计数器从自动装入的值(TIMx_ARR)开始向下计数到0,然后从自动装入的值重新开始,并产生一个计数器向下溢出事件。
中央对齐模式(向上/向下计数):计数器从0开始计数到自动装入的值-1,产生一个计数器溢出事件,然后向下计数到1并且产生一个计数器溢出事件;然后再从0开始重新计数。
简单地理解三种计数模式,可以通过下面的图形:

通用定时器工作流程

对于这个定时器框图,分成四部分来讲:最顶上的一部分(计数时钟的选择)、中间部分(时基单元)、左下部分(输入捕获)、右下部分(PWM输出)。
计数时钟的选择
计数器时钟可由下列时钟源提供:
内部时钟(TIMx_CLK)
外部时钟模式1:外部捕捉比较引脚(TIx)
外部时钟模式2:外部引脚输入(TIMx_ETR)
内部触发输入(ITRx):使用一个定时器作为另一个定时器的预分频器,如可以配置一个定时器Timer1而作为另一个定时器Timer2的预分频器。
内部时钟源

从图中可以看出:由AHB时钟经过APB1预分频系数转至APB1时钟,再通过某个规定转至TIMxCLK时钟(即内部时钟CK_INT、CK_PSC)。最终经过PSC预分频系数转至CK_CNT。
那么APB1时钟怎么转至TIMxCLK时钟呢?除非APB1的分频系数是1,否则通用定时器的时钟等于APB1时钟的2倍。
例如:默认调用SystemInit函数情况下:SYSCLK=72M、AHB时钟=72M、APB1时钟=36M,所以APB1的分频系数=AHB/APB1时钟=2。所以,通用定时器时钟CK_INT=2*36M=72M。最终经过PSC预分频系数转至CK_CNT。
由于高级定时器与通用定时器的时钟不同,上面重点介绍时钟源,对于时基单元,计数模式,定时器相关的寄存器,以及控制寄存器都大体相同,大家可以参考高级定时器的寄存器介绍。
通用定时器超时时间
超出(溢出)时间计算:
Tout=(ARR+1)(PSC+1)/TIMxCLK
其中:Tout的单位为us,TIMxCLK的单位为MHz。
这里需要注意的是:PSC预分频系数需要加1,同时自动重加载值也需要加1。
为什么自动重加载值需要加1,因为从ARR到0之间的数字是ARR+1个;
为什么预分频系数需要加1,因为为了避免预分频系数不设置的时候取0的情况,使之从1开始。
这里需要和之前的预分频进行区分:由于通用定时器的预分频系数为1~65535之间的任意数值,为了从1开始,所以当预分频系数寄存器为0的时候,代表的预分频系数为1。而之前的那些预分频系数都是固定的几个值,比如1、4、8、16、32、64等等,而且可能0x000代表1,0x001代表4,0x010代表8等等。也就是说,一边是随意的定义(要从1开始),另一边是宏定义了某些值(只有特定的一些值)。
比如,想要设置超出时间为500ms,并配置中断,TIMxCLK按照系统默认初始化来(即72MHz),PSC取7199,由此可以计算出ARR为4999。
也就是说,在内部时钟TIMxCLK为72MHz,预分频系数为7199的时候,从4999递减至0的事件是500ms。
以上就是通用定时器的基本原理介绍,如有疑问可以参考STM32F4中文参考手册。
3 STM32基本定时器
基本定时器定时器(TIM6 和 TIM7)不仅可用作通用定时器以生成时基,还可以专门用于驱动数模转换器 (DAC)。实际上,此类定时器内部连接到 DAC 并能够通过其触发输出驱动 DAC。
这些定时器彼此完全独立,不共享任何资源。
TIM6 和 TIM7 的主要特性
基本定时器(TIM6 和 TIM7)的特性包括:
● 16 位自动重载递增计数器。
● 16 位可编程预分频器,用于对计数器时钟频率进行分频(即运行时修改),分频系数介于 1 和 65536 之间。
● 用于触发 DAC 的同步电路。
● 发生如下更新事件时会生成中断/DMA 请求:计数器上溢。。

以上就是对定时器工作原理的基本介绍,如果还有疑问可以查询STM32F4XX中文参考手册,里面有详细的介绍和讲解。
定时器的应用
定时器的应用的应用场景有很多种,如下所示:
1 更新:计数器向上溢出/向下溢出,计数器初始化(通过软件或者内部/外部触发)
2 触发事件(计数器启动、停止、初始化或者由内部/外部触发计数)
3 输入捕获
4 输出比较
5 支持针对定位的增量(正 交)编码器和霍尔传感器电路
6 触发输入作为外部时钟或者按周期的电流管理
下面我重点给大家展示以下三个实验
1 定时器中断实验
2 PWM输出实验
3 输入捕获实验
定时器中断实验:
接下来我们以通用定时器 TIM3 为实例,来说明要经过哪些步骤,才能达到这个要求,并产生中断。
1 )TIM3 时钟使能。
这里我们通过 APB1ENR 的第 1 位来设置 TIM3 的时钟,因为 Stm32_Clock_Init 函数里面把APB1的分频设置为2了,所以我们的TIM3时钟就是APB1时钟的2倍,等于系统时钟(72M)。
2 )设置 TIM3_ARR 和 和 TIM3_PSC 的值。
通过这两个寄存器,我们来设置自动重装的值,以及分频系数。这两个参数加上时钟频率就决定了定时器的溢出时间。
3 )设置 TIM3_DIER 允许更 新中断。
因为我们要使用 TIM3 的更新中断,所以设置 DIER 的 UIE 位为 1,使能更新中断。
4 )允许 TIM3 工作。
光配置好定时器还不行,没有开启定时器,照样不能用。我们在配置完后要开启定时器,通过 TIM3_CR1 的 CEN 位来设置。
5 )TIM3 中断分组设置。
在定时器配置完了之后,因为要产生中断,必不可少的要设置 NVIC 相关寄存器,以使能TIM3 中断。
6 )编写中断服务函数。
在最后,还是要编写定时器中断服务函数,通过该函数来处理定时器产生的相关中断。在中断产生后,通过状态寄存器的值来判断此次产生的中断属于什么类型。然后执行相关的操作,我们这里使用的是更新(溢出)中断,所以在状态寄存器 SR 的最低位。在处理完中断之后应该向 TIM3_SR 的最低位写 0,来清除该中断标志。
定时器中断实现步骤:
1 使能定时器时钟。
RCC_APB1PeriphClockCmd();
② 初始化定时器,配置ARR,PSC。
TIM_TimeBaseInit();
③ 开启定时器中断,配置NVIC。
NVIC_Init();
④ 使能定时器。
TIM_Cmd();
⑥ 编写中断服务函数。
TIMx_IRQHandler();
void TIME3_Init(u16 arr,u16 psc)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);
TIM_TimeBaseStructure.TIM_ClockDivision=TIM_CKD_DIV1;//设置时钟分割:TDTS = Tck_tim
TIM_TimeBaseStructure.TIM_CounterMode=TIM_CounterMode_Up; //TIM向上计数模式
TIM_TimeBaseStructure.TIM_Period=arr;//设置在下一个更新事件装入活动的自动重装载寄存器周期的值
TIM_TimeBaseStructure.TIM_Prescaler=psc;//设置用来作为TIMx时钟频率除数的预分频值
TIM_TimeBaseInit(TIM3,&TIM_TimeBaseStructure);
TIM_ITConfig(TIM3,TIM_IT_Update,ENABLE ); //使能指定的TIM3中断,允许更新中断
NVIC_InitStructure.NVIC_IRQChannel=TIM3_IRQn;//TIM3中断
NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;//IRQ通道被使能
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=2; //先占优先级2级
NVIC_InitStructure.NVIC_IRQChannelSubPriority=0;//从优先级0级
NVIC_Init(&NVIC_InitStructure);
TIM_Cmd(TIM3,ENABLE);
}
void TIM3_IRQHandler(void)
{
if (TIM_GetITStatus(TIM3, TIM_IT_Update) != RESET) //检查TIM3更新中断发生与否
{
LED1=!LED1;
TIM_ClearITPendingBit(TIM3, TIM_IT_Update ); //清除TIMx更新中断标志
}
}
PWM输出实验
本节要实现通过 TIM3_CH1 输出PWM 来控制 DS0 的亮度。下面我们介绍配置步骤:
1) )启 开启 TIM1 时钟,配置 PB5 为复用输出。要使用 TIM1,我们必须先开启 TIM1 的时钟(通过 APB2ENR 设置),这点相信大家看了这么多代码,应该明白了。这里我们还要配置 PB5 为复用输出,这是因为 TIM3_CH1 通道将使用 PB5 的复用功能作为输出。
2 ))设置 TIM3 的 的 ARR 和 和 PSC 。
在开启了 TIM3的时钟之后,我们要设置 ARR 和 PSC 两个寄存器的值来控制输出 PWM 的周期。当 PWM 周期太慢(低于 50Hz)的时候,我们就会明显感觉到闪烁了。因此,PWM 周期在这里不宜设置的太小。
3) )置设置 TIM3_CH1 的 的 PWM 模式 及通道方向。
接下来,我们要设置 TIM3_CH1 为 PWM 模式(默认是冻结的),因为我们的 DS0 是低电平亮,而我们希望当 CCR1 的值小的时候,DS0 就暗,CCR1 值大的时候,DS0 就亮,所以我们要通过配置 TIM1_CCMR1 的相关位来控制 TIM3_CH1 的模式。另外,我们要配置 CH1 为输出,所以要设置 CC1S[1:0]为 00(寄存器默认就是 0,所以这里可以省略)。
4) )能 使能 TIM3 的 的 CH1 输出,使能 TIM3。
接下来,我们需要开启 TIM3 的通道 1 的输出以及 TIM3的时钟。前者通过 TIM3_CCER来设置,是单个通道的开关,而后者则通过 TIM3_CR1 来设置,是整个 TIM13的总开关。只有设置了这两个寄存器,这样我们才可能在 TIM1 的通道 1 上看到 PWM 波输出。
5) ) 设置 MOE 输出,使能 PWM 输出。 。
普通定时器在完成以上设置了之后,就可以输出 PWM 了,但是高级定时器,我们还需要使能刹车和死区寄存器(TIM1_BDTR)的 MOE 位,以使能整个 OCx(即 PWM)输出。
6) )改 修改 TIM3_CCR1 来控制占空比。
最后,在经过以上设置之后,PWM 其实已经开始输出了,只是其占空比和频率都是固定的,而我们通过修改 TIM3_CCR1 则可以控制 CH1 的输出占空比。继而控制 DS0 的亮度。
通过以上 6 个步骤,我们就可以控制 TIM3 的 CH1 输出 PWM 波了。
简化上面的过程就是:
1 使能定时器14和相关IO口时钟。
使能定时器14时钟:RCC_APB1PeriphClockCmd();
使能GPIOF时钟:RCC_AHB1PeriphClockCmd ();
2 初始化IO口为复用功能输出。函数:GPIO_Init();
3 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; //复用功能
GPIOF9复用映射到定时器14
GPIO_PinAFConfig(GPIOF,GPIO_PinSource9,GPIO_AF_TIM14);
4 初始化定时器:ARR,PSC等:TIM_TimeBaseInit();
5 初始化输出比较参数:TIM_OC1Init();
6 使能预装载寄存器: TIM_OC1PreloadConfig(TIM14, TIM_OCPreload_Enable);
7 使能自动重装载的预装载寄存器允许位TIM_ARRPreloadConfig(TIM14,ENABLE);
8 使能定时器。
9 不断改变比较值CCRx,达到不同的占空比效果:TIM_SetCompare1();
void TIME3_PWM_Init(u16 arr,u16 psc)
{
GPIO_InitTypeDef GPIO_InitStructure;
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_OCInitTypeDef TIM_OCInitStructure;//结构体声明
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); //使能定时器3时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB | RCC_APB2Periph_AFIO, ENABLE); //使能GPIO外设和AFIO复用功能模块时钟
GPIO_PinRemapConfig(GPIO_PartialRemap_TIM3, ENABLE); //Timer3部分重映射 TIM3_CH2-》PB5
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_5;
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOB,&GPIO_InitStructure);
TIM_TimeBaseStructure.TIM_ClockDivision=0;
TIM_TimeBaseStructure.TIM_CounterMode=TIM_CounterMode_Up;
TIM_TimeBaseStructure.TIM_Period=arr;
TIM_TimeBaseStructure.TIM_Prescaler=psc;
TIM_TimeBaseInit(TIM3,&TIM_TimeBaseStructure);
TIM_OCInitStructure.TIM_OCMode=TIM_OCMode_PWM2;//PWM模式1或者模式2
TIM_OCInitStructure.TIM_OCPolarity=TIM_OCPolarity_High;//比较输出极性
TIM_OCInitStructure.TIM_OutputState=TIM_OutputState_Enable;//输出使能 OR失能
// TIM_OCInitStructure.TIM_Pulse=100;//比较值,写CCRx
TIM_OC2Init(TIM3, &TIM_OCInitStructure); //根据T指定的参数初始化外设TIM3 OC2
TIM_OC2PreloadConfig(TIM3, TIM_OCPreload_Enable); //使能TIM3在CCR2上的预装载寄存器
TIM_Cmd(TIM3, ENABLE); //使能TIM3
}
int main(void)
{
delay_init(); //延时函数初始化
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
uart_init(115200);
LED_Init(); //初始化与LED连接的硬件接口
BEEP_Init();
TIME3_Init(4999,7199);
TIME3_PWM_Init(899,0);
KEY_Init();
LCD_Init();
while(1)
{
delay_ms(10);
if(dir)
led0pwmval++;
else
led0pwmval--;
if(led0pwmval》500)
dir=0;
if(led0pwmval==0)
dir=1;
TIM_SetCompare2(TIM3,led0pwmval);
}
}
输入捕获实验
下面我们介绍输入捕获的配置步骤:
1 ))开启 TIM2 时钟,配置 PA0 为下拉输入。
要使用 TIM2,我们必须先开启 TIM2 的时钟(通过 APB1ENR 设置)。这里我们还要配置 PA0为下拉输入,因为我们要捕获 TIM2_CH1 上面的高电平脉宽,而 TIM2_CH1 是连接在 PA0 上
面的。
2 ))设置 TIM2 的 的 ARR 和 和 PSC 。
在开启了 TIM2 的时钟之后,我们要设置 ARR 和 PSC 两个寄存器的值来设置输入捕获的自动重装载值和计数频率。
3 ))设置 TIM2 的 的 CCMR1
TIM2_CCMR1 寄存器控制着输入捕获 1 和 2 的模式,包括映射关系,滤波和分频等。这里我们需要设置通道 1 为输入模式,且 IC1 映射到 TI1(通道 1)上面,并且不使用滤波(提高响应速度)器。
4) ) 设置 TIM2 的 的 CCER ,开启输入捕获,并设置为上升沿捕获。
TIM2_CCER 寄存器是定时器的开关,并且可以设置输入捕获的边沿。只有 TIM2_CCER寄存器使能了输入捕获,我们的外部信号,才能被 TIM2 捕获到,否则一切白搭。同时要设置好捕获边沿,才能得到正确的结果。
5) ) 设 置 TIM2 的 的 DIER ,使能捕获和更新中断,并编写中断服务函数
因为我们要捕获的是高电平信号的脉宽,所以,第一次捕获是上升沿,第二次捕获时下降沿,必须在捕获上升沿之后,设置捕获边沿为下降沿,同时,如果脉宽比较长,那么定时器就会溢出,对溢出必须做处理,否则结果就不准了。这两件事,我们都在中断里面做,所以必须开启捕获中断和更新中断。
设置了中断必须编写中断函数,否则可能导致死机。我们需要在中断函数里面完成数据处理和捕获设置等关键操作,从而实现高电平脉宽统计。
6 )设置 TIM2 的 的 CR1 ,使能定时器
最后,必须打开定时器的计数器开关,通过设置 TIM2_CR1 的最低位为 1,启动 TIM2 的计数器,开始输入捕获。
通过以上 6 步设置,定时器 2 的通道 1 就可以开始输入捕获了,同时因为还用到了串口输出结果,所以还需要配置一下串口。
输入捕获的一般配置步骤:
① 初始化定时器和通道对应IO的时钟。
② 初始化IO口,模式为复用:GPIO_Init();
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
③设置引脚复用映射:
GPIO_PinAFConfig();
④初始化定时器ARR,PSC
TIM_TimeBaseInit();
⑤初始化输入捕获通道
TIM_ICInit();
⑥如果要开启捕获中断,
TIM_ITConfig();
NVIC_Init();
⑦使能定时器:TIM_Cmd();
⑧编写中断服务函数:TIMx_IRQHandler();
代码如下:
void TIME2_CAP_Init(u16 arr,u16 psc)
{
GPIO_InitTypeDef GPIO_InitStructure;
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_ICInitTypeDef TIM2_ICInitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); //使能TIM5时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //使能PA端口时钟
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IN_FLOATING;
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_1;
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure); //设置为浮空输入
TIM_TimeBaseStructure.TIM_ClockDivision=TIM_CKD_DIV1;
TIM_TimeBaseStructure.TIM_CounterMode=TIM_CounterMode_Up;
TIM_TimeBaseStructure.TIM_Period=arr;
TIM_TimeBaseStructure.TIM_Prescaler=psc;
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure); //根据TIM_TimeBaseInitStruct中指定的参数初始化TIMx的时间基数单位
TIM2_ICInitStructure.TIM_Channel=TIM_Channel_2;
TIM2_ICInitStructure.TIM_ICFilter=0x03;
TIM2_ICInitStructure.TIM_ICPolarity=TIM_ICPolarity_Rising;
TIM2_ICInitStructure.TIM_ICPrescaler=TIM_ICPSC_DIV1;
TIM2_ICInitStructure.TIM_ICSelection=TIM_ICSelection_DirectTI;
TIM_ICInit(TIM2,&TIM2_ICInitStructure);
TIM_Cmd(TIM2,ENABLE );
}
int main(void)
{
delay_init(); //延时函数初始化
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
uart_init(115200);
LED_Init(); //初始化与LED连接的硬件接口
BEEP_Init();
TIME3_Init(4999,7199);
TIME3_PWM_Init(899,0);
TIME2_Cap_Init(0XFFFF,72-1); //以1Mhz的频率计数
KEY_Init();
while(1)
{
delay_ms(10);
TIM_SetCompare2(TIM3,TIM_GetCapture2(TIM3)+1);
if(TIM_GetCapture2(TIM3)==300)
TIM_SetCompare2(TIM3,0);
if(TIM5CH1_CAPTURE_STA&0X80)//成功捕获到了一次上升沿
{
temp=TIM5CH1_CAPTURE_STA&0X3F;
temp*=65536;//溢出时间总和
temp+=TIM5CH1_CAPTURE_VAL;//得到总的高电平时间
printf(“HIGH:%d usrn”,temp);//打印总的高点平时间
TIM5CH1_CAPTURE_STA=0;//开启下一次捕获
}
总结
以上就是我对定时器的理解,定时器的内容很多,很杂,很复杂,需要大家自己对一些寄存器进行深入的理解,最重要的是自己要上手写代码,复制粘贴是学习STM32的大忌,希望大家可以通过自己的刻苦努力,来实现自己能力的提升。
STM32—定时器知多少
定时器的分类
定时器的分类:
定时器的类型可以查看STM32F1XX中文参考手册的第14,15,16章节,里面对定时器的知识有详细的介绍。
STM32F1系列的开发板一共有8个定时器,按照功能的不同可以分为:
高级定时器(TIM1、TIM8)
通用定时器(TIM2、TIM3、TIM4、TIM5)
基本定时器(TIM6、TIM7)
看门狗定时器
SysTick定时器
他们之间的区别情况见下表:

定时器的工作原理
1 高级定时器简介
TIM1 和 TIM8 简介
高级控制定时器(TIM1 和 TIM8)包含一个 16 位自动重载计数器,该计数器由可编程预分频器驱动。
此类定时器可用于各种用途,包括测量输入信号的脉冲宽度(输入捕获),或者生成输出波形(输出比较、PWM 和带死区插入的互补 PWM)。
使用定时器预分频器和 RCC 时钟控制器预分频器,可将脉冲宽度和波形周期从几微秒调制到几毫秒。
高级控制定时器(TIM1 和 TIM8)和通用 (TIMx) 定时器彼此完全独立,不共享任何资源。
14.2 TIM1 和 TIM8 主要特性
TIM1 和 TIM8 定时器具有以下特性:
● 16 位递增、递减、递增/递减自动重载计数器。
● 16 位可编程预分频器,用于对计数器时钟频率进行分频(即运行时修改),分频系数介于 1 到 65536 之间。
● 多达 4 个独立通道,可用于:
— 输入捕获
— 输出比较
— PWM 生成(边沿和中心对齐模式)
— 单脉冲模式输出
● 带可编程死区的互补输出。
● 使用外部信号控制定时器且可实现多个定时器互连的同步电路。
● 重复计数器,用于仅在给定数目的计数器周期后更新定时器寄存器。
● 用于将定时器的输出信号置于复位状态或已知状态的断路输入。
● 发生如下事件时生成中断/DMA 请求:
— 更新:计数器上溢/下溢、计数器初始化(通过软件或内部/外部触发)
— 触发事件(计数器启动、停止、初始化或通过内部/外部触发计数)
— 输入捕获
— 输出比较
— 断路输入
● 支持定位用增量(正交)编码器和霍尔传感器电路。
● 外部时钟触发输入或逐周期电流管理。
STM32F103中有TIM1,TIM8两个高级定时器,每一定时器都有
一个16位向上、向下、向上/下自动装载计数器
一个16位预分频器和四个独立从输入输出通道
每一个通道都可用于输入捕获、输出比较、PWM和单脉冲模式(除了基本定时器,高级定时器和通用定时器都能产生PWM)
单片机中没有时间的概念,因此定时器本质是一个以单位时间为准的计数器,计数值可以从0开始累加,也可以从一个设定值(ARR)的值递减,每隔一个固定的时间(由psc和时钟周期控制)计数器的值+1或-1,加到或减到头时会产生一个溢出信号,此时计数器的计数值清零或补充成初值,重新开始计数。
高级定时器框图

1 时钟源
由四个时钟来源,分别是
内部时钟源 CK_INT
外部时钟模式 1:外部输入引脚 TIx(x=1,2,3,4)
外部时钟模式 2:外部触发输入 ETR
内部触发输入(ITRx)
我们一般只用内部时钟源CK_INT,对于高级定时器,其时钟是由APB2总线时钟决定的

其他类型的定时器同理可以参考参考手册的RCC部分的时钟框图。
2 控制器
用来控制和发送命令的相关的寄存器有
CR1(control register 1 )控制寄存器1
CR2(control register 2 )控制寄存器2
SMCR(slave mode control register)从模式控制寄存器
CCER(capture/compare enable register)捕获比较寄存器
3 时基单元Time-base unit

时基单元的组成:
(1)1-16位的预分频器 PSC
对控制计数器的时钟进行分频,分配系数可以为1~65536。
(2)16位的计数器CNT
每经过一个计数器时钟周期这里变化1(+1或-1)。
(3)4-16位的自动重装载寄存器ARR
向上计数时,但计数器的值CNT从0计数到ARR的值的时候,会产生溢出中断(也叫更新中断,update interrupt),然后会清零重新计数向下计数时,当计数器的值CNT从ARR的值计数到0的时候会产生溢出中断,然后会重新填充ARR的值。
(4)8位的重复计数器RCR(高级定时器独有的,基本和基础定时器都没有)
向上计数时,我们把REO的值设置成10,如果有设置当计数器溢出的时候,那么此时不会产生中断,而是RCR的值+1,当加到10的时候才产生中断,向下 计数原理类似,只不过是从10减到0。
4 输入捕获

输入捕获可以对输入的信号的上升沿,下降沿或者双边沿进行捕获,常用的有测量输入信号的脉宽和测量 PWM 输入信号的频率和占空比这两种。
输入捕获的大概原理:
输入引脚检测到电平跳变时(上升沿或下降沿),把计数器CNT的值锁存到捕获寄存器CCRx中,把前后两次捕获到的 CCR x寄存器中的值相减,就可以算出脉宽或者频率。
(1)输入通道引脚
需要测量的信号从TIMx_CH1~4进入
(2)输入滤波器和边沿检测器
信号受到干扰时,滤波器可以对输入信号进行滤波,即进行重新采样。
滤波器的配置由 CR1 寄存器的位 CKD[1:0]和 CCMR1/2 的位 ICxF[3:0]控制。
边沿检测器用来设置信号在捕获的时候是什么边沿有效,可以是上升沿,下降沿,或者是双边沿,由 CCER 寄存器的位 CCxP 和 CCxNP 决定。

(3)捕获通道
捕获通道就是图中的 IC1/2/3/4,每个捕获/比较通道都有相对应的捕获寄存器 CCR1/2/3/4,当发生捕获的时候,计数器 CNT 的值就会被锁存到捕获寄存器中。

(4)预分频器
ICx 的输出信号会经过一个预分频器,用于决定发生多少个事件时进行一次捕获。具体的由寄存器 CCMRx 的位 ICxPSC 配置。
如果希望捕获信号的每一个边沿,则不分频。

(5)捕获寄存器
经过预分频器的信号 ICxPS 是最终被捕获的信号。
当发生捕获时(第一次),计数器CNT 的值会被锁存到捕获寄存器 CCR 中,还会产生 CCxI 中断,相应的中断位 CCxIF(在SR 寄存器中)会被置位,通过软件或者读取 CCR 中的值可以将 CCxIF 清 0。
如果发生第二次捕获(即重复捕获: CCR 寄存器中已捕获到计数器值且 CCxIF 标志已置 1),则捕获溢出标志位 CCxOF(在 SR 寄存器中)会被置位, CCxOF 只能通过软件清零。
5.输出比较

通过定时器的外部引脚对外输出控制信号。
有八种模式,主要使用的是其PWM1和PWM2模式。
具体使用哪种模式由寄存器 CCMRx 的位 OCxM[2:0]配置。

(1)比较寄存器
比较寄存器与捕获寄存器是同一个寄存器。
计数器 CNT 的值跟比较寄存器 CCR 的值相等的时候,输出参考信号 OCxREF 的信号的极性就会改变( OCxREF=1(高电平)称之为有效电平, OCxREF=0(低电平)称之为无效电平)。会产生比较中断 CCxI,相应的标志位 CCxIF(SR 寄存器中(状态寄存器,里面有各种状态或中断标记))会置位。
(2)死区发生器
在生成的参考波形 OCxREF 的基础上,可以插入死区时间,用于生成两路互补的输出信号 OCx 和 OCxN(OCxN的波形与OCx的相反)。

死区时间的大小具体由 BDTR 寄存器的位 DTG[7:0]配置。

这里的T_ DTS是由内部时钟T_CK_INT经过分频得到的,由CR1寄存器上的CKD[9:8]位控制的。

(3)输出控制
(4)输出引脚
输出比较的输出信号最终是通过定时器的外部 IO 来输出的,分别为 CH1/2/3/4,其中
前面三个通道还有互补的输出通道 CH1/2/3N。

2 STM32的通用定时器
通用定时器功能特点描述
STM32的通用定时器是由一个可编程预分频器(PSC)驱动的16位自动重装载计数器(CNT)构成,可用于测量输入脉冲长度(输入捕获)或者产生输出波形(输出比较和PWM)等。
STM32的通用TIMx(TIM2、TIM3、TIM4 和 TIM5)定时器功能特点包括:
位于低速的APB1总线上(注意:高级定时器是在高速的APB2总线上);
16位向上、向下、向上/向下(中心对齐)计数模式,自动装载计数器(TIMx_CNT);
16位可编程(可以实时修改)预分频器(TIMx_PSC),计数器时钟频率的分频系数 为 1~65535 之间的任意数值;
4 个独立通道(TIMx_CH1~4),这些通道可以用来作为:
输入捕获
输出比较
PWM生成(边缘或中间对齐模式)
单脉冲模式输出
可使用外部信号**(TIMx_ETR)控制定时器**和定时器互连(可以用 1 个定时器控制另外一个定时器)的同步电路。
如下事件发生时产生中断/DMA(6个独立的IRQ/DMA请求生成器):
1 更新:计数器向上溢出/向下溢出,计数器初始化(通过软件或者内部/外部触发)
2 触发事件(计数器启动、停止、初始化或者由内部/外部触发计数)
3 输入捕获
4 输出比较
5 支持针对定位的增量(正交)编码器和霍尔传感器电路
6 触发输入作为外部时钟或者按周期的电流管理
STM32 的通用定时器可以被用于:测量输入信号的脉冲长度(输入捕获)或者产生输出波形(输出比较和 PWM)等。
使用定时器预分频器和 RCC 时钟控制器预分频器,脉冲长度和波形周期可以在几个微秒到几个毫秒间调整。
STM32 的每个通用定时器都是完全独立的,没有互相共享的任何资源。
计数器模式
通用定时器可以向上计数、向下计数、向上向下双向计数模式。
向上计数模式:计数器从0计数到自动加载值(TIMx_ARR),然后重新从0开始计数并且产生一个计数器溢出事件。
向下计数模式:计数器从自动装入的值(TIMx_ARR)开始向下计数到0,然后从自动装入的值重新开始,并产生一个计数器向下溢出事件。
中央对齐模式(向上/向下计数):计数器从0开始计数到自动装入的值-1,产生一个计数器溢出事件,然后向下计数到1并且产生一个计数器溢出事件;然后再从0开始重新计数。
简单地理解三种计数模式,可以通过下面的图形:

通用定时器工作流程

对于这个定时器框图,分成四部分来讲:最顶上的一部分(计数时钟的选择)、中间部分(时基单元)、左下部分(输入捕获)、右下部分(PWM输出)。
计数时钟的选择
计数器时钟可由下列时钟源提供:
内部时钟(TIMx_CLK)
外部时钟模式1:外部捕捉比较引脚(TIx)
外部时钟模式2:外部引脚输入(TIMx_ETR)
内部触发输入(ITRx):使用一个定时器作为另一个定时器的预分频器,如可以配置一个定时器Timer1而作为另一个定时器Timer2的预分频器。
内部时钟源

从图中可以看出:由AHB时钟经过APB1预分频系数转至APB1时钟,再通过某个规定转至TIMxCLK时钟(即内部时钟CK_INT、CK_PSC)。最终经过PSC预分频系数转至CK_CNT。
那么APB1时钟怎么转至TIMxCLK时钟呢?除非APB1的分频系数是1,否则通用定时器的时钟等于APB1时钟的2倍。
例如:默认调用SystemInit函数情况下:SYSCLK=72M、AHB时钟=72M、APB1时钟=36M,所以APB1的分频系数=AHB/APB1时钟=2。所以,通用定时器时钟CK_INT=2*36M=72M。最终经过PSC预分频系数转至CK_CNT。
由于高级定时器与通用定时器的时钟不同,上面重点介绍时钟源,对于时基单元,计数模式,定时器相关的寄存器,以及控制寄存器都大体相同,大家可以参考高级定时器的寄存器介绍。
通用定时器超时时间
超出(溢出)时间计算:
Tout=(ARR+1)(PSC+1)/TIMxCLK
其中:Tout的单位为us,TIMxCLK的单位为MHz。
这里需要注意的是:PSC预分频系数需要加1,同时自动重加载值也需要加1。
为什么自动重加载值需要加1,因为从ARR到0之间的数字是ARR+1个;
为什么预分频系数需要加1,因为为了避免预分频系数不设置的时候取0的情况,使之从1开始。
这里需要和之前的预分频进行区分:由于通用定时器的预分频系数为1~65535之间的任意数值,为了从1开始,所以当预分频系数寄存器为0的时候,代表的预分频系数为1。而之前的那些预分频系数都是固定的几个值,比如1、4、8、16、32、64等等,而且可能0x000代表1,0x001代表4,0x010代表8等等。也就是说,一边是随意的定义(要从1开始),另一边是宏定义了某些值(只有特定的一些值)。
比如,想要设置超出时间为500ms,并配置中断,TIMxCLK按照系统默认初始化来(即72MHz),PSC取7199,由此可以计算出ARR为4999。
也就是说,在内部时钟TIMxCLK为72MHz,预分频系数为7199的时候,从4999递减至0的事件是500ms。
以上就是通用定时器的基本原理介绍,如有疑问可以参考STM32F4中文参考手册。
3 STM32基本定时器
基本定时器定时器(TIM6 和 TIM7)不仅可用作通用定时器以生成时基,还可以专门用于驱动数模转换器 (DAC)。实际上,此类定时器内部连接到 DAC 并能够通过其触发输出驱动 DAC。
这些定时器彼此完全独立,不共享任何资源。
TIM6 和 TIM7 的主要特性
基本定时器(TIM6 和 TIM7)的特性包括:
● 16 位自动重载递增计数器。
● 16 位可编程预分频器,用于对计数器时钟频率进行分频(即运行时修改),分频系数介于 1 和 65536 之间。
● 用于触发 DAC 的同步电路。
● 发生如下更新事件时会生成中断/DMA 请求:计数器上溢。。

以上就是对定时器工作原理的基本介绍,如果还有疑问可以查询STM32F4XX中文参考手册,里面有详细的介绍和讲解。
定时器的应用
定时器的应用的应用场景有很多种,如下所示:
1 更新:计数器向上溢出/向下溢出,计数器初始化(通过软件或者内部/外部触发)
2 触发事件(计数器启动、停止、初始化或者由内部/外部触发计数)
3 输入捕获
4 输出比较
5 支持针对定位的增量(正 交)编码器和霍尔传感器电路
6 触发输入作为外部时钟或者按周期的电流管理
下面我重点给大家展示以下三个实验
1 定时器中断实验
2 PWM输出实验
3 输入捕获实验
定时器中断实验:
接下来我们以通用定时器 TIM3 为实例,来说明要经过哪些步骤,才能达到这个要求,并产生中断。
1 )TIM3 时钟使能。
这里我们通过 APB1ENR 的第 1 位来设置 TIM3 的时钟,因为 Stm32_Clock_Init 函数里面把APB1的分频设置为2了,所以我们的TIM3时钟就是APB1时钟的2倍,等于系统时钟(72M)。
2 )设置 TIM3_ARR 和 和 TIM3_PSC 的值。
通过这两个寄存器,我们来设置自动重装的值,以及分频系数。这两个参数加上时钟频率就决定了定时器的溢出时间。
3 )设置 TIM3_DIER 允许更 新中断。
因为我们要使用 TIM3 的更新中断,所以设置 DIER 的 UIE 位为 1,使能更新中断。
4 )允许 TIM3 工作。
光配置好定时器还不行,没有开启定时器,照样不能用。我们在配置完后要开启定时器,通过 TIM3_CR1 的 CEN 位来设置。
5 )TIM3 中断分组设置。
在定时器配置完了之后,因为要产生中断,必不可少的要设置 NVIC 相关寄存器,以使能TIM3 中断。
6 )编写中断服务函数。
在最后,还是要编写定时器中断服务函数,通过该函数来处理定时器产生的相关中断。在中断产生后,通过状态寄存器的值来判断此次产生的中断属于什么类型。然后执行相关的操作,我们这里使用的是更新(溢出)中断,所以在状态寄存器 SR 的最低位。在处理完中断之后应该向 TIM3_SR 的最低位写 0,来清除该中断标志。
定时器中断实现步骤:
1 使能定时器时钟。
RCC_APB1PeriphClockCmd();
② 初始化定时器,配置ARR,PSC。
TIM_TimeBaseInit();
③ 开启定时器中断,配置NVIC。
NVIC_Init();
④ 使能定时器。
TIM_Cmd();
⑥ 编写中断服务函数。
TIMx_IRQHandler();
void TIME3_Init(u16 arr,u16 psc)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);
TIM_TimeBaseStructure.TIM_ClockDivision=TIM_CKD_DIV1;//设置时钟分割:TDTS = Tck_tim
TIM_TimeBaseStructure.TIM_CounterMode=TIM_CounterMode_Up; //TIM向上计数模式
TIM_TimeBaseStructure.TIM_Period=arr;//设置在下一个更新事件装入活动的自动重装载寄存器周期的值
TIM_TimeBaseStructure.TIM_Prescaler=psc;//设置用来作为TIMx时钟频率除数的预分频值
TIM_TimeBaseInit(TIM3,&TIM_TimeBaseStructure);
TIM_ITConfig(TIM3,TIM_IT_Update,ENABLE ); //使能指定的TIM3中断,允许更新中断
NVIC_InitStructure.NVIC_IRQChannel=TIM3_IRQn;//TIM3中断
NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;//IRQ通道被使能
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=2; //先占优先级2级
NVIC_InitStructure.NVIC_IRQChannelSubPriority=0;//从优先级0级
NVIC_Init(&NVIC_InitStructure);
TIM_Cmd(TIM3,ENABLE);
}
void TIM3_IRQHandler(void)
{
if (TIM_GetITStatus(TIM3, TIM_IT_Update) != RESET) //检查TIM3更新中断发生与否
{
LED1=!LED1;
TIM_ClearITPendingBit(TIM3, TIM_IT_Update ); //清除TIMx更新中断标志
}
}
PWM输出实验
本节要实现通过 TIM3_CH1 输出PWM 来控制 DS0 的亮度。下面我们介绍配置步骤:
1) )启 开启 TIM1 时钟,配置 PB5 为复用输出。要使用 TIM1,我们必须先开启 TIM1 的时钟(通过 APB2ENR 设置),这点相信大家看了这么多代码,应该明白了。这里我们还要配置 PB5 为复用输出,这是因为 TIM3_CH1 通道将使用 PB5 的复用功能作为输出。
2 ))设置 TIM3 的 的 ARR 和 和 PSC 。
在开启了 TIM3的时钟之后,我们要设置 ARR 和 PSC 两个寄存器的值来控制输出 PWM 的周期。当 PWM 周期太慢(低于 50Hz)的时候,我们就会明显感觉到闪烁了。因此,PWM 周期在这里不宜设置的太小。
3) )置设置 TIM3_CH1 的 的 PWM 模式 及通道方向。
接下来,我们要设置 TIM3_CH1 为 PWM 模式(默认是冻结的),因为我们的 DS0 是低电平亮,而我们希望当 CCR1 的值小的时候,DS0 就暗,CCR1 值大的时候,DS0 就亮,所以我们要通过配置 TIM1_CCMR1 的相关位来控制 TIM3_CH1 的模式。另外,我们要配置 CH1 为输出,所以要设置 CC1S[1:0]为 00(寄存器默认就是 0,所以这里可以省略)。
4) )能 使能 TIM3 的 的 CH1 输出,使能 TIM3。
接下来,我们需要开启 TIM3 的通道 1 的输出以及 TIM3的时钟。前者通过 TIM3_CCER来设置,是单个通道的开关,而后者则通过 TIM3_CR1 来设置,是整个 TIM13的总开关。只有设置了这两个寄存器,这样我们才可能在 TIM1 的通道 1 上看到 PWM 波输出。
5) ) 设置 MOE 输出,使能 PWM 输出。 。
普通定时器在完成以上设置了之后,就可以输出 PWM 了,但是高级定时器,我们还需要使能刹车和死区寄存器(TIM1_BDTR)的 MOE 位,以使能整个 OCx(即 PWM)输出。
6) )改 修改 TIM3_CCR1 来控制占空比。
最后,在经过以上设置之后,PWM 其实已经开始输出了,只是其占空比和频率都是固定的,而我们通过修改 TIM3_CCR1 则可以控制 CH1 的输出占空比。继而控制 DS0 的亮度。
通过以上 6 个步骤,我们就可以控制 TIM3 的 CH1 输出 PWM 波了。
简化上面的过程就是:
1 使能定时器14和相关IO口时钟。
使能定时器14时钟:RCC_APB1PeriphClockCmd();
使能GPIOF时钟:RCC_AHB1PeriphClockCmd ();
2 初始化IO口为复用功能输出。函数:GPIO_Init();
3 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; //复用功能
GPIOF9复用映射到定时器14
GPIO_PinAFConfig(GPIOF,GPIO_PinSource9,GPIO_AF_TIM14);
4 初始化定时器:ARR,PSC等:TIM_TimeBaseInit();
5 初始化输出比较参数:TIM_OC1Init();
6 使能预装载寄存器: TIM_OC1PreloadConfig(TIM14, TIM_OCPreload_Enable);
7 使能自动重装载的预装载寄存器允许位TIM_ARRPreloadConfig(TIM14,ENABLE);
8 使能定时器。
9 不断改变比较值CCRx,达到不同的占空比效果:TIM_SetCompare1();
void TIME3_PWM_Init(u16 arr,u16 psc)
{
GPIO_InitTypeDef GPIO_InitStructure;
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_OCInitTypeDef TIM_OCInitStructure;//结构体声明
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); //使能定时器3时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB | RCC_APB2Periph_AFIO, ENABLE); //使能GPIO外设和AFIO复用功能模块时钟
GPIO_PinRemapConfig(GPIO_PartialRemap_TIM3, ENABLE); //Timer3部分重映射 TIM3_CH2-》PB5
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_5;
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOB,&GPIO_InitStructure);
TIM_TimeBaseStructure.TIM_ClockDivision=0;
TIM_TimeBaseStructure.TIM_CounterMode=TIM_CounterMode_Up;
TIM_TimeBaseStructure.TIM_Period=arr;
TIM_TimeBaseStructure.TIM_Prescaler=psc;
TIM_TimeBaseInit(TIM3,&TIM_TimeBaseStructure);
TIM_OCInitStructure.TIM_OCMode=TIM_OCMode_PWM2;//PWM模式1或者模式2
TIM_OCInitStructure.TIM_OCPolarity=TIM_OCPolarity_High;//比较输出极性
TIM_OCInitStructure.TIM_OutputState=TIM_OutputState_Enable;//输出使能 OR失能
// TIM_OCInitStructure.TIM_Pulse=100;//比较值,写CCRx
TIM_OC2Init(TIM3, &TIM_OCInitStructure); //根据T指定的参数初始化外设TIM3 OC2
TIM_OC2PreloadConfig(TIM3, TIM_OCPreload_Enable); //使能TIM3在CCR2上的预装载寄存器
TIM_Cmd(TIM3, ENABLE); //使能TIM3
}
int main(void)
{
delay_init(); //延时函数初始化
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
uart_init(115200);
LED_Init(); //初始化与LED连接的硬件接口
BEEP_Init();
TIME3_Init(4999,7199);
TIME3_PWM_Init(899,0);
KEY_Init();
LCD_Init();
while(1)
{
delay_ms(10);
if(dir)
led0pwmval++;
else
led0pwmval--;
if(led0pwmval》500)
dir=0;
if(led0pwmval==0)
dir=1;
TIM_SetCompare2(TIM3,led0pwmval);
}
}
输入捕获实验
下面我们介绍输入捕获的配置步骤:
1 ))开启 TIM2 时钟,配置 PA0 为下拉输入。
要使用 TIM2,我们必须先开启 TIM2 的时钟(通过 APB1ENR 设置)。这里我们还要配置 PA0为下拉输入,因为我们要捕获 TIM2_CH1 上面的高电平脉宽,而 TIM2_CH1 是连接在 PA0 上
面的。
2 ))设置 TIM2 的 的 ARR 和 和 PSC 。
在开启了 TIM2 的时钟之后,我们要设置 ARR 和 PSC 两个寄存器的值来设置输入捕获的自动重装载值和计数频率。
3 ))设置 TIM2 的 的 CCMR1
TIM2_CCMR1 寄存器控制着输入捕获 1 和 2 的模式,包括映射关系,滤波和分频等。这里我们需要设置通道 1 为输入模式,且 IC1 映射到 TI1(通道 1)上面,并且不使用滤波(提高响应速度)器。
4) ) 设置 TIM2 的 的 CCER ,开启输入捕获,并设置为上升沿捕获。
TIM2_CCER 寄存器是定时器的开关,并且可以设置输入捕获的边沿。只有 TIM2_CCER寄存器使能了输入捕获,我们的外部信号,才能被 TIM2 捕获到,否则一切白搭。同时要设置好捕获边沿,才能得到正确的结果。
5) ) 设 置 TIM2 的 的 DIER ,使能捕获和更新中断,并编写中断服务函数
因为我们要捕获的是高电平信号的脉宽,所以,第一次捕获是上升沿,第二次捕获时下降沿,必须在捕获上升沿之后,设置捕获边沿为下降沿,同时,如果脉宽比较长,那么定时器就会溢出,对溢出必须做处理,否则结果就不准了。这两件事,我们都在中断里面做,所以必须开启捕获中断和更新中断。
设置了中断必须编写中断函数,否则可能导致死机。我们需要在中断函数里面完成数据处理和捕获设置等关键操作,从而实现高电平脉宽统计。
6 )设置 TIM2 的 的 CR1 ,使能定时器
最后,必须打开定时器的计数器开关,通过设置 TIM2_CR1 的最低位为 1,启动 TIM2 的计数器,开始输入捕获。
通过以上 6 步设置,定时器 2 的通道 1 就可以开始输入捕获了,同时因为还用到了串口输出结果,所以还需要配置一下串口。
输入捕获的一般配置步骤:
① 初始化定时器和通道对应IO的时钟。
② 初始化IO口,模式为复用:GPIO_Init();
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
③设置引脚复用映射:
GPIO_PinAFConfig();
④初始化定时器ARR,PSC
TIM_TimeBaseInit();
⑤初始化输入捕获通道
TIM_ICInit();
⑥如果要开启捕获中断,
TIM_ITConfig();
NVIC_Init();
⑦使能定时器:TIM_Cmd();
⑧编写中断服务函数:TIMx_IRQHandler();
代码如下:
void TIME2_CAP_Init(u16 arr,u16 psc)
{
GPIO_InitTypeDef GPIO_InitStructure;
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_ICInitTypeDef TIM2_ICInitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); //使能TIM5时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //使能PA端口时钟
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IN_FLOATING;
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_1;
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure); //设置为浮空输入
TIM_TimeBaseStructure.TIM_ClockDivision=TIM_CKD_DIV1;
TIM_TimeBaseStructure.TIM_CounterMode=TIM_CounterMode_Up;
TIM_TimeBaseStructure.TIM_Period=arr;
TIM_TimeBaseStructure.TIM_Prescaler=psc;
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure); //根据TIM_TimeBaseInitStruct中指定的参数初始化TIMx的时间基数单位
TIM2_ICInitStructure.TIM_Channel=TIM_Channel_2;
TIM2_ICInitStructure.TIM_ICFilter=0x03;
TIM2_ICInitStructure.TIM_ICPolarity=TIM_ICPolarity_Rising;
TIM2_ICInitStructure.TIM_ICPrescaler=TIM_ICPSC_DIV1;
TIM2_ICInitStructure.TIM_ICSelection=TIM_ICSelection_DirectTI;
TIM_ICInit(TIM2,&TIM2_ICInitStructure);
TIM_Cmd(TIM2,ENABLE );
}
int main(void)
{
delay_init(); //延时函数初始化
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
uart_init(115200);
LED_Init(); //初始化与LED连接的硬件接口
BEEP_Init();
TIME3_Init(4999,7199);
TIME3_PWM_Init(899,0);
TIME2_Cap_Init(0XFFFF,72-1); //以1Mhz的频率计数
KEY_Init();
while(1)
{
delay_ms(10);
TIM_SetCompare2(TIM3,TIM_GetCapture2(TIM3)+1);
if(TIM_GetCapture2(TIM3)==300)
TIM_SetCompare2(TIM3,0);
if(TIM5CH1_CAPTURE_STA&0X80)//成功捕获到了一次上升沿
{
temp=TIM5CH1_CAPTURE_STA&0X3F;
temp*=65536;//溢出时间总和
temp+=TIM5CH1_CAPTURE_VAL;//得到总的高电平时间
printf(“HIGH:%d usrn”,temp);//打印总的高点平时间
TIM5CH1_CAPTURE_STA=0;//开启下一次捕获
}
总结
以上就是我对定时器的理解,定时器的内容很多,很杂,很复杂,需要大家自己对一些寄存器进行深入的理解,最重要的是自己要上手写代码,复制粘贴是学习STM32的大忌,希望大家可以通过自己的刻苦努力,来实现自己能力的提升。
举报