当前位置: 首页 > news >正文

STM32TIM定时器

TIM 定时器(Timer)在 STM32 里是一个非常核心的外设,STM32 的 TIM 不只是“闹钟”,它还能做信号测量、波形产生、事件计数和外设同步,是一个全能的时间事件处理器。

1.整体信号流

外部脉冲 (PA0) → GPIO → ETR 外部时钟 → 外部时钟模式2 → PSC → CNT → ARR → 中断控制器 → NVIC → 中断函数

图中每个模块的详细解析 + 对应代码

GPIO 输入

位置:左下角绿色框 "GPIO"(连接到 ETR 外部时钟)

硬件功能:

将外部引脚 PA0 接收到的电平信号送入 TIM2 的 ETR 引脚

ETR = External Trigger

PA0 内部有上拉电阻,空闲时是高电平,外部脉冲会产生高低变化

GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; // 上拉输入
GPIO_InitStructure.GPIO_Pin  = GPIO_Pin_0;    // ETR 引脚
GPIO_Init(GPIOA, &GPIO_InitStructure);

上拉防止悬空

TIM2 的 ETR 功能固定映射在 PA0,所以必须开 GPIOA 时钟

ETR 外部时钟模块

硬件功能:

接收外部脉冲信号

可以设定极性(上升沿 / 下降沿)和滤波

生成一个定时器时钟 CK_PSC(送到 PSC)

TIM_ETRClockMode2Config(TIM2,TIM_ExtTRGPSC_OFF,              // 不分频TIM_ExtTRGPolarity_NonInverted, // 上升沿有效0x00                            // 不滤波
);

为什么模式 2:

模式 1 会把 ETR 当作触发输入(需要触发选择器)

模式 2 是直接用 ETR 作为时钟源,每个脉冲都推动 CNT +1,非常适合做外部计数

时钟模式选择器(黄色模块)

位置:黄色框(内部时钟模式 / 外部时钟模式1 / 模式2 / 编码器模式)

硬件功能:

决定 CNT 的驱动时钟从哪里来

TIM_ETRClockMode2Config 把模式选择器切到了 “外部时钟模式 2”

这样内部时钟就被断开,CK_CNT 直接来自 ETR

PSC 预分频器

硬件功能:

对 CK_CNT 再做一次整数分频

输出 PSCCK(预分频后的时钟)驱动 CNT

TIM_TimeBaseInitStruct.TIM_Prescaler = 1 - 1; // 0

效果:

PSC=0 → 不分频 → 每个外部脉冲直接推动 CNT +1

补充:

如果 PSC=9,就会外部脉冲 10 次 CNT 才加 1

CNT 计数器 & ARR 自动重装器

硬件功能:

CNT:记录当前计数值

ARR:最大计数值(溢出点)

TIM_TimeBaseInitStruct.TIM_Period = 10 - 1; // ARR=9

效果:

CNT 从 0 数到 9 → 触发更新事件(溢出)→ CNT 清零

用途:

用 ARR 控制溢出频率

外部脉冲计数中 ARR 越小,中断触发越频繁

中断输出控制

硬件功能:

根据事件(溢出、捕获、比较等)产生中断请求

TIM_ClearFlag(TIM2, TIM_FLAG_Update);          // 清除初始溢出标志
TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);     // 开溢出中断

作用:

确保中断只在真正溢出时产生

Update 事件就是 CNT 溢出或软件触发

NVIC

硬件功能:

管理所有中断优先级、屏蔽、触发

NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_Init(&NVIC_InitStructure);

作用:

设置中断优先级(可以和其他外设中断协调)

开启 TIM2 中断入口

中断服务函数

硬件功能:

NVIC 调用 TIM2_IRQHandler

程序员在里面写需要做的事

void TIM2_IRQHandler(void)
{if (TIM_GetITStatus(TIM2, TIM_IT_Update) == SET){Num++; // 记录溢出次数TIM_ClearITPendingBit(TIM2, TIM_IT_Update);}
}

效果:

每 10 个外部脉冲 → Num++

最终总脉冲数 = Num*10 + TIM_GetCounter(TIM2)

代码和图的对应关系

图中模块代码配置位置
RCC 内部时钟RCC_APB1PeriphClockCmd / RCC_APB2PeriphClockCmd
ETR 外部时钟GPIO_Init(GPIOA,...) + TIM_ETRClockMode2Config
PSC 预分频器TIM_TimeBaseInitStruct.TIM_Prescaler = 1-1;
ARR 自动重装器TIM_TimeBaseInitStruct.TIM_Period = 10-1;
CNT 计数器TIM_GetCounter(TIM2)
中断输出控制TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE)
NVICNVIC_Init(...)
中断执行TIM2_IRQHandler

总体代码实现

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "Timer.h"uint16_t Num;int main(void)
{OLED_Init();Timer_Init();OLED_ShowString(1,1,"Num:");OLED_ShowString(2,1,"CNT:");while(1){OLED_ShowNum(1,5,Num,5);OLED_ShowNum(2,5,Timer_GetCounter(),5);}
}
#include "stm32f10x.h"                  // Device header
extern uint16_t Num;void Timer_Init(void)
{RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);GPIO_InitTypeDef GPIO_InitStructure;GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IPU;GPIO_InitStructure.GPIO_Pin=GPIO_Pin_0;GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;GPIO_Init(GPIOA,&GPIO_InitStructure);TIM_ETRClockMode2Config(TIM2,TIM_ExtTRGPSC_OFF,TIM_ExtTRGPolarity_NonInverted,0x00);TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct;//配置 TIM2 的时间基准(即每计满多少次产生中断)TIM_TimeBaseInitStruct.TIM_ClockDivision=TIM_CKD_DIV1;TIM_TimeBaseInitStruct.TIM_CounterMode=TIM_CounterMode_Up;TIM_TimeBaseInitStruct.TIM_Period=10-1;TIM_TimeBaseInitStruct.TIM_Prescaler=1-1;TIM_TimeBaseInitStruct.TIM_RepetitionCounter=0;TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStruct);TIM_ClearFlag(TIM2,TIM_FLAG_Update);TIM_ITConfig(TIM2,TIM_IT_Update,ENABLE);NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);NVIC_InitTypeDef NVIC_InitStructure;NVIC_InitStructure.NVIC_IRQChannel=TIM2_IRQn;NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=2;NVIC_InitStructure.NVIC_IRQChannelSubPriority=1;NVIC_Init(&NVIC_InitStructure);TIM_Cmd(TIM2,ENABLE);
}uint16_t Timer_GetCounter(void)
{return TIM_GetCounter(TIM2);
}void TIM2_IRQHandler(void)
{if(TIM_GetITStatus(TIM2,TIM_IT_Update)==SET){Num++;TIM_ClearITPendingBit(TIM2,TIM_IT_Update);}
}

关键词(术语)

CK_CNT / CK_INT:定时器的输入时钟(内部时钟或外部脉冲经过选择器到来的信号)。

PSC(TIMx_PSC):预分频寄存器,设置分频值 N,实际分频比为 N+1。

CK_PSC:PSC 处理后的时钟(即 PSC 的输出,驱动 CNT 增加)。

CNT(TIMx_CNT):计数器寄存器,实际累加的值。

ARR(TIMx_ARR):自动重装寄存器,CNT 达到 ARR 时发生溢出(更新事件 UEV)。

ARPE(Auto-reload preload enable):ARR 的“预装/缓冲”开关(1=有预装,0=无预装)。

UEV(Update Event):更新事件(通常由 CNT 溢出产生),会把 shadow → active、产生 UIF 标志、中断等。

UG(在 EGR 寄存器里写 TIM_EGR_UG):软件强制产生一次 UEV,立刻将 shadow 寄存器写入 active(常用来同步载入 PSC/ARR)。

2.预分频器时序

PSC 的数值 N 表示分频比 = N + 1,也就是说外部/内部输入每来 (N+1) 个 CK_CNT 脉冲,PSC 输出一个 CK_PSC 脉冲,CNT 才会 +1

计数器计数频率:CK_CNT = CK_PSC / (PSC + 1)

PSC 的“应用时机”(buffering)

PSC 写入不是立即改变工作分频,写入 PSC 寄存器后,新的 PSC 值 要等到下一个 UE(update event)才真正加载到定时器的工作分频器里(即 PSC 有影子/缓冲机制)。如果你希望立即生效,可以在写 PSC 后强制产生一次 UG(写 EGR 的 UG 位),这样会马上把写入值载入。

时序小例子

假设外部脉冲序列(CK_CNT)为: P1 P2 P3 P4 P5 ...

若 PSC = 0(N=0) → division = 1:

每个脉冲都直接传给 CNT,CNT 增加:CNT在P1、P2、P3... 处分别 +1。

若 PSC = 3(N=3) → division = 4:

PSC 内部累加 4 次脉冲后才产生一次 CK_PSC。举例:

P1 → PSC count=1(不增CNT)

P2 → PSC count=2

P3 → PSC count=3

P4 → PSC count=4 → PSC 溢出 → 产生一个 CK_PSC → CNT 增 1(此时CNT++发生在P4的时刻)

因此 CNT 的上升点只在每 4 个输入脉冲处发生。

计数器时序

计数器溢出频率:CK_CNT_OV = CK_CNT / (ARR + 1)  = CK_PSC / (PSC + 1) / (ARR + 1)

定时器时钟源经过两级分频后,最终决定 CNT(计数器)多久溢出一次:

第一步:时钟源 CK_PSC

来自内部时钟(CK_INT)或外部时钟(ETR、ITRx等)。

CK_PSC 表示 进入预分频器 PSC 之前的时钟频率。

第二步:预分频器(PSC)分频

PSC 的值是 0~65535,实际分频系数是 (PSC + 1)。

分频后的时钟叫 CK_CNT

第三步:自动重装器(ARR)决定计数周期

CNT 从 0 计数到 ARR,然后溢出并产生更新事件(中断)。

CNT 的计数周期是 (ARR + 1) 个 CK_CNT 周期。

3.无预装(ARPE=0) vs 有预装(ARPE=1)

这是经常会搞混的部分——差别在于你写入 TIMx_ARR(或 CCRx、PSC 等)后,什么时候“成为生效值”。

ARPE = 0(无预装)——写入立即生效

写 TIMx_ARR = new 会 立即改变活动(active)ARR(不进 shadow)。定时器在下一次计数边沿(或下一次判断时)就以新 ARR 进行比较/溢出判断。

副作用:如果当前 CNT 值已经 ≥ 新 ARR,可能会在很短的下一个时钟边沿就触发一次更新事件(UEV),这个细节在参考手册的时序图中有展示(写 ARR 后可能马上导致溢出/UEV)。所以写 ARR 时要小心,可能瞬间会产生一次 UIF/中断。

ARPE = 1(有预装)——写入先进 shadow,等待 UEV 再生效

写 TIMx_ARR = new 会把 new 存入预装寄存器(shadow),不影响当前活动的 ARR。只有当 TIM 产生下一次 UEV(通常是 CNT 溢出/下次重启,或你写 EGR 的 UG 强制产生 UEV)时,shadow 的值才会传给 active ARR,开始生效。参考手册有对应的“shadow → active 在 UEV 时传输”的时序图。

可视化对比(简化时序)

假设当前活动 ARR = 9(旧值),CNT 现在为 5,外部脉冲继续来:

ARPE = 0(立即生效):

时刻 t0: CNT=5, 写入 ARR = 6(立即生效)
下一个脉冲到 t1 -> CNT变为6,检测 CNT==ARR -> 发生溢出/UEV(很快就触发)

→ 所以写入后在下一个计数边沿就可能产生 UEV。

ARPE = 1(缓冲):

时刻 t0: CNT=5, 写入 ARR = 6(写入shadow,active仍为9)
CNT 继续计数到 9 -> 在 CNT 到达 9 溢出 -> 触发 UEV
在该 UEV 上,shadow(=6) 被加载到 active ARR
从下一周期开始使用 ARR=6

http://www.dtcms.com/a/326825.html

相关文章:

  • 请求报文和响应报文(详细讲解)
  • Wed前端第二次作业
  • C语言增删查改实战:高效管理顺序表
  • docker安装searxng
  • monorepo架构设计方案
  • CICD部署流程详解文档笔记
  • 在 Ubuntu 中docker容器化操作来使用新建的 glibc-2.32
  • [激光原理与应用-244]:设计 - 光学 - CLBO晶体使用一段时间后,输出功率就会下降,原因有哪些?
  • OpenBMC中的snk-psu-manager:架构、原理与应用深度解析
  • 高防IP能为网站防御哪些网络攻击?
  • 从零开始学JAVAWeb-5
  • 腾讯云Edgeone限时免费
  • for循环详解与实战技巧
  • Edit Distance
  • 传统制造业减人不减效:一线用工优化的3个投入方向,用对工具比盲目裁员更关键
  • 对抗样本攻击检测与防御
  • 车载软件架构 --- 车辆量产后怎么刷写Flash Bootloader
  • BLE ADV
  • special topic 9 (2) and 1011(1)division one
  • 深入解析Windows系统下UDP绑定失败的原理与系统级解决方案
  • 数据库三范式入门教程
  • Windows11 PowerShell CMD
  • Ascend DrivingSDK 中的 modulated_deform_conv2d(一)
  • GESP2023年9月认证C++一级( 第三部分编程题(1)买文具)
  • MATLAB实现遗传算法求解路网路由问题
  • PTE之路--03文
  • 【08-神经网络介绍】
  • 北京-4年功能测试2年空窗-报培训班学测开-第七十三天-投递简历-[特殊字符][特殊字符]
  • Linux驱动学习day27天(USB驱动理论部分)
  • SSR-code 项目复刻与3D模型生成实现