ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

STM32定时器深度解析:从PWM生成到输入捕获的实战指南

2026/8/29 12:21:30 拓冰建站 浏览量
STM32定时器深度解析:从PWM生成到输入捕获的实战指南 1. 项目概述为什么STM32的定时器是工程师的“瑞士军刀”如果你正在用STM32F103ZET6做项目无论是驱动一个智能小车、控制步进电机还是做高精度的频率测量有一个模块你几乎绕不开那就是定时器。很多人初学单片机觉得定时器不就是“数数”嘛设置一个时间到了就触发个中断听起来很简单。但当你真正深入STM32尤其是像F103ZET6这种资源丰富的型号时你会发现它的定时器功能之强大、应用场景之广泛远超你的想象。它远不止一个简单的倒计时工具更像是嵌入在芯片里的一把“瑞士军刀”集成了定时、计数、PWM生成、输入捕获、输出比较、编码器接口甚至触发ADC采样等十几种功能。理解并玩转它是从单片机“点灯侠”迈向真正嵌入式开发者的关键一步。STM32F103ZET6作为经典的“增强型”大容量产品其定时器资源尤为豪华。它拥有多达8个定时器2个高级控制定时器TIM1, TIM84个通用定时器TIM2, TIM3, TIM4, TIM5以及2个基本定时器TIM6, TIM7。每个类型定位不同高级定时器带死区互补输出专为电机控制和数字电源设计通用定时器功能全面是日常使用的“万金油”基本定时器则专注于最简单的定时和DAC触发。网络上热门的“智能小车”需要PWM控制电机速度“捕获测频率”需要精准测量信号周期“定时器触发ADC”则需要让采样与PWM波严格同步这些全都依赖于对定时器不同工作模式的深入理解和灵活配置。今天我就结合自己多年在电机控制、电源和通信设备上的踩坑经验带你彻底拆解STM32F103ZET6的定时器从内核原理到实战配置从常见坑点到高级玩法让你手里的这颗芯片真正“活”起来。2. 定时器核心架构与工作模式深度解析2.1 定时器的“心脏”时钟源与预分频器定时器之所以能“计时”核心在于有一个稳定且准确的时钟源在驱动它“心跳”。STM32F103ZET6的定时器时钟CK_PSC可以来自多个地方最常用的是内部系统时钟SYSCLK默认是72MHz。但请注意定时器1和8在APB2总线上而2-7在APB1总线上。APB1的时钟最高36MHz当APB1预分频系数不为1时定时器2-7的时钟会是APB1时钟的2倍以达到最高72MHz。这是很多新手容易忽略导致计算错误的地方。时钟信号进入定时器后首先经过一个预分频器PSC。这是一个16位的寄存器意味着你可以将输入时钟进行1到65536的分频。它的作用是把高速的系统时钟比如72MHz降到一个适合你计数需求的频率。例如如果你想实现1ms的定时直接数72M个周期太快了你可以通过预分频将其降到1MHz72MHz / 72这样每计数1000次就是1ms。计算公式是定时器时钟 CK_PSC / (PSC 1)。这里“1”是因为分频器从0开始计数设置PSC71实际是72分频。注意预分频器有一个缓冲寄存器你写入的新值并不会立即生效而是要等到下一次更新事件计数器溢出或软件触发时才会从预装载寄存器加载到工作寄存器。这个机制保证了计时的连续性避免在计时中途改变分频导致时间错乱。在动态调整定时周期时需要理解这一点。2.2 计数器的灵魂向上、向下与中央对齐预分频后的时钟驱动着计数器CNT这个核心部件。计数器的工作模式决定了定时器的行为基线主要有三种向上计数模式计数器从0开始累加到自动重装载值ARR然后产生一个更新事件UEV并清零重新开始。这是最常用的模式像心跳一样周期循环。向下计数模式计数器从ARR值开始递减到0产生更新事件后重新加载ARR值开始递减。中央对齐模式向上/向下计数器从0开始累加到ARR然后立即转为递减到0如此往复。这个模式在生成对称PWM波时特别有用因为波形在周期中心对称可以减少电机控制中的谐波。自动重装载寄存器ARR决定了计数的上限。它和PSC一样也有预装载功能。定时器的基本周期公式为定时周期 (ARR 1) * (PSC 1) / CK_PSC。例如系统时钟72MHz要产生1kHz的PWM周期1ms我们可以设PSC71得到1MHz的计数时钟那么ARR就需要设置为999这样 (9991) * (711) / 72,000,000 0.001秒。2.3 四大功能支柱输出比较、输入捕获、PWM与编码器接口计数器是基础围绕它构建的四大功能才是定时器施展拳脚的舞台。输出比较OC你可以预先在捕获/比较寄存器CCR中设置一个值。当计数器的值CNT与CCR值相等时定时器硬件会根据你设定的模式如翻转、置高、置低自动改变对应输出通道CHx的电平。这是生成PWM、单脉冲等波形的基础。输入捕获IC与输出比较相反当输入通道同样可以是CHx但配置为输入上有特定的边沿上升沿或下降沿出现时硬件会瞬间将当前CNT的值锁存到对应的CCR寄存器中。通过连续捕获两个边沿的CNT值相减就能算出信号的周期或脉冲宽度。这就是“测频率”和“测占空比”的核心原理。STM32的输入捕获功能强大支持滤波和分频能有效抗干扰。PWM生成这是输出比较的一个直接应用。通常使用向上计数模式设置好ARR决定频率设置CCR决定占空比。当CNT CCR时输出一种电平当CNT CCR时输出另一种电平。通过改变CCR值就能动态调整占空比。高级定时器还支持互补输出和死区插入防止电机驱动桥的上下管直通。编码器接口模式这是一个硬件级的“外挂”功能。你可以将定时器配置为编码器模式将正交编码器的A、B相信号分别接到定时器的两个输入通道上。定时器硬件会自动根据A、B相的边沿关系和方向控制CNT进行向上或向下计数无需CPU干预即可实时读取位置和方向。这对于电机位置闭环控制至关重要。3. 实战配置从CubeMX到代码详解理解了原理我们动手配置。这里我以最常用的通用定时器TIM3的通道1生成一个1kHz、占空比50%的PWM波为例同时演示如何用TIM2的通道1进行输入捕获测频。我会混合使用STM32CubeMX图形化配置和直接寄存器操作两种方式让你看清底层逻辑。3.1 使用STM32CubeMX配置PWM输出时钟树配置首先在RCC设置中启用外部高速时钟HSE。然后在时钟树Clock Configuration标签页确保系统时钟SYSCLK为72MHz。观察APB1总线时钟如果预分频系数不是1记得定时器时钟是它的2倍。这里我们设APB1预分频为2则APB1时钟为36MHzTIM2、3、4、7的时钟自动倍频到72MHz。定时器外设配置找到TIM3将时钟源Clock Source选为“Internal Clock”。然后在下方的“Channel1”选择“PWM Generation CH1”。参数计算与设置Prescaler (PSC - 1)我们目标计数时钟是1MHz。输入时钟是72MHz所以PSC 72 - 1 71。填入71。Counter Mode选择“Up”向上计数。Counter Period (ARR - 1)我们需要1kHz频率即1ms周期。计数时钟1MHz计数1ms需要1000个周期。所以ARR 1000 - 1 999。填入999。Pulse (CCR - 1)占空比50%即高电平时间0.5ms对应500个计数周期。所以初始CCR 500 - 1 499。这里CubeMX的“Pulse”参数实际就是CCR值你可以运行时动态修改。其他默认的“Fast Mode”和“PWM Mode 1”通常不需改动。“CH Polarity”选择“High”表示有效电平为高。生成代码配置GPIO会自动配置PA6为复用推挽输出生成代码。在生成的工程中主要关注两个函数// 在 main.c 的初始化部分 HAL_TIM_PWM_Start(htim3, TIM_CHANNEL_1); // 启动PWM输出 // 在需要改变占空比的地方例如在某个循环或中断里 __HAL_TIM_SET_COMPARE(htim3, TIM_CHANNEL_1, new_CCR_Value); // 动态修改占空比通过__HAL_TIM_SET_COMPARE宏你可以实时、无抖动地改变PWM占空比控制电机速度或LED亮度。3.2 手动寄存器配置输入捕获以TIM2_CH1为例有时为了极致效率或理解本质需要直接操作寄存器。假设我们用PA0TIM2_CH1测量一个方波的频率。GPIO初始化将PA0配置为浮空输入或上拉输入复用功能映射到TIM2_CH1。定时器基础配置// 使能TIM2时钟 RCC-APB1ENR | RCC_APB1ENR_TIM2EN; // 设置预分频器计数器时钟为1MHz (72MHz/72) TIM2-PSC 71; // 设置自动重装载值尽可能大以获得更宽的测量范围设为最大值0xFFFF TIM2-ARR 0xFFFF; // 设置计数模式为向上计数CEN位先不开启 TIM2-CR1 ~TIM_CR1_DIR; // 向上计数输入捕获通道配置// 将通道1配置为输入模式映射到TI1即PA0引脚 TIM2-CCMR1 ~TIM_CCMR1_CC1S; // 先清零 TIM2-CCMR1 | TIM_CCMR1_CC1S_0; // 01 CC1通道配置为输入映射到TI1 // 设置输入捕获滤波器可选防抖这里不滤波 TIM2-CCMR1 ~TIM_CCMR1_IC1F; // 设置输入捕获预分频不分频每个边沿都捕获 TIM2-CCMR1 ~TIM_CCMR1_IC1PSC; // 设置捕获边沿为上升沿 TIM2-CCER ~TIM_CCER_CC1P; TIM2-CCER | TIM_CCER_CC1E; // 使能捕获中断与启动// 使能捕获/比较1中断 TIM2-DIER | TIM_DIER_CC1IE; // 在NVIC中使能TIM2中断 NVIC_EnableIRQ(TIM2_IRQn); // 启动计数器 TIM2-CR1 | TIM_CR1_CEN;中断服务函数编写volatile uint32_t capture_value1 0, capture_value2 0; volatile uint32_t period_ticks 0; volatile uint8_t capture_index 0; void TIM2_IRQHandler(void) { if (TIM2-SR TIM_SR_CC1IF) { // 检查捕获中断标志 TIM2-SR ~TIM_SR_CC1IF; // 清除标志 if (capture_index 0) { capture_value1 TIM2-CCR1; // 读取第一次捕获值 capture_index 1; } else { capture_value2 TIM2-CCR1; // 读取第二次捕获值 // 计算周期考虑计数器溢出 if (capture_value2 capture_value1) { period_ticks capture_value2 - capture_value1; } else { period_ticks (0xFFFF - capture_value1) capture_value2 1; } // 频率 1MHz / period_ticks capture_index 0; } } // 处理溢出中断如果需要测量低频信号 if (TIM2-SR TIM_SR_UIF) { TIM2-SR ~TIM_SR_UIF; // 溢出计数加一 } }这种直接寄存器操作的方式代码精简执行效率高尤其适合对实时性要求苛刻的场合。4. 高级应用与联动技巧4.1 定时器触发ADC采样用于FOC等场景在电机FOC磁场定向控制或高精度电源中需要ADC的采样时刻与PWM波的中心或底部严格对齐以获取准确的相电流。这时就需要用到定时器的触发输出TRGO功能。以高级定时器TIM1生成中心对齐的PWM波并在计数器向下计数到达0时即PWM波谷触发ADC1采样为例配置TIM1为中心对齐PWM模式设置好ARR和CCR。配置TIM1的触发源在TIMx-CR2寄存器中设置MMS位为010更新事件作为TRGO输出。这样每次更新事件UEV发生时TIM1都会在内部产生一个触发信号。配置ADC1的外部触发源在ADC1的配置中选择触发源为“EXTI line 11 / TIM1_TRGO”。具体对应关系需查数据手册配置ADC采样时序将ADC配置为外部触发模式使能相应的通道。联动效果当TIM1的计数器向下计数到0产生更新事件时会同时做两件事一是重置PWM周期二是发出TRGO信号触发ADC1启动一次转换。这样ADC的采样点就被牢牢地“锁”在了PWM周期的固定相位上实现了硬件级的严格同步避免了软件触发的随机延迟。4.2 使用从模式实现精准脉冲计数或门控定时器的从模式允许一个定时器被另一个定时器或外部信号控制。一个经典应用是“门控”模式用于精确测量一个脉冲串的个数或一个高电平的宽度。例如用TIM4作为主计数器TIM3作为从计数器工作在门控模式受TIM4的通道1输出控制配置TIM4在一个通道上输出一个已知宽度的脉冲使用PWM或单脉冲模式。配置TIM3的从模式控制寄存器SMCRTS位选择触发源为“ITR2”假设TIM4的TRGO连接到ITR2需查复用功能表。SMS位设置为“门控模式”Gated mode。配置TIM3的时钟和计数方向。启动两个定时器。当TIM4的输出为高电平时TIM3的计数器开始计数当变为低电平时TIM3停止计数。读取TIM3的CNT值再结合TIM3的计数时钟频率就能精确计算出TIM4输出脉冲的高电平宽度。这种方法比输入捕获测量脉宽更节省CPU资源精度也更高。5. 常见问题排查与性能优化心得5.1 定时不准或频率偏差大这是最常见的问题原因和排查点如下时钟源不对首先确认系统时钟配置是否正确。使用SystemCoreClock变量或通过测量MCO引脚输出来验证系统时钟频率是否真的是72MHz或你设定的值。预分频器PSC理解错误牢记公式实际分频系数 PSC 1。如果你填71是想72分频那是对的。自动重装载值ARR理解错误同理计数周期 ARR 1。想要计数到1000ARR应设为999。APB总线时钟倍频对于APB1上的定时器TIM2-7如果APB1预分频系数是2、4、8或16定时器时钟会是APB1时钟的2倍。务必在CubeMX的时钟树或代码中确认最终定时器时钟频率。中断响应延迟对于需要极高精度的定时如微秒级中断的进入和退出、更高优先级中断的抢占都会带来抖动。此时应考虑使用DMA传输或直接查询标志位甚至使用定时器的硬件输出如PWM而非软件中断。5.2 PWM输出无波形或波形异常GPIO未正确复用确保对应的GPIO引脚已配置为复用推挽输出AF_PP并且复用功能映射到了正确的定时器通道上。STM32的引脚复用功能很灵活一个引脚可能对应多个外设需要查数据手册的“Alternate function mapping”表。定时器未启动调用HAL_TIM_PWM_Start()或设置CEN位。输出比较模式错误生成PWM通常使用“PWM mode 1”或“PWM mode 2”区别在于有效电平的定义。如果模式设反可能输出一直为高或一直为低。CCR值大于ARR值如果设置的脉冲宽度CCR大于周期ARR在PWM Mode 1下输出将恒为有效电平。确保CCR ARR。高级定时器的刹车和死区对于TIM1/TIM8检查刹车寄存器BDTR的MOE位是否被使能通常需要置1。如果使用了互补输出死区时间设置过长也可能导致主输出无信号。5.3 输入捕获值跳动或不准信号噪声被测信号本身有毛刺。启用定时器输入捕获的滤波器TIMx_CCMRx中的ICxF位可以滤除短于一定时间的脉冲。边沿检测错误确保设置的捕获边沿上升沿/下降沿与实际信号一致。可以用示波器观察信号。未处理计数器溢出测量低频信号时两次捕获之间计数器可能溢出多次。必须在捕获中断中同时处理更新溢出中断UIF在溢出中断中累加一个溢出计数器。最终周期 溢出次数 * (ARR1) 第二次捕获值 - 第一次捕获值。中断优先级与处理时间输入捕获中断应设为较高优先级确保能及时响应。中断服务函数应尽可能短只做标志读取和数值存储复杂的计算放到主循环。5.4 性能优化与资源分配心得优先使用DMA对于需要频繁更新CCR如呼吸灯渐变或传输捕获数据流的情况使用定时器触发DMA可以极大解放CPU。例如将一组预计算的CCR值存入数组用定时器更新事件触发DMA自动搬运到CCR寄存器。合理分配定时器STM32F103ZET8定时器多但功能有重叠。规划时将带互补输出的高级定时器TIM1, TIM8留给电机驱动或全桥电源。将编码器接口模式分配给有正交解码需求的定时器TIM2-TIM5。基本定时器TIM6, TIM7用于产生基础的时基触发DAC或作为其他定时器的时钟源。利用定时器级联对于需要超长定时的场合如长达数小时可以将一个定时器配置为主模式输出TRGO另一个定时器配置为从模式将TRGO作为时钟源。这样第一个定时器的每次溢出相当于给第二个定时器提供一个计数脉冲实现了定时周期的乘法效应。关闭未用功能省电在低功耗应用中不用的定时器一定要关闭其时钟RCC-APBxENR对应位清零并将其置于复位或冻结状态。玩转STM32的定时器就像掌握了一套组合拳。从基础的时间基准到复杂的多定时器联动每一步都需要对寄存器位和硬件工作流程有清晰的认识。我个人的体会是初期可以多用CubeMX快速搭建框架但一定要养成阅读生成代码和参考手册的习惯搞清楚每一个HAL库函数背后对寄存器做了什么。到了对性能和时序有苛刻要求的项目阶段直接寄存器操作往往是最可靠、最高效的选择。最后示波器和逻辑分析仪是你的最佳搭档任何时序问题眼见为实。