找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

2811

积分

0

好友

389

主题
发表于 昨天 06:54 | 查看: 0| 回复: 0

定时器作为微控制器不可或缺的外设,在STM32开发中扮演着核心角色。尽管其内部结构相对复杂,但只要掌握了GPIO、串口通信等基础外设,理解定时器的工作原理并加以应用并非难事。

定时器基本介绍

1.1 STM32定时器

简单来说,定时器就是存在于STM32单片机中的一个用于精确计时的外设模块。STM32F103系列总共有8个定时器,分别是2个高级定时器(TIM1、TIM8),4个通用定时器(TIM2、TIM3、TIM4、TIM5)和2个基本定时器(TIM6、TIM7),具体型号分布如下表所示:

STM32F103定时器规格对比表

这三种定时器的主要区别在于其功能丰富程度:

STM32定时器种类与功能对比

概括来讲:高级定时器具备完整的捕获/比较通道和互补输出功能;通用定时器拥有捕获/比较通道;而基本定时器则没有以上两者,功能最为基础。

1.2 通用定时器功能和特点

在实际项目中,我们最常使用的是高级定时器和通用定时器,且高级定时器也常被配置为通用功能。下面以通用定时器为例,详细罗列其核心功能与特点:

  • 位于低速的 APB1总线上。
  • 具备16位向上向下向上/向下(中心对齐) 计数模式,并包含自动装载计数器(TIMx_CNT)。
  • 拥有16位可编程预分频器(TIMx_PSC),可在1~65535之间任意设置分频系数,并且支持运行时修改。
  • 提供4个独立通道(TIMx_CH1~4),这些通道可用于:输入捕获输出比较PWM生成(边沿或中心对齐模式)、单脉冲模式输出
  • 支持使用外部信号(TIMx_ETR) 控制定时器,以及定时器之间的互连同步。
  • 在以下事件发生时可以产生中断或DMA请求(共6个独立的IRQ/DMA请求生成器):
    • 更新事件:计数器向上/向下溢出,或计数器被初始化。
    • 触发事件:计数器启动、停止、初始化或由内/外部触发计数。
    • 输入捕获事件
    • 输出比较事件
    • 支持针对定位的增量(正交)编码器和霍尔传感器电路。
  • STM32的通用定时器常用于测量输入信号的脉冲宽度(输入捕获)产生输出波形(输出比较和PWM)
  • 通过结合定时器预分频器和RCC时钟控制器预分频器,可以对脉冲长度和波形周期进行从微秒到毫秒级别的精细调整。每个通用定时器都是完全独立的。

1.3 计数器模式

通用定时器支持三种基本的计数模式:

  1. 向上计数模式:计数器从0开始计数,达到自动加载值(TIMx_ARR)后,复位到0重新开始,并产生一个计数器溢出事件。
  2. 向下计数模式:计数器从自动装入的值(TIMx_ARR)开始向下计数到0,然后重新从ARR值开始计数,并产生一个计数器向下溢出事件。
  3. 中央对齐模式(向上/向下计数):计数器从0计数到(ARR-1),产生一个溢出事件,然后向下计数到1并再次产生溢出事件,如此循环。

三种计数器模式示意图

1.4 定时器工作原理

1.4.1 定时器框图

理解定时器的框图是掌握其工作原理的关键。许多开发者仅通过库函数配置实现了功能,却忽略了底层原理,这可能导致在复杂应用设计时出现错误。因此,深入理解下图所示的工作框图至关重要。

STM32通用定时器工作框图

整个框图可以划分为四大功能部分:

  1. 时钟产生器:负责选择并提供定时器的计数时钟源。
  2. 时基单元:定时器的核心,包含计数器、预分频器和自动重装载寄存器。
  3. 输入捕获:用于测量外部信号的脉宽或周期。
  4. 输出比较:用于产生PWM等输出波形。

1.4.2 时钟产生器部分

STM32定时器有四种时钟源可供选择(图中蓝色部分):

  1. 内部时钟(CK_INT):最常用的时钟源,来自APB总线。
  2. 外部时钟模式1:外部触发输入(ETR):通过特定的外部引脚输入时钟。
  3. 内部触发输入(ITRx):使用一个定时器作为另一个定时器的预分频器,实现定时器级联。
  4. 外部时钟模式2:外部输入脚(TIx):通过捕获/比较通道的引脚输入时钟。

定时器时钟源选择框图

1.4.3 时基单元

时基单元是定时器的心脏,它包含三个核心寄存器:

  1. 计数器寄存器(TIMx_CNT):进行实际的向上、向下或中心对齐计数。
  2. 预分频器寄存器(TIMx_PSC):对输入时钟进行1~65535的分频,从而改变计数器的实际计数频率。
  3. 自动重装载寄存器(TIMx_ARR):设定计数器的周期。当使能了预装载功能(ARPE位),ARR的值会在更新事件发生时才生效。

1.4.4 输入捕获通道

  • 输入通道IC1、IC2和IC3、IC4可以分别映射到外部引脚TI1、TI2和TI3、TI4。
  • 当检测到输入信号的边沿时,当前计数器的值会被锁存到对应的16位捕获/比较寄存器中。
  • 每次捕获发生时,相应的标志位(CCxIF)会被置1。如果中断或DMA使能,则会触发中断或DMA请求。

输入捕获通道结构框图

1.4.5 输出比较通道(PWM)

(1)PWM信号的产生
定时器通过比较计数器值(CNT)与捕获/比较寄存器值(CCRx)来产生PWM信号。ARR寄存器决定了PWM的周期,CCRx寄存器则决定了占空比。

(2)PWM对齐模式
PWM输出有两种对齐模式,主要影响在中心对齐计数模式下的波形对称性:

  • 边沿对齐模式
  • 中心对齐模式

PWM边沿对齐与中心对齐模式波形对比

定时器中断应用

2.1 内部时钟选择

通用定时器的时钟通常来自APB1总线。这里有一个关键点:除非APB1的分频系数设置为1,否则通用定时器的时钟频率等于APB1时钟频率的2倍

在默认调用 SystemInit 函数的情况下,系统时钟为72MHz,APB1预分频系数为2,因此APB1时钟为36MHz。根据上述规则,连接到APB1的通用定时器(如TIM2~TIM7)的时钟最终为 36MHz * 2 = 72MHz

APB总线时钟分频与定时器时钟关系图

定时器内部时钟生成路径

2.2 计数器模式时序详解

向下计数模式(时钟分频因子=1):
计数器从ARR值开始递减,减到0时产生更新事件并重置。

向下计数模式时序图
向下计数波形示意图

向上计数模式(时钟分频因子=1):
计数器从0开始递增,达到ARR值时产生更新事件并清零。

向上计数模式时序图
向上计数波形示意图

中央对齐计数模式(时钟分频因子=1, ARR=6):
计数器从0向上计数到ARR,然后向下计数到1,如此往复,在计数方向改变时可能产生更新事件。

中央对齐计数模式时序图
中央对齐波形示意图

2.3 定时器中断实验相关寄存器

实现定时器中断,需要配置以下几个关键寄存器:

  • 计数器当前值寄存器(TIMx_CNT)
    存储计数器的当前值。

TIMx_CNT寄存器结构

  • 预分频寄存器(TIMx_PSC)
    设置对输入时钟CK_PSC的分频系数,实际计数器时钟CK_CNT = fCK_PSC / (PSC[15:0] + 1)。

TIMx_PSC寄存器结构

  • 自动重装载寄存器(TIMx_ARR)
    存储计数器的重载值,即计数周期。

TIMx_ARR寄存器结构

  • 控制寄存器1(TIMx_CR1)
    控制计数器的基本行为,如计数方向(DIR位)、使能(CEN位)等。

TIMx_CR1寄存器部分位定义

  • DMA/中断使能寄存器(TIMx_DIER)
    用于使能或禁止各类定时器中断,如更新中断(UIE位)、捕获/比较中断(CCxIE位)等。

TIMx_DIER寄存器部分位定义

2.4 常用库函数

定时器时基初始化:

void TIM_TimeBaseInit(TIM_TypeDef* TIMx, TIM_TimeBaseInitTypeDef* TIM_TimeBaseInitStruct);

其配置结构体如下:

typedef struct
{
  uint16_t TIM_Prescaler;         // 预分频值 (PSC)
  uint16_t TIM_CounterMode;       // 计数模式 (向上、向下等)
  uint16_t TIM_Period;            // 自动重载值 (ARR)
  uint16_t TIM_ClockDivision;     // 时钟分频 (与死区时间相关,通常为TIM_CKD_DIV1)
  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);

2.5 定时器中断实现步骤

  1. 使能定时器时钟RCC_APB1PeriphClockCmd();
  2. 初始化定时器:配置ARR、PSC等参数。TIM_TimeBaseInit();
  3. 开启定时器中断并配置NVICTIM_ITConfig();NVIC_Init();
  4. 使能定时器TIM_Cmd();
  5. 编写中断服务函数TIMx_IRQHandler();

2.6 应用实例

下面是一个使用通用定时器3实现500ms中断的示例代码,中断服务函数中控制LED状态翻转。定时时间 T = (ARR+1) * (PSC+1) / Tclk。假设系统时钟72MHz,设置 ARR=4999PSC=7199,则 T = 5000 * 7200 / 72,000,000 = 0.5s

//timer.c源文件
#include "timer.h"
#include "led.h"

//通用定时器3中断初始化
//时钟选择为APB1的2倍,APB1默认分频后为36M,故TIM3时钟为72M
//arr:自动重装值。
//psc:时钟预分频数
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;
        }
}

定时器PWM输出实验

3.1 通用定时器PWM概述

PWM (Pulse Width Modulation),即脉冲宽度调制,通过对一系列脉冲的宽度进行调制,来等效地获得所需波形。它本质上是一种用数字信号对模拟信号电平进行编码的方法。占空比是指一个周期内,高电平时间占总周期的百分比。

在STM32中,可以利用定时器的输出比较功能来产生PWM波:PWM模式的频率由TIMx_ARR寄存器决定,占空比则由TIMx_CCRx寄存器决定。其工作原理如下图所示:

PWM生成原理示意图

横轴是时间,纵轴是计数器CNT的值。CNT值随时间从0递增到ARR,然后复位,循环往复。CCRx是比较值。通过配置,当CNT < CCRx时输出一种电平,反之输出另一种电平,这样就产生了PWM波形。调节ARR可改变周期,调节CCRx则可改变占空比

以通道1为例,PWM信号的产生路径如下:

定时器输出比较通道逻辑框图

信号流向:时钟源(CNT或ETRF) -> 输出模式控制器(由OC1M[2:0]位配置PWM模式) -> 极性选择器(由CC1P位设置) -> 输出使能门控(由CC1E位控制) -> 最终从OC1引脚输出。

关键寄存器位:

  • CCR1:捕获/比较寄存器,设置比较值。
  • CCMR1中的OC1M[2:0]:设置为110(PWM模式1)或111(PWM模式2)。
  • CCER中的CC1P:设置输出极性。0:高电平有效;1:低电平有效。
  • CCER中的CC1E:输出使能。0:关闭;1:打开。

3.2 PWM模式

PWM有模式1和模式2两种,区别在于有效电平的判定逻辑(参考TIMx_CCMR1寄存器的OC1M位描述):

PWM模式1与模式2寄存器定义

可以简单理解为:

  • PWM模式1:计数器值小于比较值时,通道为有效电平。
  • PWM模式2:计数器值大于比较值时,通道为有效电平。

“有效电平”的具体高低由CCxP位进一步决定。理解这一点对正确配置PWM至关重要。

下图展示了PWM模式1在向上计数时的波形实例:

PWM模式1边沿对齐波形实例

3.3 相关寄存器介绍

  • 捕获/比较寄存器1(TIMx_CCR1)
    当通道配置为输出时,用于写入PWM的比较值(占空比)。

TIMx_CCR1寄存器说明

  • 捕获/比较模式寄存器1(TIMx_CCMR1)
    每个寄存器控制两个通道,其中OCxM位用于设置PWM模式。

TIMx_CCMR1寄存器结构(上半部分)
TIMx_CCMR1寄存器结构(下半部分及位定义)

  • 捕获/比较使能寄存器(TIMx_CCER)
    CCxE位使能输出,CCxP位设置输出极性。

TIMx_CCER寄存器位定义

  • 自动重装载寄存器(TIMx_ARR)
    设定PWM的周期,通常通过库函数配置,无需直接操作寄存器。

TIMx_ARR寄存器结构

3.4 定时器输出通道引脚

PWM输出需要映射到具体的物理引脚。与定时器中断不同,必须配置正确的引脚复用功能。以下是TIM3通道引脚的重映射关系:

TIM3通道引脚重映射配置表

3.5 定时器PWM库函数配置

PWM输出配置在初始化时基之后,还需初始化输出比较通道。

输出比较初始化函数:

void TIM_OCxInit(TIM_TypeDef* TIMx, TIM_OCInitTypeDef* TIM_OCInitStruct);

其结构体成员如下:

typedef struct
{
  uint16_t TIM_OCMode;        // PWM模式1或模式2 (TIM_OCMode_PWM1/2)
  uint16_t TIM_OutputState;   // 输出使能 (TIM_OutputState_Enable/Disable)
  uint16_t TIM_OutputNState;  // 互补输出使能 (高级定时器用)
  uint16_t TIM_Pulse;         // 比较值,即CCRx的初始值
  uint16_t TIM_OCPolarity;    // 输出极性 (TIM_OCPolarity_High/Low)
  uint16_t TIM_OCNPolarity;
  uint16_t TIM_OCIdleState;
  uint16_t TIM_OCNIdleState;
} TIM_OCInitTypeDef;

初始化实例(配置TIM3通道2):

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_OC2Init(TIM3, &TIM_OCInitStructure); //初始化TIM3的通道2

动态设置比较值函数:

void TIM_SetCompareX(TIM_TypeDef* TIMx, uint16_t Compare2); // X为通道号,如SetCompare2

使能通道预装载(使CCR值的修改在更新事件生效,防止毛刺):

void TIM_OCxPreloadConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCPreload);

使能ARR预装载(同理):

void TIM_ARRPreloadConfig(TIM_TypeDef* TIMx, FunctionalState NewState);

PWM输出配置步骤:

  1. 使能时钟:使能定时器时钟 (RCC_APB1PeriphClockCmd) 和对应GPIO时钟 (RCC_APB2PeriphClockCmd)。
  2. 初始化IO为复用推挽输出GPIO_Init,模式设为 GPIO_Mode_AF_PP
  3. 引脚重映射(如果需要):开启AFIO时钟 (RCC_APB2Periph_AFIO),并调用 GPIO_PinRemapConfig
  4. 初始化定时器时基单元TIM_TimeBaseInit,设定ARR和PSC。
  5. 初始化输出比较参数TIM_OCxInit,设定PWM模式和初始占空比。
  6. 使能预装载寄存器TIM_OCxPreloadConfigTIM_ARRPreloadConfig
  7. 使能定时器TIM_Cmd
  8. 动态调节占空比:在程序中调用 TIM_SetCompareX 改变CCRx的值。

3.6 实例:呼吸灯

本例使用TIM3的通道2(映射到PB5)输出PWM,通过循环改变比较值CCR2来调节LED亮度,实现呼吸灯效果。

//timer.c源文件
#include "timer.h"
#include "led.h"
#include "usart.h"

//TIM3 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);  //使能GPIOB和AFIO时钟

    GPIO_PinRemapConfig(GPIO_PartialRemap_TIM3, ENABLE); //Timer3部分重映射 TIM3_CH2->PB5

    //设置PB5为复用推挽输出,用于输出TIM3 CH2的PWM波形
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOB, &GPIO_InitStructure);

    //初始化TIM3时基单元
    TIM_TimeBaseStructure.TIM_Period = arr;
    TIM_TimeBaseStructure.TIM_Prescaler = psc;
    TIM_TimeBaseStructure.TIM_ClockDivision = 0;
    TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
    TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);

    //初始化TIM3通道2为PWM模式2
    TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM2;
    TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
    TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
    TIM_OC2Init(TIM3, &TIM_OCInitStructure);

    TIM_OC2PreloadConfig(TIM3, TIM_OCPreload_Enable);  //使能TIM3 CCR2的预装载寄存器

    TIM_Cmd(TIM3, ENABLE);  //使能TIM3
}

在主循环中,可以不断调整 TIM_SetCompare2(TIM3, pwm_val)pwm_val 值,使其在0到ARR之间循环变化,即可观察到LED亮度平滑变化。

总结

本文系统地讲解了STM32通用定时器的基本原理、寄存器功能,并通过定时器中断PWM输出两个经典应用,给出了从寄存器配置到C语言代码的完整实现流程。理解时钟系统(如APB1的倍频关系)和计数器的工作模式(向上/向下/中心对齐)是正确配置定时器的计算机基础。对于PWM应用,关键在于理清模式1/2与有效电平的关系,以及正确完成引脚复用和重映射配置。掌握这些核心概念和网络/系统层面的硬件操作,便能驾驭定时器这一强大外设,满足各类精准定时和波形生成的需求。希望这篇详解能帮助你在嵌入式开发路上更进一步。欢迎在云栈社区交流讨论更多技术细节。




上一篇:解决CentOS7下ClamAV病毒库更新失败:Docker部署与实践查杀指南
下一篇:DeepSeek-OCR-2 模型在 Windows WSL2 环境的部署与实测:从文档到场景的识别效果分析
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-2-1 00:13 , Processed in 1.345786 second(s), 47 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表