本节摘要:硬件定时器是芯片里最被低估的外设:它不需要 CPU 参与就能数时间、出波形、量频率。本节从"预分频、计数器、比较寄存器"三件套讲起,推导 PWM 频率与占空比的完整计算链,对比输出比较与输入捕获两种工作模式,并借一次"舵机一抖、灯光就闪"的案例讲清定时器资源冲突这个经典问题。
上一节解决了"事件来了怎么抢答",这一节解决"时间本身怎么被硬件精确度量"。它紧随中断,因为定时器的本质就是"周期性中断的来源"加上"不经过中断的硬件波形机"——理解了中断,定时器的两种身份都好理解。
把定时器想象成一台三个旋钮的计数机。第一个旋钮是预分频器:把主时钟除以一个系数,决定计数器每"滴答"一次代表多长时间。第二个旋钮是计数器:每个滴答加一,数到上限自动归零(或按配置反向)。第三个旋钮是比较寄存器:设定一个门槛,计数器每次超过门槛就翻转输出或触发事件。
PWM 的全部参数都从这三个旋钮算出来。以 16MHz 主时钟、8 位分辨率(数到 255 归零)为例:预分频设为 1 时,PWM 频率等于主时钟除以 256,约 62.5kHz;占空比等于比较值除以 256。整条计算链是"主时钟 ÷ 预分频 ÷ 计数上限 = PWM 频率",反过来设计时先定频率与分辨率,再倒推预分频——分辨率与频率在天平两端:要 16 位细度就别想要高频 PWM,这是同一个计数器的两种花法。

设计 PWM 的正确顺序是倒推:需求给出目标频率与占空比精度,你反推预分频与计数上限。以 ESP32 为例,LED 外设直接提供这个接口:
// ESP32:把 13 号引脚配置为 25kHz、8 位分辨率的 PWM const int LED_PIN = 13; const int FREQ_HZ = 25000; // 25kHz,超出人耳听觉范围,避免电感啸叫 const int CH = 0; // LEDC 通道 0 const int RES_BITS = 8; // 8 位分辨率,占空比 0 到 255 void setup() { ledcSetup(CH, FREQ_HZ, RES_BITS); // 频率与分辨率在此通道绑定 ledcAttachPin(LED_PIN, CH); // 把通道接到引脚 ledcWrite(CH, 128); // 占空比 128,约 50% } void loop() { } // 波形由硬件持续输出,主循环自由
输入是三行配置,输出是一个持续的硬件波形——注意 loop 是空的,这正是硬件波形的价值:CPU 可以去睡觉或干别的,波形一赫兹都不会跑偏。作为对照,用软件在 loop 里翻转 GPIO 模拟 PWM,只要主循环里有任何耗时操作,波形立即失真——这也是第 4 章"把实时输出交给硬件"的伏笔。
舵机是另一类典型需求:50Hz 周期、0.5 至 2.5 毫秒高电平脉宽。它对频率稳定度极其敏感,因此不要与高频调光共用一个定时器通道。AVR 平台上 Arduino 的 Servo 库占用 Timer1,analogWrite 的 9、10 号脚恰好也是 Timer1——这就是著名的资源冲突源。
背景:一个云台项目,舵机由 9 号脚 Servo 库驱动,10 号脚用 analogWrite 做补光灯调光。现象:每次补光灯从暗调亮,舵机出现规律性抖动,串口无异常。
操作:先查电源——补光灯电流仅 100 毫安且独立供电,电压轨稳定,排除。再查软件冲突:查库源码可知 Servo 库用 Timer1 产生精确脉冲,而 9、10 号脚的 analogWrite 同样挂在 Timer1 上。analogWrite 一旦调用,会改写 Timer1 的配置,Servo 的 50Hz 脉冲立刻被扰成毛刺——舵机收到的脉宽忽长忽短,表现为抖动。把调光挪到 5、6 号脚(Timer0),两者井水不犯河水。
结果:调光全程舵机纹丝不动。
解读:AVR 只有三个定时器,每个 Arduino 内置功能(millis 用 Timer0、Servo 用 Timer1、音频用 Timer2)都在划走地盘。在这类平台上写驱动前先画一张"定时器占用表",是老工程师的肌肉记忆;ESP32 与 Pico 外设资源宽裕得多,冲突概率小,但"先查资源归属"的顺序不变。
变式:若项目需要更多精确 PWM 通道而定时器不够,可加一片硬件 PWM 扩展芯片走 I2C 总线(第 3 章 3.2 节),16 路波形由外部芯片生成,主控只发设定值——把"出波"这个任务彻底外包。
⚠️ 常见坑:改了定时器配置却忘了 milli 函数的基准——AVR 上动 Timer0 会让 delay 与 millis 全部失准,表现为"程序逻辑还在、时间概念全乱"。动任何定时器之前,先确认它是否已被 Arduino 核心占用。
💡 关键直觉:PWM 频率选型听两家的——负载的带宽与人耳的听觉。电机要低频大扭矩可选 1 至 8kHz,怕啸叫就上 20kHz 以上;LED 调光高于 200Hz 即无频闪感。
同一套三件套原理,落在三个平台上是三份不同的家底,选型与写驱动前各翻一遍:
| 平台 | 定时器家底 | 特色能力 | 主要雷区 |
|---|---|---|---|
| AVR(Uno/Mega) | 两三个 8 位加一两个 16 位 | 无 DMA,寄存器透明可控 | millis 与 Servo 抢占、8 位上限低频分辨率差 |
| ESP32 | 四个通用定时器加 LEDC PWM 外设 | 16 位分辨率、分组通道多 | 核心占用总线时中断抖动、射频抢占窗口 |
| RP2040 | 8 个 PIO 状态机加系统定时器 | PIO 自定义任意时序 | PIO 汇编有学习成本、无硬件除法器 |
两个平台的共性提醒:改任何定时器配置前,先查它在 Arduino 核心里是否已有主人;改完之后,millis 与延时的准确性要当场验证一遍。AVR 的 8 位定时器在低频 PWM 下分辨率堪忧——要 50Hz 舵机信号,16 位定时器或外部 PWM 扩展芯片才是正路,硬凑出来的低频波形抖动会原样传给舵机。