ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

STM32定时器实战:从PWM生成到输入捕获与ADC触发

STM32定时器实战:从PWM生成到输入捕获与ADC触发 1. 从“计时”到“控制”STM32定时器的核心价值如果你刚开始接触STM32可能会觉得定时器TIM不就是用来计时的吗设置一个时间到了就触发个中断跟51单片机里的定时器差不多。但当你真正深入项目无论是想用PWM驱动一个电机、精确测量一个脉冲的宽度还是想以固定频率触发ADC采样你都会发现STM32的定时器远不止“计时”这么简单。它更像是一个隐藏在芯片内部的、高度可编程的“时间与事件管理引擎”是连接软件逻辑与硬件实时控制的关键桥梁。我最初也把它想简单了直到在一个无人机电调项目上栽了跟头。当时需要生成一个频率25kHz、占空比可调的PWM信号来控制无刷电机用简单的延时循环或者SysTick根本做不到那么精准和稳定CPU也被完全占用。最后就是靠深入研究通用定时器的PWM模式才解决了问题。STM32的定时器家族庞大从基本定时器到高级控制定时器各有分工理解了它们你才能真正释放这颗MCU在实时控制领域的潜力。本文我就结合这些年的踩坑经验带你彻底搞懂STM32的TIM并手把手展示几个最核心、最实用的应用场景。2. STM32定时器家族全景图与核心概念辨析拿到一款STM32第一件事不是急着写代码而是先翻看它的参考手册弄清楚它有哪些定时器资源。不同系列、不同型号的STM32定时器的数量和功能差异很大。但大体上我们可以将其分为几类基本定时器TIM6, TIM7等功能最简单真的就只是个“定时器”。主要就是一个16位的递增计数器配上预分频器实现最基本的定时中断功能。它没有输入捕获和输出比较这些外设接口通常用于产生基础的时基比如给DAC提供触发时钟或者作为操作系统滴答时钟的备选。通用定时器TIM2, TIM3, TIM4, TIM5等这是你打交道最多的定时器。它具备了定时器的所有基础功能并且核心是拥有了输入捕获和输出比较这两大功能单元。这意味着它不仅能定时还能测量外部信号的脉宽、频率输入捕获也能生成精确的PWM波形、单脉冲等输出比较。我们后面要重点讲的PWM生成、输入捕获测频率主要就是靠它。高级控制定时器TIM1, TIM8等在通用定时器的基础上增加了用于电机控制和数字电源的进阶功能比如带死区插入的互补PWM输出、紧急刹车输入、编码器接口等。如果你做BLDC/PMSM电机FOC控制、开关电源那必须和它打交道。独立看门狗IWDG和窗口看门狗WWDG虽然也叫“定时器”但它们属于安全组件用于在程序跑飞时复位系统用法和上述定时器完全不同。SysTick定时器这是一个24位的递减计数器集成在Cortex-M内核中几乎所有STM32都一样。它通常由RTOS如FreeRTOS用来提供系统时钟节拍也可以用来做简单的延时。它的优先级很高但功能单一。理解了分类我们还要厘清几个关键概念这是后面所有应用的基础计数器定时器的核心就是一个寄存器可以向上、向下或中心对齐计数。计数的“一次跳动”就是一个时钟周期。预分频器PSC用来对定时器的时钟源进行分频。如果定时器时钟是72MHzPSC设置为71那么计数器实际得到的时钟频率就是72MHz / (711) 1MHz。这里有个坑写入PSC的值是分频系数减1。自动重装载寄存器ARR这是计数器的周期值。计数器从0计数到ARR向上计数或者从ARR计数到0向下计数就完成一个周期并产生一个更新事件可触发中断。计数频率与定时时间定时时间 (ARR 1) * (PSC 1) / TIM_CLK。例如TIM_CLK72MHz PSC7199 ARR9999。则定时时间 (99991)*(71991)/72,000,000 1秒。输入捕获/输出比较通道这是定时器与外部引脚交互的通道。每个通道都关联一个捕获/比较寄存器CCR。在输入捕获模式下当检测到边沿时当前计数器的值会被锁存到CCR中从而可以计算脉宽。在输出比较模式下当计数器的值匹配CCR的值时通道引脚会根据模式产生相应的动作如翻转、置高、置低。注意在STM32CubeMX中配置时它帮你计算并填写的PSC和ARR值就是基于上述公式。但你自己写代码时一定要清楚这个“1”的关系否则定时时间会差一个周期。3. 实战核心一通用定时器生成PWM驱动电机与LED生成PWM是定时器最经典的应用之一。无论是调节LED亮度、控制舵机角度还是驱动直流电机、步进电机都离不开它。这里我们以通用定时器TIM3的通道1对应某个引脚如PA6生成一个1kHz占空比50%的PWM为例从原理到代码彻底讲清楚。3.1 PWM生成的底层原理与模式选择PWM的本质就是在一个固定的周期频率内通过改变高电平的时间脉宽来传递模拟信息。定时器如何实现它依赖于输出比较模式。我们让定时器工作在PWM模式1或模式2。以向上计数、PWM模式1为例计数器从0开始向上计数。在计数值小于CCR值时通道输出有效电平可设置为高或低。当计数值等于或大于CCR值但小于ARR值时通道输出无效电平。当计数值达到ARR值时产生更新事件计数器清零重新开始同时输出跳回有效电平。这样CCR值就直接决定了PWM的占空比。占空比 CCR / (ARR 1)。ARR值决定了PWM的频率。频率 TIM_CLK / ((PSC1) * (ARR1))。模式1与模式2的区别主要在于输出极性。在模式1下当计数值 CCR时输出有效电平你定义的在模式2下则相反。通常我们配合“极性”设置来统一我们想要的逻辑。3.2 从CubeMX配置到HAL库代码详解现在我们一步步实现它。假设使用STM32F103TIM3挂载在APB1总线上时钟为72MHz。步骤1CubeMX图形化配置打开CubeMX选择你的TIM3。Clock Source选择Internal Clock。在下方通道选择中为Channel1选择PWM Generation CH1。在参数配置Parameter Settings中Prescaler (PSC - 1)设置为 71。这样分频后时钟为 72MHz / 72 1MHz。Counter Mode选择Up向上计数。Counter Period (ARR - 1)设置为 999。这样PWM周期为 (9991) / 1MHz 1ms即频率1kHz。auto-reload preload建议使能这样修改ARR时会在下次更新事件生效避免当前周期出现毛刺。在下方PWM Generation Channel 1配置中Mode选择PWM Mode 1。Pulse (CCR - 1)设置为 499。初始占空比 500 / 1000 50%。Output Compare preload建议使能原因同上。CH Polarity选择High。这意味着“有效电平”为高。结合PWM Mode 1效果就是计数值500时输出高否则输出低。生成代码。步骤2代码分析与关键APICubeMX生成的代码在tim.c中初始化了TIM3。我们需要在main.c中启动PWM并动态修改占空比。// 在main函数初始化后启动PWM输出 HAL_TIM_PWM_Start(htim3, TIM_CHANNEL_1); // 在程序运行中动态修改占空比到75% __HAL_TIM_SET_COMPARE(htim3, TIM_CHANNEL_1, 750); // CCR值设为750 // 或者使用HAL库函数效果一样 // TIM3-CCR1 750;这里有个关键点__HAL_TIM_SET_COMPARE这个宏直接操作寄存器CCR1速度极快是实时修改PWM占空比的首选方法。HAL库的__HAL_TIM_SET_COMPARE函数内部可能有一些状态检查在极高频率的实时控制中直接寄存器操作有时更可靠。3.3 驱动直流电机与步进电机的实战差异驱动直流电机有刷通常只需要一个PWM通道控制速度另一个GPIO控制方向。使用上述方法生成PWM即可。注意电机的驱动电路如H桥可能需要更高的PWM频率比如20kHz以上来避免可闻噪音。驱动步进电机以最常见的两相四线步进电机为例你需要两个PWM通道通常来自同一个定时器的两个通道如TIM1的CH1和CH2并配置为互补输出带死区模式如果使用高级定时器。这是因为步进电机驱动器需要精确的脉冲和方向信号。更复杂的微步控制则需要通过定时器中断或DMA实时计算并更新多个通道的CCR值形成正弦波表驱动。这就涉及到定时器的更新中断和DMA传输功能。踩坑心得在驱动大功率电机时PWM频率不是越高越好。频率太高开关损耗大MOS管发热严重频率太低电机运转噪音大电流纹波大。需要通过实际测试在效率、噪音和温升之间找到一个平衡点通常有刷直流电机在5k-20kHz无刷电机在10k-40kHz范围尝试。4. 实战核心二输入捕获精准测量频率与脉宽输入捕获功能是定时器的另一大利器常用于测量编码器转速、红外遥控信号解码、超声波测距回波时间等。它的原理正好与输出比较相反当指定的引脚上发生跳变上升沿或下降沿时硬件会自动将当前计数器的值“捕获”到CCR寄存器中。通过记录两次捕获的计数器值就能算出时间差。4.1 单通道脉宽测量与潜在误差测量一个正脉冲的宽度最直观的方法是将输入捕获通道设置为上升沿触发。发生上升沿时产生捕获中断在中断服务程序里读取CCR1的值记为t1同时将捕获边沿改为下降沿。发生下降沿时再次产生中断读取CCR2的值记为t2。脉冲宽度 (t2 - t1) * 计数器时钟周期。如果计数器在t1到t2之间发生了溢出还需要加上溢出次数 * 计数器周期。这里有个大坑中断响应延迟。中断从触发到进入服务函数需要一定时间压栈、跳转等。对于高频信号这个延迟会引入不可忽视的误差。虽然STM32的中断响应很快通常几十个时钟周期但在测量纳秒级脉宽或兆赫兹频率时必须考虑。4.2 双通道从模式实现高精度频率测量为了减少软件中断延迟带来的误差STM32提供了一个强大的硬件功能从模式触发。我们可以用两个通道结合从模式实现几乎无软件干预的高精度测量。以测量一个方波频率为例通道1配置为输入捕获上升沿触发。但关键一步将其设置为触发源TRGI。通道2也配置为输入捕获上升沿触发。配置定时器的从模式为Reset Mode并且触发源选择为TI1FP1即通道1的滤波后信号。工作原理当通道1TI1出现上升沿时这个边沿信号会作为触发输入TRGI触发从模式。在Reset Mode下触发事件会使计数器立刻复位清零。紧接着通道2TI2会在下一个上升沿也就是信号的下一个周期捕获当前的计数器值。由于计数器在每个信号周期开始时都被复位那么通道2捕获到的值就是一个信号周期内计数器的计数值。频率 定时器输入时钟 / (CCR2_value * (PSC1))。这个方法妙在哪里它完全用硬件实现了周期测量。通道1的边沿复位计数器通道2在下一个边沿捕获测量的是两个上升沿之间的时间。软件只需要在通道2的捕获中断里稳定地读取CCR2的值即可完全不受通道1中断延迟的影响精度极高。4.3 编码器接口模式电机测速的终极方案对于电机上的正交编码器STM32定时器提供了专用的编码器接口模式。在此模式下定时器的两个输入通道TI1和TI2分别接编码器的A相和B相。定时器硬件会自动根据两相的相位关系判断正反转并驱动计数器向上或向下计数。配置起来非常简单在CubeMX中直接将定时器模式选为Encoder Mode然后指定编码器模式通常选Encoder Mode TI1 and TI2在TI1和TI2的边沿都计数精度最高。你只需要定期比如用另一个定时器每10ms中断一次去读取计数器的值CNT这个值的增量就代表了这段时间内编码器产生的脉冲数从而轻松算出转速。个人经验在读取编码器计数器值时如果定时器位数是16位0-65535而电机转速很快可能会发生多次溢出。一个稳健的做法是在定时器更新中断溢出中断里用一个int32_t类型的软件计数器enc_total_count进行累加。读取速度时结合这个软件计数器和高16位、低16位的硬件计数器值一起计算可以避免漏计。5. 实战核心三定时器触发ADC实现精准采样同步在电源、音频处理等应用中我们常常需要以固定、精确的频率对模拟信号进行ADC采样。如果用软件延时或普通定时器中断来启动ADC其抖动Jitter会很大因为中断响应时间不确定。这时就需要用到定时器的触发输出功能。STM32的定时器可以产生多种内部触发信号TRGO如更新事件、比较匹配事件等。而这个TRGO信号可以直接连接到ADC的“外部触发”输入从而由硬件自动、同步地启动ADC转换。配置流程配置定时器以TIM2为例配置为产生更新事件UEV。计算好PSC和ARR得到你想要的采样频率。例如需要10kHz采样率定时器时钟72MHz可以设置PSC71得1MHzARR99得10kHz。配置ADC在ADC的配置中选择“外部触发转换”并选择触发源为TIM2_TRGO。同时将ADC设置为扫描模式或连续模式取决于你的需求。启用DMA强烈推荐为了不丢失采样数据且不占用CPU配置ADC使用DMA将转换结果自动搬运到内存中的数组。启动先启动ADC的DMA然后启动定时器。定时器每次溢出产生更新事件就会自动触发一次ADC转换DMA自动搬运结果。CPU完全被解放采样间隔由硬件保证极度精准。这个组合TIM ADC DMA是STM32进行高性能数据采集的黄金搭档。我在一个逆变器项目中用它来同步采样交流电压和电流对于后续的软件锁相环PLL和功率计算至关重要软件触发的抖动根本无法满足要求。6. 进阶应用与调试避坑指南掌握了上述三大核心应用你已经能解决80%的问题。下面分享一些进阶技巧和常见坑点。6.1 定时器与DMA的联动实现PWM占空比波形序列播放想象一个场景你需要让LED完成一个复杂的呼吸灯效果或者让舵机执行一连串设定好的角度动作。如果每个变化都靠CPU计算并修改CCR在复杂序列时CPU负载会很高。此时可以让DMA来帮忙。你可以将一个存储了占空比序列的数组数组元素就是CCR值的地址配置给DMA。将DMA的触发源设置为定时器的“更新事件”或“比较匹配事件”。这样每次定时器周期完成DMA就自动将数组中的下一个值搬运到TIMx-CCR寄存器中自动改变PWM占空比。CPU只需要设置好数组和启动传输就可以去处理其他任务实现了“硬件自动播放序列”。6.2 定时器级联实现超长定时一个16位的定时器即使预分频到最大其最长定时时间也有限。如果需要小时甚至天的定时怎么办可以使用定时器级联。将定时器A配置为主模式使其更新事件UEV作为触发输出TRGO。将定时器B配置为从模式触发源选择为定时器A的TRGO。这样定时器A每溢出一次就触发定时器B计数一次。定时器B就成了定时器A的“预分频器”。总定时时间是两者周期的乘积轻松实现超长定时。6.3 调试中的常见问题与排查手段没有输出/捕获不到信号首先检查时钟定时器所在的APB总线时钟是否使能在CubeMX的Clock Configuration页面确认。使用__HAL_RCC_TIMx_CLK_ENABLE()。检查引脚复用GPIO是否配置为正确的复用功能AF用CubeMX检查或查看数据手册的Alternate Function mapping表。检查基本配置PSC和ARR是否为0为0则定时器不工作。输出比较的极性是否设反用示波器查看引脚。PWM频率或占空比不对公式核对务必用公式Fpwm Fclk / ((PSC1)*(ARR1))重新计算。ARR和CCR的值是写入的值加1。预装载寄存器如果修改了ARR或CCR但希望更改在下一个周期生效必须确保auto-reload preload和output compare preload使能并且使用__HAL_TIM_SET_COMPARE或HAL_TIM_OC_ConfigChannel等函数而不是直接赋值直接赋值可能立即生效打乱当前周期。输入捕获值跳动大消抖滤波定时器输入通道有数字滤波器ICF位可以设置采样频率和滤波次数对于有毛刺的信号非常有效。中断优先级确保捕获中断的优先级足够高不能被其他长时间中断阻塞。考虑使用上文提到的从模式减少软件误差。高级定时器死区时间配置驱动H桥时死区时间是为了防止上下桥臂直通。死区时间由TIMx-BDTR寄存器的DTG位设置。计算方式有点复杂通常参考手册会给出公式和例子。一个原则死区时间必须大于你所使用的MOS管或IGBT的开关时间尤其是关断时间否则会烧管。务必用示波器双通道观察互补的两路PWM确认死区插入正确。最后STM32的定时器非常灵活也相对复杂。最好的学习方法是先在一个简单的例程比如点亮一个PWM控制的LED上跑通然后用调试器单步跟踪同时观察相关寄存器CNT, CCR, SR的变化再用示波器验证实际波形。把理论、寄存器操作和实际信号三者对照起来理解才能真正吃透。当你能熟练运用定时器去解决PWM、捕获、触发ADC这些问题时你会发现很多复杂的实时控制任务突然变得清晰和简单了。
返回列表