STM32-定时器详解
阿里云国内75折 回扣 微信号:monov8 |
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6 |
目录
前言
定时器作为微控制器不可缺少的外设在STM32中也是如此。相信不少初学者学到定时器的时候对STM32的学习热情就大打折扣甚至想要放弃了因为这一部分知识确实比较复杂。但是如果你在之前对GPIO、串口通信、外部中断的学习中把这些外设掌握了的话学习这个新知识并不难。
例本章共计1万余字从STM32定时器的原理、寄存器介绍、定时器配置以及定时器的几个常用的功能如定时器中断、定时器输出比较PWM波形、定时器输入捕获测电平长度、定时器编码器模式应用等的使用方法来教大家掌握定时器这一外设。
一、定时器基本介绍
1. STM32定时器
1、上来说就是用来定时的机器是存在于STM32单片机中的一个外设。STM32总共有8个定时器分别是2个高级定时器TIM1、TIM84个通用定时器TIM2、TIM3、TIM4、TIM5和2个基本定时器TIM5、TIM6如下图所示
这三种定时器的区别如下
即高级定时器具有捕获/比较通道和互补输出通用定时器只有捕获/比较通道基本定时器没有以上两者。
2. 通用定时器功能和特点
STM32的众多定时器中我们使用最多的是高级定时器和通用定时器而高级定时器一般也是用作通用定时器的功能下面我们就以通用定时器为例进行讲解其功能和特点包括
- 位于低速的APB1总线上(APB1)
- 16 位向上、向下、向上/向下(中心对齐)计数模式自动装载计数器TIMx_CNT。
- 16 位可编程(可以实时修改)预分频器(TIMx_PSC)计数器时钟频率的分频系数 为 1~65535 之间的任意数值。
- 4 个独立通道TIMx_CH1~4这些通道可以用来作为
① 输入捕获
② 输出比较
③ PWM 生成(边缘或中间对齐模式)
④ 单脉冲模式输出
- 可使用外部信号TIMx_ETR控制定时器和定时器互连可以用 1 个定时器控制另外一个定时器的同步电路。
- 如下事件发生时产生中断/DMA6个独立的IRQ/DMA请求生成器
①更新计数器向上溢出/向下溢出计数器初始化(通过软件或者内部/外部触发)
②触发事件(计数器启动、停止、初始化或者由内部/外部触发计数)
③输入捕获
④输出比较
⑤支持针对定位的增量(正交)编码器和霍尔传感器电路
⑥触发输入作为外部时钟或者按周期的电流管理
- STM32 的通用定时器可以被用于测量输入信号的脉冲长度(输入捕获)或者产生输出波形(输出比较和 PWM)等。
- 使用定时器预分频器和 RCC 时钟控制器预分频器脉冲长度和波形周期可以在几个微秒到几个毫秒间调整。STM32 的每个通用定时器都是完全独立的没有互相共享的任何资源。
3. 计数器模式
通用定时器可以向上计数、向下计数、向上向下双向计数模式。
①向上计数模式计数器从0计数到自动加载值(TIMx_ARR)然后重新从0开始计数并且产生一个计数器溢出事件。
②向下计数模式计数器从自动装入的值(TIMx_ARR)开始向下计数到0然后从自动装入的值重新开始并产生一个计数器向下溢出事件。
③中央对齐模式向上/向下计数计数器从0开始计数到自动装入的值-1产生一个计数器溢出事件然后向下计数到1并且产生一个计数器溢出事件然后再从0开始重新计数。
4. 定时器工作原理
a.定时器框图
下面就是STM32定时器的工作款图了是学习STM32定时器必须要掌握的。很多学习者学会了通过库函数来配置定时器实现了简单的应用却忽略了基本原理这就对导致在复杂应用的设计上出现低级的错误。所以建议读者认真掌握定时器的工作框图明白内在的原理。
框图可以分为四个大部分用红色笔表示出分别是①时钟产生器部分②时基单元部分③输入捕获部分、④输出比较部分。
b.时钟产生器部分
在第一部分时钟选择上STM32定时器有四种时钟源选择图中蓝色笔标识分别是
①内部时钟(CK_INT)
②外部时钟模式外部触发输入(ETR)
③内部触发输入(ITRx)使用一个定时器作为另一个定时器的预分频器如可以配置一个定时器Timer1而作为另一个定时器Timer2的预分频器。
④外部时钟模式外部输入脚(TIx)
这四种情况可由右图表示
其中内部触发输入口1~4除了ITR1/ITR2/ITR3/ITR4之外还有一种情况用一个定时器作为另一个定时器的分频器。
外部捕获比较引脚有两种分别是
引脚1TI1FP1或TI1F_ED
引脚2TI2FP2
c.时基单元
时基单元就是定时器框图的第二部分它包括三个寄存器分别是计数器寄存器TIMx_CNT)、预分频器寄存器TIMx_PSC)和自动装载寄存器TIMx_ARR)。对这三个寄存器的介绍如下
- 计数器寄存器TIMx_CNT)
向上计数、向下计数或者中心对齐计数
- 计数器寄存器TIMx_CNT)
可将时钟频率按1到65535之间的任意值进行分频可在运行时改变其设置值
- 自动装载寄存器TIMx_ARR)
如果TIMx_CR1寄存器中的ARPE位为0ARR寄存器的内容将直接写入影子寄存器如果ARPE为1ARR寄存器的那日同将在每次的更新时间UEV发生时传送到影子寄存器
如果TIM1_CR1中的UDIS位为0当计数器产生溢出条件时产生更新事件。
d.输入捕获通道
- IC1、2和IC3、4可以分别通过软件设置将其映射到TI1、TI2和TI3、TI4;
- 4个16位捕捉比较寄存器可以编程用于存放检测到对应的每一次输入捕捉时计数器的值;
- 当产生一次捕捉相应的CCxIF标志位被置1;同时如果中断或DMA请求使能则产生中断或DMA请求。
- 如果当CCxIF标志位已经为1当又产生一个捕捉则捕捉溢出标志位CCxOF将被置1。
e.输出比较通道PWM
- PWM模式运行产生:
定时器2、3和4可以产生4独立的信号
频率和占空比可以进行如下设定:
一个自动重载寄存器用于设定PWM的周期;
每个PWM通道有一个捕捉比较寄存器用于设定占空时间。
例如:产生一个40KHz的PWM信号:在定时器2的时钟为72MHz下占空比为50% :
预分频寄存器设置为0 (计数器的时钟为TIM1CLK/(O+1))自动重载寄存器设为 1799CCRx寄存器设为899。
- 两种可设置PWM模式:
边沿对齐模式
中心对齐模式
二、定时器中断应用
1.内部时钟选择
除非APB1的分频系数是1否则通用定时器的时钟等于APB1时钟的2倍。
默认调用SystemInit函数情况下
SYSCLK=72M
AHB时钟=72M
APB1时钟=36M
所以APB1的分频系数=AHB/APB1时钟=2
所以通用定时器时钟CK_INT=2*36M=72M
2.计数器模式
- 向下计数模式时钟分频因子=1
- 向下计数模式时钟分频因子=1
中央对齐计数模式时钟分频因子=1 ARR=6
3.定时器中断实验相关寄存器
- 计数器当前值寄存器CNT
- 预分频寄存器TIMx_PSC
- 自动重装载寄存器TIMx_ARR)
- 控制寄存器1TIMx_CR1
- DMA中断使能寄存器TIMx_DIER
4. 常用库函数
定时器参数初始化
void TIM_TimeBaseInit(TIM_TypeDef* TIMx,TIM_TimeBaseInitTypeDef*TIM_TimeBaseInitStruct);
结构体内部成员
typedef struct
{
uint16_t TIM_Prescaler;
uint16_t TIM_CounterMode;
uint16_t TIM_Period;
uint16_t TIM_ClockDivision;
uint8_t TIM_RepetitionCounter;
} TIM_TimeBaseInitTypeDef;
声明方式一般
TIM_TimeBaseStructure.TIM_Period = 4999;
TIM_TimeBaseStructure.TIM_Prescaler =7199;
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);
定时器使能函数
void TIM_Cmd(TIM_TypeDef* TIMx, FunctionalState NewState)
定时器中断使能函数
void TIM_ITConfig(TIM_TypeDef* TIMx, uint16_t TIM_IT, FunctionalState NewState);
状态标志位获取和清除
FlagStatus TIM_GetFlagStatus(TIM_TypeDef* TIMx, uint16_t TIM_FLAG);
void TIM_ClearFlag(TIM_TypeDef* TIMx, uint16_t TIM_FLAG);
ITStatus TIM_GetITStatus(TIM_TypeDef* TIMx, uint16_t TIM_IT);
void TIM_ClearITPendingBit(TIM_TypeDef* TIMx, uint16_t TIM_IT);
5. 定时器中断实现步骤
RCC_APB1PeriphClockCmd();
② 初始化定时器配置ARR,PSC。
TIM_TimeBaseInit();
void TIM_ITConfig();
NVIC_Init();
④ 使能定时器。
TIM_Cmd();
⑥ 编写中断服务函数。
TIMx_IRQHandler();
6. 应用实例
下面是使用定时器中断的代码我们设置为每500ms中断一次中断服务函数控制LED实现LED状态取反。时间计算方法为
Tout溢出时间=ARR+1)(PSC+1)/Tclk
//timer.c源文件
#include "timer.h"
#include "led.h"
//通用定时器3中断初始化
//这里时钟选择为APB1的2倍而APB1为36M
//arr自动重装值。
//psc时钟预分频数
//这里使用的是定时器3!
void TIM3_Int_Init(u16 arr,u16 psc)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); //时钟使能
//定时器TIM3初始化
TIM_TimeBaseStructure.TIM_Period = arr; //设置在下一个更新事件装入活动的自动重装载寄存器周期的值
TIM_TimeBaseStructure.TIM_Prescaler =psc; //设置用来作为TIMx时钟频率除数的预分频值
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; //设置时钟分割:TDTS = Tck_tim
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //TIM向上计数模式
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure); //根据指定的参数初始化TIMx的时间基数单位
TIM_ITConfig(TIM3,TIM_IT_Update,ENABLE ); //使能指定的TIM3中断,允许更新中断
//中断优先级NVIC设置
NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn; //TIM3中断
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; //先占优先级0级
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3; //从优先级3级
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道被使能
NVIC_Init(&NVIC_InitStructure); //初始化NVIC寄存器
TIM_Cmd(TIM3, ENABLE); //使能TIMx
}
//定时器3中断服务程序
void TIM3_IRQHandler(void) //TIM3中断
{
if (TIM_GetITStatus(TIM3, TIM_IT_Update) != RESET) //检查TIM3更新中断发生与否
{
TIM_ClearITPendingBit(TIM3, TIM_IT_Update ); //清除TIMx更新中断标志
LED1=!LED1;
}
}
//timer.h头文件
#ifndef __TIMER_H
#define __TIMER_H
#include "sys.h"
void TIM3_Int_Init(u16 arr,u16 psc);
#endif
//main.c源文件
#include "led.h"
#include "delay.h"
#include "sys.h"
#include "timer.h"
int main(void)
{
delay_init(); //延时函数初始化
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //设置NVIC中断分组2:2位抢占优先级2位响应优先级
uart_init(115200); //串口初始化为115200
LED_Init(); //LED端口初始化
TIM3_Int_Init(4999,7199);//10Khz的计数频率计数到5000为500ms
while(1)
{
LED0=!LED0;
delay_ms(200);
}
}
三、定时器PWM输出实验
1. 通用定时器PWM概述
PWM英文名Pulse Width Modulation是脉冲宽度调制缩写它是通过对一系列脉冲的宽度进行调制等效出所需要的波形包含形状以及幅值对模拟信号电平进行数字编码也就是说通过调节占空比的变化来调节信号、能量等的变化占空比就是指在一个周期内信号处于高电平的时间占据整个信号周期的百分比例如方波的占空比就是50%。PWM的功能有很多种比如控制呼吸灯、控制直流电机或者舵机等驱动原件等等是单片机的一个十分重要的功能。
在STM32单片机中可以使用定时器的输出比较功能来产生PWM波
即PWM模式可以产生一个由TIMx_ARR寄存器确定频率、由TIMx_CCRx寄存器确定占空比的信号。其框图如下图所示
可见横坐标是时间变量纵坐标是CNT计数值CNT计数值随着时间的推进会不断经历从0到ARR清零复位再到ARR的这一过程。这之中还有一个数值是CCRx即比较值通过比较值和输出配置可以使之输出高低电平逻辑这样就产生了PWM波形。通过调节ARR的值可以调节PWM的周期调节CCRx的值大小可以调节PWM占空比。
我们以通道1为例详细讲解PWM的工作过程如下图所示
从最左边进入的是时钟源由内部时钟(CNT)或者外部触发时钟(ETRF)输入进入输入模式控制器通过OCMR1寄存器的OC1M[2:0]位来配置PWM模式之后进入一个选择器由CCER寄存器的CC1P位来设置输出极性最后由CCER寄存器的CC1E位来使能输出然后通过OC1来输出PWM波。
CCR1:捕获比较(值)寄存器x=1,2,3,4):设置比较值。
CCMR1: OC1M[2:0]位 对于PWM方式下用于设置PWM模式1【110】或者PWM模式2【111】
CCER:CC1P位输入/捕获1输出极性。0高电平有效1低电平有效。
CCER:CC1E位输入/捕获1输出使能。0关闭1打开。
2. PWM模式
PWM有PWM模式1和模式2两种模式它们之间的区别用寄存器TIMx_CCMR1的OC1M[2:0]位来分析
表中红色框标识的地方就是PWM模式1和模式2的定义和区别可以简单理解为PWM模式1的情况下当前值小于比较值为有效电平PWM模式2的情况下当前值大于比较值为有效电平。
理解这一点对之后的PWM配置十分重要。
下面是对PWM模式1以及向上计数配置情况的说明
3.相关寄存器介绍
-
捕获/比较寄存器1TIMx_CCR1)
这里以寄存器1举例其它的三个寄存器CCR2、CCR3、CCR4都是一样的
-
捕获比较模式寄存器1TIMx_CCMR1)
可以看到每个捕获/比较模式寄存器可以控制两个通道这样的话每个定时器就对应两个捕获/比较模式寄存器。其最常用的位就是0C1M(OC2M位了这两个位是用来设置PWM模式的有模式1和模式2两种这就和前面所讲的对应上了。
- 捕获/比较使能寄存器TIMx_CCER)
可以看到位0CC1E)和位1CC1P)是捕获比较使能寄存器最常用的两个位分别控制输出使能和输出极性这就也和刚刚讲的对应上了。
- 自动重装载寄存器TIMx_ARR)
这个寄存器不太常用下面的库函数配置会讲解其库函数用法。
4. 定时器输出通道引脚
定时器输出PWM和定时器中断不同定时器中断只需要开启这一外设即可工作定时器输出PWM需要在单片机的引脚上输出实实在在的脉冲信号。
下面是定时器3的通道引脚可以使用部分映射或者完全映射。其它定时器的引脚可以查看芯片手册。
5.定时器PWM库函数配置
- 输出库函数配置
和定时器中断实验不同在初始化时基单元之后还需要对输出通道进行初始化
void TIM_OCxInit(TIM_TypeDef* TIMx, TIM_OCInitTypeDef* TIM_OCInitStruct);
其结构体成员如下
typedef struct
{
uint16_t TIM_OCMode; //PWM模式1或者模式2
uint16_t TIM_OutputState; //输出使能 OR失能
uint16_t TIM_OutputNState;
uint16_t TIM_Pulse; //比较值写CCRx
uint16_t TIM_OCPolarity; //比较输出极性
uint16_t TIM_OCNPolarity;
uint16_t TIM_OCIdleState;
uint16_t TIM_OCNIdleState;
} TIM_OCInitTypeDef;
初始化实例
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM2; //PWM模式2
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; //比较输出使能
TIM_OCInitStructure. TIM_Pulse=100;
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; //输出极性:TIM输出比较极性高
TIM_OC2Init(TIM3, &TIM_OCInitStructure); //根据T指定的参数初始化外设TIM3 OC2
-
设置比较值函数
void TIM_SetCompareX(TIM_TypeDef* TIMx, uint16_t Compare2);
- 使能输出比较预装载
void TIM_OC2PreloadConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCPreload);
- 使能自动重装载的预装载寄存器允许位
void TIM_ARRPreloadConfig(TIM_TypeDef* TIMx, FunctionalState NewState);
-
输出配置步骤
使能定时器3时钟RCC_APB1PeriphClockCmd();
使能GPIOB时钟RCC_APB2PeriphClockCmd();
② 初始化IO口为复用功能输出。函数GPIO_Init();
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
所以需要开启AFIO时钟。同时设置重映射。
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);
GPIO_PinRemapConfig(GPIO_PartialRemap_TIM3, ENABLE);
④ 初始化定时器ARR,PSC等TIM_TimeBaseInit();
⑤ 初始化输出比较参数:TIM_OC2Init();
⑥ 使能预装载寄存器 TIM_OC2PreloadConfig(TIM3, TIM_OCPreload_Enable);
⑦ 使能定时器。TIM_Cmd();
⑧ 不断改变比较值CCRx达到不同的占空比效果:TIM_SetCompare2();
6.实例
我们使用STM32单片机的定时器TIM3的PWM功能输出占空比可变的PWM波用来驱动LED灯从而达到LED亮度由暗变亮又从亮变暗如此循环。代码如下
//timer.c源文件
#include "timer.h"
#include "led.h"
#include "usart.h"
//TIM3 PWM部分初始化
//PWM输出初始化
//arr自动重装值
//psc时钟预分频数
void TIM3_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
//设置该引脚为复用输出功能,输出TIM3 CH2的PWM脉冲波形 GPIOB.5
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5; //TIM_CH2
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);//初始化GPIO
//初始化TIM3
TIM_TimeBaseStructure.TIM_Period = arr; //设置在下一个更新事件装入活动的自动重装载寄存器周期的值
TIM_TimeBaseStructure.TIM_Prescaler =psc; //设置用来作为TIMx时钟频率除数的预分频值
TIM_TimeBaseStructure.TIM_ClockDivision = 0; //设置时钟分割:TDTS = Tck_tim
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //TIM向上计数模式
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure); //根据TIM_TimeBaseInitStruct中指定的参数初始化TIMx的时间基数单位
//初始化TIM3 Channel2 PWM模式
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM2; //选择定时器模式:TIM脉冲宽度调制模式2
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; //比较输出使能
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; //输出极性:TIM输出比较极性高
TIM_OC2Init(TIM3, &TIM_OCInitStructure); //根据T指定的参数初始化外设TIM3 OC2
TIM_OC2PreloadConfig(TIM3, TIM_OCPreload_Enable); //使能TIM3在CCR2上的预装载寄存器
TIM_Cmd(TIM3, ENABLE); //使能TIM3
}
//timer.h头文件
#ifndef __TIMER_H
#define __TIMER_H
#include "sys.h"
void TIM3_PWM_Init(u16 arr,u16 psc);
#endif
//main.c源文件
#include "led.h"
#include "delay.h"
#include "sys.h"
#include "timer.h"
int main(void)
{
u16 led0pwmval=0;
u8 dir=1;
delay_init(); //延时函数初始化
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //设置NVIC中断分组2:2位抢占优先级2位响应优先级
uart_init(115200); //串口初始化为115200
LED_Init(); //LED端口初始化
TIM3_PWM_Init(899,0); //不分频。PWM频率=72000000/900=80Khz
while(1)
{
delay_ms(10);
if(dir)led0pwmval++;
else led0pwmval--;
if(led0pwmval>300)dir=0;
if(led0pwmval==0)dir=1;
TIM_SetCompare2(TIM3,led0pwmval);
}
}
总结
本章从STM32定时器的原理、寄存器介绍、定时器配置以及定时器的几个常用的功能如定时器中断、定时器输出比较PWM波形的使用方法来教大家掌握定时器这一外设。希望读者能够仔细学习掌握这一重要的外设。