单片机定时器原理与应用:从精准延时到PWM与输入捕获实战

发布时间:2026/8/28 18:57:09
单片机定时器原理与应用:从精准延时到PWM与输入捕获实战 1. 项目概述为什么定时器是单片机的“心跳”与“节拍器”搞单片机开发无论是51、STM32还是其他架构你绕不开的一个核心外设就是定时器。很多人学单片机把定时器当成一个“延时工具”来用这其实是大材小用了。在我十多年的项目经验里定时器远不止是delay_ms那么简单它是整个系统时序的基石是精准控制、信号测量、通信同步的幕后功臣。你可以把它理解为单片机内部的“心跳”和“节拍器”没有它程序就是一盘散沙无法与真实世界的时间维度精确交互。我们常说的“定时器”在单片机内部往往和“计数器”是同一个硬件模块只是工作模式不同。定时模式是对内部固定频率的时钟脉冲进行计数从而实现定时功能计数模式则是对外部引脚输入的脉冲信号进行计数。无论是TMR0、TMR1这类通用定时器还是STM32中功能复杂的通用/高级定时器其核心原理都是相通的。本章内容我们就来彻底拆解这个“心脏”部件从原理到应用再到常见习题的深度分析让你不仅会用更能懂其所以然在设计系统时能做出最合适的选择。2. 定时器核心原理与工作模式深度解析2.1 定时与计数的本质都是“数数”所有定时/计数器模块其最核心的部件就是一个计数器寄存器。这个寄存器可以向上计数从0加到某个值也可以向下计数从某个值减到0。它的工作方式非常简单每来一个有效的“脉冲”计数器的值就加1或减1。那么关键就在于这个“脉冲”从哪里来定时器模式脉冲来源于单片机内部的系统时钟或经过分频后的时钟。例如系统主频是12MHz经过12分频后得到1MHz的时钟即每个脉冲周期是1微秒。计数器每计1个数就代表过去了1微秒。通过设置计数器的初始值和溢出值就能实现精确的定时。所以定时本质上是“数内部时钟的个数”来度量时间。计数器模式脉冲来源于单片机外部引脚。例如连接一个光电传感器来检测流水线上的产品每过一个产品传感器产生一个脉冲信号送到计数器引脚计数器就加1。这样就可以统计产品的数量。所以计数本质上是“数外部事件的个数”。理解了这个本质再看任何型号单片机的定时器数据手册你就能抓住重点关注它的时钟源选择、分频器配置、计数模式向上/向下/中央对齐、以及计数器的位数8位、16位、32位决定了最大计数值。2.2 关键概念拆解预分频、重装载值与溢出中断要让定时器按照我们期望的时间工作需要配置几个关键参数时钟源与预分频器单片机的系统时钟频率很高几十MHz到几百MHz直接用它来计数计数器会溢出得飞快无法实现较长的定时。因此需要预分频器对系统时钟进行降频。比如系统时钟72MHz预分频系数设为7200那么输入给计数器的时钟频率就变成了10kHz周期0.1ms。预分频系数越大定时时间越长但定时精度分辨率会降低。这是一个需要权衡的地方。自动重装载寄存器这是实现周期性定时的关键。以向上计数为例计数器从0开始累加当加到与ARR值相等时就会产生一个“更新事件”溢出计数器自动清零重新开始计数。同时这个更新事件可以触发中断。ARR的值直接决定了定时周期。定时时间T (ARR 1) * (预分频系数 1) / 系统时钟频率。例如系统时钟72MHz预分频7199即7200分频ARR设为999则定时时间T (9991) * (71991) / 72,000,000 0.1秒。这样就能产生一个100Hz的周期性中断。计数模式向上计数最常用从0计到ARR溢出后归零。向下计数从ARR计到0溢出后重新加载ARR。中央对齐向上/向下先向上计数到ARR产生溢出事件后改为向下计数到0再产生溢出事件后改为向上计数如此往复。这种模式常用于生成对称的PWM波。捕获/比较通道这是定时器的高级功能。比较模式是定时器计数过程中不断与预设的比较寄存器值进行比对相等时可以控制输出引脚电平翻转从而产生PWM信号。捕获模式是当外部引脚发生特定边沿跳变时将当前计数器的值“抓取”并保存到捕获寄存器中用于精确测量外部脉冲的宽度或频率。注意不同单片机对预分频器和重装载值的处理逻辑有细微差别。比如有些芯片的预分频器是“N分频”写入寄存器的值是N-1重装载值ARR代表计数的终值定时周期是(ARR1)个时钟周期。务必以当前使用芯片的数据手册为准这是最易出错的地方之一。3. 从理论到实践典型应用场景与配置要点理解了原理我们来看看定时器在项目中具体怎么用。这里以最常见的场景为例。3.1 精准延时替代低效循环新手最常用的delay函数通常是用空循环for(i0; iX; i)来实现这被称为“软件延时”。它极度浪费CPU资源在延时期间CPU什么都干不了且时间容易受中断和编译器优化影响。使用定时器实现硬件延时是标准做法。步骤通常如下配置定时器时钟源和预分频器确定计数时钟周期。计算产生所需延时如1ms需要的计数值并设置ARR。使能定时器更新中断。启动定时器。在中断服务函数中对一个全局变量如ms_ticks进行递增。实现一个delay_ms(uint32_t ms)函数其内部记录调用时的ms_ticks值然后循环等待直到当前的ms_ticks与记录值的差值大于等于参数ms。这样CPU在“等待”延时的过程中可以执行其他任务或进入低功耗模式只在定时器中断发生的瞬间进行一下计数加一的操作极大地提高了系统效率。3.2 生成PWM信号驱动电机与LEDPWM是定时器比较输出的典型应用。例如用STM32的通用定时器TIM3的通道1产生一个1kHz、占空比50%的PWM波驱动LED实现呼吸灯或控制电机转速。配置要点时基单元配置确定PWM频率。PWM频率 定时器时钟 / ((ARR 1) * (PSC 1))。若定时器时钟为72MHz要产生1kHz PWM可设PSC7172分频则定时器计数时钟为1MHz。要使频率为1kHz则ARR需设置为999。这样计数周期就是1000个计数时钟即1ms。输出比较配置将通道1设置为PWM模式1或2。设置捕获/比较寄存器CCR1的值。占空比 CCR1 / (ARR 1)。要实现50%占空比则CCR1设置为500。GPIO配置将对应的GPIO引脚如PA6复用为定时器输出功能并推挽输出。使能定时器输出和自动重装载预装载然后启动定时器。此时引脚上就会输出稳定的PWM波。通过在主循环中修改CCR1的值就能动态改变占空比实现LED亮度渐变或电机调速。3.3 输入捕获测量脉冲宽度与频率这是“计数”功能的深化应用。比如要测量一个红外遥控信号的脉冲宽度或者测量旋转编码器的转速频率。配置与操作流程将定时器通道配置为输入捕获模式并选择捕获边沿上升沿、下降沿或双边沿。开启捕获中断。第一次捕获如上升沿发生时在中断中记录当前计数器值TIM_CNT到变量value1并将捕获边沿改为下降沿。第二次捕获下降沿发生时再次记录计数器值value2。脉冲宽度 (value2 - value1) * 计数时钟周期。如果计数器在期间发生了溢出还需要考虑溢出次数。测量频率时可以设定一个固定的闸门时间比如用另一个定时器定时1秒在此时间内用输入捕获模式统计上升沿的次数即为频率。更精确的方法是捕获两个相邻上升沿之间的时间间隔其倒数即为频率。实操心得输入捕获测量高频信号时要确保定时器的计数时钟足够快即预分频尽量小否则测量误差会很大。对于低频信号则要防止在捕获间隔内计数器多次溢出这时可以启用定时器的溢出中断来记录溢出次数。STM32等高级芯片的定时器有专门的“编码器模式”来直接接口正交编码器这比用输入捕获手动处理要方便和可靠得多。4. 经典习题分析与避坑指南理论学习后通过习题巩固是关键。下面分析几个典型题目并指出常见错误。4.1 习题一定时器初值计算题目已知51单片机采用12MHz晶振定时器T0工作在方式116位定时模式要求产生50ms的定时中断求定时器初值TH0和TL0。分析51单片机机器周期 12 / 晶振频率 12 / 12MHz 1μs。这是定时器计数脉冲的周期。方式1是16位计数最大计数值为65536。需要定时50ms 50000μs。所需计数值 N 定时时间 / 机器周期 50000μs / 1μs 50000。定时器初值 X 最大计数值 - N 65536 - 50000 15536。将15536转换为十六进制0x3CB0。所以TH0 0x3C TL0 0xB0。常见错误忘记12分频误将机器周期算作1/12MHz ≈ 0.0833μs导致计算结果完全错误。初值计算错误误用公式初值 65536 - 定时时间(ms)单位不匹配。赋值顺序在代码中应先给TL0赋值低字节再给TH0赋值高字节因为51单片机在设置初值时如果先写高字节低字节会自动清零可能引发问题。更稳妥的做法是TL0 (65536-50000) % 256; TH0 (65536-50000) / 256;。4.2 习题二PWM参数设计题目STM32F103定时器时钟为72MHz预分频器PSC设置为71自动重装载寄存器ARR设置为999。比较寄存器CCR设置为300。请问生成的PWM频率和占空比是多少分析频率计算预分频后时钟 72MHz / (711) 1MHz。计数周期 ARR 1 999 1 1000。PWM频率 1MHz / 1000 1kHz。占空比计算占空比 CCR / (ARR 1) 300 / 1000 30%。关键点一定要牢记“1”无论是PSC还是ARR数据手册中写入寄存器的值通常是分频系数-1或周期值-1。这是STM32定时器配置中最常见的坑。4.3 习题三定时器模式选择与中断处理题目用一个定时器同时实现1ms的系统时钟节拍和10ms的按键扫描该如何设计方案与解析 单纯用一个定时器硬件很难直接产生两个不同周期的中断。但可以通过软件方法实现将定时器配置为1ms中断。这是硬件定时周期。在1ms的中断服务函数中对一个静态变量soft_timer_cnt进行递增。在主循环或一个专门的任务中检查soft_timer_cnt的值。当(soft_timer_cnt - last_key_scan_tick) 10时就执行一次按键扫描程序并更新last_key_scan_tick。避坑指南中断服务函数必须高效1ms中断非常频繁中断函数里除了递增计数器不要做任何复杂操作如扫描按键否则会严重影响系统实时性。变量类型与溢出soft_timer_cnt应使用无符号整型如uint32_t并且判断超时时应使用“差值比较法”如上例而不是直接判断soft_timer_cnt 某值因为soft_timer_cnt会溢出归零。资源共享如果多个软定时器共享一个硬件定时器注意对公共计数器的访问安全在中断中修改在主循环中读取通常不需要特别保护但需注意编译器优化问题可将其声明为volatile。5. 进阶应用与性能优化实战5.1 多定时器协同与级联复杂系统可能需要多个不同定时的任务。例如一个系统需要10us的高精度ADC采样触发100ms的传感器数据读取和1s的系统状态上传。方案高精度定时使用一个高级定时器如STM32的TIM1/TIM8其时钟可以独立于系统时钟或者使用更高的时钟源配置成10us中断专门用于触发ADC采样。中等精度定时使用一个通用定时器如TIM2配置为100ms中断用于传感器数据采集。低精度定时使用基本定时器如TIM6/TIM7或者SysTick系统滴答定时器配置为1ms中断再通过软件计数器累积到1000次来实现1s定时用于通信等不要求严格同步的任务。级联应用当需要超长定时如1小时而单个定时器最大计数值不够时可以将定时器级联。例如将TIM2配置为主模式将其更新事件作为触发输出连接到TIM3的触发输入。这样TIM2每溢出一次TIM3才计数一次。实现了“定时器的定时器”极大地扩展了定时范围。5.2 低功耗系统中的定时器设计在电池供电的设备中CPU应尽可能长时间处于睡眠模式。此时定时器是唤醒系统的关键。设计要点选择低功耗定时器许多单片机有专门的低功耗定时器如STM32的LPTIM或某些芯片的RTC唤醒定时器。它们可以在深度睡眠模式下由独立的低速时钟如LSI 32kHz驱动消耗极低的电流。配置唤醒中断将低功耗定时器配置为所需的唤醒周期如1秒并使能其唤醒中断。进入睡眠主程序完成工作后配置好定时器然后执行进入低功耗模式的指令如WFI。中断唤醒定时时间到产生中断单片机退出睡眠模式执行中断服务程序然后回到主循环继续工作。实测经验使用内部低速时钟LSI时要注意其精度较差可能偏差±5%不适合对定时精度要求极高的场合。如果对精度有要求应使用外部低速晶振LSE或者定期通过外部信号如GPS秒脉冲进行时钟校准。5.3 高精度定时与时间戳应用在通信同步、数据采集同步等场景需要微秒甚至纳秒级的时间戳。实现方法使用最高速时钟选择一个定时器为其提供系统所能允许的最高频率时钟有时可以通过时钟树倍频得到并设置最小的预分频。连续运行让这个定时器在自由运行模式即ARR设置为最大值如0xFFFF下不间断计数。获取时间戳当某个事件如收到一个数据包、检测到一个边沿发生时立即读取该定时器的计数器值CNT。这个值就是一个相对于某个起点的高精度时间戳。处理溢出同时开启这个定时器的溢出中断用一个全局变量如overflow_cnt记录溢出次数。那么完整的时间戳 overflow_cnt * (ARR_MAX 1) CNT。ARR_MAX是定时器最大计数值。注意事项在读取CNT和overflow_cnt时可能会遇到“撕裂读”问题。即读取CNT后在读取overflow_cnt之前CNT恰好溢出并增加了overflow_cnt。这会导致时间戳出现一个大周期的错误。解决方法通常是先读overflow_cnt再读CNT然后再读一次overflow_cnt。如果两次读到的overflow_cnt不同说明发生了溢出需要重复上述过程或进行数据修正。