公司动态

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

📅 2026/8/28 1:38:47
单片机定时器原理与应用:从基础定时到PWM与输入捕获实战
1. 从“闹钟”到“心脏”为什么定时器是单片机的灵魂如果你刚开始接触单片机可能会觉得定时器这东西有点抽象不就是个计时的吗我写个循环让程序空跑不也能实现延时这可能是很多新手的第一反应。但当你真正开始做项目比如想让一个LED灯精确地每500毫秒闪烁一次或者需要测量一个脉冲信号的宽度又或者想生成一个频率和占空比都精准可调的PWM信号去控制电机转速时你就会发现那个简单的“空循环延时”方法完全不够用甚至可以说是“业余”的。为什么因为单片机在执行你的“空循环”时CPU被完全占用它不能去响应按键、不能去刷新显示、更不能去处理通信数据。整个系统就像被“冻住”了一样除了傻傻地数数什么也干不了。这显然不是我们想要的智能控制系统。这时定时器就该登场了。你可以把它理解成单片机内部一个完全独立、自带时钟源的“小闹钟”或者“秒表”。你只需要在程序开始时对它进行一番设置告诉它走多快、数到多少响铃然后就可以完全不用管它去执行其他任务了。当这个“小闹钟”数到了你预设的值它会通过一个叫“中断”的机制立刻“拍一下”CPU的肩膀说“时间到了该干活了” CPU则会暂时放下手头的工作去执行你预先写好的“到点处理程序”比如翻转一下LED的电平处理完再回来继续之前的工作。所以定时器的核心价值在于“解放CPU实现精准的并行时间管理”。它让单片机从“单线程傻等”变成了“多线程并发处理”这是嵌入式系统从玩具走向实用的关键一步。几乎所有你能想到的、与时间相关的功能都离不开定时器精确定时、脉冲计数、信号脉宽测量、PWM波形生成、甚至作为串口通信的波特率发生器……说它是单片机的“心脏”或“节奏之源”毫不为过。本章我们就来彻底拆解这个核心部件。无论你用的是经典的51内核还是主流的STM32、GD32抑或是其他ARM Cortex-M芯片定时器的基本思想都是相通的。我们会从最基本的原理模型讲起再深入到不同工作模式的应用最后结合典型的习题和实际工程场景让你不仅能看懂手册更能用活定时器。2. 定时器/计数器的基础模型一个可预置的累加器要理解定时器首先要建立一个清晰的物理模型。抛开所有型号的具体差异一个最基本的定时器/计数器可以抽象为以下几个核心部件计数寄存器这是一个可以递增或递减的寄存器比如8位的TMR0或16位的TMR1。它是定时器的核心其当前值代表了“数了多少下”。时钟源计数寄存器凭什么“数数”需要有个节拍。这个节拍就是时钟源。它有两个主要来源内部时钟通常来源于系统主时钟经过一个可编程的分频器。此时定时器每个时钟周期计一次数实现的是“定时”功能。例如系统时钟是12MHz分频后给定时器的时钟是1MHz那么每计一个数就代表过去了1微秒。外部引脚来自芯片特定引脚的脉冲信号。此时定时器在每个外部脉冲的边沿计一次数实现的是“计数”功能。常用于测量转速、计数产品数量等。分频器位于时钟源和计数寄存器之间。因为系统时钟往往很快几十MHz直接计数的话计数器很快就溢出了无法实现长定时。分频器可以对时钟源进行2、4、8……256等分频降低计数频率从而延长定时周期。预分频器这是一个与分频器类似但有时在结构上有所区别的概念在某些架构如PIC单片机中它是一个独立的、可与定时器配置的模块。我们可以简单理解它为另一个可配置的时钟分频环节目的是为了更灵活地获得更低的计数频率。重装载寄存器这是实现“自动重载”功能的关键。你事先把想要定时的时间对应的计数值存入这个寄存器。当计数寄存器计满溢出时硬件会自动将重装载寄存器的值重新填入计数寄存器并立即开始下一轮计数。这样就实现了连续、无间隔的周期性定时是产生PWM和精确周期信号的基础。比较/捕获寄存器比较模式你事先设置一个比较值。当计数寄存器的值等于这个比较值时硬件会自动触发一个动作如输出引脚电平翻转、产生中断同时计数器继续运行。这是生成PWM的核心机制。捕获模式当外部引脚发生特定事件如上升沿时硬件会瞬间将当前计数寄存器的值“抓取”并保存到捕获寄存器中。通过计算两次捕获值的差就能精确算出外部脉冲的宽度或周期。我们可以用一个生活中的类比来串联这些概念想象一个带有自动重置功能的水龙头滴水计数器。计数寄存器就是记录水滴数量的机械数字滚轮。时钟源水滴的滴落。如果是固定时间间隔滴水如每秒一滴那就是“定时模式”如果是根据实际下雨的水滴来计数那就是“计数模式”。分频器/预分频器在水龙头前加一个漏斗让每积累10滴水才有一滴滴下来触发计数器这样计数器就变“慢”了10倍。重装载寄存器你希望每计满1000滴就响铃一次并自动归零。那么你把这个“1000”预先设好。当计数器到999后再来一滴计数器归零溢出同时铃响并且计数器不是归零而是自动重新从你预设的某个值比如0开始计数准备下一个1000滴。比较寄存器你希望水滴数到500滴时就亮起一个警示灯。那么你设置比较值为500。当计数滚轮转到500时电路导通灯亮。捕获寄存器你想测量两次雷声之间的间隔。第一次雷声时你瞬间拍下当前水滴计数的照片值A第二次雷声时再拍一张值B。B-A再乘以每滴水的时间间隔就是雷声间隔。注意不同厂商、不同系列的单片机对这些模块的命名和整合方式有所不同。例如在标准51单片机中功能相对简单而在STM32的通用定时器中分频器、重装载寄存器、比较寄存器等功能被高度集成和强化。但万变不离其宗上面这个基础模型是理解所有定时器的钥匙。3. 工作模式深度剖析不止于定时理解了基础模型我们来看看定时器最常见的几种工作模式以及它们是如何运用上述模型的。3.1 定时模式精准的延时与心跳这是最基础的模式。配置时钟源为内部系统时钟设置好分频比和需要计数的个数。核心公式定时时间 (重装载值 - 初始值) * (时钟周期) * (分频系数)例如在STM32中系统时钟72MHz定时器时钟也设为72MHz。我们希望产生1ms的定时中断。时钟周期 1 / 72MHz ≈ 13.89ns。1ms需要计数的个数 1ms / 13.89ns ≈ 72000。对于一个16位定时器最大计数值65535 72000所以需要分频。设置分频系数为72则定时器时钟变为1MHz。此时时钟周期 1us。1ms需要计数1000个。设置自动重装载寄存器ARR 1000 - 1 999因为从0开始计数。设置初始值CNT 0。开启更新中断。这样定时器就会每计1000个数耗时1ms产生一次溢出和中断在中断服务程序里你可以进行系统时基更新、扫描按键等操作。实操心得计算重装载值时务必注意芯片手册的说明。有些定时器计数到“重装载值”时溢出有些是计数到“最大值”后下一个时钟溢出。STM32的ARR寄存器值表示计数的周期通常写入目标计数值 - 1。这是新手常踩的坑务必对照手册确认。3.2 计数模式对外部事件的度量将时钟源切换到指定的外部输入引脚定时器就变成了计数器。它忽略内部时钟只在检测到外部引脚的电平边沿上升沿或下降沿可配置时递增。典型应用产品产量计数光电传感器每通过一个产品产生一个脉冲计数器加1。电机测速电机编码器每转一圈输出若干个脉冲。通过定时读取计数器值可以计算转速。注意事项外部脉冲的频率不能超过定时器时钟频率的1/2奈奎斯特采样定理的实践体现否则会丢失脉冲。同时要注意外部信号的抖动必要时在硬件RC滤波或软件多次采样去抖上做处理。3.3 输入捕获模式测量时间的高手这是计数器模式的进阶应用用于精确测量外部脉冲的宽度或周期。需要用到前面提到的“捕获寄存器”。测量脉宽的工作流程配置定时器工作在内部时钟定时模式并设置一个合适的频率例如1MHz分辨率1us。配置输入捕获通道将其映射到外部信号引脚并设置捕获边沿为上升沿。当第一个上升沿到来时硬件自动将当前计数寄存器CNT的值复制到捕获寄存器CCR1中并可以产生一个捕获中断。在中断服务程序里将捕获边沿改为下降沿。当下降沿到来时硬件再次捕获CNT值到CCR1。计算两次捕获值的差值乘以计数周期就得到了高电平脉宽。重复步骤3-6可测量周期。避坑指南在测量较长脉宽时要考虑到计数器可能溢出。例如16位计数器1us计数最多测量65.535ms。如果脉宽可能超过这个值必须在捕获中断中维护一个“溢出次数”的软件计数器。最终脉宽 (溢出次数 * 65536 本次捕获值 - 上次捕获值) * 周期。3.4 输出比较与PWM模式控制的艺术这是定时器作为“输出”角色的核心模式用于生成精确的时序信号特别是PWM。输出比较你设置一个比较寄存器CCRx和一个输出模式。定时器CNT不断自增当CNT等于CCRx时根据输出模式硬件会自动改变对应输出引脚的电平置高、置低、翻转。之后CNT继续增长直到溢出重载开始下一轮。通过不断改变CCRx的值就能生成复杂的波形。PWM生成这是输出比较的一个固定应用。你需要设置两个参数ARR决定PWM的频率。频率 定时器时钟 / (ARR 1)。CCRx决定PWM的占空比。占空比 CCRx / (ARR 1)。定时器CNT从0开始递增在CNT CCRx期间输出一种电平通常为高当CNT CCRx但小于等于ARR期间输出另一种电平低CNT到达ARR后溢出并清零开始下一个周期。整个过程完全由硬件完成不占用CPU。一个关键技巧在电机控制等应用中我们常常需要动态调整PWM占空比。直接修改CCRx寄存器如果在CNT计数到CCRx附近时修改可能会导致当前周期输出出现毛刺。高级定时器通常支持“预装载寄存器”。你写入的新CCRx值会先到一个缓冲寄存器等到本次PWM周期结束CNT溢出时才真正生效从而保证波形稳定。4. 从习题到实战典型场景拆解理论学习之后我们通过几个典型的习题和项目场景来巩固如何运用定时器。4.1 习题场景用定时器实现精确的1秒延时题目已知51单片机采用12MHz晶振定时器T0工作在模式116位定时模式。请编写程序利用T0中断实现1秒的精确延时。分析模式选择模式1是16位不自动重载模式最大计数值65536。计算初值51单片机机器周期 12 / 12MHz 1us。定时器每1个机器周期计数一次。欲定时X微秒需计数值 N X。对于16位定时器装入的初值 65536 - N。1秒 1,000,000微秒。但65536 1,000,000所以单次定时无法实现必须采用中断多次累加。设计定时基准我们取一个方便计算的定时长度比如50ms50000us。这个时间既不太短中断不会过于频繁又能被1秒整除。计数值 N 50000。定时器初值 TH0 (65536 - 50000) / 256; TL0 (65536 - 50000) % 256。软件计数定义一个变量counter初始为0。在T0中断服务程序中counter。当counter达到20时20 * 50ms 1000ms说明1秒时间到执行相应操作并将counter清零。代码要点#include reg51.h unsigned int counter 0; // 软件计数器 void Timer0_Init() { TMOD 0xF0; // 清零T0控制位 TMOD | 0x01; // 设置T0为模式1 TH0 (65536-50000)/256; // 装入50ms定时初值高8位 TL0 (65536-50000)%256; // 装入50ms定时初值低8位 ET0 1; // 开启T0中断 EA 1; // 开启总中断 TR0 1; // 启动T0 } void Timer0_ISR() interrupt 1 { TH0 (65536-50000)/256; // 模式1需手动重装初值 TL0 (65536-50000)%256; counter; if(counter 20) { counter 0; // 在这里执行每1秒需要做的任务例如翻转一个LED P1_0 ~P1_0; } } void main() { Timer0_Init(); while(1) { // 主循环可以执行其他任务 } }注意标准51的定时器模式1在中断后需要手动重装初值这是和现代单片机自动重载最大的不同也是容易忘记导致定时不准的地方。4.2 项目场景基于输入捕获的超声波测距超声波模块如HC-SR04通过发射波和接收回波的时间差来计算距离。这个时间差通常在几十到几百微秒到几毫秒之间非常适合用定时器的输入捕获功能来测量。实现思路使用一个定时器如TIM2配置为内部时钟分频到1MHz1us分辨率。配置一个GPIO引脚连接Trig为输出用于触发发射。配置定时器的一个通道连接Echo为输入捕获模式上升沿和下降沿捕获均使能。测距流程发射Trig引脚给出一个10us以上的高脉冲。等待上升沿开启输入捕获上升沿并清零定时器CNT和溢出计数。超声波模块收到触发后会发出8个40kHz脉冲并让Echo引脚变高。捕获高电平开始Echo上升沿触发捕获中断在中断中记录捕获值start_time并将捕获边沿改为下降沿。捕获高电平结束当超声波返回Echo变为低电平时触发下降沿捕获中断记录捕获值end_time。计算时间差time_diff (溢出次数 * 65536) (end_time - start_time)。单位为微秒。计算距离距离 (time_diff * 声速) / 2。声速取340m/s即0.034cm/us。所以距离(cm) time_diff * 0.034 / 2 time_diff * 0.017。避坑经验超时处理如果超过一定时间如30ms仍未收到回波下降沿应判定为测距失败放弃本次测量并复位状态。否则程序会一直等待。中断优先级捕获中断应设置为较高优先级确保时间戳的准确性。信号滤波Echo信号可能会有毛刺可以在捕获中断中结合软件滤波连续几次采样确认电平来提高稳定性。4.3 项目场景用PWM驱动舵机舵机的控制信号是一个周期为20ms50Hz脉宽在0.5ms到2.5ms之间的PWM信号。脉宽对应舵机角度如1.5ms对应中间90度。以STM32的通用定时器TIM3_CH1为例计算ARR假设定时器时钟为72MHz。要产生50Hz20ms周期。周期T 1/50Hz 0.02s。需要的总计数次数Total_Ticks 0.02s * (72e6 Hz / Prescaler)。为了计算方便先设置预分频器PSC 72 - 1这样定时器时钟变为1MHz1us计数一次。此时Total_Ticks 20000us / 1us 20000。设置ARR 20000 - 1 19999。计算CCR脉宽0.5ms对应CCR 500 2.5ms对应CCR 2500。中间角度线性映射。例如想让舵机转到135度假设0.5ms对应0度2.5ms对应180度。脉宽 0.5ms (135/180) * (2.5-0.5)ms 0.5 1.5 2.0ms。对应的CCR 2000。配置将TIM3通道1配置为PWM模式1输出极性高使能预装载。这样你只需要在程序中修改CCR1寄存器的值就能在下一个PWM周期更新舵机角度实现平滑控制。关键点舵机对PWM周期的稳定性要求高于绝对精度。周期20ms必须稳定轻微的抖动几十us可能导致舵机抖动或嗡嗡响。因此用于生成舵机PWM的定时器其时钟源最好来自稳定的内部或外部晶振避免使用容易受干扰的时钟源。5. 进阶话题与避坑指南当你掌握了基本应用后在实际项目中还会遇到一些更复杂的情况和陷阱。5.1 定时器溢出与长周期定时的处理如前所述硬件定时器有位数限制。当需要超过其最大计数值的定时时例如用16位定时器做1分钟定时必须结合软件扩展。标准做法设置硬件定时器为一个较短的基础周期如10ms并开启更新中断。在中断服务程序中对一个32位的软件计数器进行递增。在主程序中判断这个软件计数器是否达到目标值1分钟60000ms需要6000个10ms中断。volatile unsigned long g_soft_timer_ms 0; // 软件毫秒计数器 // 在10ms的硬件定时器中断中 void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) { if(htim-Instance htim2) { // 确认是TIM2的中断 g_soft_timer_ms 10; } } // 在主循环中判断 if(g_soft_timer_ms 60000) { // 1分钟到 g_soft_timer_ms 0; // 执行任务 }5.2 多个定时器任务的调度与管理一个系统往往需要多个不同周期的定时任务LED闪烁500ms、按键扫描20ms、传感器数据读取1s。如果为每个任务都分配一个硬件定时器显然不现实。解决方案基于一个高精度的硬件定时器如SysTick滴答定时器或一个通用定时器构建一个“软件定时器”框架。硬件定时器提供稳定的时基如1ms中断一次。定义一个软件定时器结构体数组每个元素包含定时周期、倒计时计数器、回调函数、是否使能等。在1ms中断里遍历所有使能的软件定时器将其倒计时计数器减1。当某个计数器减到0时执行其对应的回调函数并重新加载定时周期值。在主程序初始化时创建多个不同周期的软件定时器任务并启动。这样你就用1个硬件定时器“虚拟”出了几十个独立的软件定时器实现了多任务调度。5.3 中断服务程序的设计要点定时器的强大离不开中断但中断服务程序编写不当会引入灾难。快进快出中断服务程序应尽可能短小精悍。只做最紧急、必须的操作如标志位置位、数据搬运、计数器加减。复杂的处理应放到主循环中通过检查中断标志来执行。避免阻塞调用严禁在中断服务程序中使用delay()、printf()等可能阻塞或耗时很长的函数。共享变量保护中断和主循环都会访问的变量如上面的g_soft_timer_ms应使用volatile关键字声明防止编译器优化。对于多字节变量如32位整数在8位或16位单片机上读写可能不是原子操作需要考虑使用关中断/开中断进行保护或者确保单次读写操作在一个指令周期内完成有些架构有硬件保证。清除中断标志进入中断服务程序后第一件事就是检查并清除对应的中断标志位否则会连续不断地进入中断。5.4 不同单片机架构下的关键差异51单片机定时器功能基础通常需要手动重装初值PWM功能弱或需要软件模拟。STM32ARM Cortex-M定时器外设非常强大且复杂。有基本定时器、通用定时器、高级定时器之分。通用定时器支持输入捕获、输出比较、PWM、编码器接口等多种模式。配置通常通过HAL库或LL库操作寄存器需要仔细阅读参考手册的“定时器”章节。PIC/AVR单片机通常有预分频器、后分频器的概念且可能和看门狗等模块共用。需要仔细阅读数据手册中关于TMR0/TMR1的框图理解信号路径。通用建议拿到一款新单片机的定时器不要急于写代码。先花时间看手册中的框图搞清楚时钟源从哪里来经过哪些分频和选择器才到达计数寄存器。然后重点研究重装载、比较/捕获、中断使能和控制这几个关键寄存器。理解了数据流配置起来就得心应手了。定时器是单片机中最经典、最核心的外设之一它的掌握程度直接决定了你嵌入式开发功力的深浅。从简单的延时到复杂的电机控制、通信同步背后都是定时器在默默工作。希望这篇长文能帮你建立起清晰的概念框架结合具体芯片的手册和实际动手操作你一定能将它运用自如。记住看十遍不如调一遍打开你的开发环境从让一个LED灯精确闪烁开始吧。