STM32单片机快速入门——TIM定时器篇
定时器定时中断是定时器计数器达到设定值(如ARR寄存器中的值)时,自动产生的中断信号,触发中断服务程序来执行预定的操作。它通常用于实现周期性或定时的任务。这个功能相信大家都会使用。要注意的是定时器的频率计算公式为:f=原始时钟频率/(PSC+1)/(ARR+1)输入捕获(IC)输入捕获是定时器的一种功能,用于捕捉外部信号的到达时刻,并将其对应的定时器计数值CNT锁存到捕获寄存器CCR中,以便后续处
一.TIM定时器简介
1.TIM总体介绍
STM32定时器是STM32系列微控制器中的一类重要外设,用于生成精确的时间延迟、频率生成、事件计数等功能。它通常用于实时操作系统、PWM输出、输入捕捉、输出比较、脉宽调制、频率测量等应用场景。STM32的定时器具有高度的灵活性和多种工作模式,能够满足复杂的应用需求。
STM32微控制器的定时器分为多种类型,包括:
- 基础定时器(如TIM6、TIM7)
- 通用定时器(如TIM2、TIM3、TIM4等)
- 高级定时器(如TIM1、TIM8)
这三种定时器的基本结构都是由时基单元组成的,时基单元包括:
- 计数器(CNT):时基单元内含有一个计数器(Counter),它可以按照预定的时钟源进行计数。计数器的值可以递增或递减,具体由设置的计数方向(向上计数或向下计数)决定。通常以向上计数居多。
- 预分频器(PSC):为了调节定时器的计时精度,时基单元中通常包含一个预分频器(Prescaler)。可对时钟进行分频,通过调整预分频器的值,可以改变计数器的更新速率,从而实现不同的时间间隔。
- 自动重载寄存器(ARR):时基单元中有一个自动重载寄存器,它用于设置计数器的最大值。当计数器达到预设的值时,会自动重载并重新开始计数,或者触发一个事件(如中断、PWM输出等)。
通过完成对这三个寄存器的配置,然后选择时钟源(默认系统时钟),最后使能定时器,就可以实现最基本的定时/计数操作了。为了便于理解与讲解,我们在以下都以系统时钟作为定时器的时钟源。
在功能上,这三种定时器有以下区别:

每种定时器具有不同的特点和应用场景。它们的工作频率通常由系统时钟或外部时钟源决定,并支持多种预分频设置,以实现不同的计时精度。
二.基本定时器
1.主要功能
- 触发DAC的同步电路(可之后再细究)。
- 在更新事件(计数器溢出)时产生中断/DMA输出。
2.基本结构
如图,来自RCC总线上的内部时钟经过控制器,再经过预分频器PSC 分频(例如 12MHz 经过 2分频 变成 6MHz,72MHz 经过 4分频 变成 18MHz),但需要注意的是,PSC预分频值和实际的分频系数相差1,即对PSC写入1,实际分频系数是2,最后作为驱动定时器的时钟频率,CNT再按照时钟频率进行 向上加 ,直到溢出后,自动重装载寄存器ARR 对 计数器CNT 重新装填,并同时触发中断,或DMA输出,或更新事件触发DAC输出。
在图片的右上角为定时器的主模式触发DAC,当把更新事件映射到触发输出TRGO时,每当产生更新事件(计数值重装载)时,便会触发特定引脚执行DAC操作,这样做的好处是不用频繁进入中断完成DAC的操作,而是由硬件全自动完成。
3.主要特性
- 只支持由内部时钟源驱动。
- 计数器CNT只支持向上计数
三.通用定时器
相较于基本定时器,通用定时器的功能更加丰富,但同时结构也更加复杂。
1.主要功能
主要功能包括但不限于:
- 定时功能:通用定时器可以配置为普通的定时器模式,通过设置自动重载寄存器(ARR)和预分频器(PSC)来控制计数器的溢出时间。当计数器溢出时,定时器会产生更新中断或者触发其他操作(例如生成PWM信号)。
- 可调节的PWM输出:通用定时器能够生成PWM信号,通过配置定时器的捕捉/比较功能,可以输出具有可调占空比和频率的PWM波。
- 输入捕获:在输入捕捉模式下,定时器可以捕捉外部信号的脉冲宽度或频率。当外部信号(如脉冲)到达指定引脚时,定时器捕捉该信号的时刻,并保存其计数值。这样,可以用定时器测量信号的频率或周期。
- 输出比较:在输出比较模式下,定时器会根据设定的比较值(CCR)与计数器的当前值进行比较。当计数器的值等于或超出设定的比较值时,定时器会触发相应的操作(如产生中断、改变输出引脚状态等)。
2.基本结构

可以看到,通用定时器的结构比基本定时器复杂的多。
其中,时基单元与基本定时器的基本相同,但通用定时器的 计数器CNT 除了支持 向上计数 外,还支持:
- 向下计数:减到0之后,回到重装值,同时申请中断
- 中央对齐:从0开始,先向上自增,回到重装值,申请中断,再向下自减,减到0,再申请中断,依次不断循环。
对于黑框部分结构:
- 输入时钟源:通用定时器不仅可以选择内部时钟,还可以选择:
- 外部的TIMx_ETR时钟:从支持该功能的引脚上可输入一定频率的方波时钟,再对极性选择,边沿检测和预分频器进行配置,最后进行滤波,即可作为驱动该定时器的时钟。
- 来自输入捕获的CH1引脚:通过这一路输入的时钟,边沿(上升沿,下降沿)均有效。
- TF1FP1,TF2FP2:分别来自CH1引脚,CH2引脚的时钟。
- ITR0,ITR1,ITR2,ITR3:这些是中断触发源,分别对应不同的定时器事件(如溢出、中断请求等)。定时器的某些事件(如计数完成或匹配)会触发中断信号,通过中断处理程序进行处理。
- TRC:通过输入时钟信号,定时器可被驱动进行计数操作。
- TRGO:在某些特定事件发生时(如计数器溢出或匹配时),定时器可以产生触发输出(TRGO)信号,或者通过触发输入(TRGI)响应外部事件,与基本定时器不同的是,其TRGO信号还可用于触发别的定时器,即可以进行多个计时器的级联。
- 编码器接口:可以读取正交编码器的输出波形。
3.定时器定时中断
3.1 定义
定时器定时中断是定时器计数器达到设定值(如ARR寄存器中的值)时,自动产生的中断信号,触发中断服务程序来执行预定的操作。它通常用于实现周期性或定时的任务。
这个功能相信大家都会使用。要注意的是定时器的频率计算公式为:
f = 原始时钟频率 /(PSC + 1)/ (ARR + 1)
3.2 代码案例
void Timer_Init(void)
{
/*开启时钟*/
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); //开启TIM2的时钟
/*配置时钟源*/
TIM_InternalClockConfig(TIM2); //选择TIM2为内部时钟,若不调用此函数,TIM默认也为内部时钟
/*时基单元初始化*/
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure; //定义结构体变量
TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1; //时钟分频,选择不分频,此参数用于配置滤波器时钟,不影响时基单元功能
TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up; //计数器模式,选择向上计数
TIM_TimeBaseInitStructure.TIM_Period = 10000 - 1; //计数周期,即ARR的值
TIM_TimeBaseInitStructure.TIM_Prescaler = 7200 - 1; //预分频器,即PSC的值
TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0; //重复计数器,高级定时器才会用到
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure); //将结构体变量交给TIM_TimeBaseInit,配置TIM2的时基单元
/*中断输出配置*/
TIM_ClearFlag(TIM2, TIM_FLAG_Update); //清除定时器更新标志位
//TIM_TimeBaseInit函数末尾,手动产生了更新事件
//若不清除此标志位,则开启中断后,会立刻进入一次中断
//如果不介意此问题,则不清除此标志位也可
TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE); //开启TIM2的更新中断
/*NVIC中断分组*/
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //配置NVIC为分组2
//即抢占优先级范围:0~3,响应优先级范围:0~3
//此分组配置在整个工程中仅需调用一次
//若有多个中断,可以把此代码放在main函数内,while循环之前
//若调用多次配置分组的代码,则后执行的配置会覆盖先执行的配置
/*NVIC配置*/
NVIC_InitTypeDef NVIC_InitStructure; //定义结构体变量
NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn; //选择配置NVIC的TIM2线
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //指定NVIC线路使能
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2; //指定NVIC线路的抢占优先级为2
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; //指定NVIC线路的响应优先级为1
NVIC_Init(&NVIC_InitStructure); //将结构体变量交给NVIC_Init,配置NVIC外设
/*TIM使能*/
TIM_Cmd(TIM2, ENABLE); //使能TIM2,定时器开始运行
}
void TIM2_IRQHandler(void)
{
if (TIM_GetITStatus(TIM2, TIM_IT_Update) == SET)
{
TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
}
}
4.输入捕获
4.1 什么是输入捕获?
输入捕获(IC)
输入捕获是定时器的一种功能,用于捕捉外部信号的到达时刻,并将其对应的 定时器计数值CNT 锁存到 捕获寄存器CCR 中,以便后续处理或测量信号的频率、脉冲宽度等特性。
每个高级定时器和通用定时器都拥有四个输入捕获通道,可配置为PWMI模式,同时测量频率和占空比;可配合主从触发模式,实现硬件全自动测量
我们以输入捕获端口CH1为例,进行讲解:
左上角的异或门是专门为电机准备的,我们在此先将它忽略。
通过配置边沿检测器,设置成捕获 上升沿 或 下降沿,信号通过 TIMx_CH1 通道,经输入滤波器滤除毛刺信号后,再由边沿检测器检测特定的边沿,当检测到了特定的边沿后,会经过TI2FP1通道进入IC1,再经过预分频器后,可以选择触发捕获中断,最后计数器CNT的值会锁存进捕获寄存器1中,通过标准库函数,就可以读取到此时的CNT值,此时就可以根据CNT,测量信号的频率,脉冲的宽度等信息。
同时,通过CH1的信号,检测到了特定的边沿后也可以向下经过TI1FP2进入IC2,再次将CNT的值锁存到捕获寄存器2中,这样就可以实现TI1FP1捕获上升沿,测量周期;TI1FP2捕获下降沿,测量占空比,把一个引脚的输入,同时映射到两个捕获单元。也就是PWMI的模式。
每次捕获之后,最好都对CNT的值进行手动清零,方便与下一次捕获计算时间差。可以用主从触发模式自动完成。

TI1FP1同时还可以触发从模式的Reset操作,硬件全自动对CNT进行清零。

所以CH1,CH2既可以独立工作,也可以联合工作,一个通道灵活切换两个引脚,或两个通道同时捕获一个引脚。
独立工作时,总体结构如下:

联合工作时,总体结构如下:

CH3,CH4同理,但CH3,CH4通道的TIxFPy没有从模式,如果想要对CNT的值清零的话,需要利用中断,不过这样的话就会频繁的进入中断,而消耗大量的软件资源了。
4.2 输入捕获的相关函数配置案例
/*开启时钟*/
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); //开启TIM3的时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //开启GPIOA的时钟
/*GPIO初始化*/
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure); //将PA6引脚初始化为上拉输入
/*配置时钟源*/
TIM_InternalClockConfig(TIM3); //选择TIM3为内部时钟,若不调用此函数,TIM默认也为内部时钟
/*时基单元初始化*/
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure; //定义结构体变量
TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1; //时钟分频,选择不分频,此参数用于配置滤波器时钟,不影响时基单元功能
TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up; //计数器模式,选择向上计数
TIM_TimeBaseInitStructure.TIM_Period = 65536 - 1; //计数周期,即ARR的值
TIM_TimeBaseInitStructure.TIM_Prescaler = 72 - 1; //预分频器,即PSC的值
TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0; //重复计数器,高级定时器才会用到
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseInitStructure); //将结构体变量交给TIM_TimeBaseInit,配置TIM3的时基单元
/*输入捕获初始化*/
TIM_ICInitTypeDef TIM_ICInitStructure; //定义结构体变量
TIM_ICInitStructure.TIM_Channel = TIM_Channel_1; //选择配置定时器通道1
TIM_ICInitStructure.TIM_ICFilter = 0xF; //输入滤波器参数,可以过滤信号抖动
TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising; //极性,选择为上升沿触发捕获
TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1; //捕获预分频,选择不分频,每次信号都触发捕获
TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI; //输入信号交叉,选择直通,不交叉
TIM_ICInit(TIM3, &TIM_ICInitStructure); //将结构体变量交给TIM_ICInit,配置TIM3的输入捕获通道
/*选择触发源及从模式*/
TIM_SelectInputTrigger(TIM3, TIM_TS_TI1FP1); //触发源选择TI1FP1
TIM_SelectSlaveMode(TIM3, TIM_SlaveMode_Reset); //从模式选择复位
//即TI1产生上升沿时,会触发CNT归零
/*TIM使能*/
TIM_Cmd(TIM3, ENABLE); //使能TIM3,定时器开始运行
5.输出比较
5.1 什么是输出比较?
输出比较(Output Compare):通过将定时器的 计数器CNT值 与一个预设的比较值(存储在CCR寄存器中)进行比较。当定时器的计数器值与CCR值匹配时,定时器会触发预定的动作,如输出信号的变化(对输出电平置0,置1,翻转等操作)、生成中断、或者更新PWM信号的占空比等。每个高级定时器和通用定时器都拥有4个输出比较通道。高级定时器的前3个通道额外拥有死区生成和互补输出的功能。
简单来说,就是CNT的值会实时的与比较寄存器CCR中的值进行比较,当
- CNT < CCR
- CNT == CCR
- CNT > CCR
时,可自行配置不同情况,OC对外输出电平的变化。
以下是输出比较的结构图


要注意的是,所有通道的输出比较都用的同一个计数器CNT值。
5.2 输出比较的几种类型

5.2 PWM知识点补充


5.3 输出比较模式结构图
这里我们以常用的PWM1模式为例

5.4 案例代码
void PWM_Init(void)
{
/*开启时钟*/
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); //开启TIM2的时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //开启GPIOA的时钟
/*GPIO初始化*/
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0; //GPIO_Pin_15;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure); //将PA0引脚初始化为复用推挽输出
//受外设控制的引脚,均需要配置为复用模式
/*配置时钟源*/
TIM_InternalClockConfig(TIM2); //选择TIM2为内部时钟,若不调用此函数,TIM默认也为内部时钟
/*时基单元初始化*/
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure; //定义结构体变量
TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1; //时钟分频,选择不分频,此参数用于配置滤波器时钟,不影响时基单元功能
TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up; //计数器模式,选择向上计数
TIM_TimeBaseInitStructure.TIM_Period = 100 - 1; //计数周期,即ARR的值
TIM_TimeBaseInitStructure.TIM_Prescaler = 720 - 1; //预分频器,即PSC的值
TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0; //重复计数器,高级定时器才会用到
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure); //将结构体变量交给TIM_TimeBaseInit,配置TIM2的时基单元
/*输出比较初始化*/
TIM_OCInitTypeDef TIM_OCInitStructure; //定义结构体变量
TIM_OCStructInit(&TIM_OCInitStructure); //结构体初始化,若结构体没有完整赋值
//则最好执行此函数,给结构体所有成员都赋一个默认值
//避免结构体初值不确定的问题
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; //输出比较模式,选择PWM模式1
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; //输出极性,选择为高,若选择极性为低,则输出高低电平取反
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; //输出使能
TIM_OCInitStructure.TIM_Pulse = 0; //初始的CCR值
TIM_OC1Init(TIM2, &TIM_OCInitStructure); //将结构体变量交给TIM_OC1Init,配置TIM2的输出比较通道1
/*TIM使能*/
TIM_Cmd(TIM2, ENABLE); //使能TIM2,定时器开始运行
}
/**
* 函 数:PWM设置CCR
* 参 数:Compare 要写入的CCR的值,范围:0~100
* 返 回 值:无
* 注意事项:CCR和ARR共同决定占空比,此函数仅设置CCR的值,并不直接是占空比
* 占空比Duty = CCR / (ARR + 1)
*/
void PWM_SetCompare1(uint16_t Compare)
{
TIM_SetCompare1(TIM2, Compare); //设置CCR1的值
}
四.高级定时器
作为入门篇,我们不在这里对高级定时器做过多详细的介绍。
1.主要功能

2.基本结构

重复次数计数器可设置当触发一定次数的更新后,再触发中断或事件,相当于再次对时钟分频。
五.总结语
有关于定时器这一部分的内容特别多,特别杂,还有很多的内容由于篇幅问题,在这里不方便展开,读者可以先从学会基本功能开始,慢慢往后探究,没必要特意全部学一遍,可以等需要用到的时候,再继续深入了解。
更多推荐





所有评论(0)