深夜的餐厅后厨,王师傅一人要照看三口锅。他的绝活不是颠勺,而是“掐表”:煎蛋必须精确到秒,炖肉的火力靠快速开关灶阀来维持,两位学徒交接一口热锅时,必须先松手、再接手,绝不能两只手同时握在锅柄上。指挥这场“厨房交响曲”的,是墙上的挂钟和手里的秒表。而在 STM32 芯片里,扮演挂钟、秒表和火力开关的,正是定时器(Timer)。 定时器听起来只是“计个时间”,真正的威力在于把时间切成无限细的刻度,并让引脚在每个刻度上做出精确反应——就像厨师在每一秒都决定火开还是关。由此衍生的三大绝技是:PWM 输出(快速开关引脚,模拟出任意大小的“火力”)、输入捕获(接住外部信号的精确时刻)、编码器接口(数轮子转了多少格)。本文就用这套“后厨时间管理学”,把定时器的原理、配置与实战一次讲透。
对 AI 从业者而言,定时器与 PWM 是“让算法落地到物理世界”的最后一公里:无论强化学习控制机械臂、视觉识别联动灯光,还是机器人里程计定位,最终都要靠这套时间机制驱动硬件。读懂它,你才能真正打通软硬件闭环。
可视化演示
展示系统处理流水线、实时性能指标曲线及多方案对比分析,包含数据流转、吞吐延迟监控与方案评估。
本节配有交互动效(进入视口后自动加载,约 3KB)。
系统可视化 处理流程:输入→处理→模型→输出 性能监控:实时指标 P=L×T
定时器工作原理
王师傅的后厨墙上挂着一只走时精确的挂钟,每秒钟滴答一下。但要等一锅汤炖 30 分钟,他不会傻数 1800 个滴答,而是换一只量程合适的秒表。挂在秒表前面的那个“换挡机构”,就是定时器的预分频器(PSC):主频滴答得再快,经过它一挡,也能变成“每 72 下才计一次数”的从容节奏。
在 STM32 里,定时器本质上是一个加 1 计数器。它每收到一个“节拍脉冲”,内部计数寄存器 CNT 就加 1。节拍来自哪里?来自系统主时钟(比如 APB1 总线上的 72MHz)。而 PSC 预分频器挡在主时钟与计数器中间,把 72MHz 的洪流按需放行。放行后的频率,就是我们真正关心的定时器计数频率。
分频的奥妙在于“从 0 开始数”:预分频寄存器写 71,实际是让计数器每数满 0 到 71(共 72 个脉冲)才输出一个节拍,相当于除以 72。此外,计数器还支持三种运行模式:向上计数(0→ARR 溢出)、向下计数(ARR→0 借位)和中心对齐模式(先上后下),后两种在电机控制中各有妙用。
挂钟的滴答还是那个 72MHz 的滴答,但经过挡位调节,煎蛋定时从“数秒”变成“数分钟”,时间一下子变得好用起来。这就是预分频存在的意义——让每一个任务都有一台量程合适的秒表。
- 系统主频 f_CLK就是挂钟每秒的滴答数,STM32 常见配置为 72MHz。
- 实际分频比 PSC+1PSC 是从 0 开始数的,写 71 表示隔 72 下才计数一次。
- 计数频率主频除以分频比,得到计数器每秒钟跳几格。
三大核心寄存器
一台专业的后厨秒表有三个旋钮:第一个旋钮决定“每格多长”,由 PSC(预分频寄存器)控制;第二个旋钮决定“走到多少回零重来”,由 ARR(自动重装寄存器)控制;第三个旋钮决定“走到哪个刻度要响铃提醒”,由 CCR(捕获/比较寄存器)控制。三个旋钮各管一摊,配在一起,秒表才能真正服务一道菜。
在硬件层面,PSC 直接决定计数节拍的频率;ARR 定义了计数上限——计数器从 0 加到 ARR 后自动清零,并产生一个更新事件(相当于秒表“叮”地响了一声),然后从头再来;CCR 则是一个实时比较器:CNT 每跳一次,硬件就把 CNT 和 CCR 比一比,相等时立刻触发比较事件。这个“相等检测”是 PWM 翻转、输入捕获等一系列高级功能的总开关。
三个旋钮就像菜谱上的三行字:时间粒度、重复节奏、提醒刻度。谁调错了,整道菜的节奏就全乱了。
- 每格时长分频后计数器跳一格要花的时间。
- 每周期步数CNT 从 0 走到 ARR,一共走 ARR+1 步(含 0 这一步)。
- 总周期时长两者相乘,得到一次完整计数循环的时间。
PWM波形生成
王师傅从不用旋钮微调火力——他直接把灶阀“啪嗒啪嗒”快速开关。只要开关够快,锅底的平均热量就能连续变化:开的时间长,火就大;开的时间短,火就小。这种“用时间占比表达大小”的技巧,正是 PWM(脉冲宽度调制)的烹饪版。
定时器把这种“啪嗒”机械化。在 PWM 模式 1 下,每个计数周期内:CNT 从 0 走到 CCR 之前,输出引脚保持高电平;CNT 一到 CCR,立刻翻成低电平,直到 ARR 清零重来。于是整整 ARR+1 步里,有 CCR 步是高电平、其余是低电平。高电平所占的比例,就是占空比。
火焰开关的“啪嗒”占空比 70%,锅底热量就是 70% 火力;LED 亮度、电机转速、扬声器音量,全是同一个“啪嗒”逻辑的换装表演。
- 高电平步数 CCRCNT 从 0 走到 CCR-1 这 CCR 步内,引脚输出高电平。
- 总步数 ARR+1CNT 从 0 走到 ARR 后清零,总共经历 ARR+1 种状态。
- 占空比高电平步数除以总步数,就是火焰开着的时间占比。
配置实战流程
好菜有菜谱,好配置有流程。王师傅教徒弟从不凭感觉,而是备料→切配→下锅→装盘四步走。配 PWM 也一样:把每个环节的“调料克数”记牢,代码就不会翻车。
用 STM32 的 HAL 库配置 PWM,核心是五步:① 开启 GPIO 与定时器时钟(备料);② 把 GPIO 复用为定时器输出通道(切配);③ 用 TIM_OC_InitTypeDef 结构体设置 PWM 模式 1,并填入 Pulse(即 CCR 值);④ 用 TIM_TimeBaseInitTypeDef 结构体填入 PSC 和 ARR;⑤ 调用 HAL_TIM_PWM_Start 启动输出(装盘)。参数怎么算?以目标 1kHz、50% 占空比为例:主频 72MHz,PSC=71 得到 1MHz 计数频率;ARR=999 得到 1kHz;CCR=500 得到 50%。
菜谱上的“盐 3 克”对应 PSC 填几、“煮 10 分钟”对应 ARR 填几、“火候半开”对应 CCR 填几——照着流程走,电磁炉和 STM32 都不难伺候。
- 先定计数频率选择 PSC=71,让计数节拍从 72MHz 降到 1MHz。
- 再定输出频率目标 1kHz,需要 1MHz ÷ 1000 = 1000 步,所以 ARR=999。
- 最后定占空比50% 占空比,CCR 取总步数的一半。
互补输出死区
两位学徒交接一锅滚油。如果一人还握着锅柄,另一人就抓上去,两股力相争,锅可能打翻;如果松手和抓握之间有个“真空期”,谁都不碰锅柄,交接才安全。电机驱动里的上下两个功率管,正是这对“交接锅柄”的学徒。
高级定时器(TIM1/TIM8)支持互补输出:CH 与 CHN 输出完全相反的波形,用来驱动 H 桥或半桥的上下桥臂。但“完全相反”不能是零延迟——如果上管还没关断、下管就导通,电源会被直通短路,瞬间烧毁功率管。所以必须插入一段死区时间 t_d:先关断一边,等一段真空期,再导通另一边。死区由 DTG 寄存器按死区时钟周期配置。
死区就是那一段“谁都不碰锅柄”的安全交接时间——太短会撞手,太长会误事,恰到好处才顺滑。
- 死区时钟周期死区计时用的参考时钟,周期越小,死区可以切得越细。
- DTG 装入值往死区寄存器里写一个数,表示要等多少个参考周期。
- 死区时长两者相乘,得到谁都不碰锅柄的安全间隙。
高级功能应用
王师傅想统计外卖窗口几点最忙,需要一只能“抓拍”时间的秒表——这就是输入捕获;他想知道餐车送了多少圈,就数轮子上的辐条——这就是编码器接口。两者都是“让定时器转过头来测量外部世界”的功夫。
输入捕获:当外部信号跳变(比如上升沿)时,硬件自动把当前 CNT 值锁存进 CCR,程序两次读到的差值,就是信号周期经过的计数步数,除以计数频率便得到真实频率或脉宽。编码器接口:STM32 定时器可对 A/B 两相正交信号进行 1x/2x/4x 倍频计数,只要知道电机每转一圈的反馈脉冲数,就能精确计算位置与转速。此外还有刹车(Break)功能:收到紧急事件立刻封锁全部 PWM 输出,相当于厨房的灭火阀。
抓拍秒表、数辐条、灭火阀——定时器从“自己走”进化到“看懂世界”的高级功能,让后厨自动化成为现实。
- 捕获值之差相邻两次上升沿之间,计数器走过的步数。
- 计数频率 f_TIM每秒钟计数器跳几格,由 PSC 决定。
- 信号频率步数之差占满一个周期,所以频率等于步数倒数乘以节拍频率。
实战综合案例
全套功夫练完,王师傅要出一桌菜:一道呼吸灯(模拟 LED 缓慢呼吸)、一道电机调速(让传送带转速随需求变化)、一道信号发生器(按需输出任意频率的方波)。
呼吸灯在定时器更新中断里,周期性改写 CCR,按正弦曲线改变占空比,LED 亮度就“呼—吸”起来;电机调速的本质是占空比决定平均电压,占空比越大、转速越高;信号发生器则在运行时改写 PSC 和 ARR,输出指定频率的波形。三者的公共分母都是前面那套公式:频率靠 PSC×ARR,占空比靠 CCR。
从一张菜谱到一桌菜,定时器的十八般武艺最终汇成一句话:想清楚时间粒度和高低电平的占比,剩下的交给硬件去“啪嗒”。
- 正弦项控制呼吸节奏,t 是经过的时间,T 是一个呼吸周期的时长。
- 缩放与偏移乘 (ARR+1)/2 并加 1 后平移,把范围归一到 0~ARR+1 之间。
- 取整限幅CCR 必须是整数且不能超过 ARR,所以最后四舍五入并做边界保护。
AI 应用场景
机械臂关节的“安全换向”:互补 PWM + 死区
阶段:模型决策下发至执行器(推理结果 → 电机控制)
解决的问题:强化学习或示教得到的关节角度指令,最终要变成电机电流。若上下桥臂直通,轻则驱动板冒烟,重则烧毁 MOS 管。只懂策略、不懂底层 PWM 死区的 AI 工程师,往往在实机验证时反复炸板。互补输出 + 死区从硬件层面保证了“先关断、再导通”的安全交接。
具体案例:某巡检机器人项目,机械臂关节用 TIM1 互补输出驱动 MOS 半桥,DTG 配置约 1μs 死区;策略网络输出的速度指令经 PID 换算为占空比,实机可安全启停、顺滑换向。
AI 视觉联动智能调光:PWM 调光
阶段:感知 → 决策 → 控制中的控制输出层
解决的问题:摄像头识别教室人数与环境亮度后,需要平滑调节 LED 亮度。传统电位器调光无法被程序连续控制,电阻调光又发热严重。用定时器 PWM 以 10kHz 驱动 LED 驱动 IC,占空比直接映射识别结果,肉眼无闪烁、功耗最优。
具体案例:智慧教室灯光系统,边缘设备用 OpenCV 检测光照与人脸,把亮度需求映射为 0~100% 占空比,定时器 PWM 输出控制 4 路 LED 灯带,调光曲线平滑无跳变。
服务机器人里程计:编码器接口 + 输入捕获
阶段:感知定位(SLAM 前端的数据来源)
解决的问题:机器人要知道自己走了多远。纯视觉估计在暗光下不稳,轮子编码器是最可靠的低层反馈;手写外部计数器易丢脉冲,而 STM32 定时器的编码器接口由硬件计数,绝不丢步。
具体案例:送餐机器人两个驱动轮各接 500 线编码器,定时器 4 倍频后每圈 2000 脉冲,500Hz 控制周期读取位置差,喂给扩展卡尔曼滤波做里程计融合,定位漂移显著减小。
Python 演示
import math CLK = 72000000 # 系统主时钟 72MHz def config_pwm(target_hz, duty=0.5): # 遍历 PSC,由目标频率反推 ARR,选出误差最小的组合 best = None for psc in range(65536): timer_clk = CLK // (psc + 1) arr = timer_clk // target_hz - 1 if arr < 0 or arr > 65535: continue freq = timer_clk // (arr + 1) err = abs(freq - target_hz) if best is None or err < best[0]: ccr = int(round(duty * (arr + 1))) best = (err, psc, arr, ccr, freq) return best def gen_wave(arr, ccr, cycles=3): # 模拟 PWM 模式 1:CNT < CCR 输出高电平,否则输出低电平 wave = [] for _ in range(cycles): for cnt in range(arr + 1): wave.append(1 if cnt < ccr else 0) return wave def main(): for target, duty in [(1000, 0.5), (10000, 0.25)]: err, psc, arr, ccr, freq = config_pwm(target, duty) wave = gen_wave(arr, ccr) real_duty = sum(wave) / len(wave) print("target=%dHz duty=%.0f%%" % (target, duty * 100)) print(" PSC=%d ARR=%d CCR=%d" % (psc, arr, ccr)) print(" actual freq=%dHz duty=%.2f%% err=%d" % (freq, real_duty * 100, err)) if __name__ == "__main__": main()
timer_clk = CLK // (psc + 1)分频后的节拍频率,别忘了 PSC 要加 1(含 0 计数)。arr = timer_clk // target_hz - 1从目标频率反推自动重装值,先除再减 1。err = abs(freq - target_hz)记录误差,保证最终选出最接近目标频率的配置。ccr = int(round(duty * (arr + 1)))占空比转比较值,乘的是总步数 ARR+1 而非 ARR。wave.append(1 if cnt < ccr else 0)PWM 模式 1 的本质:CNT 小于 CCR 输出高电平。real_duty = sum(wave) / len(wave)统计模拟波形中高电平占比,验证配置无误。
常见误区
误区我以为 PSC=100 就是 100 分频,结果算出来的频率怎么都对不上,是不是芯片坏了?
正解PSC 是寄存器里存的“序号”,从 0 开始计数,实际分频比是 PSC+1。就像排队报数,从 0 号报到 71 号一共 72 个人,写 71 才是 72 分频。主频 72MHz、PSC=71 时计数频率是 1MHz,而不是约 1.014MHz。
为何常见:日常计数从 1 开始,而寄存器编号从 0 开始,“装入值”与“实际个数”的差别非常容易被忽略。
误区占空比不就是 CCR 除以 ARR 吗?CCR=500、ARR=999,那不就是 50% 多一点?
正解计数器从 0 走到 ARR,共 ARR+1 步,所以占空比 = CCR/(ARR+1)。500/1000 = 50% 才是对的。虽然和 500/999 只差 0.05%,但这是理解错误,一旦 ARR 很小(比如 9),误差会高达 10% 以上。
为何常见:数学直觉把 ARR 当成“总数”,但含 0 计数的硬件逻辑里,步数永远比 ARR 多一步。
误区开关频率越高,波形越像理想信号,那我直接把主频拉到最大不就行了?
正解频率越高,相同时间内开关次数越多,MOS 管的开关损耗越大,驱动芯片可能过热甚至烧毁;频率过高时器件响应跟不上,波形反而失真。实际选择要匹配负载:电机常用 10~30kHz,调光电路几 kHz 就足以避免闪烁。
为何常见:只看到“越光滑越好”的直观感受,忽略了开关损耗和器件物理极限这些代价。
误区既然死区是防止短路用的,那我把它配得大大的,肯定万无一失。
正解死区是安全缓冲,但过大等于人为缩短了有效导通时间,会加剧波形失真和功率损耗,电机电流可能畸变、噪音变大。正确做法是“刚好盖过器件关断时间再加一点裕量”(几百纳秒到几微秒)。
为何常见:把“有安全机制”误当成“机制越强越好”,没意识到死区是双向付出的代价。
误区反正是乘法关系,PSC=71、ARR=999 和 PSC=719、ARR=99 都输出 1kHz,怎么配都一样。
正解两种配置频率相同,但占空比分辨率天差地别:前者总步数 1000,CCR 可调 1000 级;后者只有 100 级,调光时肉眼可见跳变。正确策略是“小 PSC、大 ARR”,优先保证够高的分辨率。
为何常见:只盯着频率公式,忽略了分辨率这个隐性指标,而它是决定控制平滑度的关键。
面试题
Q1. STM32 主频 72MHz,预分频寄存器写 71,定时器计数频率是多少?
- A.1 MHz,除七十二← 正确答案
- B.72 MHz,主时钟
- C.71 MHz,除七十一
- D.100 kHz,相差百倍
答案:A。PSC 从 0 开始计数,实际分频比为 PSC+1=72,所以计数频率 = 72MHz ÷ 72 = 1MHz。选 B 是忘了分频,选 C 是忘了加一,选 D 则数量级完全不对。
Q2. ARR=99,CCR=25,PWM 占空比约为多少?
- A.25%,高电平走二十五步← 正确答案
- B.50%,取 CCR 与 ARR 中点
- C.24.7%,把分母误算成 99
- D.75%,低电平占四分之一
答案:A。总步数是 ARR+1=100,高电平步数为 CCR=25,占空比 = 25/100 = 25%。C 选项是漏加一后的错误结果,B 和 D 则完全没有依据。
Q3. 目标输出 1kHz PWM,PSC 已取 71,ARR 应写多少?
- A.999,千步一毫秒← 正确答案
- B.1000,比例取整
- C.71,沿用分频值
- D.719,折中选取
答案:A。PSC=71 后计数频率为 1MHz,要得到 1kHz 需要 1000 步,但步数 = ARR+1,所以 ARR=999。选 B 是忘了“含 0 计数”少减了 1。
Q4. 下列哪一项主要由 CCR 比较值决定?
- A.占空比的大小← 正确答案
- B.PWM 输出频率
- C.计数节拍来源
- D.重装周期的长短
答案:A。CCR 决定高电平持续的步数,即占空比;PWM 频率由 PSC 与 ARR 的乘积决定,节拍来源由时钟树决定,重装周期由 ARR 决定,均与 CCR 无关。
Q5. PWM 模式 1(高有效)下,CNT < CCR 时引脚输出什么?
- A.高电平,先开后关← 正确答案
- B.低电平,先关后开
- C.高阻态,悬空输出
- D.随机翻转,不定状态
答案:A。PWM 模式 1 的规则是“计数小于比较值时输出有效电平”,高有效时即输出高电平。这就像火焰在计时前半段开着、到 CCR 刻度才关掉。
Q6. 下列哪些寄存器会影响 PWM 输出频率?
- A.PSC 预分频器← 正确答案
- B.ARR 自动重装值← 正确答案
- C.CCR 捕获比较值
- D.CNT 当前计数值
答案:AB。频率 = f_CLK / [(PSC+1)×(ARR+1)],只由 PSC 和 ARR 决定。CCR 只影响占空比;CNT 是只读的当前计数值,无法配置频率。
Q7. 互补输出插入死区时间的作用是什么?
- A.防止上下桥臂直通← 正确答案
- B.提高 PWM 频率上限
- C.减少控制器引脚数量
- D.确保先关断再导通← 正确答案
答案:AD。死区在两个互补信号之间制造“谁都不碰”的真空期,先保证一侧彻底关断,再允许另一侧导通,避免电源直通短路。它不改变频率上限,也不减少引脚。
Q8. 输入捕获两次相邻上升沿的 CNT 差为 1000,计数频率 1MHz,被测信号频率是多少?
- A.1 kHz,周期恰好千步← 正确答案
- B.1 MHz,与时钟同步
- C.100 kHz,差一百倍
- D.10 kHz,两数相除
答案:A。信号周期占用 1000 个计数步,每步 1μs,所以周期为 1ms,频率 = 1MHz ÷ 1000 = 1kHz。这就像秒表抓拍到两次起跑线之间隔了 1000 格。
Q9. 编码器接口模式能够实现哪些功能?
- A.位置计数← 正确答案
- B.转速测量← 正确答案
- C.温度采集
- D.脉宽调制
答案:AB。编码器接口对 A/B 正交脉冲计数,累计脉冲数即位置,单位时间脉冲数即转速。温度采集要靠 ADC,脉宽调制是定时器输出功能,与编码器输入方向相反。
Q10. 主频 72MHz,要输出 20kHz PWM,且占空比分辨率不低于 900 级,应选哪个 PSC?
- A.3,分频后 18MHz← 正确答案
- B.71,分频后 1MHz
- C.15,分频后 4.5MHz
- D.719,分频后 100kHz
答案:A。分辨率 = ARR+1 = 分频后频率 ÷ 20kHz,要 ≥900 则分频后频率需 ≥18MHz,即 PSC+1 ≤ 4,PSC 最大取 3。选 A 时 18MHz ÷ 20kHz = 900 级刚好满足;其他选项分辨率只有几十到几百级。