Brilliantwxx头像
【STM32】基础定时器 TIM 详解:从极简原理到寄存器全解析封面图

【STM32】基础定时器 TIM 详解:从极简原理到寄存器全解析


  基础定时器 TIM 详解:从极简原理到寄存器全解析  

1. 极简原理:把定时器当作一个"闹钟"

        生活中带"定时"功能的场景:发条或电池驱动闹钟按秒计时,到了设定时间(闹钟响),执行既定事项(起床、去上学)。

        定时器的本质功能:设定延时,然后超时后,通过超时触发完成某种工作。

要在单片机内实现类似效果,需要做到三点:

  1. 把时间本身进行量化(这样才能进行时间比较);
  2. 设定未来超时时间;
  3. 超时,触发完成特定工作。

1.1 量化时间(微观视角)

        想象一个以固定时钟频率对外输出电脉冲的时钟源,再想象一个计数器硬件电路——每收到一个电脉冲(上升沿)就自增 1。两者结合,就是"时间量化"的最小系统:

        ① 时钟源的"节拍"必须是均匀的

        假设时钟源输出 1000Hz 方波,那么每个时钟周期 = 1/1000s = 1ms,也就是每 1ms 产生一次上升沿。这个"周期"就是时间的"最小刻度"。

        ② 计数器按节拍自增

计数器每收到一次上升沿就 +1:收到 1 次 → 计数值 1;收到 123 次 → 计数值 123。

        ③ 数字即时间

因为节拍固定(1ms/次),所以:

计数值 123  ←→  已经过去了 123ms

在固定频率的时钟周期下,计数器数字,就是时间。

        ④ 量化精度由时钟频率决定

  • 时钟 1000Hz → 1ms 计一次 → 时间分辨率 1ms,能分辨的最短时间差是 1ms;
  • 时钟 1MHz → 1us 计一次 → 分辨率 1us;
  • 时钟频率越高,时间分辨率越高,计时越精准(这正是后面为什么要引入"分频器"和追求高频时钟源的原因)。

1.2 设定未来时间

        既然时间能被量化,当前时间和未来某个时间点当然也能被量化、能比较。

        我们再新增一个用户可以设置的寄存器(表示未来某个时间点),计数器更新时和它比较是否相等:

  • 设定 500ms → 计数器每次自增都做一次比较;
  • 不相等 → 继续计数(本质就是在做延时);
  • 相等 → 说明定时器"到点"了,该"响"了。

        这个"可设置的寄存器",就是后面 TIMx_ARR(自动重装载寄存器) 的雏形;

        "比较是否相等"的电路,就是时基单元内部的比较器。

1.3 触发完成特定工作

        当计数器数值等于或大于用户提前设定好的数字时,自动触发某种动作,完成具体工作。

        这个"数字的比较"本质就是"时间的比较"——到达预定时间之前只做计数,本质就是在做延时。

        这里关于"超时/溢出"的条件说得很模糊("等于还是大于"?),后面在定时器内部会专门阐述——结论是溢出条件是 CNT = ARR + 1("凡溢出,必超过"),先留个悬念。

1.4 小结:定时器"三要素"

要素硬件载体说明
量化时间时钟源 + 计数器(CNT)把时间变成数字
设定未来时间可设寄存器(ARR)设定"闹钟响"的时刻
超时触发事件/中断/DMA 电路到点后自动干活

2. 基础定时器是什么?

        定时器的核心就是一个硬件计数器,它按固定节奏数数,数到你设定的数字时就触发一个动作。


3. 为什么要有基础定时器?

如果每隔 2s 让 LED 翻转一次、每隔 2s 打印一次数据,之前只能:

while(1)
{
    HAL_Delay(XX);   // 软件延时
    // 做动作
}

软件延时看似简单,其实有三大痛点:

痛点说明
占用 CPU

HAL_Delay 期间 CPU 只能空转等待,什么活都干不了。

定时器一次只配 1 个 LED,如果配 10 个任务、每个都要延时,CPU 就被"卡死"在延时里

时间不精准软件延时的计时基于 SysTick 中断,实际触发时间会受中断优先级、其他中断抢占、代码执行时间影响,误差不可控
无法并发多个任务需要不同延时周期时,软件延时只能顺序执行,无法"并行计时"

硬件定时器的优势正好相反:

        由硬件独立计数,CPU 零负担;

        超时通过中断/事件/DMA 精准触发;

        多个定时器可以并行工作,互不干扰。

类比:

        软件延时 = 你亲自盯着秒表倒数;

        硬件定时器 = 交给一个独立的人(硬件)看表,到点了他拍你肩膀(中断)或者直接替你干活(DMA/事件)。


4. STM32 定时器类别介绍

对于 STM32F103ZE 芯片,按功能分三类:

类别定时器功能特点
基础定时器TIM6、TIM7仅有基本的计时功能和 DAC 转换触发功能(本章主讲)
通用定时器TIM2、TIM3、TIM4、TIM5基础全部功能 + 输入捕获、输出比较、PWM 生成、正交编码器输入
高级定时器TIM1、TIM8前两者全部功能 + 互补 PWM 输出与死区控制、紧急刹车、主从模式(电机领域用)

器件一览(数据手册 STM32F103 功能配置表):

型号FlashSRAM通用定时器高级定时器基础定时器GPIO
F103Rx256K48K4(TIM2~5)2(TIM1/8)2(TIM6/7)51
F103Vx384K64K42280
F103Zx512K64K422112

"多达 11 个定时器"的由来:

        8 个片上定时器(TIM1~8)+ 系统定时器 SysTick + 2 个看门狗(IWDG 独立看门狗、WWDG 窗口看门狗)。

认知细节:

        基础、通用、高级定时器看起来都是定时器,但最好把他们当作 3 个不同的外设。

        这样可以用独立视角理解不同定时器,不容易把概念搞混。


5. 基础定时器原理

5.1 定位:片上外设

        基础定时器(TIM6/TIM7)属于 MCU 内部的片上外设。

        从系统结构图看,它挂在 APB1 总线上,与 I2C1/2、SPI2、USART2/3、DAC 等"慢速外设"同住一屋。

        后面研究基础定时器原理时,就认为是研究 TIM6 或 TIM7 这样的片上外设即可。

5.2 原理:基本定时器框图(图144)

整个结构框图(参考手册 stm32f103xx):

来自RCC的TIMxCLK → 内部时钟CK_INT → 触发控制器 → 预分频器PSC → CK_CNT → 计数器CNT
                                                └→ 自动重装载寄存器ARR(预加载→影子)◄──┘(比较)
                                                             │
                                          CNT 溢出 → 事件 UEV → 中断和DMA输出 / TRGO至DAC

下面一部分一部分来看。

5.2.1 时钟源(时钟树全景)

        定时器要根据固定频率更新计数器,就必须要有时钟源。

        在 STM32F103ZET6 中,这个时钟源由 RCC 模块时钟树提供。

完整时钟路径(标准配置下):

HSE 外部晶振 8MHz
    → PLL 锁相环 ×9
    → SYSCLK 系统时钟 72MHz
    → AHB 预分频器(HCLK 分频=1)
    → HCLK 72MHz
    → APB1 预分频器(PPRE1=/2)
    → PCLK1 = 36MHz  (APB1 总线时钟)
    → 定时器时钟补偿 ×2
    → TIMxCLK = 72MHz

        其中 APB1 预分频系数由 RCC_CFGR 寄存器的 PPRE1[2:0] 位决定(000=不分频、100=/2、101=/4...)。我们不用手动配它(标准库/CubeMX 已配好),但要理解它的后果。

关键规则:

        基础定时器挂接在 APB1 总线上,APB1 最大频率 36MHz。

输入到定时器内部的时钟频率和 APB1 预分频器配置系数有关:

如果 APB1 分频系数 = 1,则 TIMxCLK = APB1
如果 APB1 分频系数 ≠ 1,则 TIMxCLK = 2 × APB1
场景HCLKAPB1 分频APB1 频率TIMxCLK
① 标准全速(最常见)72MHz236MHz36MHz × 2 = 72MHz
② 低速运行(不翻倍)36MHz136MHz36MHz
③ 大比例分频72MHz418MHz18MHz × 2 = 36MHz

为什么会有这么奇怪的"×2"设定? 本质是对定时器时钟的精度补偿:

  • 定时器时钟源频率越高越好(计数精度、计时精度越高)——希望它跑 72MHz;
  • 但 APB1 总线上挂了很多 UART、I2C 等"慢速"外设,总线频率太高它们受不了、功耗激增——APB1 必须 ≤ 36MHz;
  • 于是规定"只要 APB1 分频 > 1,定时器时钟 = APB1 的 2 倍"——既满足慢速外设,又尽量不损失定时器精度。

三个时钟概念:

名称含义
TIMxCLK定时器外设时钟(来自 RCC),频率取决于挂载总线和 RCC 倍频设置
CK_INT内部时钟。定时器选择"内部时钟源"模式时作为驱动源,基础定时器只会用 CK_INT
CK_PSC预分频器输入时钟,真正输入到 PSC 的时钟

细节:

        对基础定时器而言,TIMxCLK = CK_INT = CK_PSC,可以把这三个概念当成"一根导线"上的概念。

        之所以存在三组概念,是为了手册表达方便和未来硬件扩展(比如通用定时器可以通过外部引脚输入时钟,那时 CK_INT 就不等于 TIMxCLK 了)。

5.2.2 时基单元(Time-Base Unit)

这是基础定时器的最核心部分,由三个 16 位寄存器组成:

寄存器位数取值范围作用

TIMx_CNT

(计数器)

16 位[0, 65535]时钟信息到来时自动计数,完成时间的量化

TIMx_ARR

(自动重装载寄存器)

16 位[0, 65535]与未来超时时间相关:设置未来时间点,时基单元比较 CNT 与 ARR

TIMx_PSC

(预分频器)

16 位[0, 65535]动态配置时间分辨率(分频)

① TIMx_PSC 预分频器(偏移 0x28,复位值 0x0000)

寄存器位定义:

15  14  13  12  11  10   9   8   7   6   5   4   3   2   1   0
┌───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┐
│                        PSC[15:0]  (rw)                        │
└───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┘
  • 位 15:0 PSC[15:0]:预分频器数值;
  • 计数器的时钟频率 CK_CNT = fCK_PSC / (PSC[15:0] + 1);
  • 在每一次更新事件(UEV)时,PSC 的数值被传送到实际的预分频寄存器(影子)中。

为什么分频系数是 PSC+1 而不是 PSC?

         因为 PSC=0 时至少要"1 分频"(不分频)。

        如果公式是 /PSC,PSC=0 就会除以 0——所以硬件规定 实际分频系数 = PSC + 1:

需求PSC 设置实际分频系数
1 倍分频(不分频)PSC = 01
1000 倍分频PSC = 9991000
最大分频PSC = 6553565536
实际分频系数 = PSC[15:0] + 1,范围 [1, 65536]
F_CK_CNT = F_CK_PSC ÷ (PSC[15:0] + 1)

② TIMx_CNT 计数器(偏移 0x24,复位值 0x0000)

寄存器位定义:

15  14  13  12  11  10   9   8   7   6   5   4   3   2   1   0
┌───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┐
│                        CNT[15:0]  (rw)                        │
└───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┘
  • 位 15:0 CNT[15:0]:计数器数值;
  • 有了分频后的 CK_CNT,计数器每经历一个时钟周期就自动计数一次;
  • 基础定时器只支持向上计数(0,1,2,3,4...),其他计数方式(向下、中央对齐)是通用定时器的功能,不用考虑;
  • 为什么是 16 位?16 位最大 65535,配合分频器足以覆盖绝大多数定时需求(比如 72MHz 最高分辨率下,ARR=65535 时最长周期约 0.91ms,但加上 PSC 最大分频 65536,最长可达约 59.6s)。

③ TIMx_ARR 自动重装载寄存器(偏移 0x2C,复位值 0x0000)

寄存器位定义:

15  14  13  12  11  10   9   8   7   6   5   4   3   2   1   0
┌───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┐
│                        ARR[15:0]  (rw)                        │
└───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┘
  • 位 15:0 ARR[15:0]:自动重装载数值;
  • ARR 的数值将传送到实际的自动重装载(影子)寄存器中;
  • 如果自动重装载数值为 0,则计数器停止;
  • 为什么名字这么怪("自动重装载")?——因为未来一旦超时,定时器会把预加载寄存器中的值自动重新装载(重装)到影子寄存器中,这个动作是硬件自动完成的,所以叫"自动重装载"。后面讲影子寄存器时会彻底明白,此处先当普通 16 位寄存器。
5.2.3 溢出与超时(重点概念,逐步拆解)

假设设置 ARR = 3、时钟 1000Hz(1ms 一次计数)。我们用表格跟踪计数器的一生:

脉冲序号时间点CNT 值与 ARR=3 比较是否触发动作
(启动)0ms0未到否
第 1 个脉冲1ms1未到否
第 2 个脉冲2ms2未到否
第 3 个脉冲3ms3CNT == ARR否(临界溢出,不算)
第 4 个脉冲4ms4(溢出)CNT == ARR+1是!触发超时

关键认知:

  • CNT == ARR 在硬件角度并不是溢出,也不会触发任何动作,最多称之为临界溢出;
  • 按数字溢出的认识:凡溢出,必超过——计数器计到 ARR 设定值后,再计一个脉冲时发生的归零(或翻转)动作,才叫做定时器溢出;
  • 计数器溢出后自动从 4 变回 0(支持重新开始,可设置);同时时基单元硬件产生一个"溢出信号"触发后续动作。

结论:

        超时条件是 CNT = ARR + 1。

        当你设置 ARR=3、1000Hz 时,超时时间其实是 4ms——超时时间由 ARR+1 决定。

例:设置 ARR=999,表示 1000ms 之后超时;反过来,要 1000ms 超时,就设置 ARR=999。

概念同步:定时器视角里,定时器溢出 = 定时器超时,后续当做一个概念。

5.2.4 计算练习(公式推导 + 典型例题)

先把公式推导一遍(面试要能现场推):

第 1 步:CK_CNT = CK_PSC / (PSC + 1)        ← 分频后的计数时钟
第 2 步:单个周期时长 = 1 / CK_CNT = (PSC + 1) / CK_PSC   ← 计一次数花的时间
第 3 步:超时时间 = 单个周期时长 × (ARR + 1)  ← 共需 ARR+1 次计数才溢出
第 4 步:代入 → 超时时间 = ((PSC + 1) × (ARR + 1)) / CK_PSC  ← 总公式

练习一(正向计算):TIMxCLK=72MHz,分频 PSC=71,ARR=999,求单次超时时间和 1s 内超时次数?

① 频率被切分:72 / (71+1) = 1MHz → 即 1us 触发一次计数
② 超时时间:(999+1) × 1us = 1000us = 1ms
③ 1s 内超时次数:1s / 1ms = 1000 次

练习二(逆向计算):需求每 500ms 触发一次超时,怎么设置参数?

① 让定时器工作在最高频率 TIMxCLK = 72MHz
② 设定分频:期望每 100us 产生一次脉冲触发计数 → CK_CNT = 10kHz
③ CK_CNT = CK_PSC / (PSC+1) → PSC+1 = 72MHz / 10kHz = 7200 → PSC = 7199
   (预分频器 16 位最大 65535,7199 < 65535,设置合法)
④ 500ms / 100us = 5000 次计数 → ARR = 5000 - 1 = 4999
   (自动重装载 16 位最大 65535,4999 < 65535,设置合法)

练习三(综合):想获得 1s 周期、2us 分辨率,怎么配?

① 分辨率 2us → CK_CNT = 500kHz → PSC+1 = 72MHz / 500kHz = 144 → PSC = 143
② 1s / 2us = 500000 次计数 → 但 ARR 最大只能 65535!
③ 结论:单靠基础定时器无法同时满足"高分辨率 + 长周期",需要换方案
   (提高分辨率放弃周期 / 降低分辨率 / 用 32 位计数器组合——这正是通用定时器/级联的意义)

小结:定时器配置的本质是用 PSC 定"刻度",用 ARR 定"总数",二者共同决定超时时间。

5.2.5 影子寄存器(关键机制,三阶段拆解)

自动重装载寄存器 TIMx_ARR(Auto-Reload Register)有些特殊,由 2 个寄存器组成:

  1. 预加载寄存器(Preload Register):程序员可以看见的、用于读/写操作的寄存器;
  2. 影子寄存器(Shadow / Active Register):程序员看不见,但在内部真正起作用的寄存器。

读写路径:

        对 ARR 的读、写操作,实际是通过读写预加载寄存器实现的;

        预加载寄存器中的数值会在特定时机(更新事件 UEV)自动传输到影子寄存器中(单向传输,就像通信主题里数据寄存器和移位寄存器)。

超时比较的对象:

        超时比较,都是计数器 CNT 和影子寄存器比较——影子寄存器才是硬件使用的寄存器。

        参考手册上看到的寄存器其实是预加载寄存器,而硬件内部参与比较的是影子寄存器。

未来一旦超时,基础定时器会自动:

  1. TIMx_ARR 自动把预加载寄存器的值设置到影子寄存器中("自动重装载"名字的由来);
  2. 计数器 CNT 自动清 0,方便下次重新开始计数。

为什么需要影子寄存器?(三阶段场景)

假设:

        时钟源 1ms 一个周期,计数器 1ms 自增一次,超时时间是 100ms 之后,当前计数器已经计数 60ms。

场景一(无影子寄存器):突然想把超时时间从 100ms 改为 50ms。

① 修改立即生效(无影子缓冲)
② 当前计数器已经是 60 > 新设定 50
③ 超时规则是"计数器 == 自动重装载值 + 1",可当前计数器已经超过了
④ 只能等计数器计数到 65535,回绕到 0,再重新计数到 50+1 才触发超时
→ 当前周期错乱,完全不满足要求!

场景二(有影子寄存器):同样突然把超时时间从 100ms 改为 50ms。

① 用户修改 → 只改预加载寄存器(程序员看见的)
② 影子寄存器保持 100 不动 → 老的计数功能正常进行
③ 当前周期完整跑完(计数到 100+1 触发超时)
④ 超时瞬间 → 硬件自动把预加载寄存器的新值 50 装载到影子寄存器
⑤ 计数器清零,开启新的计数周期(按 50 跑)
→ 整个时序完全正确!

影子寄存器的本质意义:

        把"参数修改的时刻"和"参数生效的时刻"解耦。

        预加载寄存器本质是影子寄存器的缓存。

可以不用缓存吗?

        可以。TIMx_CR1 的 ARPE 位(位7) 控制:

ARPE行为风险
0(禁用缓冲)写入预加载寄存器的值直接传输到影子寄存器,修改立即生效可能在计数器运行过程中打断当前周期
1(启用缓冲)新值暂存预加载寄存器,只在更新事件 UEV 时才传到影子寄存器,下一周期生效无(推荐)

        PSC 预分频器也有影子寄存器,但不支持用户使能、默认就是开启的——你直接修改 PSC 即可,当定时器超时更新时,PSC 的影子寄存器也会被同步更新。

        最佳实践:推荐使用带缓冲的定时器。

5.2.6 超时事件与中断(触发)

对定时器要有一个认知:定时器的核心功能之一就是"触发"。四类触发行为:

触发类型说明状态
触发自己更新超时后:① 重装 ARR 预加载到影子寄存器(无新配置就继续用老的);② 计数器 CNT 重新清零;③ PSC 预分频器自动更新Done
触发执行代码基本定时器连接 NVIC,直接向 CPU 触发中断,CPU 执行定时器中断处理方法Done
触发拷贝数据基本定时器连接 DMA,超时时间触发时向 DMA 发起请求进行数据拷贝(定时器只是触发源,协助其他外设搬运)Done
触发其他硬件功能计数器溢出,推动触发控制器,通过 TRGO 产生脉冲,触发其他硬件工作(最核心的是 DAC)后面 DAC/ADC 章节讲

中断路径细节:

  • 基础定时器连接 NVIC(嵌套向量中断控制器),超时事件直接向 CPU 触发中断;
  • 中断号:TIM6_IRQn = 54、TIM7_IRQn = 55(向量表地址 0x0000_0118 / 0x0000_011C);
  • 在 HAL 库中,两个定时器共用同一个中断服务函数框架:TIM6_IRQHandler / TIM7_IRQHandler 内部调用 HAL_TIM_IRQHandler,最终回调 HAL_TIM_PeriodElapsedCallback——用户只需要重写这个回调函数即可。

DMA 路径细节:

        定时器连接 DMA 只是未来作为触发源,协助其他片上外设进行数据搬迁(例如定时器定时把内存中的数据搬到 DAC 寄存器,实现波形输出);UP = Update,即"时间到了"的信号。

事件与中断(重要辨析):

  • 事件(广义):硬件状态的特定变化(如电平翻转、计数溢出),是内部逻辑电路产生的一个脉冲信号,代表"发生了什么"——是客观发生的事实;
  • 中断路径:CPU 响应某个事件并暂停当前任务去处理它的过程——是 CPU 对特定事件的一种处理方式;
  • 事件路径(狭义):事件脉冲不经过 CPU,直接送往其他硬件模块(触发 DAC/ADC 启动、触发 DMA 搬运数据),实现外设之间的自主协同——是其他硬件对特定事件的一种自主处理方式。

(类比:快递来了是"事件";你自己去取是"中断路径";让你舍友去取是"事件路径"。事件是客观发生的,处理方式可以选。)

5.2.7 使能相关(DIER / SR)

        定时器超时后产生事件,可以触发 CPU 中断、DMA、DAC 等各种动作,但不一定每次全部生效——必须按需设置,相关配置寄存器就是 TIMx_DIER(DMA/中断使能寄存器,偏移 0x0C):

15  14  13  12  11  10   9   8   7   6   5   4   3   2   1   0
┌───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┐
│       Reserved          │UDE│        Reserved        │     │UIE│
└───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┘
                          │rw                              │rw
位名称作用
位8UDE更新 DMA 请求使能:0 禁止 / 1 使能更新 DMA 请求
位0UIE更新中断使能:0 禁止 / 1 使能更新中断

(DIER = Timer DMA/Interrupt Enable Register。)

与事件相关的还有状态寄存器 TIMx_SR(偏移 0x10):

15  14  13  12  11  10   9   8   7   6   5   4   3   2   1   0
┌───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┐
│                     Reserved                       │     │UIF│
└───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┘
                                                     │rc w0
  • 位 15:1:保留,始终读为 0;
  • 位 0 UIF(Update interrupt flag):更新中断标志,硬件在更新中断时设置该位,由软件清除;
  • 0 = 没有产生更新;1 = 产生了更新中断。下述情况下由硬件设置:
    • 计数器产生上溢/下溢并且 CR1 中 UDIS=0;
    • 如果 CR1 中 URS=0 且 UDIS=0,当使用 EGR 寄存器的 UG 位重新初始化计数器 CNT 时。

也就是说:

        只要产生了事件,这个标志位就被设置了,和你是否使能了中断无关。

        有的用户喜欢轮询检测这个标志位来确认是否有事件产生(虽然很少这么干),所以它叫"状态寄存器"。

轮询 vs 中断:

方式原理适用
轮询主循环里不断读 SR.UIF,为 1 就处理并软件清零简单场景、学习验证
中断DIER.UIE=1,NVIC 使能,超时自动跳中断服务函数实际工程主流做法
5.2.8 补充话题:两个关键控制寄存器

TIMx_CR1(控制寄存器1,偏移 0x00)——定时器行为的主控:

15  14  13  12  11  10   9   8   7   6   5   4   3   2   1   0
┌───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┐
│                Reserved   │ARPE│Res│     │OPM│URS│UDIS│CEN│
└───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┘
                            │rw  │   │rw  │rw │rw  │rw │rw │
位名称作用
位0

CEN

(Counter Enable)

计数器使能位 = 定时器"总开关"。

1 启动计数;

0 停止并保持当前值。

默认情况下时钟脉冲并不会让计数器立即开始计数,计数功能本身也要被使能(配置完 PSC 和 ARR 后,最后一步通常就是置 1)

位1

UDIS

(Update Disable)

更新禁用位。

0 允许更新(溢出或软件 UG 触发 UEV,影子寄存器被加载);

1 禁用更新(修改的 PSC/ARR 不生效)。

常用于原子性精密配置(见下)

位2

URS

(Update Request Source)

更新请求源选择。

0:溢出、软件 UG、从模式触发都会发中断/DMA 请求;

1:只有计数器溢出才发请求(防止写 UG 初始化时意外进中断)。

基础定时器只考虑溢出触发,此位通常设为 1

位3

OPM

(One-Pulse Mode)

单脉冲模式。

0 循环计数(溢出后自动归零继续);

1 单次计数(下一次更新事件后硬件自动清 CEN,定时器停止)——一次性闹钟还是周期性闹钟的选择

位7ARPE自动重载预装载使能(即"是否使用缓存",前面讲过)

UDIS 的原子性应用场景(重点):

场景:

        需要同时更改定时器的 PSC(频率)和 ARR(周期),且必须保证这两个值在同一个计数周期内生效。

风险:

        如果先写了 PSC,此时定时器刚好溢出产生更新事件 UEV,那么下一周期会变成"新频率 + 旧周期"的错误组合,导致输出波形畸变。

锁定逻辑:

① 先把 UDIS 设为 1(关门,禁止更新)
② 从容地写入新的 PSC
③ 从容地写入新的 ARR
④ 再把 UDIS 恢复为 0(开门)

        这样,刚才写进去的两个值会等待下一次溢出时整整齐齐地一起生效。

URS 的场景:

        当 URS=0 时,用软件写 UG 位也会发出中断/DMA 请求——这会导致"我只想手动刷一下参数,结果误进了一次中断"。

        设 URS=1 后,只有计数器溢出才发请求,写 UG 就不会打扰中断逻辑。

        基础定时器只需要考虑溢出触发,这个位通常也是 1。

OPM 的场景:

        你设定闹钟的时候,不也有一次性闹钟和周期性闹钟?

        OPM=1 就是一次性:下一次更新事件(溢出)后,硬件自动将 CEN 位清零,定时器停止工作;OPM=0 是循环:溢出后自动归零重新开始。


TIMx_EGR(事件产生寄存器,偏移 0x14)——定时器的"手动强制按钮":

15  14  13  12  11  10   9   8   7   6   5   4   3   2   1   0
┌───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┐
│                        Reserved                        │UG │
└───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┘
                                                        │w
  • 位 0 UG(Update Generation):只写(Write-only)的触发开关,该位由软件设置、由硬件自动清除;
  • 0:无作用;
  • 1:重新初始化定时器的计数器并产生对寄存器的更新(注意:预分频器也被清除,但预分频系数不变)。

写 UG=1 后的三个连带动作:

  1. 计数器归零:当前 CNT 立即复位为 0;
  2. 强制影子寄存器更新:修改过的 PSC/ARR 新值立刻生效,无需等待当前周期结束;
  3. 产生信号:若使能了中断(UIE=1),立即置位 UIF 并触发中断。

为什么需要它(问题背景):

        STM32 的很多关键寄存器(PSC、ARR)都有影子寄存器机制。

        如果在代码里改了频率,定时器必须等当前的这一轮(可能很长)数完了、产生溢出后新频率才会生效。

        如果你不在意当前计数完、更在意立即让配置生效,就需要一种"手动让配置生效"的机制——就是 EGR 的 UG 位。

常见场景:

        初始化时修改了 PSC 等参数,硬件通常要等一个计数周期结束才更新,导致第一次中断往往时间不对(依然使用默认旧参数)。

解决:

        在启动定时器(CEN=1)前,手动将 EGR 的 UG 位置 1——强制新参数立即同步到影子寄存器并清零计数器,确保定时器从第一微秒起就按新配置运行。

5.2.9 触发控制器(TRGO / CR2)

        如果把定时器比作"闹钟",TRGO(Trigger Output)就是闹钟响时往外拉出的一根"信号线":让定时器在特定时刻(溢出、复位)自动向其他硬件(DAC、ADC 或其他定时器)发送硬件级启动信号,无需 CPU 介入。

  • TRGO 的本质:一条内部连接线,直接把定时器与 ADC、DAC 或其他定时器互联;
  • 逻辑上的通知关系:触发控制器反而是被时基单元通知的——时基单元超时进行事件分发时,其中一路分发给触发控制器,用来控制其他片上外设。

相关寄存器 TIMx_CR2(控制寄存器2,偏移 0x04),只有一组 MMS[2:0](主模式选择) 需要设置:

15  14  13  12  11  10   9   8   7   6   5   4   3   2   1   0
┌───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┐
│                        Reserved              │ MMS[2:0] │Res│
└───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┘
                                               │   rw     │
MMS模式说明
000复位手动强刷(UG 位)时发送脉冲,用于多定时器中途强行对齐(基础定时器不考虑)
001使能只要计数器在跑就输出高电平,充当其他外设启停的同步总开关(不考虑)
010更新每次溢出时发送节拍脉冲,驱动 DAC/ADC 全自动定时转换的核心模式——最常用

MMS 的本质:

        决定 TRGO 输出信号的生成规则——把哪种内部事件"翻译"成对外信号。

最佳实践:

        CR2 最长设置的就是 010。

        当时基单元超时的时候,自动让 TRGO 触发脉冲信号,触发下级设备工作,最常见外设就是 DAC。

典型应用:

        DAC 波形发生器——定时器定时触发 DAC 转换,DMA 定时搬运采样点数据到 DAC 数据寄存器,三硬件全自动协作,CPU 全程不参与,输出正弦波/三角波。这部分后面学 DAC/ADC 会细说。


6. 总结:基础定时器功能框图(寄存器视角)+ 初始化流程

6.1 完整功能框图

APB1 总线 ← RCC_TIMxCLK → 触发控制器/控制逻辑 → CK_PSC → 预分频器 PSC(/N) → CK_CNT → 计数器 CNT
                                                                                          │
                                         自动重装载寄存器 ARR(预加载 → 影子,设定计数值)◄─┘
                                                                                          │ CNT == ARR+1 → 溢出
                                     ┌───────────────── 更新事件 UEV(溢出脉冲)────────────────┐
                                     ▼                ▼                 ▼                ▼                ▼
                              更新自身配置       触发外设 TRGO      数据搬运 DMA        状态标记 SR      申请中断 NVIC
                            (影子寄存器同步)  (CR2 MMS=010→DAC)  (DIER UDE=1)    (置位 UIF)    (DIER UIE=1)

核心机制:时基单元溢出脉冲(Update Event)会同步触发 5 种动作:

  1. 更新自身配置(影子寄存器):将 ARR 和 PSC 的预装载值立即同步到物理执行寄存器,确保下一周期参数生效且不产生频率抖动;
  2. 触发外设(TRGO):根据 CR2 配置(如 MMS=010)将溢出信号映射为 TRGO 脉冲,直接驱动 DAC 转换或 ADC 采样;
  3. 数据搬运(DMA):产生 DMA 请求,全自动从内存将下一个采样点数据传送到 DAC 寄存器,无需 CPU 参与;
  4. 状态标记(SR):在状态寄存器中挂起更新标志位 UIF;
  5. 申请中断(NVIC):若使能了 DIER 中的中断位,则通知 CPU 执行回调函数。

触发 DMA 或中断是可配置的,其他都会自动触发完成。

6.2 基础定时器初始化流程(寄存器级,可背)

① 使能时钟:RCC 打开 TIM6/TIM7 时钟(APB1 上)
② 配置分频:写 TIMx_PSC = 分频系数 - 1          ← 定"刻度"(分辨率)
③ 配置周期:写 TIMx_ARR = 计数次数 - 1           ← 定"总数"(超时时间)
④ 决定缓冲:CR1.ARPE = 1(推荐带缓冲)
⑤ 强制生效:写 TIMx_EGR 的 UG = 1               ← 让新参数立即同步到影子寄存器
⑥ 使能中断:DIER.UIE = 1;NVIC 使能 TIM6/TIM7 中断并配优先级
⑦ 启动计时:CR1.CEN = 1                        ← 计数器开始跑
⑧ 处理超时:在 HAL_TIM_PeriodElapsedCallback 里写业务代码(或轮询 SR.UIF)

附录 A:定时器存储器映像

定时器属于片上外设,TIM2~TIM7 挂在 APB1(基址 0x4000 0000 区段):

外设地址范围
TIM20x4000 0000 - 0x4000 03FF
TIM30x4000 0400 - 0x4000 07FF
TIM40x4000 0800 - 0x4000 0BFF
TIM50x4000 0C00 - 0x4000 0FFF
TIM60x4000 1000 - 0x4000 13FF
TIM70x4000 1400 - 0x4000 17FF
TIM10x4001 2C00(挂 APB2)

(寄存器偏移 + 外设基址 = 寄存器绝对地址,如 TIM6->CR1 = 0x4000 1000 + 0x00。)

附录 B:基础定时器寄存器速查表(必背)

寄存器偏移复位值关键位
TIMx_CR10x000x0000CEN(0)、UDIS(1)、URS(2)、OPM(3)、ARPE(7)
TIMx_CR20x040x0000MMS2:0
TIMx_DIER0x0C0x0000UIE(0)、UDE(8)
TIMx_SR0x100x0000UIF(0)
TIMx_EGR0x140x0000UG(0)
TIMx_CNT0x240x0000CNT[15:0]
TIMx_PSC0x280x0000PSC[15:0]
TIMx_ARR0x2C0x0000ARR[15:0]

附录 C:定时器超时公式(推导结论)

1. CK_CNT = CK_PSC / (PSC + 1)
2. 单个周期时长 = 1 / CK_CNT = (PSC + 1) / CK_PSC
3. 定时器超时时间 = 单个周期时长 × (ARR + 1)
4. 定时器超时时间 = ((PSC + 1) × (ARR + 1)) / CK_PSC

基础定时器 TIM 面试题

一、概念题

Q1. 定时器的本质功能是什么?它是怎么把"时间"变成可比较的"数字"的?时间分辨率由什么决定?

A:

        本质功能是设定延时,超时后触发完成某种工作。

        通过"固定频率时钟源 + 计数器"实现:时钟源每触发一次脉冲,计数器自动 +1;因为时钟周期固定(如 1000Hz=1ms/次),计数器数字就对应时间(123 → 123ms)。

        时间分辨率 = 时钟周期的长度,时钟频率越高分辨率越高(1MHz → 1us)。


Q2. STM32F103ZE 的定时器分哪三类?分别有哪些?基础定时器只有什么功能?

A:

        ① 基础定时器(TIM6、TIM7):仅基本计时功能和 DAC 转换触发;

        ② 通用定时器(TIM2~TIM5):基础功能 + 输入捕获、输出比较、PWM 生成、正交编码器输入;

        ③ 高级定时器(TIM1、TIM8):再增加互补 PWM 与死区控制、紧急刹车、主从模式。

        "11 个定时器" = 8 个片上定时器 + SysTick + IWDG + WWDG。


Q3. 软件延时(HAL_Delay)有哪些缺点?为什么需要硬件定时器?

A:

        ① 占用 CPU:延时期间 CPU 空转;

        ② 时间不精准:受中断抢占、代码执行时间影响;

        ③ 无法并发:多任务不同周期无法并行计时。硬件定时器由硬件独立计数,CPU 零负担、超时精准触发、多定时器可并行。

二、时钟题

Q4. TIMxCLK 和 APB1 的关系是什么?为什么有"×2"的设定?

A:

        APB1 分频系数 = 1 时,TIMxCLK = APB1;

        分频系数 ≠ 1 时,TIMxCLK = 2 × APB1。

        原因是对定时器时钟的精度补偿:APB1 总线上挂着 UART/I2C 等慢速外设,总线频率不能太高(≤36MHz),而定时器希望跑在最高频(72MHz)保证计时精度——所以只要分频就自动翻倍。


Q5. 描述 STM32F103 标准配置下的时钟树路径(从晶振到定时器时钟)。

A:

        HSE 8MHz → PLL ×9 → SYSCLK 72MHz → AHB(HCLK=72MHz)→ APB1 预分频 /2(PCLK1=36MHz)→ 定时器时钟补偿 ×2 → TIMxCLK=72MHz。

        APB1 分频由 RCC_CFGR 的 PPRE1[2:0] 决定。


Q6. TIMxCLK、CK_INT、CK_PSC、CK_CNT 的关系?

A:

        对基础定时器,TIMxCLK = CK_INT = CK_PSC(一根导线上的三个名字);

        CK_CNT = CK_PSC/(PSC+1) 才是真正驱动计数器的时钟。

三、寄存器与计算题(高频)

Q7. TIMx_PSC、TIMx_CNT、TIMx_ARR 各自的作用、取值范围?为什么分频系数是 PSC+1?

A:

        三者都是 16 位寄存器,范围 [0,65535]。

        PSC 调整时间分辨率,实际分频系数 = PSC+1([1,65536]),因为 PSC=0 时必须"至少 1 分频",公式不能除以 0;

        CNT 被 CK_CNT 驱动向上计数(基础定时器只支持向上计数),量化时间;

        ARR 设定未来超时时间,与影子寄存器配合。


Q8. 什么是定时器"溢出"?超时条件到底是什么?"临界溢出"指什么?

A:

        溢出 = 计数器超过 ARR 设定值后再计一个脉冲时发生的归零动作。

        硬件上 CNT == ARR 不是溢出(最多叫临界溢出),超时条件是 CNT = ARR + 1("凡溢出,必超过")。

        设置 ARR=3、1000Hz 时超时时间是 4ms——超时时间由 ARR+1 决定。


Q9. 计算:TIMxCLK=72MHz、PSC=71、ARR=999,求单次超时时间和 1s 内超时次数。

A:

        CK_CNT = 72/(71+1) = 1MHz(1us/次);

        超时时间 = (999+1)×1us = 1ms;

        1s 内超时 1000 次。


Q10. 计算:要求每 500ms 触发一次超时(TIMxCLK=72MHz),给出一种配置。

A:

        取 CK_CNT=10kHz(100us/次)→ PSC+1 = 72MHz/10kHz = 7200 → PSC=7199;500ms/100us=5000 次 → ARR=4999。(均 < 65535 合法。)


Q11. 背出定时器超时总公式,并现场推导。

A:

        定时器超时时间 = ((PSC+1) × (ARR+1)) / CK_PSC。

        推导:CK_CNT = CK_PSC/(PSC+1) → 单周期时长 = 1/CK_CNT = (PSC+1)/CK_PSC → 溢出需 (ARR+1) 次计数 → 超时时间 = 单周期时长 × (ARR+1)。


Q12. 想同时获得高分辨率(2us)和长周期(1s),基础定时器能做到吗?为什么?

A:

        不能。2us 分辨率 → PSC+1=144;1s 需要 500000 次计数,ARR 最大只有 65535。基础定时器单靠 16 位 ARR 无法兼顾"高分辨率 + 长周期",需要降分辨率、或用通用定时器级联等方案。

四、影子寄存器题(重点难点)

Q13. 什么是影子寄存器?为什么 ARR 需要它?

A:

        ARR 由预加载寄存器(程序员可读写)和影子寄存器(实际生效、用户不可见)组成。写 ARR 实际写的是预加载寄存器,在更新事件 UEV 时才传到影子寄存器,硬件比较的是 CNT 与影子寄存器。

原因:

        无影子时中途改 ARR 立即生效,若当前计数器已超过新值,要等回绕一整圈才能触发超时,当前周期错乱;

        有影子时当前周期完整执行完、新周期才生效——本质是把"参数修改时刻"和"参数生效时刻"解耦。


Q14. ARPE 位的作用?

A:

        自动重载预装载使能(CR1 位7)。

        ARPE=0:ARR 无缓冲,修改立即生效(可能打断当前周期);

        ARPE=1:ARR 有缓冲,新值在下一个 UEV 才生效,保证当前周期完整。

        最佳实践:推荐带缓冲。


Q15. PSC 有影子寄存器吗?和 ARR 的影子有什么区别?

A:

        有。但 PSC 的影子寄存器不支持用户使能、默认就是开启的——直接修改 PSC 即可,超时更新时自动同步。ARR 的影子则由 ARPE 位控制是否启用缓冲。

五、事件与中断题

Q16. 事件(广义)、中断、事件路径(狭义)的区别?

A:

        事件是硬件状态的客观变化(计数溢出产生的脉冲),代表"发生了什么";

        中断是 CPU 响应事件并暂停当前任务去处理(自己取快递);

        **事件路径(狭义)**是事件脉冲不经过 CPU、直接送往其他硬件(DMA/DAC/ADC)实现外设自主协同(让舍友取快递)。


Q17. 定时器超时后(溢出脉冲)会同步触发哪些动作?哪些可配置?

A:

        五路:

                ① 更新自身配置(影子寄存器同步 + CNT 清零)——自动;

                ② TRGO 触发外设(如 DAC,CR2.MMS=010)——自动;

                ③ DMA 请求搬运(DIER.UDE)——可配置;

                ④ 置位 SR.UIF 状态标志——自动;

                ⑤ 申请 NVIC 中断(DIER.UIE)——可配置。


Q18. TIMx_DIER 和 TIMx_SR 里分别有哪些关键位?UIF 有什么特性?

A:

        DIER(0x0C):位8 UDE(更新 DMA 请求使能)、位0 UIE(更新中断使能);

        SR(0x10):位0 UIF(更新中断标志,硬件置位、软件清除)。

        只要产生事件 UIF 就被置位,与是否使能中断无关——所以可以轮询 SR.UIF 判断事件。

六、控制与实战题

Q19. CR1 寄存器中 CEN、UDIS、URS、OPM 分别是什么?

A:

        CEN(位0)= 计数器使能"总开关",默认时钟脉冲不会让计数器计数,配置完 PSC/ARR 后最后一步置 1;

        UDIS(位1)= 更新禁用,用于原子性配置(先关门→写 PSC/ARR→开门,避免"新频率+旧周期"错配);

        URS(位2)= 更新请求源选择,1 表示只有溢出才发请求(防写 UG 意外进中断);        

        OPM(位3)= 单脉冲模式,0 循环、1 单次(溢出后自动清 CEN)。


Q20. 什么场景下需要用到 UDIS?简述"原子性配置"的步骤和原因。

A:

        需要同时修改 PSC 和 ARR 且保证同一周期生效时。

风险:

        先写 PSC 时若定时器刚好溢出,下一周期变成"新频率+旧周期"的错配组合。

步骤:

        ① UDIS=1 关门(禁止更新);

        ② 写新 PSC;

        ③ 写新 ARR;

        ④ UDIS=0 开门——两个值等下一次溢出时整整齐齐一起生效。


Q21. EGR 寄存器的 UG 位是什么?初始化时为什么要先写它?写它有三个什么连带动作?

A:

        UG(位0)是只写触发开关,写 1 强制立即产生 UEV:

        ① 计数器归零;

        ② 强制影子寄存器更新(PSC/ARR 新值立刻生效);

        ③ 若 UIE=1 则置位 UIF 并触发中断。

        初始化时在 CEN=1 前先写 UG=1,能让新参数从第一微秒起就生效,否则第一次中断会按旧参数运行。


Q22. CR2 的 MMS 是什么?为什么 010(更新)最常用?

A:

        MMS(主模式选择,CR2 位 4:6)决定 TRGO 输出信号的生成规则:000 复位(UG 强刷时发脉冲)、001 使能(计数器跑就输出高电平)、010 更新(每次溢出发节拍脉冲)。

        010 最常用是因为它让定时器在每次溢出时自动给 DAC/ADC 发触发脉冲,实现全自动定时转换(如定时器+DMA+DAC 三硬件协作输出波形),无需 CPU 参与。


Q23. 口述题:描述基础定时器从配置到产生一次定时中断的完整流程。

A(答题要点):

        ① RCC 时钟树产生 TIMxCLK(基础定时器 = APB1 ×2,72MHz);

        ② 配置 PSC 分频得到 CK_CNT(如 PSC=71 → 1MHz);

        ③ 配置 ARR 设定超时时间(如 999 → 1ms);

        ④ 若用影子缓冲,配置 CR1.ARPE=1;

        ⑤ 写 EGR.UG=1 强制新参数立即同步到影子寄存器;

        ⑥ 配置 DIER.UIE=1 使能更新中断;

        ⑦ CR1.CEN=1 启动计数器;

        ⑧ CK_CNT 驱动 CNT 向上计数,CNT 达到 ARR+1 时溢出 → 产生 UEV:影子寄存器重装、CNT 清零、SR.UIF 置位、NVIC 触发中断 → CPU 跳转 TIM6/TIM7 中断服务函数,调用 HAL_TIM_PeriodElapsedCallback,清除标志,循环往复。


Q24. 如果 UIF 标志产生后不软件清除,会发生什么?

A:

        UIF 是硬件置位、软件清除。

        若中断模式不清理,中断服务函数退出后会再次进入中断(因为中断挂起标志一直有效),导致中断死循环、主程序被饿死。

        所以 HAL 库回调里/中断处理中必须读 SR 后写 0 清除 UIF(HAL 库的 HAL_TIM_IRQHandler 会自动 __HAL_TIM_CLEAR_FLAG 处理)。


转载自 CSDN-专业IT技术社区

原文链接:https://blog.csdn.net/2602_95649725/article/details/167126683

文章来源转载

评论

评论列表

微信小程序
QQ小程序

关于作者

点赞数:0
关注数:0
粉丝:0
文章:0
关注标签:0
加入于:--