尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

STM32定时器与PWM原理详解:从呼吸灯到舵机控制实战

STM32定时器与PWM原理详解:从呼吸灯到舵机控制实战 1. 从“嘀嗒”到“呼吸灯”为什么定时器是STM32的脉搏如果你刚开始玩STM32F103点亮LED、串口打印这些基础操作可能已经让你觉得“不过如此”。但当你想要让LED以特定频率闪烁或者驱动一个舵机精准地转动到某个角度时你很快会发现仅仅依赖HAL_Delay()这类阻塞延时函数是远远不够的。它会让你的CPU“傻等”无法处理其他任务程序效率极低更无法产生精确的时间基准。这时你就需要请出单片机里的“瑞士军刀”——定时器。在STM32F103的世界里定时器远不止是一个简单的“闹钟”。它更像一个高度集成、功能强大的时间管理单元是驱动PWM脉冲宽度调制、输入捕获、编码器接口等高级功能的核心引擎。我最初学习时也曾被那一大堆寄存器、预分频、自动重载值、计数模式搞得头晕。但当我真正理解其工作原理并用它做出了第一个呼吸灯、驱动了第一个舵机后那种“一切尽在掌握”的感觉是无与伦比的。本篇笔记我们就来彻底拆解STM32F103的定时器并聚焦于其最经典的应用之一PWM输出。我们将从最基本的定时中断开始逐步深入到PWM的原理与配置最终实现一个可调占空比的呼吸灯。你会发现掌握了定时器你就掌握了让单片机“活”起来的节奏。2. STM32F103的定时器家族图谱与核心概念解析STM32F103的定时器资源非常丰富根据功能和复杂度可以大致分为几个梯队。理解这个分类有助于你在项目中快速选型。2.1 定时器家族分类基本定时器TIM6, TIM7这是功能最简单的定时器只能向上计数没有外部IO口主要用途就是产生基础的定时中断或者作为DAC的触发源。你可以把它理解为一个纯粹的、内部的“秒表”。通用定时器TIM2, TIM3, TIM4, TIM5这是最常用、功能最全的一类。支持向上、向下、中央对齐计数具备输入捕获可以用来测频率、脉宽、输出比较产生PWM、单脉冲等、编码器接口等功能。我们本章重点学习的PWM输出主要就是用通用定时器来实现。高级定时器TIM1, TIM8在通用定时器的基础上增加了互补输出、死区插入、刹车Break功能等主要用于电机控制、数字电源等需要复杂PWM波形和保护的场合。对于大多数控制应用如LED调光、舵机控制、蜂鸣器发声等通用定时器TIM2-TIM5已经完全够用且是首选。2.2 理解定时器的“心脏”与“节拍”定时器工作的核心是它对时钟脉冲进行计数。我们可以用一套比喻来理解其关键参数时钟源CK_PSC这是定时器的“心脏”决定了它跳动的原始频率。通常来源于系统时钟SYSCLK经过APB总线分频后的时钟如72MHz。预分频器PSC心脏跳得太快72MHz一秒钟跳7200万次我们没法直接数。预分频器就像一个“分频齿轮”将心脏的跳动频率降低。例如设置PSC71那么每72个时钟脉冲才给计数器一个脉冲。此时计数器的实际时钟频率 72MHz / (711) 1MHz。这里有个关键点写入PSC寄存器的值实际分频系数是PSC1。这是新手常踩的坑。计数器CNT这是一个可以向上、向下计数的寄存器它按照预分频后的节拍如1MHz即1微秒计一次数进行累加或递减。自动重装载寄存器ARR这是计数器的“目标值”。当CNT计数到ARR向上计数模式时就会产生一个“更新事件”CNT清零重新开始同时可以触发中断。ARR决定了定时周期。定时周期的计算公式是核心中的核心定时周期 (ARR 1) * (PSC 1) / 定时器时钟频率举个例子我们希望用TIM3产生一个1ms0.001秒的定时中断。假设定时器时钟频率为72MHz。我们可以先设定PSC7199这样分频后计数器时钟 72MHz / (71991) 10KHz即每0.1ms计数一次。然后设定ARR9这样计数10次0到9后产生更新事件时间正好是 10 * 0.1ms 1ms。代入公式验证(91)*(71991)/72,000,000 10*7200/72,000,000 0.001秒。2.3 计数模式简介向上计数CNT从0开始加到ARR溢出后归零如此循环。最常用。向下计数CNT从ARR开始减到0溢出后重回ARR。中央对齐CNT从0加到ARR再从ARR减到0如此往复。常用于需要对称PWM波形的场合。理解了这些定时器如何“定时”就清晰了。接下来我们让它从“计时”走向“控制”学习如何产生PWM信号。3. PWM深度解析不仅仅是“开关”PWM中文叫脉冲宽度调制。听起来很高大上但其本质非常简单在一个固定频率的方波中通过改变高电平时间所占的比例即占空比来模拟不同的“平均电压”。3.1 一个生活化的类比想象一下你用手电筒照一面墙。如果你以非常快的速度比如每秒100次开关手电筒由于人眼的视觉暂留你会觉得墙一直是亮的只是亮度不同。如果每次开0.01秒关0.09秒占空比10%你会觉得墙很暗。如果每次开0.05秒关0.05秒占空比50%你会觉得墙中等亮。如果每次开0.09秒关0.01秒占空比90%你会觉得墙很亮。这里的“开关频率”就是PWM频率“开的时间占比”就是占空比。LED调光、电机调速原理皆出于此。3.2 STM32如何产生PWM输出比较单元STM32的通用定时器通过“输出比较”功能来产生PWM。其核心是三个寄存器计数器CNT、捕获/比较寄存器CCRx、以及自动重装载寄存器ARR。工作流程以向上计数、PWM模式1为例CNT从0开始向上计数。在计数过程中硬件会持续比较CNT和CCRx的值。当CNT CCRx时输出一种电平例如高电平。当CNT CCRx时输出反转的电平例如低电平。CNT继续计数直到达到ARR值产生溢出CNT归零同时输出电平根据新的CNT与CCRx的比较结果可能发生变化开始下一个周期。这里的关键是CCRx的值直接决定了高电平的宽度ARR决定了PWM的周期频率CCRx决定了脉冲的宽度占空比。PWM频率计算公式PWM频率 定时器时钟频率 / ((ARR 1) * (PSC 1))占空比计算公式占空比 CCRx / (ARR 1)例如定时器时钟72MHzPSC71ARR999。PWM频率 72MHz / ((9991)(711)) 72,000,000 / (100072) 1000Hz。若设置CCRx300则占空比 300 / 1000 30%。3.3 为什么PWM能驱动舵机和调速电机舵机舵机控制需要的是周期为20ms50Hz高电平宽度在0.5ms到2.5ms之间的PWM信号。0.5ms对应0度2.5ms对应180度。STM32产生一个50Hz、占空比可精确调节的PWM波就能精准控制舵机角度。这里要注意舵机对PWM频率的稳定性要求较高频率偏差太大会导致舵机抖动或无法工作。直流电机调速通过MOS管或电机驱动芯片将PWM信号转换为电机的平均供电电压。占空比越大平均电压越高电机转速越快。LED调光LED的亮度与其通过的电流近似于平均电压成正比PWM调光无频闪效率高。4. 实战从零配置TIM3的PWM输出驱动LED呼吸灯理论说得再多不如动手做一遍。我们以最经典的LED呼吸灯为例使用TIM3的通道2对应PA7引脚根据最小系统板LED连接情况可能需要调整来输出PWM。4.1 硬件连接与工程准备首先确认你的STM32F103开发板上的LED连接在哪一个GPIO上。假设LED阴极接在PA7阳极通过电阻接3.3V低电平点亮。那么我们需要配置PA7为复用推挽输出模式并将其映射到TIM3的通道2。使用STM32CubeMX或直接寄存器/标准库编程均可。这里以CubeMX流程为例兼顾原理说明。时钟配置在RCC中使能外部高速时钟HSE在Clock Configuration标签页将系统时钟SYSCLK配置为72MHz。确认APB1定时器时钟APB1 Timer clocks也为72MHz如果APB1预分频系数不为1定时器时钟会倍频但F103默认配置下通常是72MHz。GPIO配置找到PA7将其模式设置为Alternate Function Push-Pull并为其选择复用功能为TIM3_CH2。定时器配置找到TIM3进行关键参数设置。Clock Source: Internal Clock内部时钟Channel2: PWM Generation CH2PWM生成通道2Parameter Settings:Prescaler (PSC): 71。计算我们希望计数器时钟为1MHz72MHz/(711)1MHz。Counter Mode: Up向上计数Counter Period (ARR): 999。这样PWM周期 (9991) / 1MHz 1ms即PWM频率为1KHz。1KHz对于LED调光来说足够高人眼看不到闪烁。Internal Clock Division (CKD): No Divisionauto-reload preload: Enable使能自动重装载预装载可以避免修改ARR时产生毛刺波形下方PWM Generation Channel 2的具体设置:Mode: PWM Mode 1在向上计数时CNTCCR为有效电平Pulse (CCR2): 初始值设为0代码中动态修改它来实现呼吸效果。Output Compare preload: Enable使能预装载同样为了波形稳定CH Polarity: High有效电平为高。这里需要特别注意因为我们的LED是低电平点亮而PWM模式1在CNTCCR时输出有效电平高。所以我们需要在后续代码中将LED的亮灭逻辑与PWM输出反相。更简单的做法是直接将CH Polarity设置为Low这样有效电平就是低电平PWM输出低电平时LED亮占空比越大低电平时间越长LED越亮。我们采用这种更直观的方式。4.2 生成代码与关键函数解读生成代码后在main.c的/* USER CODE BEGIN 2 */部分初始化PWM输出。HAL_TIM_PWM_Start(htim3, TIM_CHANNEL_2); // 启动TIM3的通道2输出PWM现在PWM已经开始输出但占空比还是初始值0CCR20LED是常亮还是常灭取决于你设置的极性。由于我们设置了有效电平为低且CCR20意味着CNT(0~999)永远不小于CCR2(0)所以输出一直为有效电平低LED常亮。4.3 实现呼吸灯效果呼吸灯的本质是让占空比即CCR2的值从0到ARR999缓慢增加再缓慢减少循环往复。我们需要在while(1)主循环中修改CCR2的值。uint16_t pwmVal 0; // PWM比较值即CCR2 uint8_t dir 1; // 方向1为增加0为减少 while (1) { HAL_Delay(1); // 延时1ms控制呼吸速度 if(dir 1) { pwmVal; if(pwmVal 999) { // 增加到最大值 dir 0; } } else { pwmVal--; if(pwmVal 0) { // 减少到最小值 dir 1; } } // 修改TIM3通道2的捕获比较寄存器值即改变占空比 __HAL_TIM_SET_COMPARE(htim3, TIM_CHANNEL_2, pwmVal); }代码解析与避坑指南__HAL_TIM_SET_COMPARE宏这是HAL库提供的用于安全设置CCRx值的宏。它会在更新CCRx时考虑预装载寄存器确保波形稳定。切忌直接操作htim3.Instance-CCR2 pwmVal;除非你很清楚何时关闭了预装载。呼吸速度HAL_Delay(1)决定了每次改变占空比的间隔。1ms改变一次从0到999需要约1秒完成一个呼吸周期约2秒。你可以调整这个延时来改变呼吸快慢。极性与亮度关系因为我们设置了CH Polarity: Low有效电平是低。pwmVal越大低电平时间越长LED越亮。所以pwmVal从0到999增加时LED是从暗到亮。如果你发现呼吸方向反了检查极性和LED硬件接法。使用HAL_Delay的弊端这里为了简化使用了阻塞延时。在实际项目中这会影响其他任务执行。更好的做法是利用定时器中断例如用另一个基本定时器TIM6产生1ms中断来更新pwmVal或者使用PWM模式中的“渐变”功能高级定时器支持通用定时器需用DMA。编译下载程序你应该能看到LED呈现出柔和的呼吸效果。这证明你的PWM配置成功了5. 进阶应用与深度避坑指南掌握了基础PWM输出后我们来看看更实际的应用和那些容易踩进去的“坑”。5.1 驱动舵机精确的20ms周期与0.5-2.5ms脉宽舵机控制对定时器精度要求较高。以50Hz20ms周期为例我们重新计算TIM3的参数。目标PWM频率50Hz周期20ms 0.02秒。定时器时钟72MHz。为了得到精确的20ms我们需要精心选择PSC和ARR。一个常见的配置是PSC7199ARR199。计算计数器时钟 72MHz / (71991) 10KHz即每计数一次耗时0.1ms。PWM周期 (ARR1) * 0.1ms 200 * 0.1ms 20ms。完美。脉宽控制0度对应0.5ms高电平需要计数值 0.5ms / 0.1ms 5。所以CCRx 5。180度对应2.5ms高电平需要计数值 2.5ms / 0.1ms 25。所以CCRx 25。因此控制舵机角度其实就是将CCRx的值在5到25之间线性映射。关键点务必使用示波器或者逻辑分析仪测量实际输出的PWM波形确认周期和脉宽是否准确。软件计算是理想的但时钟源精度、软件开销可能导致微小误差对于舵机这种模拟器件微小误差累积可能导致角度漂移。5.2 使用DMA自动更新PWM占空比实现复杂效果对于像LED灯带如WS2812或者需要复杂、高速PWM序列的应用在主循环里修改CCRx太慢且会占用大量CPU。这时就需要DMA直接存储器访问出场。你可以将一组预先计算好的CCRx值存放在一个数组里然后配置DMA让定时器的更新事件ARR溢出自动触发DMA将数组中的下一个值搬运到TIMx_CCRy寄存器中。这样CPU只需要初始化好数组和DMA就可以去处理其他任务PWM波形会由DMA和定时器硬件自动、精确地播放出来。配置思路在CubeMX中使能TIM3的更新事件DMA请求TIM3_UP。配置一个DMA通道方向为存储器到外设外设地址为TIM3-CCR2存储器地址为你的数组地址数据宽度为半字16位循环模式。在代码中启动DMA传输HAL_TIM_PWM_Start_DMA(htim3, TIM_CHANNEL_2, (uint32_t*)pwm_buffer, BUFFER_SIZE);5.3 常见问题排查避坑精华没有PWM输出检查时钟首先确认定时器对应的APB总线时钟是否使能。CubeMX一般会自动配置但手动编程时容易遗漏__HAL_RCC_TIMx_CLK_ENABLE()。检查GPIO复用确认GPIO已正确配置为复用功能并映射到了正确的定时器通道上。参考芯片数据手册的“Alternate function mapping”表格。检查输出使能是否调用了HAL_TIM_PWM_Start()或者对于高级定时器是否使能了主输出__HAL_TIM_MOE_ENABLE()检查预装载与使能顺序有时需要先设置ARR、PSC、CCRx最后再使能定时器。PWM频率或占空比不对公式核对反复用频率 时钟 / ((PSC1)*(ARR1))和占空比 CCRx / (ARR1)验算你的参数。1陷阱牢记PSC和ARR寄存器写入的值实际分频系数和周期值都要加1。这是最最常见的错误来源。时钟源确认确认你计算所用的“定时器时钟频率”是正确的。APB1下的定时器TIM2-4时钟可能因APB1预分频系数不为1而倍频。呼吸灯闪烁或有频闪感PWM频率过低LED调光PWM频率建议在100Hz以上最好在500Hz-1KHz低于100Hz人眼会感觉到闪烁。提高频率的方法是减小ARR或PSC在时钟分辨率允许的范围内。主循环延时不稳定如果使用HAL_Delay更新占空比且主循环中有其他耗时操作会导致更新间隔不均匀产生抖动。改用定时器中断来更新可以解决。修改CCRx值波形有毛刺使能预装载务必在CubeMX中或代码里使能TIMx-CCMR1寄存器中的OCxPE位输出比较预装载使能并确保TIMx-CR1中的ARPE位自动重装载预装载使能也使能。这样对CCRx和ARR的修改会等到下一个更新事件时才生效避免当前周期波形被撕裂。使用库函数坚持使用__HAL_TIM_SET_COMPARE()和__HAL_TIM_SET_AUTORELOAD()等宏或函数来修改参数它们内部处理了预装载逻辑。通过以上从原理到实战再到进阶与排坑的梳理相信你已经对STM32F103的定时器和PWM有了一个立体而扎实的理解。定时器是STM32的精华功能之一投入时间掌握它将为你的项目打开一扇新的大门。不妨现在就动手尝试用PWM去控制一个舵机或者让多个LED以不同的节奏呼吸在实践中巩固这些知识。
返回列表