首页
/
行业洞察
/
正文
INDUSTRY INSIGHT · 深度
STM32定时器全解析:从时基单元到PWM、输入捕获与DMA
📅 2026/10/21 6:43:13
✍️ 爱科研究院
👁 阅读 3,247
1. 定时器家族总览先搞清楚你手上的是哪一种很多朋友一上来就抱着《参考手册》啃定时器章节结果越看越糊涂。原因很简单——STM32的定时器不是“一个外设”而是一整个家族。不同系列的定时器硬件资源、计数位数、时钟源复杂度、PWM输出能力完全不同学的时候必须分清楚层次。先给一张我自己的分类表这也是我后来带新人时必讲的第一张表定时器类型常见编号计数位宽核心能力典型用途高级控制定时器TIM1、TIM816位时基、PWM、互补输出、死区、刹车、编码器电机控制、逆变电源通用定时器TIM2~TIM5部分系列TIM2/3为32位16位或32位时基、PWM、输入捕获、输出比较、编码器普通PWM、脉宽测量、计数基本定时器TIM6、TIM716位只能时基触发DAC定时中断、延时基础低功耗定时器LPTIM16位低功耗模式下工作休眠唤醒、低频计数从这张表能读出很多信息。比如你想驱动一个直流电机做速度闭环那大概率要选TIM1或者TIM8因为需要互补PWM输出才能控制全桥电路如果你想做一组呼吸灯那找个通用定时器完全够用了没必要去跟高级定时器较劲。还有一个容易忽略的点位宽不同溢出周期天差地别。16位定时器在72MHz时钟下最大计数值65535算下来脉冲周期大概是0.91ms左右。如果你直接用这个定时器做1秒钟的定时中断那就需要软件计数器累加麻烦得很。而32位定时器最大计数值高达42亿多在72MHz下直接计数能到59秒以上做秒级任务就轻松很多。所以学习的正确顺序是先确认自己板子上有哪些定时器每个定时器的位宽和时钟频率然后再去研究寄存器怎么配。如果连选哪个定时器都没想清楚就直接抄代码大概率会卡在一个莫名其妙的配置错误上。2. 时基单元计数器、预分频器、自动重载值到底怎么协作定时器的“心脏”是一套时基单元很多文档把它表述为“Counter Prescaler Auto-reload Register”三个组件。我换一种说法大家就好记了预分频器管“跳几次才算一次”计数器管“数到多少算到头”自动重载值管“到头之后跳回哪里”。先说预分频器。它的作用不是把时钟频率变低而是把时钟脉冲“按比例吞掉”。假设TIMx的输入时钟是72MHz预分频系数设为71那么实际给计数器的时钟就是72MHz/(711)1MHz。这里有一个新手必踩的坑寄存器里写的是N真实分频是N1。也就是说你想分频到1MHz寄存器里写71而不是72这个“加一”的问题几乎每个初学者都至少错过一次。再看计数器。它从0往上数每收到一个时钟脉冲就加1。数到什么时候停下来这就轮到自动重载值ARV登场。当计数值等于ARR时计数器清零并产生更新事件UIF。所以PWM频率或者定时周期的计算公式是定时周期 (ARR 1) * (PSC 1) / Tclk注意两边都有加一。ARR寄存器写99实际是100个时钟周期PSC写71实际是72分频。这个公式我建议大家抄在便签上贴到显示器旁边因为后面所有定时器计算都绕不开它。理解了三个组件的关系之后还要理解一个执行细节预分频器和自动重载值在运行时修改不会立刻生效而是等到“更新事件”发生后才装载到影子寄存器。这个机制叫“影子寄存器”机制。很多朋友在运行中动态调ARR想改变PWM频率发现没反应就是因为在下一个更新事件之前硬件还在用旧值工作。解决方案是先把新值写到寄存器然后手动触发一次更新事件置位UG位EGR寄存器或者直接等当前周期结束。我在某个产品里做动态变周期输出时就因为这个影子寄存器机制排查了一个晚上。当时表面上所有寄存器值都写对了逻辑分析仪一看波形完全没变化后来翻手册看到“Shadow Register”才恍然大悟。所以大家学习时基单元时不只要背公式还要理解“硬件什么时候真正用上你写的值”。最后补一句关于时钟源的选择。STM32定时器的时钟不是只能来自内部总线有些定时器还可以选择外部时钟模式比如从某个IO引脚输入脉冲或者让TIM1和TIM2组成级联。这种骚操作在普通项目里用得少但如果你遇到“需要把两个频率信号关联起来”的场景外部时钟模式往往比用外部中断计数优雅得多。3. 输出比较与PWM理解“比较”是理解PWM的钥匙输出比较是定时器最强大的功能之一也是PWM输出的底层机制。很多人直接拿库函数去设置PWM频率和占空比却不知道背后发生了什么。其实PWM的本质就是计数器不断从0计到ARR同时有一个比较值CCR当计数值与CCR相等时硬件自动翻转输出引脚电平。这样说起来很抽象我用一个生活类比计数器就像一个沿操场匀速跑步的人从起点出发跑一圈的时间就是ARR1个时钟周期。操场某一圈那儿立了一个标记杆位置就是CCR。跑者经过标记杆的瞬间控制电路就“啪”一下拨动输出开关。于是电平就在标记杆处翻转一次在终点也就是起点再翻转一次。这样一轮下来高电平持续的时间就是CCR这一段低电平就是ARR1-CCR那一段。占空比自然就出来了。你可能会问那我直接改ARR不就能改频率改CCR不就能改占空比吗理论上是对的但有一个工程细节必须注意运行时修改ARR会导致占空比突变甚至瞬时异常。因为PWM比较发生在每一个计数周期内如果你改了ARR而CCR比新的ARR还大那这个周期就只有低电平相当于输出直接掉到0%。对于电机来说这就叫瞬间丢转对于LED来说就是闪一下。所以正确做法是需要调频率时尽量保持CCR与ARR的比值稳定需要调脉宽时只改CCR不改ARR。再说得专业一点很多高级应用里会用“中心对齐模式”而不是边沿对齐模式来做PWM因为中心对齐模式下PWM占空比的线性度更好谐波更小。STM32的定时器里都有Center-Aligned Mode你只需要把计数模式从向上计数改成中心对齐计数代码改动量很小但波形质量提升明显。再聊一个容易被忽略的功能比较中断与DMA联动。输出比较产生的事件不仅仅可以翻转引脚还可以触发中断或者DMA。比如你要产生N个占空比按正弦规律变化的PWM脉冲传统做法是在中断服务函数里挨个改CCR能跑但CPU压力大。更优雅的方案是把一张正弦查找表放到内存里然后配置定时器的比较事件作为DMA请求源让DMA自动把表格数据搬运到CCR寄存器。CPU从头到尾只负责初始化后续完全不用管。我在一个LED呼吸灯项目里就用了这个方案。当时PWM频率1kHz要做2000级的呼吸渐变如果用中断那每秒就是200万次中断单片机基本啥也不用干了。换成DMA搬表之后CPU负载几乎为零。这个经验对于做电机控制、音频输出、精密光源照明的朋友特别有用。4. 输入捕获与脉宽测量定时器是怎么“看”外部信号的定时器不只是往外输出波形它还能反着用——测量外部信号。输入捕获的思路也不复杂计数器一直在自由跑当外部引脚检测到指定的边沿上升沿或下降沿时硬件立刻把当前计数值“快照”到捕获寄存器里同时产生一个捕获事件。关键点在于这个快照。它记录的是“捕捉瞬间”的计数值而不是你自己去读CNT得到的值。因为轮询读CNT存在时间差这个时间差在高频信号下就是灾难。而硬件捕获能把误差控制在1个时钟周期以内。那么怎么测一个方波信号的频率很简单把输入捕获配置为“上升沿触发”捕获到当前计数值后立刻清零计数器这样两次上升沿之间的计数值就反映了信号的周期。频率 定时器时钟频率 / 捕获值。注意事项是如果被测信号频率太低捕获值会溢出16位或32位计数范围这时候需要结合溢出中断做扩展计数也就是在每一次溢出中断里给一个变量加1最终周期 溢出次数 * 最大计数值 当前捕获值。虽然麻烦但这是多周期测频的基本功建议练熟。还有一种常见场景是测高电平脉宽而不是测整个周期。做法是输入捕获设置为“上升沿和下降沿都触发”IRQ里先记录上升沿的计数值等下降沿再来一次再记录一个计数值两个值相减就是高电平持续的时间。同理如果把两个沿的顺序反过来测到的就是低电平脉宽。这个“双边沿测量法”在红外遥控解码、编码器信号质量检测、UART空闲符测量里都用得上。输入捕获还有一个非常有用的变种编码器接口模式。STM32的TIM1、TIM2等定时器可以直接接正交编码器信号A相、B相硬件根据两个通道的相位差自动判断方向并加减计数。这样外部只要负责输出A/B信号定时器自己就能完成“轴位置计数”和“方向判断”。相比用外部中断去数脉冲硬件编码器模式不需要软件干预而且不会漏脉冲。我在很多伺服、无人机云台、旋钮控制的项目里都推荐别人优先用编码器模式而不是自己写GPIO中断计脉冲。做输入捕获时还有一个经常被问的问题计数器的最大频率上限是多少这个数据手册里有但实际工程中还要考虑信号上升沿质量、滤波时间、引脚抖动等。如果输入信号边沿特别慢建议先在引脚前面加一个施密特触发器或整形电路否则很容易出现一次触发多次捕获的情况——你以为测到一个周期其实测到的是一串噪声造成的乱跳值。用硬件上的输入滤波器也能减弱这个问题STM32几乎所有的捕获通道都支持数字滤波配置时不要只关注边沿方向而忘了滤波参数。5. 更新中断、捕获中断、DMA请求定时器的“通知机制”怎么选定时器的价值一半在时基另一半在它产生事件后怎么通知CPU或者外设。很多新手把这些事件混为一谈代码里中断写法乱七八糟事件丢失了也找不出原因。这一节专门理清定时器的事件分发逻辑。先分清楚几个“名字很像但不是一回事”的标志位标志位来源事件常见用途UIF更新事件计数器上溢/下溢/清零定时中断定时器触发DAC等CC1IF~CC4IF捕获/比较事件PWM调节、脉冲测量、比较翻转TIF触发事件可由外部信号或其他定时器触发定时器级联、ADC同步触发BIF刹车输入事件电机安全保护、紧急停机这里有一个特别容易犯的错PWM输出模式下CCxIF标志其实也会置位因为输出比较事件发生了。很多人以为只有输入捕获才会有CCxIF结果在输出PWM时查中断标志发现CCxIF一直为1就以为代码错了。实际上这是正常的关键在于你有没有使能该中断以及有没有正确清除标志。清除方式通常是“写0清除”也就是先读状态寄存器再回写零或者直接对相应位写0。接着说DMA。STM32的定时器事件可以作为DMA请求这是把“硬件事件驱动”发挥到极致的关键。常见组合有定时器更新事件 → DMA搬运数据 → 驱动DAC输出波形定时器捕获事件 → DMA批量存储采样值 → 后续CPU批量分析定时器比较事件 → DMA更新CCR → 实现任意波形PWM。用DMA替代中断的最大收益是确定性。中断会有响应延迟延迟大小取决于当时CPU在执行什么指令、有没有更高优先级中断插队。而DMA不需要CPU介入它由总线矩阵直接干活碰到冲突时才可能等待整体响应比中断可控得多。对于波形生成这类对时序要求极苛刻的任务DMA几乎是唯一靠谱的方案。但是DMA也不是万能药。DMA传输本身会占用总线带宽如果同时开多个DMA通道互相抢总线可能导致某个事件的DMA请求不能及时响应。另一个问题是DMA配置错误时容易“静默失败”——数据没搬但代码看不到任何报错。我建议在调试时给DMA传输完成事件额外开一个中断一旦DMA做完就置一个软件标志用来验证DMA链路是否真的通。最后必须提一下定时器级联。STM32允许一个定时器用另一个定时器的输出作为自己的时钟源或者触发源。比如TIM2可以触发TIM3启动两个定时器间形成主从关系。这个机制在需要“产生N个PWM脉冲后自动停止”的场景非常有用TIM1做主定时器产生脉冲TIM8做从定时器计数脉冲个数数满后关闭TIM1。整个过程完全由硬件完成CPU只需要设置好参数然后睡大觉。还记得之前我说的那个呼吸灯和电机控制场景吗这类任务用级联DMA就是教科书级别的优雅方案。6. 从零开始的标准配置流程不抄库函数也能把定时器跑起来很多人学定时器最大的障碍是直接陷入寄存器或库函数的汪洋大海。我的建议是不管用什么开发方式先按下面这个流程走一遍走通了你对定时器的理解就有了骨架。以通用定时器输出PWM为例我给出一个不依赖具体库版本的标准步骤打开定时器时钟。STM32每个外设都有独立的时钟门控不打开时钟就操作寄存器大概率进HardFault。这步看起来简单但“忘了开时钟”是排第一的错误源。配置引脚复用功能。把定时器通道对应的GPIO引脚设置为复用模式并选对AF编号。不同定时器的通道可能有多个引脚选项这时候要查数据手册的“Alternate Function Mapping”表。设置计数模式、分频系数PSC、自动重载值ARR。计算目标频率后把这些参数写入寄存器。PWM频率记得先用公式Fpwm Tclk / ((PSC1) * (ARR1))。设置比较值CCR也就是初始占空比。配置输出极性、空闲状态和输出使能。这一步决定高电平有效还是低电平有效很多项目里接的驱动电路恰好是反逻辑忘了改极性输出就会反。如果是高级定时器还要处理主输出使能MOE位。TIM1/TIM8默认主输出是关闭的即使所有寄存器配好没有置位MOE引脚上也不会有波形。启动计数器用软件触发更新事件让影子寄存器装载。这个流程我每次带新人都会强调一遍。只要你能把每一步和硬件行为对应上而不是“因为例程里写了所以我也写”之后再移植到其他定时器、其他系列就轻松多了。举个例子。我在某项目里需要输出4路相位差90度的PWM直接用一个定时器的4个通道就能实现——把4个CCR分别设为0、ARR/4、ARR/2、3*ARR/4然后在中心对齐模式下输出。这个方案比用4个独立定时器稳定得多因为4路信号共享同一个时钟、同一个计数器相位关系天然精确。如果偷懒用4个独立定时器还要考虑相位同步问题麻烦得多。对于不需要PWM只需要定时的场景配置流程还能更简化但核心时基计算公式完全一致。唯一需要注意的是更新中断只在计数器溢出时触发如果你的ARR太大、计数器翻转周期很长而业务逻辑需要更短的周期执行就直接把ARR设小一点比如需要1毫秒中断就设ARR999、PSC7172MHz / 72 1MHz1MHz计数1000次正好1ms。7. 常见故障排查案例把这几个典型问题刻进脑子里学定时器最大的敌人是“看似配好了却没有任何反应”。下面这几个问题是我在实际项目里遇到过、也在论坛上反复看到的新手高频困扰每个都给排查思路不直接给答案帮助大家建立自己的调试直觉。第一个典型问题定时器中断从未触发。排查顺序建议是先看时钟门是否打开再看NVIC里中断是否使能、优先级是否配置然后检查中断标志位是否一直在硬件层面产生排除标志没被清除导致的假象最后看回调函数或ISR向量名是否写对被填入中断向量表。很多情况下ISR函数名写错了编译器不会报错中断也静默不触发——这是最坑的一种一定记得用调试器查看中断向量表指向哪个函数。第二个典型问题PWM输出引脚没有波形。优先检查引脚复用功能配置、高级定时器的MOE位、输出极性是否配置反了导致一直输出低电平。还可以用逻辑分析仪观看开漏或推挽配置是否正确。我在某次调试里发现引脚波形是有了但幅度特别小查到最后是GPIO配置成了开漏输出且没有上拉电阻把高电平拉到了1V多。第三个典型问题输入捕获的数据忽大忽小、明显不合理。这时候要怀疑信号边沿本身是不是有抖动。用示波器看信号如果边沿有毛刺配置输入滤波如果信号频率特别高检查捕获值是否溢出以及溢出计数是否正确。另外检查是不是用了双边沿触发而清除了错误标志。第四个典型问题动态修改ARR后PWM频率变了但输出瞬间出现毛刺。这就是前面说的影子寄存器问题修改后等到下一个更新事件才能生效。要“立即”生效就手动触发UG位。但你要想清楚更新事件也会清零计数器手动触发UG本质上是放弃当前周期如果你在PWM周期中间修改并且立即UG那波形会短暂异常这在电源、电机驱动里可能导致严重后果。第五个典型问题用DMA更新CCR但PWM占空比完全不变。大概率是DMA请求源配置错了。DMA外设定义了每种请求对应哪个定时器事件你如果选成了更新事件而不是比较事件数据只会被搬运到错误的时间点。另一个常见问题是DMA方向错了写成从外设到内存结果CCR被外设数据覆盖了。这些问题有一个共同的解题套路把问题分成“配置层”“中断/事件层”“波形/信号层”逐层排查。先用调试器看寄存器真实值再判断事件标志有没有产生最后用示波器或逻辑分析仪看最终的物理信号。永远不要一上来就怀疑硬件坏大部分都是配置和时序问题。我处理过至少几十个定时器排查case总结下来就一句话STM32定时器的寄存器设计非常规整所有问题几乎都能从“时钟是否通”、“配置是否进影子”、“事件是否到NVIC”、“信号是否被滤波/极性反转”这四个方向里找到答案。你把这些方向练成肌肉记忆定时器这块基本就出师了。
📌 标签:
工业官网
设计趋势
AI 建站
SEO
获取完整报告 →
RELATED ARTICLES
推荐阅读
2026/10/21 6:43:13
SAP会计科目表配置避坑指南:从设计逻辑到编码规则落地
2026/10/21 6:38:12
机器人控制中的AI Agent Harness工程:安全约束与落地实践
2026/10/21 6:38:12
mediasoup业务类初始化实战:Worker池、Router与Transport管理
2026/10/21 7:33:26
Notepad++ 7.3.2免安装版实战:便携配置、避坑与高效技巧
2026/10/21 7:33:26
网页消息提醒音实现:用AudioContext破解浏览器自动播放限制
2026/10/21 7:33:26
用C++写嵌入式UART驱动:从寄存器映射到中断路径的完整实践
2026/10/21 7:33:26
深度 | 大模型「周抛时代」:44天一代,四巨头同台换代的军备竞赛
2026/10/21 7:33:26
大模型原理之投机解码
2026/10/21 7:28:26
为什么国内很少有人用Linux桌面?生态、硬件与学习成本的现实困境
2026/10/21 0:01:44
ClickHouse实战:从列式存储到亿级数据秒级查询
2026/10/21 0:01:44
高效筛选arXiv机器学习论文:四步法实战指南
2026/10/21 0:01:44
Python爬取百度迁徙数据:接口签名、反爬与SQLite存储实战
2026/10/20 16:07:12
OpenResearch实战:Claude Code、Codex、OpenCode、Cursor 工作流搭建与故障排查
2026/10/20 7:57:25
知识管理核弹:用Obsidian构建第二大脑的工程化方案(TaoToken 统一 Key 接入版)
2026/10/20 16:49:38
金融AI落地指南:从调研报告到技术选型与工程实践
2026/10/20 2:24:03
我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频
2026/10/20 2:23:57
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证
2026/10/20 2:23:53
2026 大模型集体涨价:用 Python 做企业 Token 成本测算与选型避坑(附配置)