资讯动态

STM32定时器时间基准详解:从PSC、ARR到PWM与输入捕获

发布时间:2026/10/3 7:08:51 来源:尧图企业网站定制
1. 从定时器到底在数什么这个问题说起很多人第一次接触 STM32 的定时器脑子里冒出来的第一个念头是定时器嘛就是定时的。然后打开参考手册看到 TIMx_PSC、TIMx_ARR、TIMx_CNT 这几个寄存器照着例程配一通灯闪起来了就觉得学会了。可一旦要算一个精确的 1kHz 中断、要输出一个特定频率的 PWM、要用输入捕获测外部信号频率立刻就卡住了——因为根本不清楚那个数到底在数什么数一下代表多长时间。这个问题看起来基础但它恰恰是 STM32 定时器所有高级用法的地基。PWM 输出、输入捕获、编码器接口、单脉冲模式、从模式门控全部建立在时间基准这个概念之上。时间基准搞不清楚后面所有配置都是碰运气。我见过太多项目里定时器参数是抄来的——别人给个 PSC71、ARR999能用就行至于为什么是这两个数说不出来。等到换一颗主频不同的芯片或者要改频率就完全不知道怎么下手。这篇内容就是要把这件事彻底讲透。我会从时钟树讲到预分频器从计数器讲到自动重装载寄存器把定时器在数什么这个问题拆到寄存器级别。同时会给出实际项目里常用的配置计算方法、CubeMX 里的对应关系、以及几个我踩过的坑。不管你是刚学 STM32 的新手还是用了几年但一直没系统梳理过时钟链路的开发者应该都能从中拿到点东西。关键词里出现的 STM32、定时器、TIM1、PSC、ARR基本就是本文的主线。TIM1 是高级定时器功能最全拿它当例子讲清楚之后TIM2~TIM5 这些通用定时器、TIM6/TIM7 这些基本定时器原理完全一致只是功能裁剪不同。2. 定时器的数从哪来时钟源到计数器的完整链路2.1 定时器不是自己会走它靠总线时钟喂要理解定时器在数什么第一步得搞清楚它的心跳从哪来。STM32 的定时器本质上是一个计数器它每来一个时钟脉冲就加一或减一。这个时钟脉冲不是凭空产生的而是从系统时钟树上一级一级分下来的。以最常见的 STM32F103 为例定时器的时钟来源大致是这样一条链路外部晶振通常是 8MHz→ PLL 倍频 → SYSCLK72MHz→ AHB 预分频器 → APB2 或 APB1 总线 → 定时器时钟。这里有个非常容易被忽略的细节APB 总线的定时器时钟有一个倍频规则。当 APB 预分频系数为 1 时定时器时钟等于 APB 总线时钟当 APB 预分频系数不为 1也就是 2、4、8、16时定时器时钟等于 APB 总线时钟的 2 倍。在 F103 的标准配置里APB2 预分频系数为 1所以 APB2 时钟是 72MHz挂在 APB2 上的 TIM1 时钟也是 72MHz。而 APB1 预分频系数为 2APB1 总线时钟是 36MHz但因为预分频不为 1挂在 APB1 上的 TIM2~TIM7 时钟要乘以 2也就是 72MHz。这个规则我第一次看到的时候也觉得别扭为什么分频了反而要乘 2其实这是 ST 的一种设计取舍APB1 最高只能跑 36MHz但定时器本身能跑 72MHz所以给定时器单独开了一条倍频通路让定时器不受 APB1 低速的限制。理解这一点很重要因为很多人算定时器频率时直接拿 APB1 的 36MHz 去算结果算出来的频率差了一倍。提示在 CubeMX 的 Clock Configuration 页面里你能直接看到 APB1 Timer clocks 和 APB2 Timer clocks 这两栏它们显示的就是定时器实际拿到的时钟频率不用自己手动推。这是验证时钟配置最快的方法。2.2 预分频器 PSC把快时钟变慢定时器拿到 72MHz 的时钟之后如果直接拿它计数那计数器的变化速度快到 1/72 微秒一次根本没法用。所以需要先减速这就是预分频器PrescalerPSC的作用。PSC 是一个 16 位的寄存器取值范围 0~65535。它的工作方式是每来 PSC1 个时钟脉冲才输出一个计数脉冲给计数器。注意这里是PSC1不是 PSC。因为 PSC 寄存器写 0 的时候表示不分频也就是 1 分频。所以计数器实际拿到的计数频率是计数频率 定时器时钟 / (PSC 1)举个例子定时器时钟 72MHzPSC 设为 71那么计数频率 72MHz / (711) 72MHz / 72 1MHz。也就是说计数器每 1 微秒加一。这个 1MHz 就是很多人做微秒级延时或者微秒级时间戳时常用的基准。这里有个实操经验PSC 的值是在计数器运行过程中可以改的但改动不会立即生效要等到下一次更新事件UEV才会把新值装载进影子寄存器。如果你在运行中动态改 PSC 想实现变频必须注意这个延迟否则会出现一次旧频率的周期。我早期做可变频率 PWM 的时候就栽在这上面改完 PSC 发现输出频率要过一个周期才变排查了半天。2.3 自动重装载寄存器 ARR数到多少算一轮计数器CNT在计数频率的驱动下不断累加那它数到多少的时候归零呢这就由自动重装载寄存器Auto-Reload RegisterARR决定。ARR 也是一个 16 位寄存器TIM1 等高级定时器在某些模式下可以到 16 位基本定时器也是 16 位取值范围 0~65535。计数器从 0 数到 ARR然后产生一个更新事件Update Event同时计数器归零重新开始下一轮。所以一轮计数的总时间也就是定时器的溢出周期是溢出周期 (PSC 1) × (ARR 1) / 定时器时钟 溢出频率 定时器时钟 / ((PSC 1) × (ARR 1))还是拿 72MHz 举例。如果 PSC71ARR999那么计数频率 72MHz / 72 1MHz溢出频率 1MHz / 1000 1kHz溢出周期 1ms这就是一个标准的 1ms 定时中断配置。每 1ms 进一次中断中断里可以对一个变量加一加 1000 次就是 1 秒这就是软件 RTC 或者任务调度的基础。2.4 CNT、PSC、ARR 三者的关系用一张表说清寄存器全称作用位宽关键点CNT计数器实际在数的值16位每个计数脉冲加一或减一PSC预分频器对定时器时钟分频16位实际分频系数是 PSC1ARR自动重装载决定计数上限16位实际周期是 ARR1 个计数把这三个寄存器串起来理解定时器时钟经过 PSC 分频后驱动 CNT 计数CNT 数到 ARR 就溢出并归零如此循环。所谓定时器在数什么数的就是经过分频后的时钟脉冲个数数满 ARR1 个就是一轮。3. 为什么 PSC 和 ARR 都要加一影子寄存器的存在3.1 加一是因为0 代表 1 分频很多人对 PSC1 和 ARR1 感到困惑觉得为什么不直接写分频系数。这其实是硬件设计的惯例寄存器值 0 表示不分频或计数 1 次这样设计的好处是复位后默认值 0 就能工作不需要额外初始化。从数学上看PSC 寄存器能表示 0~65535 共 65536 个值对应分频系数 1~65536。同理 ARR 能表示 1~65536 个计数周期。这个加一的偏移是理解定时器计算的关键算错这一步频率就会差一个量级。我见过一个很典型的错误有人想配 1kHz定时器时钟 72MHz他算 72MHz / 1000 72000然后 PSC 写 72000ARR 写 1000。结果 PSC 直接溢出超过 65535而且频率完全不对。正确的做法是把 72000 拆成两部分比如 PSC71分频 72ARR999计数 100072 × 1000 72000正好。3.2 影子寄存器为什么改了值不当场生效STM32 的 PSC 和 ARR 都有影子寄存器shadow register。你写进去的值先存到预装载寄存器真正参与计数的是影子寄存器。只有在更新事件发生时预装载的值才会被拷贝到影子寄存器。这个机制的意义在于保证一个计数周期的完整性。如果 ARR 改了立刻生效那么当前这个周期可能数到一半就突然改变上限导致周期长度不可预测。有了影子寄存器当前周期照旧走完下一个周期才用新值。ARR 是否启用预装载由 TIMx_CR1 寄存器的 ARPE 位控制。ARPE0 时写 ARR 立即生效ARPE1 时写 ARR 要等更新事件才生效。PSC 则总是有预装载缓冲写进去后等更新事件生效。注意在 CubeMX 里配置定时器时有个 auto-reload preload 选项对应的就是 ARPE 位。做固定频率应用时这个选项影响不大但做动态变频比如变频 PWM、可变周期中断时一定要搞清楚它是否使能否则会出现周期抖动。3.3 更新事件的产生与中断更新事件UEV在以下几种情况下产生计数器溢出向上计数数到 ARR、计数器下溢向下计数数到 0、或者软件主动置位 UG 位。更新事件可以触发中断、DMA 请求也可以只用来重装载影子寄存器。在定时中断的配置里我们使能的是 UIEUpdate Interrupt Enable位。每次更新事件产生如果 UIE 使能就会进中断服务函数。中断里通常要做的事情是清除更新中断标志位UIF否则会反复进中断。这里有个新手常犯的错误忘记清中断标志。结果程序一进中断就出不来或者主循环完全跑不动。STM32 的 HAL 库在 HAL_TIM_IRQHandler 里会自动清标志但如果你用标准库或者直接操作寄存器就必须手动清。4. 从公式到代码几种典型定时器配置的完整推导4.1 定时中断1ms 周期怎么配这是最常见的需求。假设定时器时钟 72MHz要 1ms 中断一次。目标溢出频率 1kHz溢出周期 1ms。公式72,000,000 / ((PSC1) × (ARR1)) 1000所以 (PSC1) × (ARR1) 72000。72000 的因数分解有很多种组合常见的有PSC172ARR11000 → PSC71ARR999PSC1720ARR1100 → PSC719ARR99PSC11000ARR172 → PSC999ARR71选哪种这里有个经验PSC 和 ARR 的分配会影响计数精度和动态调整的灵活性。如果 ARR 大计数范围大适合做需要细调周期的场合如果 PSC 大计数频率低适合做长周期定时。对于 1ms 这种常用周期我一般选 PSC71、ARR999因为计数频率正好是 1MHzCNT 的值直接就是微秒数调试时读 CNT 就能知道当前在一个周期的哪个位置非常直观。CubeMX 里的配置对应关系Prescaler 填 71Counter Period 填 999Clock Division 一般填 No DivisionCounting Mode 选 Up。4.2 PWM 输出频率和占空比怎么算PWM 模式下ARR 决定 PWM 周期CCR捕获/比较寄存器决定占空比。PWM 频率 定时器时钟 / ((PSC1) × (ARR1))占空比 CCR / (ARR1) × 100%比如要输出 1kHz、占空比 50% 的 PWM定时器时钟 72MHz先定 (PSC1) × (ARR1) 72000选 PSC71ARR999占空比 50% → CCR 500如果要动态调占空比只改 CCR 即可频率不变。如果要动态调频率改 ARR 或 PSC但要注意前面说的影子寄存器延迟。这里有个细节PWM 模式 1 和模式 2 的区别。模式 1 是 CNT CCR 时输出有效电平模式 2 是 CNT CCR 时输出无效电平。配合输出极性控制可以实现各种相位关系。做互补 PWM 驱动电机或者做全桥的时候这两个模式和极性配置要一起考虑。4.3 输入捕获测频率时定时器在数什么输入捕获模式下定时器还是在数 PSC 分频后的时钟但 CNT 的值会在捕获事件发生时被拷贝到 CCR 寄存器。通过两次捕获的 CCR 差值就能算出外部信号的周期。比如定时器计数频率 1MHz每微秒加一捕获到上升沿时 CCR11000下一次上升沿 CCR26000那么周期 (6000-1000) 微秒 5000 微秒 5ms频率 200Hz。这里要注意溢出处理。如果外部信号周期比定时器溢出周期还长CNT 会溢出多次两次捕获的差值就不对了。解决办法是开启溢出中断记录溢出次数最终周期 溢出次数 × (ARR1) (CCR2 - CCR1)。这个逻辑在测低频信号时是必须的很多人测高频没问题一测低频就出错就是没处理溢出。4.4 一张表对比几种模式的配置差异模式PSC/ARR 作用关键寄存器典型用途定时中断决定中断周期PSC, ARR, UIE任务调度、软件计时PWM 输出ARR 定频率CCR 定占空比PSC, ARR, CCR, CCMR电机驱动、LED 调光输入捕获CNT 值被捕获到 CCRPSC, ARR, CCR, CCER测频率、测脉宽输出比较CNT 与 CCR 比较触发PSC, ARR, CCR波形生成、单脉冲编码器接口CNT 随编码器脉冲增减PSC, ARR, SMCR电机测速、位置检测5. 那些年我在定时器上踩过的坑5.1 时钟源搞错频率差一倍前面提到的 APB1 倍频规则是我踩过的第一个大坑。当时用 TIM3 做 1ms 中断我按 APB136MHz 算配了 PSC35、ARR999以为得到 1kHz。结果实测中断周期是 0.5ms频率 2kHz正好差一倍。后来才反应过来 TIM3 实际拿到的是 72MHz。这个坑的教训是算定时器频率前先确认定时器挂在哪条总线上再确认 APB 预分频系数最后套用倍频规则。最稳妥的办法是直接在 CubeMX 的时钟树页面看 APB1 Timer clocks 的数值不要自己推。5.2 中断里忘了清标志程序卡死用标准库或者寄存器操作时进中断第一件事应该是清中断标志。我早期写代码中断服务函数里只写了业务逻辑忘了清 UIF结果中断反复触发主循环完全没机会执行现象就是程序跑飞了。HAL 库的 HAL_TIM_IRQHandler 会帮你清标志但前提是你调用的是它。如果你自己写了 TIM3_IRQHandler 却没调用 HAL 的处理函数标志照样不清。这个细节在混合使用 HAL 和寄存器操作时特别容易出问题。5.3 动态改 ARR 导致周期抖动做可变频率 PWM 时我在运行中直接改 ARR。因为 ARPE 默认可能是关闭的改完立即生效导致当前周期被截断输出波形出现一个异常窄或异常宽的脉冲。后来把 ARPE 打开让新值在更新事件时生效波形就干净了。如果你的应用对波形连续性有要求比如驱动电机、做音频 PWM一定要使能 ARR 预装载并且最好在更新中断里改 ARR保证改的时刻是周期边界。5.4 计数频率选太高中断开销扛不住有人为了追求高精度把计数频率设得很高比如 72MHz 不分频ARR 设很小导致中断频率极高。结果 CPU 大部分时间都在进中断、出中断主程序几乎跑不动。中断频率和 CPU 开销是直接相关的。一般来说1kHz~10kHz 的中断频率是比较舒服的范围再高就要考虑用 DMA 或者硬件外设来分担。如果只是要精确计时完全可以用 1MHz 计数频率配大 ARR中断频率还是 1kHz但 CNT 的分辨率是 1 微秒两全其美。5.5 TIM1 的高级功能带来的额外配置TIM1 是高级定时器有刹车输入、死区插入、互补输出这些功能。用 TIM1 做普通 PWM 时如果忘了使能 MOEMain Output Enable主输出使能位PWM 是出不来的。这个位在 TIMx_BDTR 寄存器里标准库和 HAL 库都有对应的宏或者函数。我第一次用 TIM1 输出 PWM 时配置全对但没波形查了半天才发现是 MOE 没开。6. 把时间基准这件事真正用起来6.1 用 CNT 做微秒级时间戳当计数频率设为 1MHz 时CNT 的值就代表当前周期内经过的微秒数。配合一个记录溢出次数的变量可以拼出一个微秒级的时间戳volatile uint32_t overflow_count 0; void TIM3_IRQHandler(void) { if (TIM3-SR TIM_SR_UIF) { TIM3-SR ~TIM_SR_UIF; overflow_count; } } uint64_t get_micros(void) { uint32_t ov; uint16_t cnt; do { ov overflow_count; cnt TIM3-CNT; } while (ov ! overflow_count); // 防止读取过程中发生溢出 return ((uint64_t)ov 16) | cnt; }这段代码的关键是那个 do-while 循环先读溢出计数再读 CNT再检查溢出计数有没有变。如果变了说明读 CNT 的过程中发生了溢出CNT 已经归零需要重读。这个技巧在读取跨溢出的计数值时非常有用做精确时间测量时必备。6.2 定时器级联扩展位宽16 位定时器最大只能数 65536 个计数。如果计数频率 1MHz那最大周期只有 65.536ms。要测更长的周期可以把两个定时器级联一个定时器溢出时触发另一个定时器计数这样位宽就扩展到 32 位。STM32 的定时器支持内部触发级联通过 TIMx_SMCR 寄存器的 TS 位选择触发源。比如 TIM2 作为从定时器TIM3 作为主定时器TIM3 的更新事件作为 TIM2 的时钟。这样 TIM2 每收到一次 TIM3 的溢出就加一等效于把计数范围扩大了 65536 倍。这个技巧在测低频信号、做长周期计时时很有用但配置起来稍微复杂要同时设置主定时器的 TRGO 输出和从定时器的触发输入。6.3 用 CubeMX 验证配置但别完全依赖它CubeMX 能自动算出 PSC 和 ARR省去手动计算。但它的自动计算不一定符合你的需求——它可能给你一个 PSC0、ARR7199 的组合虽然频率对但计数频率是 72MHzCNT 的分辨率是 1/72 微秒读起来不直观。我的习惯是用 CubeMX 生成初始化框架但 PSC 和 ARR 的值自己算、自己填。这样每个数字都有明确含义调试时心里有数。而且换芯片、改主频时自己会算就能快速调整不用重新摸索。6.4 不同系列芯片的差异要注意STM32 不同系列的定时器位宽和功能有差异。F1 系列的定时器基本都是 16 位F4 系列部分定时器是 32 位比如 TIM2 和 TIM5G0、G4 等新系列的定时器数量和功能也有变化。做跨系列移植时先确认目标芯片的定时器位宽和时钟树别直接照搬参数。比如 F4 的 TIM2 是 32 位ARR 可以设到 0xFFFFFFFF做长周期定时就不需要级联了。而 F1 的 TIM2 是 16 位同样的需求就得级联或者降低计数频率。7. 回到最初的问题定时器到底在数什么把上面这些串起来答案其实很清晰定时器数的是经过预分频器分频后的时钟脉冲个数数满 ARR1 个就完成一轮产生更新事件。这个数的速率由定时器时钟和 PSC 共同决定这个数的上限由 ARR 决定。理解了这个本质再看那些具体的应用——PWM、输入捕获、输出比较、编码器接口——它们无非是在这个基础计数机制上增加了在特定计数值时做什么的逻辑。PWM 是在 CNT 和 CCR 比较时翻转输出输入捕获是在外部事件时把 CNT 存进 CCR输出比较是在 CNT 等于 CCR 时触发动作。底层还是那个 CNT 在数数。我个人在实际项目里的体会是定时器配置出问题九成以上是时间基准算错了或者时钟源搞错了。把时钟树、PSC、ARR 这三者的关系理清楚把公式推导一遍再动手配置比盲目试参数高效得多。尤其是做电机控制、电源管理这类对时序敏感的应用时间基准的准确性直接决定系统能不能稳定工作。最后分享一个小技巧调试定时器时把 CNT 的值实时输出到 DAC 或者用 GPIO 翻转来观察能非常直观地看到计数器的运行状态和周期。比如在更新中断里翻转一个 GPIO用示波器测这个 GPIO 的频率就能验证定时器周期是否和预期一致。这个方法比在 IDE 里看变量靠谱得多因为它是硬件层面的真实信号。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑