ARTICLE DETAIL

资讯详情

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

STM32定时器配置:PSC、ARR和时钟源的隐藏坑与调试手册

STM32定时器配置:PSC、ARR和时钟源的隐藏坑与调试手册 写 STM32 定时器配置时PSC、ARR 和时钟源这三个参数几乎是我每次调试都要反复核对的地方。不是因为它有多难而是因为这三个参数之间相互耦合一个地方差 1最后出来的频率和占空比就全变了。而且时钟源这块还有个隐藏的 2 倍频规则如果不注意你按公式算出来的值和示波器实测值能差一倍排查起来特别折磨人。这篇东西不是那种照抄参考手册的教程而是我自己在这些参数上踩坑之后的总结。包括每个公式里那个1到底怎么来的APB 预分频之后定时器时钟为什么会悄悄翻倍以及运行时改 PSC 和 ARR 为什么会出现改了没反应或者波形突然跳变的现象。不管你是刚接触 STM32还是已经在用定时器做项目我相信看完都能避开不少弯路。1. 三个参数为什么会形成一个不可能三角1.1 定时器的核心计时链路STM32 的通用定时器本质上就是一条流水线时钟源产生脉冲预分频器 PSC 决定多少个脉冲算一个计数周期计数器从 0 往上加到 ARR溢出一次就产生更新事件同时输出 PWM 波形或者触发中断。这条链路里的三个关键点正好对应标题里的三个参数时钟源决定输入到定时器的原始脉冲频率。它可以是内部时钟来自 RCC也可以是外部引脚信号甚至可以是其他定时器的输出。PSC预分频器对时钟源做一次粗分频把高频脉冲降下来。ARR自动重装载值决定计数器数到多少算一轮也就是最终的周期。问题在于这三个参数不是独立的。最终频率的公式是f f_tim_clk / ((PSC 1) * (ARR 1))只要任何一个参数理解错了整个计算结果就偏了。而最坑的是PSC、ARR 和时钟源各自都有一个隐形规则不是直接套公式就能完事的。1.2 我那个差点成玄学的调试经历我第一次用 STM32F103 做 1kHz 的 PWM 输出时设了 PSC71、ARR999心里想的是72MHz 除以 72 等于 1MHz再数 1000 个就是 1kHz这没问题吧结果示波器一测频率是 2kHz。我当时第一反应是示波器接错了第二反应是晶振有问题折腾了快两个小时最后才发现问题出在时钟源上STM32F103 的 APB1 预分频为 2 时挂在 APB1 上的 TIM2~TIM7 的时钟并不是 36MHz而是 72MHz。也就是说我以为的输入时钟 36MHz实际是 72MHz于是频率直接翻倍。这件事之后我每次配置定时器前都会先在时钟树里把 TIMxCLK 确认一遍再谈 PSC 和 ARR。因为时钟源错了后面两个参数算得再漂亮也白搭。2. PSC预分频器最容易踩的 3 个坑2.1 PSC 不是分频倍数PSC1 才是PSC 这个参数在 STM32 参考手册里的定义是预分频器值但它和实际分频倍数之间有一个固定的偏移实际分频倍数等于 PSC 1。原因是计数器电路从 0 开始计数数到 PSC 之后才触发一次输出。所以 PSC 存的是多少个脉冲算一轮减 1。这个从 0 开始的思维贯穿整个定时器设计。举个例子输入时钟 72MHz想得到 1MHz 的计数时钟也就是做 72 分频。很多人就直接写 PSC72结果实际分频倍数是 73计数时钟变成了 986.3kHz最终 PWM 频率也偏了。正确写法是PSC 72 - 1 71在代码里可以看到HAL 库的__HAL_TIM_SET_PRESCALER函数直接写入寄存器值而寄存器只认 PSC 字段所以需要你在软件里自己减 1。CubeMX 的图形界面里也是填 PSC71不是 72。我见过不少新手在这里反复困惑因为他们把 PSC 的语义当成了分频系数。这个语义偏差是 PSC 相关的第一个大坑也是最容易踩的。2.2 边界条件从最小分频到 16 位上限PSC 是一个 16 位寄存器取值范围是 0~65535所以有效分频倍数范围是 1~65536。也就是说PSC0 时最小分频倍数 1计数器时钟 输入时钟不做任何分频。PSC65535 时最大分频倍数 65536。这个边界本身不难记难的是实际计算时经常忽略最大能分多少频这个约束。比如你用 72MHz 输入时钟想让计数器时钟变成 1Hz那么需要的分频倍数是 72000000远超 65536。这时候只靠 PSC 是不够的必须把 ARR 也拉大甚至需要两级定时器级联。这里有个选型思路在满足周期需求的前提下尽量让计数时钟高一些ARR 大一些这样 PWM 的分辨率更高中断定时也更平滑。比如需要 1kHz PWM你可以 PSC71、ARR999也可以 PSC7199、ARR9。前者一个周期的计数步数很多占空比可以细调到 0.1%后者只有 10 个计数步占空比精度很差。同样的频率不同的参数组合效果差很多。2.3 改 PSC 不生效的隐藏条件另一个 PSC 的常见坑是运行中修改 PSC 后发现波形完全没变或者要等很久才变一次。原因在于PSC 寄存器的值不是立即生效的它依赖更新事件Update Event来装载。如果定时器正在运行你直接给 PSC 寄存器写新值这个值会先被写入预装载寄存器等到下一次更新事件才会真正生效。而更新事件的产生条件是计数器溢出。如果计数器没溢出或者溢出周期特别长PSC 的修改就一直悬空。解决办法有两种。第一种通过软件触发更新事件__HAL_TIM_SET_PRESCALER(htim2, 71); htim2.Instance-EGR | TIM_EGR_UG; // 产生更新事件装载PSC __HAL_TIM_CLEAR_FLAG(htim2, TIM_FLAG_UPDATE);第二种先停止定时器修改 PSC再重新初始化计数器。日常调试时我用前者比较多因为它不需要打断 PWM 输出只是在更新事件时刻同步切换。还要提醒一句如果定时器已经配置了更新中断手动置位 UG 也会触发一次更新中断所以有时候你明明没周期到中断却提前来了。这就是为什么很多人在调试时会看到莫名其妙多了一次中断。建议在手动触发 UG 后立即清除更新标志位或者用标志位判断过滤。3. ARR频率、占空比、更新事件里的加减一3.1 周期来自 ARR1不是 ARRARR 的坑比 PSC 更大因为 ARR 同时影响 PWM 频率、占空比和更新中断周期而且每一处都有1的影子。计数器从 0 增加到 ARR需要 arr 1 个计数时钟周期。所以 PWM 频率的完整公式是f_pwm f_tim_clk / ((PSC 1) * (ARR 1))如果 ARR 设置错误最常见的情况是实际频率比预期高一点。例如前面那个例子PSC71、输入时钟 72MHz计数时钟是 1MHz。想得到 1kHz PWM需要的计数周期数是 1000那么 ARR 1000 - 1 999。如果写成 ARR1000实际频率就是 1MHz / 1001 ≈ 999Hz差距只有 0.1%。这种误差在 ARR 比较大的时候几乎看不出来但在 ARR 很小的时候会非常明显。比如你需要 100kHz PWM计数时钟 10MHz那么 ARR 100 - 1 99。如果写成 100实际频率是 10MHz / 101 ≈ 99.01kHz虽然差距依然不大但如果你是做电机控制或者开关电源这种频率偏差可能直接导致环路参数不对。还有极端情况ARR0 时计数器每个计数周期都溢出频率等于计数时钟频率即 PWM 频率等于 PSC 分频后的频率。这经常让新手惊讶其实符合公式ARR11。3.2 PWM 占空比的准确算法占空比的计算同样要建立在ARR1 个周期的基础上。在 PWM 模式 1 下向上计数时当 CNT CCR 时输出有效电平默认高电平当 CNT CCR 时输出无效电平默认低电平所以一个完整周期里有效电平持续的时间是 CCR 个计数周期。占空比公式为占空比 CCR / (ARR 1)注意这里的分母是 ARR1分子是 CCR两个地方都可能出错。举个例子ARR999想要 50% 占空比CCR 应该是 500因为 500 / 1000 50%。有人会误算成 CCR 999 * 0.5 499.5取整后变成 499 或 500这时候 499 得到的占空比是 49.9%其实差别不大。但如果 ARR 很小比如 ARR1用 ARR 做分母和用 ARR1 做分母差别就非常明显。还有一个容易忽略的点占空比取整误差。ARR999想要 33.33% 占空比CCR333实际 33.3%没问题。但想要 25% 且 ARR10CCR2.5只能取 2 或者 3误差就有 2.5%。这种量化误差在小 ARR 场景下无可避免只能尽量提高计数时钟频率、增大 ARR 来降低影响。另外如果你把 CCR 设置为 0则输出一直为无效电平把 CCR 设置为 ARR1输出一直为有效电平。很多人以为 CCR 最大只能写到 ARR其实在 STM32 的某些定时器里CCR 可以写得比 ARR 大最后的效果就是占空比接近 100%。这个技巧在做直流电机全速输出时偶尔能用到但要看参考手册的具体描述不是所有系列都一致。3.3 运行中改 ARR 必须处理的预装载问题运行时动态调整 PWM 频率或中断周期是电机控制、调光等场景的常见需求。这时候 ARR 的更新方式又会出现一个坑。ARR 寄存器本身也有预装载机制。如果开启了 ARPE 位Auto-reload preload enable那么你在运行中写入新的 ARR它不会立刻生效而是要等下一次更新事件才装载。这个机制的好处是保证修改 ARR 时计数器不会出现一个不完整的周期避免波形毛刺。如果没开 ARPE新 ARR 会立即写入影子寄存器这时候可能出现一种情况计数器计数到 50你把 ARR 从 999 改成 100计数器不会立刻清零而是继续数到 100 才溢出那么这个周期就变成了 51 个计数周期而不是之前的 1000 个。在 PWM 输出上会表现为一个很窄的脉冲在电机控制里就可能造成电流冲击。我个人的做法是所有需要运行中改 ARR 的场景一律开启 ARPE。写入新值之后让它自然等到更新事件生效。如果需要精确知道新参数从哪个周期开始生效就同时监听更新事件标志位在中断里做同步。修改 ARR 还有第二个坑当你把 ARR 改小之后计数器当前值可能已经大于新的 ARR 了。这种情况下按照 STM32 的行为计数器会继续向上计数到旧的上溢点才溢出。也就是说即使开了 ARPE新 ARR 的生效时机可能比你预期晚一个周期。这个问题在 STM32 参考手册的 TIMx_CR1 寄存器描述部分有说明属于细节中的细节但调试时真的会遇到。4. 时钟源被忽略的 2 倍频与外部输入4.1 APB 预分频不为 1 时定时器时钟要乘 2这是整个定时器配置里最隐蔽的一个坑也是我开头那个翻车案例的元凶。STM32 的系统时钟经过 AHB 预分频后得到 HCLKHCLK 再经过 APB1 和 APB2 预分频分别得到 PCLK1 和 PCLK2。挂在 APB1 上的定时器TIM2~TIM7还有一些低功耗定时器和挂在 APB2 上的定时器TIM1、TIM8 等它们的输入时钟 TIMxCLK 并不直接等于 PCLKx而是遵循一条规则如果 APBx 预分频器的分频系数为 1则 TIMxCLK PCLKx如果 APBx 预分频器的分频系数不为 1则 TIMxCLK PCLKx × 2以 STM32F103 为例系统时钟 72MHzAPB1 预分频为 2PCLK1 36MHz但 TIM2 的时钟是 36MHz × 2 72MHz。APB2 预分频为 1PCLK2 72MHzTIM1 的时钟就是 72MHz不需要乘 2。这个设计的原因很简单APB 总线上外设的时钟可以降频以省电但定时器本身希望拿到尽可能高的时钟以便获得更好的计数精度。所以在 APB 分频器后面芯片单独给定时器加了一个 2 倍频器补偿 APB 分频带来的损失。所以配置定时器前一定要确认两件事一是定时器挂在 APB1 还是 APB2二是对应的 APBx 预分频系数是多少。在标准外设库时代可以用RCC_GetClocksFreq读出 PCLK1、PCLK2再根据RCC-CFGR里的 PPRE 位判断是否需要乘 2。到了 HAL 库虽然 CubeMX 的时钟树页面会直接显示 TIMxCLK但如果你在裸机代码里手动操作还是很容易忘了乘 2。我后来养成一个习惯写代码之前先在时钟树配置里截个图把自己当前工程所有定时器的输入时钟列出来。比如 F103 常用的组合是系统时钟 72MHz、APB1 分频 2、APB2 分频 1那么 TIM1/TIM8 的时钟是 72MHzTIM2~TIM7 的时钟也是 72MHz。而到了 F407 这种更高频率的芯片APB1 分频 4、APB2 分频 2PCLK142MHz、PCLK284MHz乘 2 之后 TIM2~TIM7 是 84MHzTIM1/TIM8 是 168MHz。不同系列差别很大不能凭经验套用。4.2 外部时钟模式与从模式级联除了内部时钟定时器还可以选择外部时钟作为时钟源。两种典型场景第一种是外部时钟模式 1通过 SMCR 寄存器的 SMS 配置定时器从 TI1、TI2 或 ETR 引脚接收外部脉冲每来一个脉冲计数器加一。这个模式经常用于测频率、测转速、或者做简单的脉冲计数。它不经过 APB 的 2 倍频逻辑直接用外部信号作为计数时钟所以计算参数时要把输入信号频率看清楚尤其是信号经过了滤波器或分频器之后。第二种是外部时钟模式 2通过 SMCR 寄存器的 ECE 位配置直接从 ETR 引脚接收外部时钟。ETR 引脚前面还带一个 ETRP 分频器1、2、4、8 分频以及一个可编程滤波器。很多人实际测到的计数值比预期低就是忘了 ETRP 分频器默认会分频。还有一类常见的时钟源是定时器级联。比如 TIM2 的 TRGO触发输出接到 TIM3 的 ITR0TIM3 就可以以 TIM2 的更新事件作为时钟源。这种方案在需要长定时、或者需要多个定时器同步时很常见。级联时上游定时器的周期决定了下游定时器的时钟周期计算参数时要把上游定时器的输出频率当作 TIMxCLK否则下游定时器的周期会完全不对。我踩过的坑是级联时下游定时器的预分频 PSC 如果被设成默认值 0那么它每个上游更新事件就计数一次。如果上游周期是 1ms下游 ARR 设成 999那么下游中断周期就是 1 秒。这个逻辑本身没问题但如果你同时把下游的 PSC 也改成 99那实际周期就变成了 100ms很多人会在这种多级配置里把自己绕晕。4.3 用 CubeMX 和代码验证时钟配置我强烈建议开始写定时器代码之前先打开 CubeMX在 Clock Configuration 页面里看清 TIMxCLK 的数值。这一步能帮你省下至少一个小时的排查时间。CubeMX 的时钟树配置页面里每个定时器输入时钟都会显示一个数值比如 TIM2 后面会写 72 MHz 或者 84 MHz。如果你改了系统时钟或者 APB1/APB2 预分频这个数值会跟着变。很多时候你算出来的和实测不一致问题就出在系统时钟某个环节被改过而 CubeMX 已经把结果给你了只是你没看。如果不用 CubeMX也可以写一小段代码来验证uint32_t pclk1 HAL_RCC_GetPCLK1Freq(); uint32_t pclk2 HAL_RCC_GetPCLK2Freq(); // 判断 APB1 预分频系数 uint32_t temp READ_BIT(RCC-CFGR, RCC_CFGR_PPRE1); uint32_t apb1_prescaler ((temp 8) 0x7); // 根据数值范围判断是否乘 2大于 0 则 TIMxCLK PCLK * 2这种验证方法在调试中非常有用尤其是当你换了一块开发板晶振频率不同或者时钟树配置被改过示波器实测频率和预期对不上时先用代码打印出实际的 TIMxCLK后面就好查了。5. 一套可复用的定时器参数计算流程附综合实例5.1 参数计算的正确顺序每次配置定时器我推荐按这个顺序走而不是从 PSC 或者 ARR 中间开始确定定时器输入时钟 TIMxCLK。查时钟树确认 APBx 预分频是否导致乘 2。确定需要的目标频率或周期。比如 PWM 频率 1kHz、中断周期 1ms。先决定计数时钟。也就是先选 PSC。计数时钟 TIMxCLK / (PSC1)选择时考虑 ARR 的分辨率需求。再由计数时钟和目标周期计算 ARR。ARR 计数时钟 / 目标频率 - 1。最后回头检查 PSC 和 ARR 是否在寄存器范围内。顺序很重要。如果你上来就拍脑袋定一个 PSC再反推 ARR往往会发现 ARR 超出上限或者分辨率不够。先想清楚需要多少个计数周期再反推 PSC 和 ARR才是正路。5.2 实例生成 1kHz PWM 与 1 秒定时中断用一个常见的需求来演示完整流程在 STM32F103C8T6 上用 TIM2 输出 1kHz、50% 占空比的 PWM用 TIM3 产生 1 秒周期的定时中断。第一步确认时钟。STM32F103C8T6 系统时钟 72MHzAPB1 预分频 2PCLK136MHzTIM2 和 TIM3 的输入时钟 TIMxCLK 72MHz。这个结论来自前面说的乘 2 规则可以在 CubeMX 里复验。第二步算 TIM2 的 PSC 和 ARR。目标 PWM 频率 1kHz周期 1ms。为了让占空比分辨率高一些我选择计数时钟为 1MHz也就是一个计数周期 1us。那么PSC 1 72MHz / 1MHz 72 PSC 71一个 PWM 周期内需要 1MHz / 1kHz 1000 个计数周期所以ARR 1 1000 ARR 99950% 占空比对应 CCR 500。参数确定后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_PWM_Init(htim2); TIM_OC_InitTypeDef sConfigOC; sConfigOC.OCMode TIM_OCMODE_PWM1; sConfigOC.Pulse 500; sConfigOC.OCPolarity TIM_OCPOLARITY_HIGH; HAL_TIM_PWM_ConfigChannel(htim2, sConfigOC, TIM_CHANNEL_1); HAL_TIM_PWM_Start(htim2, TIM_CHANNEL_1);第三步算 TIM3 的 PSC 和 ARR。目标中断周期 1 秒输入时钟同样是 72MHz。如果直接用 ARR 来凑 1 秒ARR 最大可以到 65535 或者 32 位上限但 PSC 和 ARR 都由 16 位限制约束TIM3 的 ARR 是 16 位最大 65535。如果把计数时钟设成 1MHz那么 1 秒需要 1,000,000 个计数周期ARR 999999超出 16 位上限。所以要把计数时钟降下来比如设 PSC 7199计数时钟 72MHz / 7200 10kHz一个计数周期 0.1ms。那么 1 秒需要 10000 个计数周期ARR 999916 位范围内。PSC 7199 ARR 9999代码TIM_HandleTypeDef htim3; htim3.Instance TIM3; htim3.Init.Prescaler 7199; htim3.Init.CounterMode TIM_COUNTERMODE_UP; htim3.Init.Period 9999; htim3.Init.ClockDivision TIM_CLOCKDIVISION_DIV1; HAL_TIM_Base_Init(htim3); HAL_TIM_Base_Start_IT(htim3);然后到 HAL_TIM_PeriodElapsedCallback 里做 1 秒周期的业务逻辑。这个例子看起来简单但每一步都踩了前面说的坑TIMxCLK 要用 72MHz 而不是 36MHzPSC 要减 1ARR 要减 116 位范围要检查。把这些都走一遍定时器配置就算入门了。6. 常见问题速查与调试技巧6.1 一张表查完 80% 的定时器配置问题日常答疑时我整理过一张表覆盖了绝大多数定时器配置翻车现场。这里分享给你现象可能原因解决方法频率是预期的 2 倍APB 预分频不为 1 时忘了定时器时钟乘 2按 TIMxCLK PCLK × 2 重新计算频率略高于预期PSC 或 ARR 忘记减 1统一用公式 f TIMxCLK / ((PSC1)*(ARR1)) 反推占空比偏小或偏大CCR 计算时用 ARR 当分母而不是 ARR1改用 CCR / (ARR1) 计算修改 PSC 后波形没变PSC 预装载未触发更新事件手动置位 UG 位或者触发一次更新修改 ARR 后出现毛刺或异常脉冲未开启 ARPE 预装载置位 TIM_CR1 的 ARPE写入后等待更新事件中断频率远低于预期ARR 超出 16 位上限实际被截断降计数时钟或改用 32 位定时器外部脉冲计数不准ETR 分频器或滤波器配置不对检查 ETPS、ETF 字段先禁用滤波器测试级联定时器周期不对下游定时器把上游 TRGO 当成时钟源却忘了 PSC 影响从上游输出频率重新推导 TIMxCLK这张表不是让你背的而是排查时拿来对照。我自己每次遇到定时器异常都会按时钟源到 PSC 到 ARR的顺序一项一项排除而不是先怀疑中断没配置好。6.2 调试技巧与验证方法定时器配置对不对不能只看代码要配合测量工具验证。最简单的办法是用 PWM 输出接到示波器或逻辑分析仪直接测频率和占空比。没有示波器的话也可以把定时器更新事件引出到一个 GPIO在中断里翻转电平再用另一个定时器或者手机秒表粗略估算。还有一个小技巧用定时器输入捕获测自己的 PWM。比如 TIM2 输出 PWMTIM4 输入捕获测量 TIM2 的引脚频率然后把频率通过串口打印出来。这样不依赖外部设备也能自测而且在排查疑难问题时很好用。// 伪代码示例TIM4 输入捕获测量频率 HAL_TIM_IC_Start_IT(htim4, TIM_CHANNEL_1); // 在回调中计算两次捕获的差值得到周期这个方法特别适合在没有示波器的现场快速判断问题也是我常用的排查第一步。最后再给一个个人经验写定时器代码前把公式写在注释里留档。/* TIM2 PWM: f 72MHz / ((711)*(9991)) 1kHz */ /* CCR 500, 占空比 500/1000 50% */这样做一是方便之后回来改参数二是逼自己在写代码时把计算过程过一遍很多加减一的坑在写注释时就被发现了。我后来发现凡是报错定时器频率不对的人十有八九是没写过这个注释。
返回列表