首页 / 资讯中心 / 文章详情

STM32 PWM生成实战:频率占空比计算与HAL库配置指南

STM32 PWM生成实战:频率占空比计算与HAL库配置指南 ★ FEATURED ARTICLE
很多朋友第一次接触PWM都是从那颗经典的呼吸灯开始的——LED渐渐变亮又变暗看着挺神奇。但等你自己真正要在STM32上生成PWM信号的时候问题马上就来频率选多少占空比怎么算为什么波形出不来为什么电机一堵转板子就烧这些坑我在项目里几乎全踩过一遍。这篇东西就按我实际调PWM的顺序从原理讲到HAL库配置再到不同应用场景的参数选择尽量把生成PWM信号这件事讲透。适合正在用STM32做LED调光、电机驱动或者刚开始接触PWM的嵌入式开发者和电子爱好者。1. 别把PWM想简单了它不只是电平翻转1.1 从开关灯理解PWM的等效输出PWM的全称是Pulse Width Modulation脉冲宽度调制。说白了就是在一段固定周期内控制高电平持续时间和低电平持续时间的比例。你可能觉得这不就是让IO口高低翻转嘛但工程上真正的价值在于当翻转频率远高于负载的响应速度时负载感受到的是一个连续可调的平均值。我经常用一个类比你肯定秒懂门外太阳光太强你想让房间暗一点。如果慢慢拧调光旋钮让灯变暗那是模拟调压如果装一个百叶窗快速地把叶片打开又合上只要切换频率足够高人眼感知到的就是介于全开和全关之间的一个稳定亮度。PWM就是那个百叶窗。LED并不关心电压是不是阶梯变化它只知道亮了多久、灭了多久的平均效果电机也是一样负载的机械惯性会把高频脉动过滤掉最后体现在转速上。1.2 为什么必须要硬件定时器不能靠延时函数新手最容易踩的坑是想用delay函数在main循环里翻转IO来生成PWM。比如这样写拉高、延时、拉低、延时、循环。这个方法在呼吸灯试验里确实能亮能暗但放到项目里就是灾难CPU百分之百被占用什么别的代码都跑不了延时的长短受中断影响波形抖动得一塌糊涂占空比稍微调细一点主循环就卡得跟幻灯片一样。这也是为什么单片机里都有专门的定时器外设。定时器硬件不依赖CPU自己就能按照预分频值、自动重载值、比较值协同工作。你只需要配一次参数PWM就自己跑起来了CPU只在需要调整参数时写一下寄存器就能改变输出。做工程和做试验的区别就在这里试验能亮就行工程必须让CPU腾出来干正事。1.3 硬件PWM的三个关键角色PSC、ARR、CCR以STM32的通用定时器为例生成PWM的核心就是配置这三个寄存器PSCPrescaler预分频器对定时器输入时钟做分频降低计数器步进速度ARRAuto-Reload Register自动重载值计数器计数的最终值配合预分频器决定PWM周期CCRCapture/Compare Register捕获比较寄存器计数器计到这个值时触发输出翻转具体触发成高还是低由PWM模式和极性共同决定。有些教程管ARR叫Period或者重载值管CCR叫Pulse或者比较值指的都是同一个东西。你只要记住计数器的时钟决定时间精度ARR决定周期长短CCR决定占空比大小。这三个参数就是后面所有计算的基础建议刚开始学的时候在纸上把这三个值算一遍再填进CubeMX比直接填数值理解深刻得多。2. 生成PWM之前先把频率、占空比、分辨率算明白2.1 占空比计算公式先搞懂再调参占空比的定义很简单一个周期内高电平的时间占总周期的比例。Duty Ton / (Ton Toff) × 100%在STM32的向上计数模式下ARR1对应整个周期CCR对应模式1下高电平持续的长度所以占空比计算公式也可以写成Duty CCR / (ARR 1) × 100%举例来说ARR999CCR500占空比就是50%CCR700占空比就是70%。网上搜的那堆pwm占空比计算公式基本都是这个意思别把它想复杂了。需要注意占空比的分子是CCR本身不是周期减去CCR。我第一次调舵机时就是搞反了结果占空比越调越小舵机反而往反方向转查了半天才发现是这种低级错误。这种基础公式最好自己动手推一遍不要死记。2.2 频率计算PSC和ARR怎么配才能得到目标频率PWM频率的计算公式是f_PWM TIM_CLK / ((PSC 1) × (ARR 1))其中TIM_CLK是定时器输入时钟。公式里的两个1都容易漏因为PSC和ARR从0开始计数。我第一次配置时钟树时漏了1算出来的频率就差一点用示波器一量才发现不对。举个真实例子STM32G070CBT6内部高速时钟16MHz我想产生1kHz的呼吸灯PWM。取PSC15分频后的定时器时钟是16MHz / (151) 1MHz 取ARR999PWM频率就是1MHz / (9991) 1kHz。在这个配置下占空比每变化1就相当于变化0.1%分辨率是千分之一。如果你需要更细腻的亮度过渡可以反着配PSC0、ARR15999同样输出1kHz16MHz/16000但分辨率提高到了1/16000。代价是计数器数值变大处理比较和中断时计数频率也会跟着升高。成本和收益要自己权衡。2.3 同一个频率在不同负载上感受完全不同为什么选频率会成为搜索热词因为频率不是拍脑袋定的它取决于负载的物理响应速度。LED和人眼人眼对几十赫兹的闪烁已经不太敏感几百赫兹以上更是无感所以呼吸灯用500Hz到1kHz完全够电机和人耳电机转动时电磁力和机械结构会发出声音声音频率恰好等于PWM频率。如果PWM频率落在人耳最敏感的1kHz到8kHz区间你就会一直听到滋——的尖叫。所以直流电机调速常用16kHz到20kHz因为大多数人听不到这个频段舵机内部舵机内部有一个标准的RC接收电路以50Hz作为基准周期、以0.5ms到2.5ms的脉宽对应角度。这时PWM频率由舵机协议决定必须用50Hz不是你随便选个20kHz就能让它转的。频率越高越好是新手很容易踩的误区。MOS管和驱动电路每一次开关都有损耗频率翻倍开关损耗基本也翻倍过高频率还会引入电磁干扰和驱动不足的问题。后面第5章我会按具体场景给出参考值。3. STM32 HAL实战从CubeMX配置到示波器出波3.1 CubeMX里到底要配哪些地方就拿STM32G070CBT6配PB6输出PWM这个案例来说。PB6在STM32G070上默认复用就是TIM3_CH1对应搜索里stm32g070cbt6 pwm输出程序 hal库函数那些朋友多数就是在这个芯片上卡住了其实配置思路和F030系列是完全一样的。打开CubeMX后做四件事时钟树给定时器选一个时钟源。如果只是验证PWM用内部HSI 16MHz就够了不用外接晶振。先确定APB定时器时钟那一路是多少后面计算才准TIM3把Clock Source勾选为Internal Clock然后在Parameter Settings里填PSC和ARR。以刚才的1kHz为例Prescaler填15Counter Period填999Counter Mode选Up通道在TIM3的PWM Generation Channel 1里Mode选PWM mode 1Pulse填初始值比如499OC Polarity默认High不动GPIO在Pinout页把PB6选为TIM3_CH1复用功能CubeMX会自动把引脚配置成复用输出。这里有个提醒F030/G070这类芯片的GPIO复用功能不是随便映射的PB6能复用成TIM3_CH1而PB7可能是TIM3_CH2要查数据手册或用CubeMX的pinout提示。如果你把引脚配置成普通推挽输出再用软件翻转那就是前面说的软件PWM性能和可靠性都差一个量级。3.2 代码骨架启动PWM和动态调参CubeMX生成工程后PWM其实已经初始化好了你在用户代码里要做的事并不多。启动一路PWM就一行HAL_TIM_PWM_Start(htim3, TIM_CHANNEL_1);这一句的作用是让定时器真正输出PWM波形。很多人的误区是CubeMX配完就以为波形出来了实际上不调用Start通道输出一直是静止的某个电平。运行过程中改占空比用宏/* 把占空比改为700/1000 70% */ __HAL_TIM_SET_COMPARE(htim3, TIM_CHANNEL_1, 700);改频率则改ARR/* 把ARR从999改成499PWM频率变为2kHz */ __HAL_TIM_SET_AUTORELOAD(htim3, 499); __HAL_TIM_SET_COMPARE(htim3, TIM_CHANNEL_1, 349); // 注意同步调整比较值为什么要同步调整CCR因为ARR一旦变化CCR不跟着按比例调整占空比会突然跳变。比如ARR从999改成499CCR保持700会超过ARR输出直接变成常高或常低整个PWM就废了。改频率这种事计算完一定看一眼参数范围再写。3.3 实测中我踩过的四个坑第一个坑Pulse填0导致什么都看不到。CubeMX里Pulse是初始比较值你填0模式1下计数器一开始就大于比较值输出一直为低。这种没有波形最容易让人怀疑是时钟问题或引脚问题实际只是初始值没填对。第二个坑GPIO复用没配对。PB6选成TIM3_CH1后还要确保没有其他外设抢占同一引脚。比如你同时把PB6配给了另一个通信协议的片选两个外设打架波形就是出不来或时好时坏。第三个坑极性搞反。OC Polarity设成High比较匹配前是高电平、匹配后是低电平设成Low就正好反过来。如果驱动电路是低电平点亮LED极性问题会让亮度变化方向和代码完全相反你会看到占空比加大反而变暗非常迷惑。第四个坑在呼吸灯里用HAL_Delay阻塞延时。每个亮度等级延时20ms占空比从0到1000循环一圈要20多秒期间CPU全被延时卡死。真正做呼吸灯一般用定时器中断更新占空比或者把亮度变化放在主循环里配合系统的tick调度中间不要阻塞。4. PWM模式1/模式2、单脉冲、中心对齐配置细节里的门道4.1 PWM模式1和模式2到底差在哪CubeMX里PWM Mode有两个选项PWM mode 1和PWM mode 2。它们的波形差别在于向上计数模式计数器从0计到ARR。模式1下计数到CCR之前输出有效电平一般算高到达CCR后翻转模式2则相反计数到CCR之前输出无效电平过了CCR才是有效电平。向下计数模式逻辑正好反过来。所以模式1和模式2本质上就是高电平在前还是高电平在后的差别。选择上没有绝对对错完全看外部电路的逻辑如果你的驱动电路要求高电平使能用模式1配合High极性如果要求低电平使能可以用模式2配合Low极性也可以模式1配Low极性。关键是搞清自己用的是哪种极性别配反了。4.2 单脉冲模式能不能连续发射PWM波搜索词里有单脉冲模式 连续发射pwm波这个问题很典型。STM32定时器的单脉冲模式One Pulse ModeOPM是一次性的计数器收到触发信号后启动按预设的周期和比较值输出一个脉冲然后计数器停止等待下一次触发。它本质就是受外来事件触发的单次PWM。所以答案是单脉冲模式不能连续自发地输出PWM波。如果你需要连续PWM直接关闭OPM用普通PWM模式就行如果你需要来一个触发信号就输出一个精确宽度的脉冲那OPM是正解。它的典型用途包括超声波驱动、精确短脉冲输出或者做一次性的脉宽校准。4.3 中心对齐模式电机控制里值得关注的选择前面说的都是边沿对齐模式也就是计数器从0加到ARR再瞬间回0。中心对齐模式Center-Aligned下计数器先从0加到ARR再从ARR减到0输出脉冲的上升沿和下降沿都相对于计数值中点对称。很多做电机控制的老工程师喜欢用中心对齐因为它的有效开关频率是边沿对齐的两倍且输出波形谐波更小电磁噪声更柔。代价是配置复杂一点更新比较值还要注意计数方向。如果你是新手刚开始用边沿对齐完全够用等你做无刷电机驱动时发现电流噪声偏大再切到中心对齐试试往往有惊喜。5. 应用场景决定参数LED、直流电机、舵机、无刷电机各用多少频率5.1 呼吸灯和RGB调光频率主要照顾眼睛呼吸灯基本就是给LED的亮度做正弦或线性渐变。频率选500Hz到1kHz完全够人眼无感LED也不会有残影发虚。至于亮度曲线不要用线性占空比直接变化因为人眼对亮度的感知是对数的线性变化会让人觉得前半段很亮后半段几乎不变。可以用查找表放一组指数或正弦曲线呼吸效果会自然很多。RGB调光稍微讲究一点。三种颜色的LED要同时工作PWM频率建议1kHz以上最好2kHz到5kHz否则在快速移动物体上可能出现分色伪影。另外三路PWM的初始相位最好对齐这样合成颜色时不容易闪。用STM32多个定时器通道分别控制R、G、B是常规做法如果通道不够就用同一个定时器的不同通道它们在时间上天然同步比分散在不同定时器里更好。5.2 直流电机和舵机频率是给负载选的直流电机调速的PWM频率我一般从16kHz起步。再低一点比如10kHz会有人觉得能听到高频啸叫高到20kHz以上MOS开关损耗和驱动发热又开始上升。这里要根据具体驱动芯片和电机的电感参数去微调最好在示波器上看电流波形再定。有朋友拿着MG310P20这类集成驱动板在7.4V电压下问该用多少PWM频率。我的实用建议是先确认驱动板输入逻辑电平和你的MCU匹配再从16kHz开始调。如果电机啸叫明显就往下调几千赫兹比如14kHz或12kHz如果驱动板发热厉害就往回调。弄清自己板子上的开关器件参数比纠结一个固定值更有意义。舵机是另一个极端。标准模拟舵机内部按50Hz周期、0.5ms到2.5ms脉宽判断角度也就是说周期固定20ms脉宽在0.5ms0°附近到2.5ms180°附近之间线性映射。生成方法就是ARR设成20ms对应计数值CCR设成目标脉宽对应计数值。数字舵机虽然接收频率可以更高但兼容起见先用50Hz不会错。常见PWM应用参数参考应用推荐PWM频率备注呼吸灯500Hz ~ 1kHz人眼无感占空比按指数曲线变化RGB调光1kHz ~ 5kHz防止分色伪影尽量三通道同步直流电机调速16kHz ~ 20kHz避开人耳频段兼顾开关损耗模拟舵机50Hz周期20ms脉宽0.5ms ~ 2.5msBLDC六步换向16kHz ~ 20kHz需要互补PWM加死区单相PWM整流10kHz ~ 20kHz由电流纹波和EMI综合决定5.3 BLDC六步换向多路PWM和死区六步换向pwm波形这个搜索词说明有人被无刷电机驱动卡住了。六步换向本质是在三相桥臂上按转子位置切换导通组合每个60°电角度区间内上下桥臂按特定规律导通。常见的调制策略有上桥PWM下桥恒通、上桥恒通下桥PWM以及PWM_ON_PWM这类改进型。硬件上通常需要三路互补PWM输出加死区。STM32的TIM1/TIM8高级定时器就是为这个设计的每路都能输出互补的CHx和CHxN还有硬件死区寄存器。死区时间一般从几百纳秒到几微秒具体看MOS管的关断延迟。设小了上下桥臂直通直接烧管子设大了波形畸变和发热增加。调试六步换向时建议先在低速下空载运行用示波器同时看三对桥臂确认换向逻辑和PWM调制都符合预期再接负载。5.4 从电机到电源PWM整流器是另一个物种如果你是在做单相PWM整流器、单相全桥PWM整流这类电源方向那PWM的频率选择逻辑又不一样了。这种场景里PWM开关频率直接决定电感电流纹波和EMI常见在10kHz到20kHz级别。Simulink做双闭环仿真时PWM模块通常就是拿三角载波和调制波做比较载波频率就是开关频率。电流内环的PI参数和电压外环的带宽都要基于开关频率设计开关频率定多少直接决定电感选多大、谐波能不能过标准。这个话题可以单独写一篇这里只提醒一点电源类应用一定要把开关频率和器件损耗放在一起算不是越高越好。你在仿真里把频率调高波形漂亮了但实际硬件里的MOS管会告诉你什么叫热崩溃。6. PWM故障保护为什么你的板子一堵转就会烧6.1 软件保护为什么往往来不及我见过很多开发板烧MOS十有八九是电机堵转。转子卡死电流从正常运行时的几百毫安飙升到几十安培MOS管的结温在几十毫秒内就能突破极限。这个时候如果靠主循环里循环判断ADC值再关闭PWM黄花菜都凉了。就算你用了中断ADC采样启动到转换完成再到CPU读寄存器和关输出中间少说也有几十到上百微秒在这种故障条件下足够把器件烧穿。6.2 用高级定时器的刹车功能做硬件级保护STM32高级定时器有个特性经常被忽略刹车输入BKIN和主输出使能MOE。你可以把比较器输出或电机驱动芯片的故障信号引到BKIN引脚当硬件电平触发刹车时定时器会在一个时钟周期内强行关闭所有PWM输出完全不走CPU。这个响应速度是软件中断比不了的。HAL库里有专门的配置函数关键配置项如下TIM_BreakDeadTimeConfigTypeDef sConfig {0}; sConfig.OffStateRunMode TIM_OSSR_DISABLE; sConfig.OffStateIDLEMode TIM_OSSI_DISABLE; sConfig.BreakState TIM_BREAK_ENABLE; sConfig.BreakPolarity TIM_BREAKPOLARITY_HIGH; sConfig.BreakFilter 0; HAL_TIMEx_ConfigBreakDeadTime(htim1, sConfig);具体成员名在不同系列HAL库里略有差异以你用的头文件为准。BreakPolarity要和你故障信号的有效电平一致弄反了会一上电就锁死输出。配置好后故障信号一来PWM立刻全灭直到你清除触发条件并重新使能MOE。注意MOE被刹车后不会自己恢复你需要在代码里显式调用恢复函数这也是安全设计的一部分。6.3 软件兜底和逐波限流硬件刹车做第一层软件层还是要做第二层。ADC那一路用来做过流预警一旦电流超过阈值除了关PWM还要置一个故障标志、停止换向逻辑、通过串口上报状态。更讲究的做法是逐波限流用比较器在每个PWM周期内实时限制电流峰值超过阈值就在本周期内关断该路PWM下一个周期再重新评估。这样做的好处是电机堵转时电流波形是削顶的器件温度可控不会立刻烧毁。逐波限流和软件关闭是不同的保护深度做无刷驱动时优先考虑前面那一层。不要指望一套软件逻辑包打天下硬件保护永远要比软件快。7. 跳出STM32CCU6、FlexIO这类专用外设怎么玩PWM7.1 英飞凌CCU6电机控制的老牌选手搜索词里出现ccu6 pwm的多半在接触英飞凌的XC2000或者AURIX系列。CCU6的全称是捕获比较单元6设计目标就是电机控制。它内部有T12和T13两个定时器组T12可以产生三路带硬件死区的互补PWM正好对应三相逆变器的三对桥臂T13单独产生一路PWM常常用来做PFC或额外的一路开关。CCU6的调制模式里也有类似模式0/模式1的概念理解了STM32的PWM模式1/2的逻辑之后再看CCU6的调制模式文档就不会发怵。它的硬件死区、故障封锁脚CTRAP和刹车机制本质上和STM32的TIM1/8是同一套思路可靠性靠硬件不靠CPU。7.2 FlexIOFXIO灵活IO也能当PWM用fxio pwm这个热词很多情况下指的是NXP一些MCU上的FlexIO功能。FlexIO本来是个柔性串行外设用来模拟UART、SPI、I2C这类通信协议但它的定时器模块也可以配置成PWM输出。好处是引脚映射极其灵活输出通道多还能和DMA配合做LED矩阵、单总线灯条这类需要多路脉宽信号的场景很方便。用FlexIO生成PWM的坑在于配置项特别多每个定时器引脚的模式都要从文档里一个个查。建议先跑官方的例程确认能用再改参数不要上来就自己从零配。和CCU6以及高级定时器对比起来FlexIO更适合中等分辨率、多通道、IO需要灵活迁移的场景而不是高可靠性电机驱动。PWM外设怎么选重点看这几个维度PWM外设常见平台典型特点CCU6英飞凌XC2000/AURIX三路PWM加一路PFC、硬件死区、CTRAP故障封锁FlexIO/FXIONXP及部分FPGA/MCUIO灵活、DMA联动、适合多路LED/灯条TIM1/TIM8高级定时器STM32互补输出、硬件死区、BKIN刹车通用定时器STM32单路或多路PWM无互补适合LED/简单驱动7.3 PWM外设选型重点看这几项不管是CCU6、FlexIO还是STM32定时器选型时抓住五个维度通道数量够不够有没有互补输出和死区有没有硬件故障输入定时器时钟和计数器位数能不能支持你的频率与分辨率以及能不能和DMA联动批量更新多个通道。最后一条很多人忽略在需要同时更新三路占空比的无刷电机里用DMA批量写比较寄存器比逐路调用宏要高效得多且时序更一致。说到底生成PWM这件事外设帮我们省下的每一微秒 CPU 时间最后都会变成系统稳定性和流畅度上的回报。说了这么多原理、配置和平台差异最后讲个我自己的习惯调PWM一定要养成看波形的习惯示波器也好逻辑分析仪也好。没有示波器的时候我见过太多人靠猜调试——LED亮度不对就改占空比电机啸叫就改频率改来改去不知道哪一步起了作用。引脚上接个探头频率、占空比、上升沿毛刺一眼看清很多玄学问题当场破案。PWM生成并不难难的是你愿意在动手之前想清楚它在你的系统里担任什么角色——是给眼睛看的给耳朵听的还是给电机和电源干重活的。想清楚这个参数和配置自然就有方向了。
阅读完成 · 觉得有帮助?
咨询建站