ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

STM32定时器时间基准与PSC/ARR配置详解

STM32定时器时间基准与PSC/ARR配置详解 1. 从一个让人抓狂的现象说起刚接触 STM32 那会儿我遇到过一个特别典型的问题用 TIM1 做 1ms 的定时中断代码里 PSC 设成 71ARR 设成 999满心欢喜地以为中断会老老实实每毫秒来一次。结果示波器一挂波形周期差了将近一倍。当时第一反应是芯片坏了换了块板子还是一样折腾了大半天才反应过来——我压根没搞清楚这个定时器到底在数什么它的“时间基准”是从哪儿来的。这个问题其实非常普遍。很多人学 STM32 定时器上来就是抄一段 CubeMX 生成的代码PSC、ARR 两个参数填进去能跑就行。但一旦遇到“为什么我的定时不准”“为什么换个芯片同样的参数结果不一样”“为什么 TIM1 和 TIM2 表现不同”这类问题就彻底懵了。根子就在于定时器本质上只是一个计数器它数的是“时钟脉冲”而不是“时间”。你给它什么样的时钟它就按什么样的节奏数数你让它数多少个脉冲它就多久溢出一次。所谓“定时”不过是“数脉冲”这件事在时间维度上的投影。所以这篇内容我想把这件事彻底讲透STM32 的定时器时间基准到底从哪里来PSC 和 ARR 这两个寄存器在背后做了什么TIM1 这种高级定时器和普通定时器在时钟来源上有什么差别以及实际配置时那些容易踩的坑。不管你是刚点亮第一个 LED 的新手还是已经用定时器做过 PWM、输入捕获、编码器接口的老手只要你对“时间基准”这件事还有一丝模糊这篇都值得从头看到尾。我会尽量用生活化的类比把原理讲清楚再配上可以直接抄的参数计算和实操步骤让你下次配置定时器时心里有底而不是靠试。2. 定时器的本质它到底在数什么2.1 把定时器想象成一个“数豆子的人”要理解定时器先别想寄存器想一个场景有个人坐在传送带旁边数豆子传送带每送来一颗豆子他就在本子上加一。数到某个数比如 1000他就喊一声“到点了”然后把本子清零重新数。这个“喊一声”就是定时器中断“本子清零”就是自动重装载。现在关键问题来了豆子送来的速度由谁决定如果传送带转得快豆子来得密他数到 1000 就快传送带转得慢数到 1000 就慢。传送带的速度就是定时器的时钟源他数到多少喊一次就是 ARR而传送带前面如果还加了个减速齿轮把原始速度降下来那个减速比就是 PSC。这个类比里最重要的洞察是定时器本身不知道“一秒”是多久它只知道“数了多少个脉冲”。时间基准完全来自外部输入的时钟频率。你给它 72MHz它就以 72MHz 的节奏数你给它 36MHz同样的 ARR 值溢出时间就翻倍。这就是为什么同一个工程换到不同主频的芯片上定时会不准——因为时钟源变了而你的 PSC/ARR 没跟着变。2.2 时钟源时间基准的源头活水STM32 的定时器时钟不是凭空来的它有一条清晰的“血脉”。以最常见的 STM32F103 为例通用定时器 TIM2~TIM5 挂在 APB1 总线上高级定时器 TIM1、TIM8 挂在 APB2 总线上。这里有个非常容易被忽略的细节APB 预分频系数会影响定时器时钟。具体规则是这样的如果 APB 预分频系数为 1那么定时器时钟就等于 APB 总线时钟如果 APB 预分频系数大于 1也就是 2、4、8、16那么定时器时钟等于 APB 总线时钟的 2 倍。这个“2 倍”规则坑过无数人。比如系统主频 72MHzAPB1 预分频设为 2那么 APB1 总线时钟是 36MHz但 TIM2 的时钟不是 36MHz而是 72MHz。很多人算 PSC 时按 36MHz 算结果定时直接差一倍。我个人的习惯是配置时钟树的时候先把每个定时器的实际输入时钟在纸上写清楚再动 PSC 和 ARR。这一步花两分钟能省掉后面两小时的调试。你可以用 CubeMX 的 Clock Configuration 界面直接看到各总线频率但要注意它显示的是总线频率定时器时钟还得按上面那条规则自己推一遍。2.3 PSC 和 ARR一个管“节奏”一个管“次数”PSC预分频器和 ARR自动重装载寄存器是定时器配置里最核心的两个参数但它们的角色完全不同很多人会混淆。PSC 决定的是“数数的节奏”。它的工作原理是每来 PSC1 个时钟脉冲计数器才加一。也就是说PSC 把输入时钟做了分频。如果输入时钟是 72MHzPSC 设为 71那么计数器实际的工作频率就是 72MHz / (711) 1MHz也就是每 1 微秒计数器加一。注意这里为什么是 71 而不是 72——因为分频系数是 PSC1这是 STM32 的硬件设计寄存器里存的是“减一后的值”。ARR 决定的是“数到多少归零”。计数器从 0 开始往上数数到 ARR 的值时产生溢出事件更新事件然后回到 0 重新开始。如果 ARR 设为 999计数器会经历 0、1、2……999 共 1000 个状态所以一个完整周期是 1000 个计数脉冲。把两者合起来定时周期就是T (PSC1) × (ARR1) / 定时器时钟频率。还是用 72MHz、PSC71、ARR999 的例子T 72 × 1000 / 72000000 0.001 秒 1ms。这就是那个经典组合的由来。2.4 为什么“数豆子”这个视角如此重要理解了“定时器在数脉冲”这件事很多疑难杂症就迎刃而解了。比如有人问为什么我把 ARR 设成 0 定时器还能工作因为 ARR0 时计数器数到 0 就溢出每个脉冲都触发一次相当于不分频。再比如为什么修改 ARR 后要等下一个周期才生效因为当前周期已经在数了新值要等更新事件后才装载进去。还有为什么 TIM1 的更新中断有时候会莫名其妙多触发一次这涉及到重复计数器和预装载寄存器的细节后面会专门讲。这些问题的答案都藏在“它只是在数脉冲”这个朴素的事实里。一旦你脑子里有了这个模型看数据手册就不再是天书配置寄存器也不再是玄学。3. 时钟从哪来一条完整的血脉追踪3.1 从晶振到定时器信号走过的路我们顺着时钟信号走一遍看看它从最源头的晶振到定时器计数器中间经过了哪些环节。以 STM32F103 用 8MHz 外部晶振、跑到 72MHz 的经典配置为例第一步8MHz 晶振HSE输入到芯片。第二步经过 PLL 倍频8MHz × 9 72MHz这是系统时钟 SYSCLK。第三步SYSCLK 经过 AHB 预分频器通常设为 1得到 HCLK 72MHz。第四步HCLK 经过 APB1 预分频器如果设为 2得到 PCLK1 36MHz。第五步关键的一步——因为 APB1 预分频系数大于 1定时器时钟 TIMxCLK PCLK1 × 2 72MHz。所以最终 TIM2 拿到的是 72MHz。这条链路里任何一环变了定时器的时间基准就变了。比如你把 APB1 预分频改成 4PCLK1 变成 18MHz但 TIMxCLK 还是 36MHz18×2定时周期就翻倍了。我建议你在第一次配置某个定时器时把这条链路完整写一遍标上每一级的频率。这个习惯看起来笨但它是排查定时问题的第一把钥匙。3.2 APB 预分频那个“2 倍”规则到底怎么回事这个“2 倍”规则不是 STM32 故意刁难人它有实际的硬件考量。APB 总线本身跑得比定时器慢是为了降低外设总线的功耗和时序压力但定时器需要更高的分辨率来做精确计时和 PWM所以硬件设计上给定时器单独提供了一路倍频后的时钟。理解了这个设计意图你就不会觉得它是“坑”而是一个合理的取舍。实际配置时如果你用 CubeMX它会在时钟树里自动处理这个关系你只要看最终显示的定时器时钟频率就行。但如果你用标准库或者寄存器直接配置就必须自己算清楚。我见过太多人在这里栽跟头尤其是从 F103 换到 F4 或者 G0 系列时时钟树结构变了老经验直接失效。3.3 内部时钟、外部时钟与触发输入上面讲的是定时器最常用的内部时钟源CK_INT。但 STM32 的定时器其实支持多种时钟源这也是它强大的地方。除了内部时钟还可以选择外部时钟模式 1来自 TIMx_ETR 引脚、外部时钟模式 2来自 TIMx_CH1 或 CH2 引脚、以及内部触发输入ITRx来自其他定时器。这些模式在特定场景下非常有用。比如你想用外部信号作为计数脉冲来测频率就可以用外部时钟模式想让两个定时器级联一个的输出作为另一个的时钟就用内部触发。但要注意一旦切换到外部时钟源时间基准就不再是系统时钟了PSC 和 ARR 的计算方式也要跟着变。这部分内容展开能写一整篇这里先记住一个原则先确认时钟源再算 PSC 和 ARR。3.4 TIM1 和 TIM2 的时钟差异TIM1 是高级定时器挂在 APB2 上TIM2 是通用定时器挂在 APB1 上。在 F103 里APB2 的预分频通常设为 1所以 PCLK2 72MHzTIM1CLK 也是 72MHz。而 APB1 预分频设为 2PCLK1 36MHz但 TIM2CLK 因为那个 2 倍规则也是 72MHz。所以在这个典型配置下TIM1 和 TIM2 的时钟频率恰好都是 72MHz看起来一样。但如果你把 APB1 预分频改成 1PCLK1 变成 72MHz此时 TIM2CLK 就等于 72MHz因为预分频为 1 时不翻倍。而 APB2 如果也保持 1TIM1CLK 还是 72MHz。这种情况下两者仍然相同。真正会出现差异的是当 APB1 和 APB2 预分频设置不同的时候。所以不要想当然地认为“高级定时器时钟一定和通用定时器一样”一定要看具体的时钟树配置。4. PSC 和 ARR 的实操计算与配置4.1 手把手算一个 1ms 定时假设 TIM2 的时钟是 72MHz我们要一个 1ms 的定时中断。目标是让计数器每 1ms 溢出一次。先定 PSC。为了让计算简单我们通常先把计数器频率降到 1MHz也就是每微秒加一。72MHz / 1MHz 72所以分频系数是 72PSC 72 - 1 71。再定 ARR。计数器频率 1MHz要 1ms 溢出需要数 1000 个脉冲。ARR 1000 - 1 999。验证T (711) × (9991) / 72000000 72 × 1000 / 72000000 0.001s 1ms。正确。如果你想改成 500ms有两种思路一是保持 PSC71把 ARR 改成 499999二是保持 ARR999把 PSC 改成 35999。两种都行但要注意 ARR 是 16 位的对于大多数通用定时器最大值 65535所以 499999 超了只能用第二种。这就是为什么大定时通常靠调 PSC 而不是 ARR。4.2 参数选择的三个约束选 PSC 和 ARR 不是随便填的有三个硬约束第一ARR 的位宽限制。通用定时器 TIM2~TIM5 的 ARR 是 16 位最大 65535高级定时器 TIM1、TIM8 也是 16 位。但有些系列的定时器比如 F4 的 TIM2、TIM5ARR 是 32 位的能到 40 多亿。用之前查清楚。第二PSC 的位宽限制。PSC 也是 16 位最大 65535所以最大分频系数是 65536。第三分辨率与周期的权衡。PSC 越大计数器频率越低能实现的最大定时越长但时间分辨率越差。比如 PSC7199计数器频率 10kHz每 100 微秒才加一你就没法做小于 100 微秒的精确计时了。实际选参数时我一般先确定需要的分辨率再反推 PSC最后用 ARR 凑周期。比如需要 1 微秒分辨率那计数器频率就得是 1MHzPSC 就定死了然后看周期需要多长算 ARR 是否在范围内。4.3 预装载寄存器为什么改了 ARR 不立即生效这里要引入一个重要的概念预装载寄存器Preload Register。ARR 和 PSC 都有影子寄存器你写入的值先存到预装载寄存器等更新事件发生时才真正生效。ARR 的预装载是可以通过 ARPE 位控制的。如果 ARPE1ARR 的预装载使能新值在下一个更新事件时生效如果 ARPE0写入 ARR 立即生效。PSC 则总是有预装载的写入后要等更新事件才生效。这个机制的好处是防止在计数过程中修改参数导致波形异常。比如你在做 PWM中途改 ARR 来调频率如果立即生效当前周期可能被截断产生一个畸变的脉冲。有了预装载新值在周期边界生效波形就干净了。但这也带来一个常见困惑为什么我改了 ARR中断频率没马上变因为要等当前周期结束。如果你需要立即生效可以手动产生一个更新事件置位 UG 位强制装载新值。4.4 一个完整的配置示例下面用标准库的写法给一个 TIM2 配置 1ms 中断的完整例子关键地方我加了注释void TIM2_Config(void) { TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; NVIC_InitTypeDef NVIC_InitStructure; // 使能 TIM2 时钟 RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); // 时钟源为内部时钟72MHz // PSC 71分频后 1MHz TIM_TimeBaseStructure.TIM_Prescaler 71; // ARR 9991ms 溢出 TIM_TimeBaseStructure.TIM_Period 999; // 向上计数 TIM_TimeBaseStructure.TIM_CounterMode TIM_CounterMode_Up; // 时钟分频用于采样滤波这里不分频 TIM_TimeBaseStructure.TIM_ClockDivision TIM_CKD_DIV1; // 重复计数器高级定时器才有通用定时器写 0 TIM_TimeBaseStructure.TIM_RepetitionCounter 0; TIM_TimeBaseInit(TIM2, TIM_TimeBaseStructure); // 使能更新中断 TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE); // 中断优先级配置 NVIC_InitStructure.NVIC_IRQChannel TIM2_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority 1; NVIC_InitStructure.NVIC_IRQChannelSubPriority 1; NVIC_InitStructure.NVIC_IRQChannelCmd ENABLE; NVIC_Init(NVIC_InitStructure); // 使能定时器 TIM_Cmd(TIM2, ENABLE); }中断服务函数里记得清标志位void TIM2_IRQHandler(void) { if (TIM_GetITStatus(TIM2, TIM_IT_Update) ! RESET) { TIM_ClearITPendingBit(TIM2, TIM_IT_Update); // 你的业务代码 } }这段代码看起来简单但每一行背后都有前面讲的原理支撑。PSC71 是因为 72MHz 要降到 1MHzARR999 是因为要数 1000 个脉冲使能更新中断是因为溢出时才需要通知 CPU。5. 那些年我踩过的定时器坑5.1 定时不准的五大元凶定时器配置好了但时间不对这是最常见的问题。我总结下来原因基本逃不出这五类现象可能原因排查方法定时差整数倍时钟源频率算错忽略了 APB 的 2 倍规则打印或示波器测实际时钟核对时钟树定时差一点点晶振实际频率偏差或 HSE 起振失败用了 HSI检查 RCC 配置测 MCO 输出中断偶尔丢失中断服务函数执行时间过长或优先级被抢占缩短 ISR检查优先级分组第一次定时偏短计数器初值不是 0或配置后立即触发配置完先产生一次更新事件改了参数不生效预装载寄存器未更新手动置位 UG 或等下一个周期其中“差整数倍”是最典型的。我见过有人把 72MHz 当成 36MHz 算结果定时正好差一倍也见过 APB1 预分频改了但忘了定时器时钟会翻倍同样差一倍。这类问题的排查方法很简单用示波器测一个 GPIO 翻转看实际周期是多少和理论值一比差多少倍就回去查时钟。5.2 TIM1 更新中断的“多一次”问题TIM1 作为高级定时器有个重复计数器 RCR。如果 RCR 设成 0每个周期都产生更新中断如果设成 N则每 N1 个周期产生一次。很多人从通用定时器转过来忘了这个寄存器结果中断频率不对。还有一个更隐蔽的问题TIM1 在中心对齐模式下更新事件的产生时机和边沿对齐模式不同。中心对齐时计数器先向上数到 ARR再向下数到 0一个完整周期产生一次更新事件。如果你按边沿对齐的思路算就会觉得中断来得“不是时候”。5.3 中断优先级与实时性的平衡定时器中断的优先级配置是个技术活。配得太低容易被其他中断打断导致定时任务执行不准配得太高又可能影响更关键的中断响应。我的经验是把定时器中断的抢占优先级设在中高水平但 ISR 里只做最必要的事比如置个标志位、翻转个 GPIO把耗时操作放到主循环里。另外如果多个定时器同时工作要注意它们的优先级关系。比如一个做系统滴答一个做 PWM 更新通常系统滴答的优先级要更高保证时间基准的稳定。5.4 从 F1 到 F4时钟树变了经验也得变STM32F1 和 F4 的时钟树结构差别很大。F1 的定时器时钟那个“2 倍规则”在 F4 上依然存在但 F4 的 APB 预分频和定时器时钟关系更复杂还涉及到 APB1 和 APB2 的不同上限频率。更麻烦的是F4 有些定时器挂在 APB1有些挂在 APB2时钟频率可能不同。我建议每换一个系列都重新走一遍时钟树别直接套用旧参数。CubeMX 在这方面帮了大忙它会自动算出每个定时器的输入时钟你只要在配置界面确认一下就行。但前提是你得知道去哪里看、看什么。6. 进阶话题让时间基准更可靠6.1 用 DWT 做微秒级延时有时候你需要的不是定时中断而是一个精确的微秒级延时。STM32 的 DWTData Watchpoint and Trace单元里有个 CYCCNT 计数器它直接数内核时钟周期精度极高。配置好之后读 CYCCNT 的差值就能算出经过了多少个时钟周期进而换算成时间。这个方法的好处是不占用定时器资源精度到单个时钟周期。缺点是它依赖内核时钟如果系统时钟变了换算系数也得跟着变。而且 DWT 在低功耗模式下可能停止计数不适合做长时间的计时。6.2 定时器级联扩展定时范围16 位定时器最大定时时间有限。以 72MHz、PSC65535、ARR65535 计算最大定时约 59.6 秒。如果需要更长的定时比如几分钟甚至几小时可以用两个定时器级联一个定时器的溢出事件作为另一个定时器的时钟输入。这样定时范围就是两个定时器周期的乘积轻松做到几小时。级联的配置稍微复杂一点需要把一个定时器配成主模式输出更新事件作为触发另一个配成从模式时钟源选内部触发。具体寄存器操作这里不展开但思路就是“把慢的当快的的时钟”。6.3 低功耗模式下的定时器行为在 STOP 或 STANDBY 模式下大部分定时器会停止工作因为它们的时钟源被关闭了。但有些定时器比如 RTC 和某些低功耗定时器 LPTIM可以在低功耗模式下继续运行用来周期性唤醒系统。如果你的应用需要低功耗定时唤醒就得选对定时器并且配置好时钟源通常用 LSE 或 LSI。这里有个容易忽略的点从低功耗模式唤醒后定时器的计数器和预装载寄存器的状态可能和你预期的不一样需要重新初始化或者做特殊处理。我建议在低功耗应用里唤醒后先检查定时器状态再决定是否需要重新配置。7. 我个人的几条实操心得配置定时器这件事说难不难说简单也不简单。核心就是把“时钟源—PSC—ARR—溢出”这条链路理清楚。我自己的习惯是每配置一个新定时器先在纸上画一条时间轴标上时钟频率、分频后频率、计数周期、溢出时间确认无误再写代码。这个动作花不了几分钟但能避免绝大多数低级错误。另外示波器是排查定时问题最好的朋友。理论算得再漂亮不如实际测一下 GPIO 翻转的周期。我通常会先配一个最简单的 GPIO 翻转测出实际周期和理论值对比确认时间基准没问题了再去写复杂的业务逻辑。最后别怕看数据手册。定时器章节确实厚但真正用到的就那么几个寄存器。把 TIMx_PSC、TIMx_ARR、TIMx_CR1、TIMx_DIER 这几个搞明白大部分应用就够用了。遇到问题再回去查对应的位比从头啃一遍效率高得多。这个内容后续还可以往输入捕获测频率、PWM 输出、编码器接口这些方向扩展它们都建立在同一个时间基准之上。把今天讲的这条链路吃透那些高级功能不过是换个方式使用同一个计数器而已。
返回列表