
1. 先从一次烧糊的延时开始时间基准错了会怎样前阵子一个朋友拿着块开发板来找我说程序跑起来板子烫手LED闪烁频率明显不对怀疑是硬件短路。我接过来一看代码里延时函数直接写了个Delay(500)里面用的是SysTick而 SysTick 的时钟源配置写的是SystemCoreClock 168000000。问题来了这块板子上的主控是 STM32F103最高主频只有 72MHz。他把 168MHz 当成基准去配 SysTick结果每 1ms 实际走的 SysTick 计数次数比真实时间多了将近一倍延时函数还在那儿傻等中断拼命触发板子当然烫。这不是个例。我见过太多人用定时器做延时、做 PWM、做输入捕获却不清楚定时器背后数的到底是什么。这篇文章想聊透一个最基础但最容易忽略的问题定时器的时间基准从哪里来它到底在数什么。先说结论定时器根本不认识时间这个东西它只认识脉冲边沿。所谓的 1ms、1s全是我们拿一个已知频率的时钟源数了固定个数的脉冲之后换算出来的。所以整个问题的核心就是搞清楚定时器输入端那个脉冲的频率是多少、怎么配置出来的。这篇文章适合的对象是这样一群人会用 CubeMX 点两下生成一个定时器配置能跑起来但问到PSC 为什么填 71 而不是 72就卡壳的或者已经在用定时器做 PWM、做捕获测频但偶尔遇到计数值完全对不上的情况不知道怎么排。看完这篇你可以不用看寄存器手册也能推出生成的配置为什么是这样更重要的是遇到时间基准配置错了的问题能快速定位是哪里错了。2. STM32 的时钟树定时器的时间到底从哪里来2.1 从 HSI 到 PLL主频只是时间基准的源头要搞清定时器时钟来源第一个要面对的就是时钟树。STM32 内部有一个完整的时钟分配网络最终输出的是一路或几路SYSCLK其中一部分还会继续分频给外设总线定时器的时钟就是从这条链路上某个节点拉出来的。大多数 STM32 的时钟源头有三种HSI内部高速 RC 振荡器频率一般是 8MHz部分系列是 16MHz。上电默认走的就是它。优点是不需要外部晶振缺点是精度一般温漂大通常在 1% 上下。用 HSI 做 USB 外设、做波特率敏感的串口通信误差会大到影响通信稳定性。HSE外部高速晶振常见 8MHz、12MHz、25MHz。优点就是精度高稳定性好温漂小像 25MHz 晶振精度能做到几十 ppm 级别。绝大多数对时序有要求的项目主时钟都应该走 HSE。PLL锁相环倍频器。把 HSI 或 HSE 作为输入乘上一个倍频系数得到更高频率的 SYSCLK。例如 F103 上 HSE 8MHzPLL 倍频到 72MHzF407 上 HSE 25MHz倍频到 168MHz这样主频才能拉起来。配置时钟树有一个最常见的坑PLL 不能乱配。很多人在 CubeMX 的 Clock Configuration 界面里看到某一路是红的顺手改了一个分频系数导致整条链路频率跑偏而定时器的时钟也会跟着整个偏掉。我之前排查过一个ADC 采样的时间总不对的问题最后发现就是有人把 PCLK1 分频从 /1 改成了 /2而 ADC 的采样时钟正好挂在 PCLK2 那一路时间基准整个变慢所有换算系数全作废。时钟树配置完成后真正给定时器供时钟的其实是PCLK1和PCLK2这路总线时钟。F103 里 APB1 最高 36MHzAPB2 最高 72MHz。很多人以为定时器时钟等于这两路总线时钟到了 F407/F429 这种系列上就错了。2.2 APB1 与 APB2 的2倍频陷阱这是 STM32 定时器时间基准最坑的一点也是我看到现场翻车频率最高的一点当 APB1 或 APB2 的分频系数不等于 1 时定时器的时钟源反而会变成该总线时钟的 2 倍。拿 F103 来举例APB1 总线最高 36MHz分频器可以配 /1、/2、/4…默认大部分工程的配置是 SYSCLK 72MHzAPB1 预分频为 /2所以 PCLK1 36MHz。但是 TIM2、TIM3、TIM4、TIM5、TIM6、TIM7 这些挂在 APB1 上的定时器它们的时钟并不是 36MHz而是36MHz × 2 72MHz。为什么因为当初设计芯片的时候为了让定时器的最高计数频率不被总线频率限制住在定时器时钟路径上加了一个倍频器。只要 APB 预分频不是 1定时器就能拿到两倍的总线频率。很多人用 CubeMX 生成 F103 工程后在SystemClock_Config里看到APB1CLK 36MHz然后算定时器 1ms 中断时直接拿 36MHz 去算 PSC 和 ARR配置出来是能产生 1ms 中断没错但跟你实际想要的分频关系完全是两回事。再看 F407/F429 这类 M4 内核的片子SYSCLK 168MHzAPB1 预分频 /4 得到 42MHzAPB2 预分频 /2 得到 84MHz。TIM2~TIM7 和 TIM12~TIM14 挂在 APB1它们的定时器时钟就是 42 × 2 84MHzTIM1 和 TIM8~TIM11 挂在 APB2定时器时钟是 84 × 2 168MHz。所以 F407 上不同定时器的时钟源可能是 84MHz 也可能是 168MHz调用HAL_RCC_GetPCLK1Freq()拿到的那个值并不能直接当成定时器时钟用还得再判断 APB 分频是不是 1。如果不用 HAL 库而是直接操作寄存器那更要注意这个点。很多参考代码直接写死TIM_Prescaler 72因为作者手上的板子是 72MHz 主频 APB1 不分频的组合你要是换到 APB1 分了 /2 的工程里同样的配置时间就翻倍了定时器实际溢出频率变成了原来的一半。2.3 一张表看清单片机上所有时间来源为了防止自己记混我把常用 STM32 系列里定时器时钟与总线时钟的关系整理成了表格项目里对着查一下至少不会出方向性错误系列典型 SYSCLKAPB1 总线APB2 总线挂在 APB1 的定时器时钟挂在 APB2 的定时器时钟F10372MHz36MHz (/2)72MHz (/1)72MHz (36×2)72MHz (72×1)F407168MHz42MHz (/4)84MHz (/2)84MHz (42×2)168MHz (84×2)F429180MHz45MHz (/4)90MHz (/2)90MHz (45×2)180MHz (90×2)G0 系列64MHz64MHz (/1)64MHz (/1)64MHz (64×1)64MHz (64×1)注意 F103 那行APB2 分频是 1TIM1 这种挂在 APB2 上的定时器就是 72MHz 没翻倍APB1 分频是 2所以 TIM2 反而拿到了 72MHz。也就是说F103 上所有高级定时器和通用定时器最终时钟全都是 72MHz这算是一个巧合但也让很多人误以为定时器时钟就是主频到 F407 上就晕了。G0 系列更特殊APB 可以做到不分频所以定时器时钟就老老实实等于总线时钟没有 2 倍频机制说明这个2倍频不是所有 STM32 都有的特性换系列一定要重新查手册。3. 预分频与自动重载定时器如何把时钟变成时间3.1 PSC 和 ARR 的数学含义搞清楚时钟源频率之后定时器要变成时间中间还隔着两个关键寄存器预分频器PSC和自动重载寄存器ARR。先看一个具体例子。假设定时器时钟是 72MHz想产生 1ms 的定时中断需要数多少个脉冲答案是 72000 个。因为 72MHz 意味着每秒有 72000000 个脉冲1ms 就是 1/1000 秒脉冲数就是 72000000 ÷ 1000 72000。但定时器的计数器 CNT 不可能配成 72000 次溢出一次因为 ARR 是 16 位的F103 通用定时器最大值 65535装不下 72000所以要先经过 PSC 做一次预分频。预分频器的意思很直白每收到 PSC1 个脉冲计数器才加 1。为什么是 1因为 PSC 0 时就是每个脉冲计数一次所以实际分频系数是 PSC1。PSC 71 时定时器计数器的输入频率就变成 72MHz ÷ 72 1MHz也就是每 1 微秒计数一次。此时想要 1ms 溢出一次ARR 就该填 999因为计数器从 0 数到 999 一共 1000 个计数周期1000 微秒 1ms。完整公式就是定时器溢出周期 (PSC1) × (ARR1) ÷ 定时器时钟频率或者反过来溢出频率 定时器时钟 ÷ ((PSC1) × (ARR1))。很多初学者会把 PSC 和 ARR 填反PSC 填 999ARR 填 71结果时间变成了 (9991)×(711) ÷ 72MHz 1.111ms看起来误差不大是不是但如果你把值换大一点PSC 35999、ARR 999结果应该是 500ms如果填反了变成 PSC 999、ARR 35999结果直接变成 500 × 1000 ÷ 72 ≈ 6.94 秒。方向全反的时候时间误差就不是百分之几是数量级的错误。所以我强烈建议先定计数器输入频率再算 ARR不要两个寄存器一起凭手感填。3.2 计数模式对时间基准的影响另一个容易被忽略的点是计数模式。STM32 定时器有向上计数、向下计数、中心对齐三种模式。向上计数是 CNT 从 0 加到 ARR然后溢出回 0向下计数是从 ARR 减到 0 再回 ARR中心对齐是先从 0 加到 ARR再从 ARR 减到 0一个完整周期等于两个 ARR。注意看中心对齐模式一次完整往返是 2×(ARR1) 个计数周期。如果你在中心对齐模式下直接套用上面的公式算出来的时间会正好差一倍。比如前面的例子里PSC71、ARR999向上计数溢出周期是 1ms中心对齐模式下中断周期变成 2ms。PWM 占空比在这种模式下也会出现非预期表现因为一个完整 PWM 周期是加计数一段加不加计数一段拼出来的。我在一个电机驱动项目里踩过这个坑当时想生成 20kHz 的 PWM用中心对齐模式按照向上计数公式算出 ARR1799计数器频率 72MHz 的话上示波器一看频率只有 10kHz愣了几分钟才想起来中心对齐的周期乘了 2。所以只要发现 PWM 频率、定时器中断频率正好是预期值的一半先检查是不是中心对齐模式。3.3 为什么 ARR 不一定要配到刚刚好关于 ARR 还有一个经验性的点它不一定非得追求整数对应到正好 1ms。实际项目中我更喜欢反着来——先定一个能整除的计数器频率再让 ARR 对应到需要的时间。这样虽然中断时间没有精确到小数但计算方便后续改频率也直观。举个例子项目中如果需要一个 500Hz 的定时中断直接算 PSC1 72得到计数器频率 1MHz然后 ARR1 2000中断频率就是 500Hz。每一步都能口算不用拿计算器点半天。如果反过来先定死必须整数毫秒遇到要求 3.7ms 这种需求除出来的 PSC 或者 ARR 经常带小数最终只能靠四舍五入误差反而不可控。当然如果对时间精度有硬性要求比如音频采样、步进电机脉冲那就别省这个功夫老老实实算小数点后的值然后选误差最小的那组分频组合必要时 PSC 和 ARR 都取非整数值的近似替代并且用定时器输出比较模式去对齐相位而不是只依赖定时中断。4. HAL 库底层配置一个定时器时发生了什么4.1 HAL_TIM_Base_Init 到计数器启动CubeMX 生成的代码看起来就几步MX_TIM3_Init()里设置 PSC、ARR、计数模式然后主程序里HAL_TIM_Base_Start_IT(htim3)。但如果你只在应用层看永远不会理解时间基准是在哪儿定下来的。拆开HAL_TIM_Base_Init内部核心其实就是四条语句对应几个寄存器位timer-Instance-CR1 TIM_CR1_CEN; // 只有这一行真正让计数器开始数 timer-Instance-PSC 71; // 预分频器 timer-Instance-ARR 999; // 自动重载值 timer-Instance-CR1 | TIM_CR1_DIR; // 计数方向默认0是向上TIM_CR1_CEN是CR1里的位 0置 1 时计数器才使能。很多人调试定时器发现 CNT 不动第一反应是查中断有没有开启、回调有没有写对其实最该查的是 CEN 有没有置位。一旦 CEN 为 0你前面配的 PSC 和 ARR 再好也是摆设。HAL_TIM_Base_Start_IT会额外使能更新中断TIM_IT_UPDATE并且打开 NVIC 对应的中断通道。但这里有个细节HAL 库默认不使用定时器的主输出功能。如果有人拿通用定时器想让引脚输出波形会发现怎么配引脚都不出波形因为TIM_CCER的 CC1E 位默认是 0通道输出被禁止了。这个在 PWM 模式里需要专门调属于典型 HAL 库使用误区。从代码生成顺序来看HAL_TIM_Base_Init本身还要经过_HAL_TIM_INIT的前置检查包括时基结构体里的 Prescaler、Period、ClockDivision、CounterMode 几个字段一一对应写入寄存器。有个常见的坑是ClockDivision这个字段很多人不填或者填错它影响的是输入滤波器的采样时钟分频跟时基不直接相关但如果开了输入捕获滤波采样频率不对会导致捕获边沿识别不到或抖动误触发表现为捕获到的周期忽大忽小。4.2 影子寄存器为什么改 PSC 要重新触发这是定时器时间基准问题里最隐蔽的一个机制PSC 和 ARR 都有影子寄存器。意思是说你写入TIMx_PSC或TIMx_ARR的值并不是立刻生效于计数器的而是会被暂存起来在下一次更新事件发生后被影子到真正参与计数的寄存器里。为什么芯片要这么设计因为如果允许计数器在运行过程中直接用新值可能造成 ARR 已经过了、PSC 还没切换的中间状态导致一个周期的时间忽长忽短。影子寄存器机制保证了要么用旧值跑完整个周期要么新值在整个周期内生效不会出现半路换挡。对应到操作上如果你在定时器运行中调用了__HAL_TIM_SET_PRESCALER(htim3, 143)这个值并不会立刻影响当前的计数周期要等一次更新事件溢出或软件触发才真正生效。想立刻生效需要主动产生一个更新事件__HAL_TIM_SET_COUNTER配合__HAL_TIM_SET_AUTORELOAD或者直接调用__HAL_TIM_ENABLE后手动置位EGR寄存器的UG位。手工置 UG 位会同时把 CNT 清零如果你的应用是想要无缝改变频率比如电机调速过程中平滑变速就得注意清零 CNT 会导致相位跳变。我之前做过一个三相逆变想在运行中改载波频率直接置 UG 位之后三相 PWM 的相位全乱了母线电压瞬间出现尖峰。后来改成用TIM_OC的输出比较模式去对齐或者在死区时间内去改 ARR才把尖峰消掉。所以改 PSC 和 ARR 不是写个寄存器那么简单你得明确知道影子寄存器什么时候把新值带进来。4.3 从寄存器视角排查定时器不工作当定时器完全不工作的时候新手的第一反应是查代码老手的习惯是先看寄存器和时钟源。我一般按这个顺序排查时钟有没有开。RCC-APB1ENR或RCC-APB2ENR里对应位是不是 1。HAL 库用__HAL_RCC_TIM3_CLK_ENABLE()只能开一次但如果在中断服务函数里不小心调用了DeInit再Init有可能把时钟配置冲掉。CEN 是不是 1。看TIMx-CR1的位 0。这个位在某些系列里默认是 0 的如果主函数里只调用了MX_TIM3_Init而没有调HAL_TIM_Base_Start_IT定时器根本不会跑。PSC/ARR 影子值是否是你期望的值。直接读TIMx-PSC和TIMx-ARR如果发现 PSC 被写成其他值了八成是有别的代码改过它。中断是否到点。用调试器全速运行等一会儿暂停看TIMx-SR的 UIF 位位 0有没有置 1。如果一直没置 1说明计数器根本没溢出过如果置 1 但程序没进中断问题就在 NVIC 或者中断服务函数的命名上。这套方法我几乎每周都在用。做嵌入式跟做 Web 不一样没有那么多黑盒寄存器的状态一清二楚老老实实看寄存器永远比猜代码快。5. 时间基准的实战落点延时、输入捕获与频率测量5.1 SysTick 与 HAL_Delay 的底层机制定时器的时间基准理解透了再回头看 SysTick 就简单很多。SysTick 是 ARM 内核自带的一个 24 位倒计数定时器不是 STM32 外设所以它不归 RCC 管时钟来源是内核时钟的 1/8 或者直接用内核时钟取决于芯片实现和配置。HAL 库的HAL_Delay就是拿 SysTick 做毫秒计数的。它内部会在启动时把 SysTick 的重载值设成SystemCoreClock / 1000也就是每 1ms 触发一次中断然后维护一个全局变量uwTick每次中断加 1。HAL_GetTick返回这个值HAL_Delay就是把当前uwTick加上延时毫秒数然后死等。这个机制带来一个很经典的坑如果在中断服务函数里调用HAL_Delay程序会死锁。为什么因为 SysTick 中断的优先级如果设得比当前中断低当前中断在运行时 SysTick 中断进不来uwTick不会更新HAL_Delay就一直等不到目标值。反之如果 SysTick 中断优先级比当前中断高HAL_Delay能跳出来但这种嵌套调用会平白增加中断响应延迟出问题的时候特别难查。所以项目里写裸机代码我很少跨中断去调延时。如果某个外设中断处理里确实要延一点时间我会直接在中断里开一个硬件定时器或者用阻塞式的for循环走 CPU 空转前提是时间很短、系统不要求实时响应。另一个跟 SysTick 时间基准相关的点如果你用调试器在HAL_Delay里下了断点停下来超过 1msSysTick 中断不会在停止期间触发uwTick就缺了几千甚至几万毫秒恢复运行后所有基于HAL_GetTick的超时判断全部失效。这是调试器带来的假象不是代码问题。遇到一进调试就报超时一全速跑就好的现象先怀疑这一点。5.2 定时器输入捕获测频率时间基准如何变成测量精度输入捕获是定时器另一个高频应用也是这篇文章标题下热搜词里出现频率很高的一个内容点stm32定时器捕获测频率。时间基准在这里直接决定了测量结果的分辨率。思路是这样把待测方波接到定时器的某个捕获通道上配置该通道为上升沿捕获。每次上升沿到来时硬件会自动把计时器当前计数值CNT锁存到捕获寄存器CCR同时触发一次捕获中断。在中断里读两次 CCR 的差值就是两个上升沿之间经过的计数周期数。有了计数周期数和计数器时钟频率就能算出待测方波的周期和频率。核心换算公式是待测频率 定时器时钟 ÷ (PSC1) ÷ (CCR 两次差值)举例定时器时钟 72MHzPSC 设 71计数器频率 1MHz。测一个 1kHz 方波两次上升沿之间的计数器差应该是 1000。差值是 500 那就是 2kHz差值是 2000 那就是 500Hz。那测量精度怎么提升主要看两方面计数器频率越高测量分辨率越高。同样的 1kHz 方波用 1MHz 计数频率测得周期分辨率是 1 微秒1kHz 方波周期是 1000 微秒误差 0.1%如果计数器频率提到 72MHz分辨率提升到 13.9 纳秒理论上测 1kHz 精度能提高 70 倍以上。待测频率越低测周期越准测频率越不准。反之待测频率很高时直接测两次上升沿差值可能只有几个计数周期误差大得离谱这种时候应该测多少个上升沿发生在固定时间内也就是用一个固定时间窗口数边沿次数再换算频率。我做过一个测电机转速的项目电机额定转速 3000 转/分霍尔传感器输出 3 个脉冲/转那信号频率是 150Hz。如果只用捕获测周期计数器差值是 720000 ÷ 150 4800 个计数周期分辨率还行但如果想测启动瞬间的瞬时转速转速从 0 冲到 3000 转可能不到 1 秒每个脉冲周期变化非常剧烈一个周期一个数值地读 CPU 负载太高我改成固定 10ms 窗口计脉冲个数精度完全够用代码也简单很多。所以选测周期还是测频率不是看哪个高大上而是看信号频率范围。5.3 一个实测案例测量 1kHz 方波用一个 F103 板子做例子把完整流程走一遍。CubeMX 里配置 TIM3 为输入捕获模式只开通道 1PSC 填 71ARR 填 65535最大计数值这样计数器以 1MHz 频率运行CNT 从 0 加到 65535 溢出回 0溢出周期 65.535ms测 1kHz 方波周期 1ms完全够用。启动捕获中断后在回调函数里做两次差值volatile uint32_t last_capture 0; volatile uint32_t frequency 0; void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim) { if (htim-Instance TIM3 htim-Channel HAL_TIM_ACTIVE_CHANNEL_1) { uint32_t current_capture __HAL_TIM_GET_COMPARE(htim, TIM_CHANNEL_1); uint32_t diff; if (current_capture last_capture) diff current_capture - last_capture; else diff (65535 - last_capture) current_capture; // 处理CNT溢出回绕 frequency 1000000 / diff; // 计数器频率1MHzdiff单位是微秒频率1000000/diff last_capture current_capture; } }注意代码里处理了计数器溢出的情况CNT 是 16 位的如果两次捕获之间 CNT 回绕过直接减会得到负数需要按环形计数器的思路补上溢出段。这段逻辑很多教程里没有但实际测低频信号、或者捕获间隔接近溢出周期时没有这段代码读数会偶尔蹦出一个异常大的数。实测 1kHz 方波串口打印出来的 frequency 稳定在 999 或 1000偶尔跳到 1001原因是信号源本身不是绝对精确的 1kHz这个误差完全来自信号源而不是测量电路。把计数器频率从 1MHz 改成 72MHz也就是 PSC 填 0读数跳动幅度会更小但要注意处理溢出时 diff 可能超过 16 位需要上 32 位计算或者用中断过度。关于输入捕获还有一个容易错的配置点要设预装载否则滤波器的采样时钟不对。CubeMX 生成代码时会自动在TIM_IC_InitTypeDef里保留ICPrescaler和ICFilter两个字段其中ICPrescaler是捕获分频默认 0每 1 个边沿捕获一次一般没错但ICFilter如果设了非 0 值表示要使能输入滤波而滤波器的工作频率取决于ClockDivision字段。当你看到捕获值偶尔跳变、多捕获或少捕获一个边沿的情形优先查 ICFilter 和 ClockDivision 的匹配关系。6. 我在实际项目里总结的定时器时间基准使用四查法在做多个基于定时器的项目之后我把排查和验证时间基准配置的流程固化成了四查法每次定时器表现不对就按照这个顺序查一遍能省掉大量无头苍蝇式的试错6.1 查时钟树配置与实际主频是否一致第一查永远是时钟树。方法很简单进调试器看SystemCoreClock变量或者用示波器测 MCO 引脚如果开了的话确认实际跑的主频和代码里认为的主频一致。我见过不止一个例子外部晶振焊接不良导致 HSE 起振失败片子自动回退到 HSI 8MHz而工程师以为主频是 72MHz所有外设的时间基准全部变成理论值的 9 倍。这种问题从时序上看哪儿都不对但根源只有一个。6.2 查预分频边界与计数溢出是否匹配第二查 PSC 和 ARR 的乘积有没有超过定时器最大计数范围。16 位定时器最大计数到 65535如果你要用 1MHz 计数频率数满 1 秒需要的 ARR 值是 999999根本装不下实际溢出时间会远远短于预期。这种时候要么用预分频把计数器频率降下来PSC 拉大要么改用 32 位定时器比如 TIM2/TIM5 在某些系列上支持 32 位模式要么用计数器级联的方式做长时间基准。6.3 查中断标志与回调是否干扰第三查中断路径。定时器更新中断是 UIF 置位捕获中断是 CC1IF 置位两者在TIMx-SR里是独立位如果开了更新中断又开了捕获中断回调函数里要分别处理或者至少确认回调不会因为一个标志没清干净而频繁误入。HAL 库HAL_TIM_IRQHandler会自动清标志但如果你在回调里又做了一堆操作甚至调用了HAL_Delay中断响应时间拉长下一个标志来了排队就会表现为捕获值偶尔偏差。还有一个很隐蔽的坑不同定时器的中断回调函数共用同一个函数名。HAL_TIM_PeriodElapsedCallback是所有定时器共享的如果你用了 TIM2 和 TIM3 两个定时器回调里必须判断htim-Instance是哪个再分支处理否则 TIM2 溢出去执行 TIM3 的逻辑时序完全乱套。6.4 查计数器复位时机与重装载是否同步第四查修改参数过程中的同步。前面说过影子寄存器机制如果程序在运行中改变 PSC 或 ARR一定要确认新值在什么时刻生效。需要同步改两个参数的时候还有一个细节先改 ARR再触发更新事件然后再改 PSC的顺序会不一样。因为 ARR 和 PSC 都是经影子寄存器同步的如果同时在更新事件前写入新的 PSC 和新的 ARR 会在下一个周期同时生效这个没问题但如果只改了 PSC 没触发更新事件PSC 值要等一个完整的旧周期才切换中间计数器可能已经按旧频率溢出了若干次中断的统计次数就不对了。这个坑在高频动态调参场景尤其明显。比如 FOC 驱动里载波频率随转速变化每次转速跳变时都想立刻改定时器定时参数如果没同步触发 UG 位会有一段过渡时间定时中断周期忽长忽短电流波形跟着出现毛刺。更讲究的做法是用定时器的一次脉冲模式或者用 DMA 去更新 ARR把时序交给硬件而不是靠软件安排。最后回到本文的题目定时器到底在数什么它就是在一个已知频率的时基信号上数脉冲边沿。时间基准的源头是时钟树传递路径是预分频器落点是计数器溢出最终才换算成中断间隔、PWM 频率或者捕获周期。把这条链路上每一个环节都确认一遍定时器的问题基本都能解决。我个人的体感是STM32 上 80% 的定时器问题根本不是定时器本身的问题而是时钟源配错了、总线分频算漏了、寄存器影子没生效。踩过几次坑之后我现在动手配定时器之前会先在纸上把这条链路完整写出来晶振频率是多少PLL 倍频多少APB 分频多少定时器时钟是多少PSC 是多少ARR 是多少。写完了再打开 CubeMX 填参数不凭感觉配。这个习惯从 F103 用到 F407从裸机用到 RTOS一直没翻过车。