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

PWM电机调速实战:从占空比原理到H桥驱动与代码实现

PWM电机调速实战:从占空比原理到H桥驱动与代码实现 ★ FEATURED ARTICLE
PWM电机调速这东西圈子里的朋友应该都不陌生。说原理三句话能讲完——通过改变方波的高电平占空比改变电机两端的平均电压进而控制转速。但真到了自己动手做项目的时候频率选多少、H桥怎么搭、驱动芯片怎么选、死区要不要配、代码怎么写才能稳一步一步踩过去每一步都有能绊倒人的坑。这篇文章就把整个链路摊开讲从PWM调速的底层逻辑到硬件选型和方案对比再到51、STM32、Arduino三套代码实现最后把频率选择、死区配置、飞车保护这些实战里最容易出问题的点单独拎出来说。适合手上有电机、想自己做调速板又不满足于只抄一段代码跑通的读者。1. PWM调速的核心思路方波里的“有效电压”1.1 先搞懂占空比和平均电压的关系PWM的全称是Pulse Width Modulation脉冲宽度调制。它输出的不是连续的模拟电压而是一串周期性的方波。这串方波里有三个关键参数周期T一个完整波形的时间单位秒s频率f每秒波形重复次数f 1/T单位赫兹Hz占空比D高电平时间Ton占整个周期的百分比D Ton / T × 100%占空比这个值才是调速的关键。电机接在PWM输出和地之间它看到的电压并不是一瞬间的方波而是一个平均值。这个平均电压约等于[ U_{eff} U_{max} \times D ]举个直观的例子一颗12V的直流电机给它输入占空比30%的PWM信号那么等效平均电压就是12 × 0.3 3.6V。电机的转速大致正比于端电压所以理论上3000rpm的电机在30%占空比下会降到900rpm左右。当然实际值会因为摩擦、反电动势、驱动器压降等因素略有偏差但趋势是单调的占空比越大平均电压越高转速越快。我调试时习惯直接用示波器去看PWM输出高电平宽度一眼就清楚了。没有示波器的话用万用表直流电压档也能量出平均电压数值基本等于U_max × D这个值可以帮你快速验证代码逻辑对不对。1.2 电机为什么吃这一套感性负载的电流惯性很多新手会疑惑方波明明是0V和12V来回跳电机为什么不会一抖一抖地转这里的关键在于电机本身的特性——它是一个典型的感性负载。电感有一个核心特性电流不能突变。当PWM高电平时电源向电机供电电流上升当PWM切到低电平时电源断开但电机的绕组电感会阻止电流瞬间归零电流通过续流二极管或H桥的下管继续流动缓慢下降。只要PWM频率足够高电流波动就很小电机看到的实际上是一个非常平稳的连续电流而不是断续的脉冲。可以这么理解电机靠电流产生转矩转矩决定转速。电感把方波电压“滤”成了近似平稳的电流所以电机不是在“抖动”而是在一个稳定的平均电流下匀速转动。这也解释了为什么PWM调速在电机驱动里特别适合——它利用了负载本身的物理特性而不是硬生生地靠外部滤波电路来平滑电压。1.3 为什么调速不用调压三种方案对比既然要控制电机端电压为什么不直接用可调电源我见过不少新手其实是被这个思路卡住的。放在实际工程里有三种调速思路串联电阻调速在电源和电机之间串一个可调电阻分压来降低电机电压。简单是简单但电阻上消耗的功率等于电流的平方乘以电阻能量全变成热量散掉了。低速时尤其浪费而且电阻值一变负载一变转速也跟着漂。线性稳压器调速用LM317这类线性稳压IC调输出电压。精度比电阻好但线性稳压器本质上也是靠管压降把多余的电压“吃掉”效率和发热问题依旧大电流场景基本用不了。PWM开关调速MOS管或三极管工作在饱和/截止两个状态要么全通要么全断通态损耗很小截止时漏电流极小开关切换过程的损耗也远小于线性方案整体效率能做到90%以上。这就是为什么实际产品里小到玩具车、电动牙刷大到电动汽车主驱动清一色都在用PWM调速。它不是“最精确”的方案但它是“效率、成本、电路复杂度”平衡下来最现实的选择。2. 硬件选型H桥、驱动芯片与MOS管驱动2.1 H桥的基本原理与防直通设计PWM只能调速不能换向——要控制电机正反转得靠H桥。H桥由四个开关管MOS管或三极管组成电机接在桥臂中间形状像字母H所以叫H桥。正转左上Q1和右下Q4导通电流从电源经过电机左边流到右边反转右上Q2和左下Q3导通电流方向反过来刹车同时导通同一边上下两管或对地短路让电机绕组短接快速停止这里有一个绝对不能碰的禁忌同一侧的上管和下管Q1和Q2绝对不能同时导通。一旦导通电源直接通过这两个管子对地短路电流会瞬间飙升到几十安培把MOS管甚至整个板子烧掉。这个现象有个名字叫直通Shoot-through。为了防止直通硬件上要保证上下桥臂的切换有足够的时间间隔——先彻底关断上管再打开下管。这个间隔就是死区Dead Time后面第4节我会专门展开讲。2.2 从L298N到TB6612三款驱动芯片怎么选自己用四个MOS管搭H桥不是不行但驱动电路特别是栅极驱动电平匹配、续流保护、死区控制对新手来说太容易翻车。工程上我更推荐直接用封装好的H桥驱动芯片省心得多。下面是市面上最常见的三款芯片输出电流供电电压逻辑电平典型场景特点L298N2A/通道峰值3A5-35VTTL 5V教学实验、大功率玩具管压降大、发热严重、效率偏低TB6612FNG1.2A/通道峰值3.2A逻辑2.7-5.5V电机6-15VTTL/3.3V小型机器人、智能小车内阻约0.5Ω效率高带刹车功能DRV88331.5A/通道峰值2A2.7-10.8VTTL/3.3V小型电机、玩具自带过流保护PWM频率可以很高我的建议是如果做教学或大功率演示用L298N便宜好买资料也多如果是做小型机器人、云台、智能车优先TB6612体积小、效率高特别是电池供电的场景省电优势非常明显DRV8833适合讲究集成度和保护功能的项目故障状态引脚可以直接反馈给单片机。注意一点这些芯片的电源引脚和逻辑引脚要分开供电逻辑电源负责芯片内部控制电路电机电源负责电机本身。接线时一旦把电机电源错接到逻辑引脚芯片大概率直接烧掉。2.3 555定时器生成PWM和逻辑电平MOS方案在没有单片机的场景下一颗NE555定时器就能搭出一个纯硬件的PWM发生器。核心思路是用两个二极管分别控制充放电回路让充电和放电走不同的路径调节电位器就能改变占空比同时基本不影响频率。555的输出端可以直接驱动小功率电机或者接一个MOS管去驱动大电流电机。这个方案我以前上课时经常用好处是电路直观、不需要写任何代码五分钟就能搭出来缺点是频率和占空比受温度和电源电压影响漂移没有故障保护不能做PID闭环。它的定位是学习工具或者做一个临时性的手动调速器真到产品阶段还得靠MCU控制。如果要自己用MOS管搭驱动我建议从逻辑电平的N-MOS入手比如AO3400A。这种管的栅极阈值电压很低5V甚至3.3V的PWM信号就能让它完全导通导通内阻只有20-40mΩ电流定额4A左右对小功率直流电机、风扇、玩具车绰绰有余。需要注意的是AO3400A是N-MOS只适合低边驱动——电机接电源正极MOS管接在电机负极和地之间PWM直接加在栅极上。想做高边驱动就得搞自举电路新手别一上来就挑战这个。3. 代码实现三种平台的PWM写法3.1 51单片机软PWM定时器中断控制占空比51单片机本身没有硬件PWM外设部分增强型型号除外所以要靠定时器中断软件模拟。基本思路是开一个固定周期的定时器中断比如10us中断一次中断服务程序里维护一个计数器和占空比比较值计数器小于比较值输出高电平否则输出低电平。下面是标准的51软PWM模板晶振12MHzP1.0口输出PWM#include reg51.h sbit PWM_OUT P1^0; unsigned char duty 50; // 初始占空比 50% unsigned int cnt 0; const unsigned int period 100; // 一个PWM周期对应的中断次数 void Timer0_Init(void) { TMOD 0x02; // 定时器08位自动重装模式 TH0 256 - 10; // 12MHz晶振每10us产生一次中断 TL0 256 - 10; ET0 1; EA 1; TR0 1; } void Timer0_ISR(void) interrupt 1 { cnt; if (cnt period) { cnt 0; } PWM_OUT (cnt duty) ? 1 : 0; } void main(void) { Timer0_Init(); while (1) { // 主循环里只需要改duty变量比如按键调速、 // 串口指令调速、PID运算结果写duty等等 } }这里几个细节要搞清楚period是100所以PWM频率 1 / (10us × 100) 1kHz。如果觉得频率太低后面会讲为什么太低的频率不好就把period改小比如period20频率就变成5kHz。代价是占空比分辨率从1%变成5%每档精度变粗了。duty的范围是0到100对应0%到100%占空比。想要更细的分辨率就加大period但频率会降下来这就是软件PWM的精度和频率之间的矛盾。中断服务程序里做的事情越少越好别放延时、别放串口发送不然中断还没跑完下一个中断就来了PWM波形就会毛刺。3.2 扩展思路怎么用51模拟出16位高精度PWM有些场合需要更高精度的占空比控制比如舵机角度控制、云台微调8位PWM分辨率1/256往往不够。51单片机的定时器是16位的理论上能模拟出16位分辨率的PWM但做法不是简单地加一个计数器。推荐的做法是分两级处理第一级定时器工作在16位模式产生一个高频率的时基中断比如10us第二级在中断里维护一个16位的“周期计数器”同时用一个16位的“比较寄存器”。当计数器在0到compare之间时输出高电平达到period后清零。这种方法可以让占空比分辨率做到1/65536频率部分牺牲掉。#include reg51.h sbit PWM_OUT P1^0; unsigned long cnt 0; unsigned int compare 16384; // 占空比比较值范围0~65535 const unsigned int period_16 65536; const unsigned int unit_cnt 10; // 每次中断对应的时间单位 // Timer1 16位模式1us中断一次 void Timer1_Init(void) { TMOD 0x10; // 定时器116位模式 TH1 (65536 - 1000) / 256; // 大致1ms中断一次具体按晶振计算 TL1 (65536 - 1000) % 256; ET1 1; EA 1; TR1 1; } void Timer1_ISR(void) interrupt 3 { TH1 (65536 - 1000) / 256; TL1 (65536 - 1000) % 256; cnt; if (cnt period_16) { cnt 0; } PWM_OUT (cnt compare) ? 1 : 0; } void main(void) { Timer1_Init(); while (1) { // 通过调节compare实现高精度占空比变化 } }这种用法比较取巧它牺牲了实时性换分辨率因为主循环里改compare的瞬间可能会和中断里的读取产生竞争。实际工程里如果对时序要求严格还是建议用带硬件PWM的增强型51比如STC系列或者直接上ARM内核的MCU。3.3 STM32硬件PWM从初始化到互补输出死区配置STM32的定时器自带硬件PWM输出CPU不需要参与波形产生这是它比51软PWM先进的核心点。以最常用的STM32F103加HAL库为例配置一个频率10kHz、占空比50%的PWM只需要这几步。TIM_HandleTypeDef htim2; TIM_OC_InitTypeDef sConfigOC; void MX_TIM2_Init(void) { htim2.Instance TIM2; htim2.Init.Prescaler 72 - 1; // 72MHz / 72 1MHz计数时钟 htim2.Init.Period 100 - 1; // 1MHz / 100 10kHz PWM频率 htim2.Init.CounterMode TIM_COUNTERMODE_UP; htim2.Init.ClockDivision TIM_CLOCKDIVISION_DIV1; HAL_TIM_PWM_Init(htim2); sConfigOC.OCMode TIM_OCMODE_PWM1; sConfigOC.Pulse 50; // 初始占空比 Pulse / Period 50% sConfigOC.OCPolarity TIM_OCPOLARITY_HIGH; sConfigOC.OCFastMode TIM_OCFAST_DISABLE; HAL_TIM_PWM_ConfigChannel(htim2, sConfigOC, TIM_CHANNEL_1); HAL_TIM_PWM_Start(htim2, TIM_CHANNEL_1); } // 动态调速任何时候调用下面这句话即可 // __HAL_TIM_SET_COMPARE(htim2, TIM_CHANNEL_1, newDuty);解释几个关键点Prescaler和Period的乘积决定了PWM频率。72MHz主频预分频72得到1MHz计数时钟Period设为100就得到10kHz。这个对照关系一定要算清楚改频率时别只动一个参数。Pulse寄存器决定占空比它必须小于Period否则输出一直为高。这一点在动态调速时特别容易踩比如PID输出超调把Pulse推过了Period电机突然全速飞转。PWM1模式的含义是计数值小于Pulse时输出有效电平大于Pulse时输出无效电平。反过来PWM2模式是小于Pulse时无效大于时有效。理解这个逻辑对调试输出极性很有帮助。STM32还有一类应用会用到互补输出CH1N对应热搜词里的“stm32 pwm generation ch1n”。CH1N的输出波形和CH1相反专门用来驱动H桥上下桥臂。使用互补输出时必须配置死区时间否则上下管直通。只有TIM1和TIM8这类高级定时器才有互补输出通道配置方式和通用定时器类似额外多一个死区参数TIM_BreakDeadTimeConfigTypeDef sBreakDeadTimeConfig; sBreakDeadTimeConfig.DeadTime 100; // 死区时间 100 / (死区时钟频率) HAL_TIMEx_ConfigBreakDeadTime(htim1, sBreakDeadTimeConfig);那句很典型的“stm32 高级定时器 pwm 中心对齐模式和 adc 采样时刻点设置”本质上也是定时器配合ADC同步采样的应用在中心对齐模式下计数器向上和向下计数都会更新PWM输出PWM波形以周期中点对称适合无刷电机换相和全桥应用。ADC采样时刻设定在PWM的中间点可以避开开关噪声最大的边沿时刻采样到的电流值最干净。这个优化在电机电流环里非常实用。3.4 Arduino/ESP32十行代码跑通的极简方案如果只是快速验证电机的调速逻辑Arduino是最省事的平台。AVR系列的analogWrite()就是硬件PWM输出不需要配寄存器一行代码就搞定const int pwmPin 9; // 注意只有带~标记的引脚才支持PWM void setup() { pinMode(pwmPin, OUTPUT); } void loop() { // 从0到255逐渐加速再减速回来 for (int duty 0; duty 255; duty) { analogWrite(pwmPin, duty); delay(5); } for (int duty 255; duty 0; duty--) { analogWrite(pwmPin, duty); delay(5); } }Arduino的analogWrite()频率默认约490Hz或980Hz取决于引脚太低了驱动电机通常会发出让人难受的啸叫声。如果想提高频率需要用定时器寄存器改底层配置或者换ESP32这种可以用ledcSetup()自定义频率的芯片const int pwmPin 4; void setup() { ledcSetup(0, 20000, 8); // 通道020kHz PWM频率8位分辨率 ledcAttachPin(pwmPin, 0); // 把引脚4绑定到LEDC通道0 } void loop() { for (int duty 0; duty 255; duty) { ledcWrite(0, duty); delay(5); } for (int duty 255; duty 0; duty--) { ledcWrite(0, duty); delay(5); } }ESP32的LEDC模块本质上就是一个硬件PWM生成器既能调频率又能调分辨率很多舵机控制库底层就是基于它实现的。做电机控制原型验证时我经常先用ESP32跑通逻辑再移植到正式MCU上。4. 频率、死区、故障保护那些容易踩的坑4.1 频率不对电机要么抖要么烫PWM频率的选择有两个主要约束一个是听觉一个是效率。先说听觉。人耳的听觉范围是20Hz到20kHzPWM频率一旦落在这个范围里电机绕组在电流脉冲作用下会产生高频啸叫声音尖锐刺耳听着非常难受。工程上一般避开1kHz到8kHz这个最敏感的区间选择10kHz以上。我见过有人为了追求完全静音直接把频率拉到25kHz甚至更高但高频带来的结果是MOS管开关损耗急剧上升——开关瞬间管子的电压电流同时不为零瞬时功率很大频率越高单位时间内开关次数越多发热越明显。再说低频率的问题。如果PWM频率低于1kHz电机在低速时会出现可见的顿挫感一卡一卡地转动。这是因为电流波动过大转矩脉动超过了电机本身的惯性滤除能力。频率越低这个问题越明显。给一个实用参考值负载类型推荐频率说明标准RC舵机50Hz周期20ms信号频率固定控制脉宽1-2ms小型直流电机10-20kHz避开人耳敏感区效率可接受带减速箱的直流电机10kHz左右变速箱本身会放大噪音无刷电机ESC16-25kHz高频换相具体看电调设计4.2 死区不是锦上添花是保命配置死区这个概念在普通单端PWM驱动里用不上只要一做H桥或者半桥驱动死区就是必须处理的硬性问题。还是回到H桥那个电路。假设当前正转左上管Q1导通右下管Q4导通现在要切换成反转右下管Q4关断右上管Q2导通这时Q1和Q2就同时处于一个桥臂。如果Q4关断后立刻打开Q2那么Q1和Q2之间存在一个极短暂的共同导通窗口——电源正极到地之间没有电阻只有两个正在导通的MOS管瞬间电流会暴涨到安培级别轻则发热重则直接炸管。解决的办法就是插入死区时间从Q4关断到Q2导通之间留出几百纳秒的等待时间这个时间必须大于MOS管自身的关断延迟时间。STM32高级定时器配置死区是通过TIM_BreakDeadTimeConfig完成的我之前在3.3节里已经给了代码片段。如果不使用互补输出而是用软件分别控制两个IO口就得自己在代码里加延时这其实是比较危险的做法因为中断会打断延时的精确性极有可能在某个特定时序下出现直通。所以我的原则是凡是要驱动H桥一律用带硬件死区的定时器通道不要省这个功能。4.3 “飞车”和常见故障排查速查表热搜词里的“pwm电机飞车”是很多人在实际调试中真实遇到的问题。电机飞车就是电机突然全速运转无法停止通常原因有几类程序跑飞或复位后IO口默认状态不确定直接输出高电平电机全速转PID闭环里占空比饱和Pulse值超过Period输出持续高电平H桥芯片逻辑电平受干扰使能脚误触发电源跌落导致单片机复位复位后初始化代码没跑PWM管脚悬空被干扰拉高针对性的解决办法软件上加看门狗超时未刷新就进入安全状态硬件上给PWM输出加下拉电阻让默认电平为低继电器或MOS管驱动端的控制脚做逻辑互锁PID输出做限幅处理Pulse值必须严格限制在0到Period-1之间。下面整理了一张排查表都是我实测中遇到过的问题现象可能原因排查方法解决办法电机不转PWM有输出使能脚未拉高/驱动芯片没有电源万用表量芯片供电脚检查ENA/ENB引脚和电机电源电机全速飞车程序跑飞/占空比饱和/IO默认电平示波器抓输出波形、复位测试加看门狗、管脚下拉、PID输出限幅电机低速一顿一顿PWM频率过低示波器看频率提到10kHz以上MOS管发热严重开关频率过高/栅极驱动不够手摸或红外温枪测温度降低频率、加强栅极驱动能力上电瞬间电机转半圈IO默认电平不确定/驱动芯片上电误动作示波器抓上电瞬间波形加下拉、MCU初始化后延时再使能舵机抖动信号占空比漂移/供电电压不足万用表量工作电压独立电源供电、加电解电容调速时电机尖叫PWM频率落人耳敏感区听声音、看波形频率提到20kHz左右4.4 我在调试中踩过的几个实际问题调了这么多年PWM驱动印象最深的是有一次给一个中型直流电机做H桥调速用的是TB6612加STM32的互补PWM。代码逻辑看起来完全没问题占空比50%输出也对但电机一启动示波器上电机两端波形出现明显的尖峰毛刺持续了几十毫秒后恢复正常。后来排查发现是电机启动瞬间电流过大把电源电压拉低了几百毫伏TB6612逻辑电路跟着掉电PWM输出瞬间乱掉。解决方法是电机电源和逻辑电源分开供电或者在电机电源端并一个大电容做瞬间能量缓冲。还有一个常见坑是频率选择上早期为了图静音把PWM频率提高到32kHz结果带来两个问题一是MOS管温度明显上升二是低速时电机扭矩变小了。后来查资料才理解频率过高时每个PWM周期的时间太短电感电流还没充上来就放电了电流峰值上不去平均转矩自然就掉了。后来降到20kHz静音效果还在扭矩和发热都正常了。这些经验常规文档里不会写只有实际调过才知道。所以我一直建议调PWM驱动示波器是必需品电流探头最好也备一个先看波形再猜代码排查效率能翻几倍。从原理到代码再到调试经验这条链路的完整度已经能支撑你独立完成一个PWM电机调速项目了。如果你手头正好有电机和开发板建议按这个顺序动手先用Arduino或者51跑通调速逻辑再用示波器看波形确认频率和占空比最后再上H桥做正反转和死区配置。每一步都有明确的验证点出问题也知道在哪一步排查。
阅读完成 · 觉得有帮助?
咨询建站