基础定时器 TIM 详解:从极简原理到寄存器全解析
1. 极简原理:把定时器当作一个"闹钟"
生活中带"定时"功能的场景:发条或电池驱动闹钟按秒计时,到了设定时间(闹钟响),执行既定事项(起床、去上学)。
定时器的本质功能:设定延时,然后超时后,通过超时触发完成某种工作。
要在单片机内实现类似效果,需要做到三点:
- 把时间本身进行量化(这样才能进行时间比较);
- 设定未来超时时间;
- 超时,触发完成特定工作。
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 打印一次数据,之前只能:
cppwhile(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 功能配置表):
型号 Flash SRAM 通用定时器 高级定时器 基础定时器 GPIO F103Rx 256K 48K 4(TIM2~5) 2(TIM1/8) 2(TIM6/7) 51 F103Vx 384K 64K 4 2 2 80 F103Zx 512K 64K 4 2 2 112 "多达 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):
cpp来自RCC的TIMxCLK → 内部时钟CK_INT → 触发控制器 → 预分频器PSC → CK_CNT → 计数器CNT └→ 自动重装载寄存器ARR(预加载→影子)◄──┘(比较) │ CNT 溢出 → 事件 UEV → 中断和DMA输出 / TRGO至DAC下面一部分一部分来看。
5.2.1 时钟源(时钟树全景)
定时器要根据固定频率更新计数器,就必须要有时钟源。
在 STM32F103ZET6 中,这个时钟源由 RCC 模块时钟树提供。
完整时钟路径(标准配置下):
bashHSE 外部晶振 8MHz → PLL 锁相环 ×9 → SYSCLK 系统时钟 72MHz → AHB 预分频器(HCLK 分频=1) → HCLK 72MHz → APB1 预分频器(PPRE1=/2) → PCLK1 = 36MHz (APB1 总线时钟) → 定时器时钟补偿 ×2 → TIMxCLK = 72MHz其中 APB1 预分频系数由 RCC_CFGR 寄存器的 PPRE12:0 位决定(000=不分频、100=/2、101=/4...)。我们不用手动配它(标准库/CubeMX 已配好),但要理解它的后果。
关键规则:
基础定时器挂接在 APB1 总线上,APB1 最大频率 36MHz。
输入到定时器内部的时钟频率和 APB1 预分频器配置系数有关:
如果 APB1 分频系数 = 1,则 TIMxCLK = APB1 如果 APB1 分频系数 ≠ 1,则 TIMxCLK = 2 × APB1
场景 HCLK APB1 分频 APB1 频率 TIMxCLK ① 标准全速(最常见) 72MHz 2 36MHz 36MHz × 2 = 72MHz ② 低速运行(不翻倍) 36MHz 1 36MHz 36MHz ③ 大比例分频 72MHz 4 18MHz 18MHz × 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 = 0 1 1000 倍分频 PSC = 999 1000 最大分频 PSC = 65535 65536
实际分频系数 = 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 比较 是否触发动作 (启动) 0ms 0 未到 否 第 1 个脉冲 1ms 1 未到 否 第 2 个脉冲 2ms 2 未到 否 第 3 个脉冲 3ms 3 CNT == ARR 否(临界溢出,不算) 第 4 个脉冲 4ms 4(溢出) 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 计算练习(公式推导 + 典型例题)
先把公式推导一遍(面试要能现场推):
bash第 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 个寄存器组成:
- 预加载寄存器(Preload Register):程序员可以看见的、用于读/写操作的寄存器;
- 影子寄存器(Shadow / Active Register):程序员看不见,但在内部真正起作用的寄存器。
读写路径:
对 ARR 的读、写操作,实际是通过读写预加载寄存器实现的;
预加载寄存器中的数值会在特定时机(更新事件 UEV)自动传输到影子寄存器中(单向传输,就像通信主题里数据寄存器和移位寄存器)。
超时比较的对象:
超时比较,都是计数器 CNT 和影子寄存器比较**------影子寄存器才是硬件使用的寄存器。**
参考手册上看到的寄存器其实是预加载寄存器,而硬件内部参与比较的是影子寄存器。
未来一旦超时,基础定时器会自动:
- TIMx_ARR自动把预加载寄存器的值设置到影子寄存器中("自动重装载"名字的由来);
- 计数器 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
位 名称 作用 位8 UDE 更新 DMA 请求使能:0 禁止 / 1 使能更新 DMA 请求 位0 UIE 更新中断使能: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,定时器停止)------一次性闹钟还是周期性闹钟的选择 位7 ARPE 自动重载预装载使能(即"是否使用缓存",前面讲过) 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 后的三个连带动作:
- 计数器归零:当前 CNT 立即复位为 0;
- 强制影子寄存器更新:修改过的 PSC/ARR 新值立刻生效,无需等待当前周期结束**;**
- 产生信号:若使能了中断(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),只有一组 MMS2: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 种动作:
- 更新自身配置(影子寄存器):将 ARR 和 PSC 的预装载值立即同步到物理执行寄存器,确保下一周期参数生效且不产生频率抖动;
- 触发外设(TRGO):根据 CR2 配置(如 MMS=010)将溢出信号映射为 TRGO 脉冲,直接驱动 DAC 转换或 ADC 采样;
- 数据搬运(DMA):产生 DMA 请求,全自动从内存将下一个采样点数据传送到 DAC 寄存器,无需 CPU 参与;
- 状态标记(SR):在状态寄存器中挂起更新标志位 UIF;
- 申请中断(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 区段):
外设 地址范围 TIM2 0x4000 0000 - 0x4000 03FF TIM3 0x4000 0400 - 0x4000 07FF TIM4 0x4000 0800 - 0x4000 0BFF TIM5 0x4000 0C00 - 0x4000 0FFF TIM6 0x4000 1000 - 0x4000 13FF TIM7 0x4000 1400 - 0x4000 17FF TIM1 0x4001 2C00(挂 APB2) (寄存器偏移 + 外设基址 = 寄存器绝对地址,如 TIM6->CR1 = 0x4000 1000 + 0x00。)
附录 B:基础定时器寄存器速查表(必背)
寄存器 偏移 复位值 关键位 TIMx_CR1 0x00 0x0000 CEN(0)、UDIS(1)、URS(2)、OPM(3)、ARPE(7) TIMx_CR2 0x04 0x0000 MMS2:0 TIMx_DIER 0x0C 0x0000 UIE(0)、UDE(8) TIMx_SR 0x10 0x0000 UIF(0) TIMx_EGR 0x14 0x0000 UG(0) TIMx_CNT 0x24 0x0000 CNT15:0 TIMx_PSC 0x28 0x0000 PSC15:0 TIMx_ARR 0x2C 0x0000 ARR15: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 的 PPRE12: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处理)。












