STM32定时器时钟链与PSC/ARR参数计算实战指南

发布时间:2026/10/3 16:46:07
STM32定时器时钟链与PSC/ARR参数计算实战指南 1. 从一个让人抓狂的现象说起刚接触 STM32 那会儿我调一个 TIM1 的定时中断目标是 1ms 翻转一次 LED。代码写完了PSC 填 7199ARR 填 99心里默念“72MHz 除以 7200 再除以 100正好 1kHz”烧进去一看LED 闪得跟抽风一样快。改了几次参数要么快得看不清要么慢得像卡死。当时最想不通的一个问题是定时器到底在数什么它数的那个“1”究竟有多长这个问题看起来基础但它卡住了太多人。你会发现网上教程一上来就甩公式T (PSC1)*(ARR1)/Fclk可你真去改参数的时候还是不知道这个 Fclk 到底是谁、从哪来、中间经过了哪些分频。更麻烦的是TIM1、TIM2、TIM3 挂在不同的总线上时钟源不一样你抄别人的参数到自己板子上就是不对。这篇东西就是把我自己踩过的坑、翻过的参考手册页、用示波器一遍遍量出来的经验整理出来。核心就讲清楚一件事STM32 的定时器时间基准从哪里来PSC 和 ARR 到底在干什么怎么算、怎么验、怎么不踩坑。不管你是刚点亮第一颗 LED 的新手还是已经在做电机 FOC、超声波测距、串口超时这些活的老手只要你的代码里出现过 TIM这篇都值得过一遍。2. 定时器的本质它数的不是时间是“节拍”2.1 把定时器想象成一个只会数数的傻子很多人一开始就把定时器理解成“计时器”觉得它内部有个秒表。其实不是。定时器的核心是一个计数器CNT它只会做一件事每来一个时钟脉冲就加一或者减一。它根本不知道“秒”是什么它只知道“我又数了一个”。那“时间”是怎么来的靠换算。你知道每个脉冲之间的间隔是固定的比如 1/72 微秒那数了 72000 个脉冲就是 1 毫秒。定时器负责数数你负责把数出来的个数翻译成时间。这就是全部的秘密。所以问题的关键变成了这个“每个脉冲之间的间隔”是谁给的答案是一条时钟链从芯片内部的振荡器一路传到计数器的输入端中间要经过好几道分频。搞懂这条链你就搞懂了定时器。2.2 时钟链的完整路径从晶振到 CNT以最常见的 STM32F103 为例一条典型的时钟路径是这样的外部晶振通常 8MHz→ PLL 倍频到 72MHz → AHB 总线72MHz→ APB2 预分频器 → APB2 外设时钟 → TIM1 的时钟输入 → TIMx_PSC 预分频器 → CNT 计数器。这里有几个关键点必须掰开说系统时钟 SYSCLKF103 最常见是 72MHz这是整个芯片的主节拍。AHB 和 APB 总线AHB 通常等于 SYSCLKAPB1 和 APB2 可以再分频。F103 默认 APB1 是 36MHzAPB2 是 72MHz。一个容易被忽略的规则当 APB 预分频系数不为 1 时挂在该总线上的定时器时钟会被自动倍频。也就是说APB1 虽然是 36MHz但挂在 APB1 上的 TIM2~TIM7 实际拿到的是 72MHz。这个规则坑过无数人你以为定时器时钟是 36MHz算出来的时间就全错了。TIM1 和 TIM8 挂在 APB2 上APB2 是 72MHz 且预分频为 1所以它们直接拿 72MHz。TIM2~TIM7 挂在 APB1 上APB1 是 36MHz 但预分频不为 1所以它们实际也拿 72MHz。结果就是 F103 上几乎所有通用定时器的输入时钟都是 72MHz这也是为什么大家算参数时都按 72MHz 来。2.3 PSC 和 ARR一个管“步子多大”一个管“走多少步”现在进入正题。定时器输入时钟比如 72MHz进来之后先经过PSC预分频器再驱动CNTCNT 数到ARR自动重装载寄存器的值就溢出产生更新事件。用走路来类比输入时钟 72MHz 意味着每秒有 7200 万个脉冲步子太密了。PSC 决定你一步跨多大。PSC7199就是把 7200 个脉冲合并成 1 个计数脉冲相当于一步跨 7200 个原始脉冲。此时计数频率 72MHz / 7200 10kHz。ARR 决定你走多少步到达终点。ARR99就是数 100 步0 到 99溢出一次。溢出周期 100 / 10kHz 10ms。所以完整公式是定时周期 T (PSC 1) × (ARR 1) / Fclk注意那个 1。PSC 和 ARR 都是从 0 开始计数的写 0 表示 1 分频写 7199 表示 7200 分频。这个 1 是新手最容易漏的地方漏了之后误差就是几千倍。2.4 为什么是 PSC 和 ARR 两个寄存器而不是一个有人会问既然都是分频为什么不用一个寄存器搞定原因是这两个寄存器解决的是不同粒度的问题。PSC 负责粗调把高频时钟降到计数器能处理的频率范围ARR 负责精调决定计数的周期长度。如果只有一个寄存器你要么分频比太大导致分辨率不够要么太小导致计数值溢出。两个配合就能在很宽的频率范围内灵活调节。举个实际例子。你要产生 50Hz 的 PWM 驱动舵机目标周期 20ms。如果 PSC71计数频率 1MHzARR 需要 20000-1 19999。ARR 是 16 位的最大 65535装得下。如果 PSC7199计数频率 10kHzARR 需要 200-1 199。也行但分辨率只有 200 级PWM 占空比调节会很粗糙。如果 PSC0计数频率 72MHzARR 需要 1440000-1超过 16 位上限装不下。所以选 PSC 的原则是在 ARR 不溢出的前提下尽量让计数频率高一些这样占空比分辨率更高。这就是为什么舵机代码里常见 PSC71、ARR19999 这种组合。3. 不同定时器的时钟来源差异别抄错参数3.1 高级定时器、通用定时器、基本定时器的区别STM32 的定时器不是铁板一块。F103 上大致分三类类型代表挂载总线时钟来源特点高级定时器TIM1, TIM8APB272MHz带互补输出、死区、刹车用于电机控制通用定时器TIM2~TIM5APB172MHz倍频后带捕获、比较、PWM基本定时器TIM6, TIM7APB172MHz倍频后只能定时无外部引脚关键差异在于功能而不是时钟。时钟这块F103 上它们最终都是 72MHz所以参数算法一样。但如果你换到 F4、F7、H7 这些芯片总线结构变了定时器时钟可能来自不同的 APB 分支甚至有的定时器时钟可以独立配置。换芯片第一件事就是去查参考手册的时钟树章节确认你的定时器到底拿多少频率。3.2 内部时钟、外部时钟、内部触发定时器不止一种“数法”前面讲的都是内部时钟CK_INT也就是从总线来的时钟。但定时器还能从别的地方拿时钟外部时钟模式 1从 TIMx_ETR 引脚输入外部脉冲每个脉冲计数一次。做频率计、外部事件计数就用这个。外部时钟模式 2从 TIMx_CH1 或 CH2 引脚输入。内部触发由其他定时器或外设触发比如 TIM1 触发 TIM2做级联。这几种模式的配置方式完全不同涉及 SMCR 寄存器的 SMS 位。如果你用 CubeMX在 Clock Source 里选 Internal Clock 还是 ETR2 之类的就是在切这个。新手最常见的错误是明明想用内部时钟定时结果 Clock Source 选错了计数器根本不动还以为是代码问题。3.3 一个真实案例为什么我的 TIM3 中断频率是别人的两倍我遇到过一次同事从 F103 换到 F407把 TIM3 的 PSC 和 ARR 直接照搬结果中断频率翻倍。原因就是 F407 的 APB1 定时器时钟结构和 F103 不同。F407 的 APB1 最高 42MHz但定时器倍频规则不一样实际拿到的时钟可能是 84MHz。参数没变时钟变了频率自然就变了。教训换芯片必须重新确认定时器输入时钟不能无脑复制参数。确认方法有两个一是查参考手册的时钟树图二是用示波器直接量定时器输出引脚反推实际频率。4. 手把手算一遍从需求到 PSC 和 ARR4.1 已知定时周期反推参数假设需求用 TIM2 产生 1kHz 中断即周期 1ms。已知 TIM2 时钟 72MHz。第一步确定计数频率。计数频率 1 / 定时分辨率。如果你希望 ARR 不超过 65535计数频率至少是 1kHz × 65536 ≈ 65.5MHz这不可能。所以要先选一个合理的计数频率。通常做法是先定 PSC让计数频率落在几十 kHz 到几 MHz 之间。比如选计数频率 1MHz则 PSC 72MHz / 1MHz - 1 71。第二步算 ARR。ARR 定时周期 × 计数频率 - 1 1ms × 1MHz - 1 1000 - 1 999。验证T (711) × (9991) / 72MHz 72 × 1000 / 72e6 1e-3 s 1ms。正确。4.2 已知 PSC 和 ARR算实际频率反过来如果你拿到一份代码PSC7199ARR9999想算中断频率计数频率 72MHz / (71991) 10kHz。 中断频率 10kHz / (99991) 1Hz。也就是每秒中断一次。这个组合常见于做 1 秒计时。4.3 参数选择的三个原则ARR 尽量大ARR 越大定时分辨率越高调节越细腻。但别超过 6553516 位定时器。PSC 尽量小PSC 小意味着计数频率高配合大 ARR 能覆盖更长的定时范围。但 PSC 太小会导致 ARR 溢出。留余量如果算出来 ARR 接近 65535稍微调大 PSC给后续调整留空间。4.4 代码示例标准库和 HAL 库的写法标准库配置 TIM2 为 1ms 中断TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); TIM_TimeBaseStructure.TIM_Period 999; // ARR TIM_TimeBaseStructure.TIM_Prescaler 71; // PSC TIM_TimeBaseStructure.TIM_ClockDivision TIM_CKD_DIV1; TIM_TimeBaseStructure.TIM_CounterMode TIM_CounterMode_Up; TIM_TimeBaseInit(TIM2, TIM_TimeBaseStructure); TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE); TIM_Cmd(TIM2, ENABLE);HAL 库写法TIM_HandleTypeDef htim2; htim2.Instance TIM2; htim2.Init.Prescaler 71; htim2.Init.CounterMode TIM_COUNTERMODE_UP; htim2.Init.Period 999; htim2.Init.ClockDivision TIM_CLOCKDIVISION_DIV1; HAL_TIM_Base_Init(htim2); HAL_TIM_Base_Start_IT(htim2);注意 HAL 库的Period就是 ARRPrescaler就是 PSC填的都是“减一”后的值。CubeMX 里填的也是减一后的值但界面上会显示实际分频比别被绕晕。5. 实测验证别只信计算用示波器说话5.1 用 GPIO 翻转验证定时精度最直接的验证方法在定时中断里翻转一个 GPIO用示波器或逻辑分析仪量引脚波形。如果算出来是 1kHz示波器应该显示 1kHz 方波。我习惯在中断里写GPIOA-ODR ^ GPIO_Pin_0;然后探头夹 PA0。这样量出来的频率就是中断频率的一半因为一个周期翻转两次心里要有数。5.2 用 MCO 引脚输出时钟STM32 有个 MCO 引脚可以把内部时钟输出到引脚上直接用示波器看。配置 RCC 的 MCO 相关位选择输出 SYSCLK、HSE、PLL 等。这个方法能帮你确认系统时钟到底跑多少排除时钟配置错误。5.3 用 DWT 周期计数器测代码耗时如果你想知道某段代码执行了多少个时钟周期可以用 DWT 的 CYCCNT 寄存器。先使能 DWT读两次 CYCCNT差值就是周期数除以主频就是时间。这个方法在调优定时器中断服务函数时特别有用能发现中断里是不是干了太多活。5.4 常见测量误差来源示波器探头带宽不够测高频信号时波形失真频率读数不准。GPIO 翻转速度限制GPIO 有最大翻转频率超过之后波形变形。中断响应延迟中断不是瞬间响应的有入栈、取向量等开销高频中断时这个延迟占比很大。时钟配置错误最常见系统时钟没跑到预期值所有定时全错。6. 踩坑实录那些年我调定时器翻过的车6.1 PSC 和 ARR 忘了减一这是最经典的坑。公式里写的是(PSC1)和(ARR1)但寄存器里填的是 PSC 和 ARR 本身。如果你在代码里写TIM_Prescaler 7200实际分频是 7201误差虽然小但在精密定时场景下会累积。正确写法是填 7199。6.2 中断标志没清程序卡死定时器中断里必须清中断标志否则会一直进中断。标准库用TIM_ClearITPendingBit(TIM2, TIM_IT_Update);HAL 库用__HAL_TIM_CLEAR_IT(htim2, TIM_IT_UPDATE);。漏了这句程序就死在中断里出不来。6.3 中断优先级配置不当如果定时器中断和其他中断比如串口冲突优先级没配好会导致定时不准甚至丢中断。用NVIC_PriorityGroupConfig配好分组再给定时器中断设一个合理的抢占优先级和响应优先级。高频中断建议给高抢占优先级。6.4 在中断里做耗时操作有人在定时中断里做浮点运算、串口打印、甚至延时结果中断执行时间超过定时周期程序直接跑飞。中断服务函数要短平快只做标志位设置、数据搬运这类轻量操作重活放到主循环里做。6.5 时钟没使能RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);这行忘了写定时器根本不工作。CubeMX 会自动生成但手写代码时容易漏。6.6 不同系列芯片的时钟差异前面提过F1 和 F4 的定时器时钟不同。还有 L4、G0 这些低功耗系列时钟树更复杂有的定时器时钟可以选 LSE、LSI 等低速时钟。换芯片必须重新看时钟树。7. 进阶话题定时器还能这么用7.1 PWM 输出ARR 决定频率CCR 决定占空比PWM 模式下ARR 还是决定周期但占空比由 CCR捕获比较寄存器决定。CCR/ARR 就是占空比。比如 ARR999CCR300占空比就是 30%。这个在调电机、调 LED 亮度时天天用。7.2 输入捕获测频率和占空比输入捕获模式下定时器记录外部信号边沿到来时的 CNT 值两次捕获的差值就是信号周期对应的计数值。配合 PSC 换算就能测出外部信号频率。超声波测距、红外解码都用这个。7.3 编码器模式直接读电机转速TIM 的编码器模式能直接对接增量式编码器的 A/B 相硬件自动计数CPU 只管读 CNT 就行。做电机闭环控制时非常方便。7.4 单脉冲模式精确输出一个脉冲单脉冲模式下定时器收到触发后输出一个指定宽度的脉冲然后自动停止。用于精确控制步进电机走一步、激光打一个点这类场景。7.5 从模式定时器级联一个定时器可以作为另一个定时器的预分频器实现超长定时。比如 TIM1 溢出触发 TIM2 计数两个 16 位定时器级联就能实现 32 位定时范围。8. 常见问题速查表现象可能原因排查方法定时器完全不工作时钟没使能检查 RCC 使能代码中断频率不对PSC/ARR 算错重新按公式计算注意减一中断进不去NVIC 没配置检查中断优先级和使能程序卡死中断标志没清加清标志语句频率是预期两倍时钟源选错查时钟树确认定时器时钟波形抖动中断里耗时太长精简中断服务函数换芯片后不对时钟结构不同重新确认定时器输入时钟PWM 占空比不对CCR 配置错误检查 CCR 值和极性设置9. 我个人在实际操作中的几点体会调定时器这件事说到底就是搞清楚“时钟从哪来、经过什么分频、数到什么值溢出”这三件事。公式就那一个但背后的时钟树每个芯片都不一样这是最容易翻车的地方。我的习惯是拿到一个新芯片先不急着写业务代码先配一个最简单的定时中断翻转 GPIO用示波器量一下。确认时钟和分频都对上了再往上叠功能。这一步花十分钟能省后面几小时的调试。还有一点CubeMX 虽然方便但它生成的时钟配置有时候和你的预期不一样尤其是自动计算 PSC 和 ARR 的时候。我建议关键参数自己手算一遍和 CubeMX 显示的对照不一致就以手册为准。工具是辅助理解才是根本。最后定时器中断里千万别放耗时操作。我见过太多人因为在中