从任务板上的P0标签说起——在一个真实的产品开发流程里,P0意味着“优先级最高、阻塞一切、必须先跑通”。而PWM电机开环控制,恰恰就是几乎所有电机应用的第一块多米诺骨牌:轮式机器人要动起来先靠它,风机水泵要转起来先靠它,连后面要做的闭环调速、位置控制、力控,本质上都是在开环这个“底座”上长出来的。这篇文章就围绕这个“底座”来写:从PWM控制电机的物理本质讲起,到H桥驱动电路、PWM参数选型,再到STM32平台上的代码落地和实测数据,最后聊聊开环控制的使用边界和往闭环演进的思路。不管你是刚接手第一个电机项目的学生,还是被分派去调驱动板的工程师,这篇文章都按“自己重新做一遍”的标准来写。
1. 先把最基本的物理关系搞清楚:占空比如何变成转速
1.1 电机转子为什么“听”PWM的话
很多人一开始会困惑一件事:我明明给电机的是一个个脉冲方波,不是连续的直流电压,电机怎么就能平稳转起来?这里的关键在于电机本身就是一个天然的低通滤波器。
直流电机在电气上可以简化成一个模型:绕组电阻R、绕组电感L、以及一个反电动势源e(t)。反电动势的大小和转子转速成正比,e = Ke·ω(Ke是反电动势系数,ω是角速度)。当PWM高电平期间,电源电压Vcc加在绕组上,电流上升;PWM低电平期间,绕组通过续流二极管继续导通,电感中的电流缓慢下降。因为机械系统的惯性远大于电气系统,转速对电压的响应是缓慢的,所以电机感知到的其实是PWM波形的“平均电压”。
这个逻辑一定要想透:PWM不是靠“脉冲冲击”驱动电机的,而是靠“平均等效电压”驱动电机的。脉冲的频率越高、电感越大,电流纹波越小,电机运行越平稳。
1.2 平均电压的计算:占空比的本质
占空比D定义为高电平时间Ton占整个周期T的比例:
D = Ton / (Ton + Toff) = Ton / T
那么施加在电机上的平均电压就是:
U_avg = D × Vcc
举个例子:12V电源、占空比50%,平均电压就是6V。但要注意,这里说的是“平均电压”,实际绕组上的电压是一个方波,电流才是相对平滑的。电流纹波大小可以估算:
ΔI ≈ (Vcc × D × (1 - D)) / (f × L)
这公式很实用,后面选PWM频率时要用到它。f是PWM频率,L是电机绕组电感。可以看出,D越接近50%,纹波越大;频率越高,纹波越小;电感越大,纹波越小。
1.3 空载转速与电压的近似线性关系
在空载情况下,电机的稳态转速近似满足:
U_avg ≈ Ke × ω + I × R
空载电流很小,I×R可以忽略,所以转速基本和平均电压成正比。这就是为什么通过调节占空比就能调节转速——本质上你在调的是一台“数字化的可调直流电源”。
不过这里的“线性”只是近似,实际会有两个明显偏差:一是占空比很小时,电压不足以克服电机转子上的静摩擦力和齿槽力矩,电机根本不转,这段称为启动死区;二是随着转速升高,轴承摩擦和风阻变大,转速会略低于线性预期。这些都是开环控制天然带有的误差来源,后面实测部分会看到。
2. H桥与驱动电路:开环系统的“执行机构”
2.1 为什么单管驱动不够用
大多数MCU的GPIO引脚驱动能力只有几毫安到二十毫安,直接带电机是不可能的。更基础的方案是单管驱动:一个MOS管串联在电机和地之间,PWM控制MOS管的通断。这个方案能做调速,但有两个限制:电机只能朝一个方向转;并且当PWM关断时,电机绕组的续流需要靠一个反向二极管释放,否则电感和寄生电容会产生很高的尖峰电压,打穿MOS管。
如果项目要求电机正反转——比如小车前进后退、舵机正反转、云台俯仰——那就得上H桥。H桥由四个开关管组成,电机接在桥臂中间。通过控制不同开关管组合,可以让电流以两种方向流过电机,从而实现正转、反转、刹车和滑行四种状态。
2.2 H桥的四种工作状态与PWM调制方式
用Q1/Q2表示左侧上下管,Q3/Q4表示右侧上下管,状态如下:
| 状态 | 导通的开关管 | 电机行为 |
|---|---|---|
| 正转 | Q1 + Q4 | 电流正向流过电机 |
| 反转 | Q2 + Q3 | 电流反向流过电机 |
| 刹车 | Q1 + Q3(或Q2+Q4) | 电机两端短路,急停 |
| 滑行 | 全部关断 | 电机自由转动,靠惯性 |
开环调速最常用的调制方式是“单极性PWM”:比如正转时,Q4常通,Q1由PWM波控制;反转时反过来。这样每周期只有一个管子在做高频开关,开关损耗相对小。还有“双极性PWM”方式,对角线两个管子同时通断,正转和反转交替出现,转矩响应更快,但开关损耗和电流纹波都更大,一般用在需要高动态响应的伺服场合,普通开环调速用不上。
2.3 死区时间:桥上最容易炸管的细节
H桥最忌讳的是上下两个管子同时导通——这叫直通(shoot-through),相当于电源直接短路,瞬间电流能把管子烧穿。解决方案就是插入死区时间:在上下管切换时,先关断导通的管子,等待一小段时间,再打开另一侧的管子。这个时间一般是几百纳秒到几微秒,具体看MOS管的关断延迟和驱动芯片的能力。
高级定时器(比如STM32的TIM1/TIM8)自带互补PWM输出和死区插入功能,硬件上自动处理这个时序。如果用普通定时器做H桥,就得在软件里手动加延时,或者在驱动芯片上选带死区控制的专用栅极驱动IC。这块不做,一定会交学费。
2.4 常用驱动模块怎么选:L298N、TB6612、AT8236
对于学习和快速验证,最常见的现成模块有三个,我直接说结论:
| 模块 | 最大电流 | 压降 | 适合场景 |
|---|---|---|---|
| L298N | 2A(实测折半) | 高,约2V以上 | 大扭矩教学演示,效率无所谓 |
| TB6612 | 1.2A | 低,约0.5V | 小型机器人,3.3V逻辑兼容好 |
| AT8236 | 6A左右 | 低 | 稍大功率电机、电动工具验证 |
L298N压降大、发热严重,但胜在便宜大碗,很多人入门都用它。TB6612的MOS管压降低得多,同样电压下电机实际获得的电压更高,转速也更高,适合电池供电的移动平台。AT8236是国产驱动里比较能打的,散热好,适合驱动大一点的直流减速电机。
如果自己画板子,驱动级一般选半桥驱动芯片(如IR2104、EG2104)加外部MOS管,或者直接选集成栅极驱动的智能半桥(如DRV8870、A4950),本质都是把H桥的“脑”(逻辑控制)和“肌肉”(功率管)分工处理好。
3. PWM参数选型:频率不是越高越好
3.1 频率选择的三个约束
PWM频率不是拍脑袋定的,至少要同时满足三个约束:
第一,频率要高于人耳可听范围或者避开机械共振点。频率落在20Hz到18kHz之间时,电机绕组和外壳会发出刺耳的啸叫声,很多项目就是因为PWM频率设在了16kHz附近,结果整机都在吱吱叫。常见的做法是直接跳到20kHz以上,进入超声波频段,人耳就听不到了。
第二,频率要远高于电机的电气时间常数。电机绕组的电气时间常数τe = L/R,一般在几十微秒到几毫秒。PWM周期最好比τe小一个数量级以上,这样电流纹波才足够小。举个例子,电感1mH、内阻2Ω,τe就是0.5ms,那PWM周期至少得在50µs以内,也就是频率要高于20kHz左右。
第三,频率要匹配MCU定时器的时钟分辨率。频率越高,一个PWM周期内的计数器计数值越少,占空比分辨率就越低。如果ARR只有499,那占空比步进就是0.2%,可能不够精细。
3.2 占空比分辨率:高频率的隐藏代价
PWM占空比分辨率本质上由ARR值决定,ARR越大,可表示的占空比档位越多:
分辨率 = 1 / (ARR + 1) × 100%
以72MHz主频的STM32F103为例:
- 设置PSC=0,即计数器时钟72MHz。若输出20kHz,则ARR+1 = 72MHz / 20kHz = 3600,占空比精度约0.028%,充裕。
- 但如果你用主频只有几十MHz的Cortex-M0,比如48MHz,输出50kHz时ARR+1 = 960,精度约0.1%,还是可用的。
- 如果频率推到100kHz,ARR+1 = 480,占空比档位不到500级,对精密调速就不太够了。
所以高频PWM不是免费的,你牺牲的是占空比精度。工程上一般按“频率尽量高、但占空比分辨率不低于0.1%~0.2%”来反推频率上限。
3.3 实测对比:不同频率下的表现差异
我在一个12V直流减速电机上做过简单对比,用示波器看电机电流波形,数据大致如下:
| PWM频率 | 听觉感受 | 电流纹波幅度 | 电机温度 | 噪声评价 |
|---|---|---|---|---|
| 1kHz | 明显啸叫 | 很大 | 发热较快 | 不可接受 |
| 8kHz | 轻微啸叫 | 中等 | 温升正常 | 勉强可用 |
| 20kHz | 听不到 | 较小 | 温升正常 | 推荐 |
| 50kHz | 听不到 | 更小 | 温升正常 | 对MCU资源要求高 |
注意,频率不是无限高就好。频率升高后,MOS管每周期开关损耗增大,驱动芯片的功耗也会上升,而且MCU定时器中断配合ADC采样的时序会变紧张。20kHz到25kHz是大多数直流电机开环应用的一个甜点区。
3.4 电流纹波公式如何指导选频
回头看电流纹波公式:
ΔI ≈ (Vcc × D × (1 - D)) / (f × L)
一辆12V供电的智能小车,电机电感0.5mH,占空比50%时纹波最大。如果频率选10kHz,则:
ΔI ≈ (12 × 0.5 × 0.5) / (10×10³ × 0.5×10⁻³) = 3 / 5 = 0.6A
如果电机额定电流1A,0.6A的纹波叠加上去就是1.3A的峰值,对MOS管电流应力和电机换向器寿命都不友好。把频率提高到20kHz,纹波直接减半到0.3A,效果显著。这就是为什么我用频率计算公式而不是凭感觉选参数。
4. STM32平台落地:定时器配置、代码骨架和实测数据
4.1 定时器选型和通道规划
以STM32F103为例,定时器资源大致分为三类:
- TIM1/TIM8:高级定时器,支持互补PWM输出和硬件死区插入,驱动H桥首选。
- TIM2~TIM5:通用定时器,支持4路独立PWM,适合单电机调速或多路舵机控制。
- TIM6/TIM7:基本定时器,不带PWM输出,只能做定时中断。
开环控制最常用的其实就是TIM2/TIM3,每路PWM占用一个通道。如果后面要加编码器测速,编码器接口需要额外占用两个通道,所以规划引脚时要把编码器接口和PWM通道分开,免得后面引脚冲突。
4.2 PWM输出配置:一份最小可用的CubeMX/寄存器配置
在STM32CubeMX里,选择一个定时器的通道,设置为PWM Generation CHx,具体参数按照20kHz输出配置:
| 参数 | 值 | 计算依据 |
|---|---|---|
| Prescaler (PSC) | 71 | 72MHz/(71+1) = 1MHz计时频率 |
| Counter Period (ARR) | 49 | 1MHz/(49+1) = 20kHz |
| Pulse | 0~50 | 占空比 = Pulse/50 |
| CH Polarity | High | 高电平有效 |
对应的HAL代码片段:
TIM_HandleTypeDef htim2; TIM_OC_InitTypeDef sConfigOC = {0}; htim2.Instance = TIM2; htim2.Init.Prescaler = 71; htim2.Init.CounterMode = TIM_COUNTERMODE_UP; htim2.Init.Period = 49; htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1; HAL_TIM_PWM_Init(&htim2); sConfigOC.OCMode = TIM_OCMODE_PWM1; sConfigOC.Pulse = 0; // 初始占空比0% 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, 25); // 占空比50%这个接口背后其实是往定时器的CCR寄存器里写值,硬件会在下一个计数周期自动装载,不会产生脉宽毛刺,这是PWM调速必须依赖的机制——绝对不能写在GPIO电平的软件延时里模拟PWM。
4.3 软件PWM?控制周期与调速平滑度
在51单片机上很多人用过软件延时翻转IO口生成PWM,比如驱动WS2811 RGB灯。这种做法的优点是引脚随意、不依赖定时器外设;缺点也非常明显:软件PWM会阻塞CPU,而且占空比调变时容易产生毛刺,导致电机抖动。如果只是点个灯、驱动个蜂鸣器,软件PWM没问题;但做电机转速控制,出现一个很长的异常高电平,电机会突然顿挫,严重时电流冲击过大还会损坏驱动管。
如果MCU定时器资源确实紧张,折中方案是“定时器中断+计数器变量”。比如用1kHz定时中断,中断里维护一个周期计数和一个比较值,电平翻转逻辑放在中断里。这种方法频率上限一般在几kHz,且CPU占用率会随着保持频率线性上升。所以结论很明确:只要芯片有硬件PWM外设,就优先用硬件PWM,这是电机控制的基本素养。
4.4 开环实测数据:占空比与转速的真实曲线
我在一个额定12V、空载转速2400RPM的直流减速电机上记录了一组数据,电机无负载,电源是稳压电源,PWM频率设为20kHz:
| 占空比 | 实测转速(RPM) | 理论平均电压(V) | 非线性偏差 |
|---|---|---|---|
| 0% | 0 | 0 | — |
| 10% | 0 | 1.2 | 启动死区,不转 |
| 15% | 90 | 1.8 | 勉强开始转 |
| 20% | 210 | 2.4 | 略低于线性 |
| 30% | 570 | 3.6 | 基本接近线性 |
| 50% | 1090 | 6.0 | 基本接近线性 |
| 80% | 1880 | 9.6 | 略低于线性 |
| 100% | 2400 | 12.0 | 基准点 |
有几个结论值得记下来:
第一,10%以下是不转的,这是静摩擦力和齿槽力矩导致的,不是PWM过零问题。要让电机可靠启动,多数情况下需要占空比跳到15%~20%以上。
第二,中段(30%~80%)曲线几乎线性,这也是开环控制“够用”的范围。
第三,超过80%之后转速增长又慢下来,因为转速越高,机械损耗(风阻、轴承摩擦)占比越大,电压更多地消耗在克服阻力上。
这就是开环控制的基本特征:你给定一个占空比,电机会运行在某个转速上,但这个转速到底是多少,受负载、电源电压、温度影响很大。同一占空比,空载和带载的转速可能差30%以上。理解这一点,才算真正理解“开环”的含义。
5. 开环控制的软肋:摩擦死区、负载扰动、电源波动
5.1 启动死区与静摩擦力
前面实测里,占空比10%电机不转,这就是启动死区。原因是电机转子要克服静摩擦力和齿槽力矩才能开始运动,而这个力矩对应的电流需要一定的电压才能建立起来。占空比太低,平均电压不足以建立足够的启动电流。
解决启动死区有几种工程手段:
- 启动阶段直接给一个较高的占空比“冲一下”,转速建立后再回调到目标值。很多电动工具、风机启动时就是这么干的。
- 用PWM频率调制,低频时脉宽更宽,电流峰值更大,更容易克服静摩擦。但低频会带来噪音,不推荐作为常规手段。
- 如果对启动特性要求高,就得放弃开环,上编码器做转速闭环,让控制器自动补偿死区。
5.2 负载变化时的转速跌落
开环控制最让新人不爽的一点:程序里写死30%占空比,空载时转速700RPM,轮子一压到障碍物,转速立刻掉到400RPM,甚至直接堵转。原因不复杂,根据电机稳态方程:
U_avg = Ke·ω + I·R
负载增大时,I增大,I·R压降变大,在U_avg不变的情况下,Ke·ω必然减小,转速就掉下来了。开环系统没有测速反馈,控制器不知道转速跌了,自然也不会修正占空比。
如果项目需要恒速运行——比如循迹小车希望左右两轮始终同步、云台希望旋转速度恒定——那开环是做不到的。此时必须引入闭环:用编码器或霍尔传感器测转速,PID控制器根据转速误差调整占空比,这也就是PID闭环控制电机转速的常规做法。
5.3 电源电压波动:开环最容易被忽视的问题
电压波动对开环的影响比很多人想的大得多。理想12V供电是不存在的:电池供电时,满电13V、放电末端10V,压降3V直接让平均电压偏差25%。同一个50%占空比,满电时电机转速明显更高。
这个问题的解决办法有几个层次:
- 系统层面,选择带稳压的电源方案,或者用DCDC把母线电压稳住。
- 控制层面,把电压归一化——用ADC实时采样母线电压Vbus,算占空比时用目标电压除以实际电压得到一个修正后的占空比。
- 最彻底的办法还是闭环:转速闭环本身就能主动对抗电源扰动,因为任何扰动最终都会反映到转速上,PID会去修正。
5.4 开环控制里的故障保护:不闭环也要有底线
“开环”指的是转速没有反馈调节,但不代表可以不设防。PWM电机驱动里最基础的三重保护是:过流保护、过温保护、欠压保护。
过流保护可以在主回路串采样电阻,将电流信号放大后输入MCU ADC,一旦超过阈值立刻切断PWM输出。也可以用驱动芯片自带的电流检测引脚,比如DRV8870的IPROPI引脚,原理相同。
欠压保护的目的是防止电池电压过低时电机强行启动导致电压塌陷。在许多锂电池供电的设备中,欠压保护阈值通常设在单节3.0V左右,多节串联则按比例计算。
过温保护要看功率管和电机温度。MOS管的结温上限一般是125℃~150℃,电机的绕组温升也很关键,用手摸电机外壳超过70℃就应该考虑降额或者停机。
需要特别注意的是,PWM故障保护动作要直接作用于PWM输出,而不是靠软件循环。把刹车逻辑放在定时器崩溃或MCU卡死的场景下就很难用了。所以更稳妥的方案是用硬件比较器检测过流信号,信号直接硬连到PWM输出禁止引脚或驱动芯片的EN/FAULT引脚,这样才能实现微秒级关断。
6. 从开环到闭环:速度PID的最简扩展思路
6.1 开环是天梯的第一级,不是终点
标题是“P0:PWM电机开环控制”,P0意味着这是第一优先级任务,但不代表做完就完了。在实际项目中,开环跑通之后几乎一定会往闭环走,区别只是走多远。
最简单的闭环是速度闭环:电机上装一个编码器,MCU通过定时器的编码器模式读取转速,PID控制器根据目标转速和实际转速的偏差计算PWM占空比。增量式PID的输出量就是PWM占空比的修正量,和开环代码的衔接非常自然:
int speed_ctrl(int target_rpm, int current_rpm) { static int err_prev = 0, err_integral = 0; int err = target_rpm - current_rpm; err_integral += err; // 积分限幅,防止“积分饱和” if (err_integral > INTEGRAL_MAX) err_integral = INTEGRAL_MAX; if (err_integral < -INTEGRAL_MAX) err_integral = -INTEGRAL_MAX; int output = KP * err + KI * err_integral + KD * (err - err_prev); err_prev = err; return output; // 加到当前PWM占空比上 }增量式PID和位置式PID最大的区别在于输出的是“增量”而不是“绝对占空比”,这样做的好处是即使PID参数没调好,输出也不会瞬间变化很大,对机械系统的冲击更小。
6.2 占空比限幅:开环阶段就该养成的好习惯
不管开环闭环,PWM占空比输出都应该做限幅。限幅的直接目的有两个:一是防止电机启动瞬间电流过大烧驱动,二是防止PID积分饱和后输出超过100%。
很多电机堵转电流是额定电流的5~10倍,如果允许PID在错误状态下把占空比推到100%,驱动芯片很可能直接过温。所以建议在代码里维护一个全局的PWM输出上下限变量,比如最大占空比限制在90%,并且响应过流信号后自动降回安全值。这个习惯到了做电流环、位置环的时候会更加重要。
6.3 位置环、电流环:后面的路怎么走
速度闭环跑通之后,再往上走就是位置环:云台、机械臂关节里常用的位置闭环,最外层位置PID,中间层速度PID,最内层电流PID,形成串级结构。每一层都需要更快的控制周期和更好的系统建模,但是底层的PWM输出、H桥驱动逻辑、死区配置、保护机制,全都是从P0开环阶段打好的底子。
回想我做过的几个项目,凡是前期把开环平台的硬件保护和软件框架做扎实的,后面上闭环基本就是水到渠成;凡是急着跳步、开环都没调稳就去写PID的,最后都会回头补硬件保护的课。PWM电机开环控制这个看似基础的P0任务,其实是在给整个电机控制系统“打地基”——地基的深度,决定了上层建筑的高度。