
带P0这个编号说明这已经不是你第一次写电机控制的项目代码了。我接到这个项目时的第一反应是电机开环控制这种题目资料满地都是连学生毕设都能跑起来。但真正把它当作一个项目的第零优先级去认真做会发现很多细节是你最开始根本不会去想的。这篇就记录我完整走一遍PWM电机开环控制的全过程从控制拓扑的确定、占空比和频率的选择、驱动芯片方案选型到STM32定时器配置、实测中踩过的坑再到为后续闭环预留的接口设计。这些东西单独拿出来看都不复杂但串起来就是你从“这颗电机能转吗”到“这颗电机已经可以稳定作为执行机构用了”的完整链路。1. 开环PWM控制项目第一阶段为什么只解决“让它转起来”1.1 先定边界在没有任何反馈的前提下你能控制什么开环控制在术语上听起来很高大上实际含义就是“不看结果只发命令”。你对电机发PWM波形电机的电压变了理论上转速跟着变但实际转速是多少、负载变了会掉多少你一概不让系统感知。有朋友经常跟我争既然转速会漂、转矩会变干嘛还要做开环这里要理清楚P0阶段的真正目标验证电气通路、驱动逻辑和基础PWM调节能力。我的习惯是任何电机项目的第一步都是先让电机以固定占空比转起来这时候追求的不是转得准而是“受控”。P0阶段能回答清楚三个问题就够了电机有没有按预期方向旋转占空比的变化有没有带来转速变化持续运行的温升、噪声、电流是否在安全内。这三个问题都不需要编码器、不需要霍尔传感器、不需要PID一块带PWM模块的单片机加一块驱动芯片就能完成。等你把这些确认完毕再开始往上叠加闭环算法定位问题的时候才不会被一堆变量干扰。举个实际例子我接过一个用无刷电机做云台的活对方一开始就上了PID闭环结果电机低速抖得厉害排查了三个晚上最后发现是PWM频率配置太低导致驱动波形畸变——这个根因放在开环阶段十分钟就能抓到。1.2 开环和闭环的分界P0阶段不碰PID不是偷懒开环与闭环的本质区别在于系统是否把输出采样回来与目标比较生成误差信号。很多人把闭环神话了好像只有PID才能体现技术水平。但一个工程素养在线的人会告诉你闭环算法依赖的执行器品质是在开环阶段打下的。如果开环PWM驱动本身就有死区不均匀、频率不合理、电源纹波大等问题PID参数调到天荒地老也补不回来。在P0阶段你要做的是把PWM输出当作一个“线性度良好的可控电源”来验证。线性度好意味着占空比从10%加到90%电机平均电压跟着线性上升频率稳定意味着没有可听见的高频啸叫、没有明显的转矩脉动。这些都属于电机驱动硬件水准的范畴闭环算法是在此之上的修饰层。2. 占空比、频率和死区开环控制绕不开的三个数值2.1 占空比计算先理解PWM提供的不是“电压”而是“平均电压”PWM控制直流电机的核心逻辑不是把电压降低了送进去而是把全电压的电源以极高的速度开通、关断让电机在宏观上“感觉到”一个平均值。这里的数学很简单U_eff D × U_supply其中U_eff是等效电压D是占空比U_supply是电源电压。我做过一个12V供电的直流电机项目目标等效电压6V那占空比就是6/1250%。如果目标等效电压9V占空比就是75%。实际配置中要留有余量不要直接算到100%。电机启动和带载瞬间电流会突然变大如果电源本身有压降100%占空比下实际等效电压反而比90%占空比时更低——这个现象我遇到过听起来反直觉电机全速反而比九成速转得慢。原因是供电端被拉到保护状态电压跌落严重。所以P0阶段我一般把最大占空比限制在95%以内留一点系统余量。占空比的设定频率也关系到平均电压的纹波大小。PWM频率越高单位周期内斩波次数越多等效电压纹波越小电流更连续。但频率不是越高越好下面专门展开。2.2 频率为什么默认奔着20kHz去听觉禁区、开关损耗与电流连续的平衡PWM频率是开环控制里最容易被忽略、但又最能直观影响体验的参数。我见过不少初学者把PWM频率设成1kHz结果电机一上电就发出刺耳的“滋滋”声——那是PWM斩波的固有噪声落在人耳最敏感的频段。人耳听觉范围大约是20Hz到20kHz1kHz到5kHz之间尤其敏感所以最省事的做法就是把PWM频率推到20kHz以上让开关噪声直接超出人耳上限。但这并不代表20kHz是万能答案。频率升高带来的代价是开关损耗上升。以MOSFET驱动为例每次开关过程都存在开通损耗和关断损耗频率翻倍这部分损耗基本翻倍。驱动模块发热、供电电源的纹波抑制需求都会跟着变苛刻。对不同电机的经验取值普通有刷直流电机10kHz到20kHz功率小的可以适当拉高大功率直流电机4kHz到10kHz优先保障开关损耗必要时加消噪措施舵机航模标准50Hz是信号标准频率脉宽1ms到2ms对应角度这个不能乱套PWM高频逻辑无刷电机BLDC方波驱动15kHz到20kHz常见兼顾听觉与开关损耗。曾经我在一个项目里把有刷电机的PWM频率从10kHz改成22kHz转速没变但电机温度下去了三四度因为高频下电流连续性变好、铁损反而降低。所以频率要结合电机本身的电感特性去权衡电感越大的电机允许较低的频率也能维持连续电流电感小的电机就需要更高频率降低电流脉动。2.3 死区时间H桥上下管换流时那几十纳秒的保命窗口死区是PWM电机控制绕不开的概念。驱动H桥时同一桥臂的上下两个开关管不能同时导通否则电源直接被短路轻则发热重则爆管子。理论上你可以在代码里保证上半桥和下半桥的控制信号严格互补但实际器件存在导通延迟和关断延迟的差异尤其在不同温度下延时还会漂移。死区时间就是刻意在互补信号之间插入的一小段“两个管子都断开”的时间等一个管子完全关断后再让另一个管子导通。一般MOS管的死区设定在几百纳秒到1微秒之间IGBT则要2到3微秒。公式估算可以这么写t_deadtime t_off_max - t_on_min 安全裕量其中t_off_max是器件关断延迟的最大值t_on_min是导通延迟的最小值。设大了会增加波形畸变和损耗设小了又有直通风险。你在STM32高级定时器里配置死区时大概率会直接写一个固定值但至少要知道这个值是怎么推出来的。中心对齐模式下死区的影响还会被放大因为每个周期有两次换流点死区产生的误差角比边沿对齐更明显。3. 驱动级方案H桥原理与驱动芯片选型对比3.1 H桥的四个工作状态正转、反转、刹车和滑行直流电机要双向旋转最经典的做法就是四个开关管组成H桥。电机接在桥的两个中点之间四个开关管控制电流路径。P0阶段你不需要自己拿四个MOS管手搓驱动板除非零成本要求但必须理解这四种状态因为选芯片时看的核心能力就是这个左上与右下导通电流正向流过电机电机正转右上与左下导通电流反向流过电机电机反转同侧两管同时导通例如左上左下电机两端短路实现刹车动能瞬间变成电流和热四管全关断电机靠惯性自由滑行反电动势通过续流二极管形成回路。开环控制最容易忽略的就是刹车状态的应用。很多人做直流电机控制只发正反转不利用刹车结果系统停机靠惯性溜出很长的距离。在你后续做闭环定位时刹车会是你最频繁使用的动作之一P0阶段就应该把刹车逻辑跑通至少确认驱动芯片在短接状态下能够承受瞬间的大电流。3.2 常见驱动芯片的硬件底子L298N、TB6612、AT8236怎么挑市面上直流电机驱动方案多得让人眼花缭乱我列一个自己的对比看法型号最高电压峰值电流逻辑电压内阻/压降频率上限特点L298N46V2A5V压降大(约2V)~40kHz经典但效率低发热严重L293D36V1.2A5V内置续流~5kHz适合小电流频率上不去TB661215V3.2A3.3V/5V内阻低100kHz体积小适合3.3V逻辑AT823615V4.5A3.3V/5V内阻低100kHz中功率H桥带使能我的选型原则非常简单先定电压电流再看逻辑电平最后才看重不看重品牌。L298N是目前市面上极容易出现“能驱动但发热巨大”的芯片它内部是三极管结构导通压降接近2V这意味着12V供电实际到电机两端可能只有10V高占空比下芯片温度直线上升。作为学习可以玩工程上我基本不建议。TB6612和AT8236是功率器件里性价比比较高的选择内部是MOSFET结构导通阻抗低适合10到15V的中小型直流电机。如果你的项目里控制器是3.3V的MCU比如STM32F4系列驱动芯片的逻辑电平兼容性就非常重要。TB6612的逻辑输入可以接3.3V而老一代L293D明确要求5V逻辑接口直接接3.3V单片机可能出现逻辑电平不可靠的问题还得自己加电平转换。对于2A以上的中大功率电机我更建议直接选集成驱动模块或者MOSFET半桥驱动方案。如果项目对成本敏感退而求其次用两个AO3400A做半桥也可以支撑起中小功率电机的单向PWM控制但你就得自己处理续流和死区问题了——这在P0阶段会增加不少工作量。3.3 能不能用分立三极管搭驱动可以但要接受下限热词里反复出现“三极管控制电机”说明很多人想用S8050、AO3400A搭一个最简驱动。这个思路在超小功率电机额定电流几百毫安以内是可行的比如玩具电机、3D打印机的冷却风扇。用一颗NPN三极管做低速端开关电机接在电源和集电极之间PWM信号加到基极电机另一端接地控制逻辑就通了。但说实话这套方案做单向控制还能凑合做双向控制至少需要4个管子加隔离还要认真设计上下管的驱动电路因为NPN和PNP的对称性、门极驱动能力都会成为瓶颈。我的建议是P0阶段只要条件允许优先选集成桥式驱动芯片。你自己搭H桥容易踩的坑驱动芯片厂商早就替你趟平了。自己做电调用于学习和理解没问题但作为项目基础时间应该花在控制逻辑上而不是反复烧管子。4. STM32定时器PWM输出实操从硬件通道到代码配置4.1 定时器选型为什么P0阶段也要用高级定时器STM32的定时器资源非常丰富基础定时器不带PWM输出通用定时器如TIM2、TIM3、TIM4、TIM5支持PWM高级定时器TIM1和TIM8在通用定时器的基础上多了互补输出、死区插入和刹车功能。做电机开环控制如果电机驱动芯片需要的是单端PWM信号通用定时器完全够用但如果你用了需要互补PWM和死区的半桥驱动方案就必须上高级定时器。另一个容易被忽略的点是定时器通道与引脚的映射关系。STM32的定时器输出通道不一定在你习惯用的GPIO上配置之前先翻数据手册的外设连接表。没做这一步就上板子最常见的问题就是“明明HAL_TIM_PWM_Start成功了示波器却量不到波形”。原因大概率是查了默认引脚是PA8结果焊的板子把PB13引出来配置成了TIM1_CH1N对应的通道功能对不上号。4.2 边沿对齐与中心对齐不只差在波形形状上STM32的PWM计数模式有边沿对齐和中心对齐两种中心对齐模式下的PWM波形更对称在电机控制中能有效降低谐波成分但代价是计数周期翻倍。中心对齐下定时器的计数值要从0加到ARR再减回0一个完整PWM周期实际是2×ARR个计数时钟。这会让你按“想输出20kHz”代入常规公式时出现计算误差通用公式边沿对齐f ck_psc / (ARR1)中心对齐f ck_psc / (2×(ARR1))比如72MHz时钟经过72分频得到1MHz计数频率你要得到20kHz PWM边沿对齐时ARR49中心对齐时ARR实际要设为24。这个问题在热词里被反复搜索说明大家都被坑过。中心对齐模式下还有一个和ADC采样相关的小技巧把ADC触发点设置在定时器计数到达峰值或谷值的时刻采样到的电流是最小纹波点这在后面做电流采样闭环时特别关键。P0阶段你可以先不管但定时器基本配置时留出这种扩展余地。4.3 HAL库配置代码一个可以直接落地的样例很多朋友习惯用STM32CubeMX的图形界面生成初始化代码这个没问题但最好读懂生成的关键参数含义。下面给出一个用TIM1输出中心对齐PWM的配置片段我习惯把时钟预分频和自动重装值分开写方便之后动态调整频率void PWM_Motor_Init(void) { TIM_TimeBaseInitTypeDef sFdConfig; // 以72MHz主频为例 RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1, ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOE, ENABLE); // 根据你的引脚映射调整 GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Pin GPIO_Pin_9; // PE9对应TIM1_CH1 GPIO_InitStructure.GPIO_Mode GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOE, GPIO_InitStructure); sFdConfig.TIM_Prescaler 71; // 72MHz / 72 1MHz sFdConfig.TIM_CounterMode TIM_CounterMode_CenterAligned1; sFdConfig.TIM_Period 24; // 1MHz / (2*(241)) 20kHz sFdConfig.TIM_ClockDivision TIM_CKD_DIV1; sFdConfig.TIM_RepetitionCounter 0; TIM_TimeBaseInit(TIM1, sFdConfig); TIM_OCInitTypeDef ocInit; ocInit.TIM_OCMode TIM_OCMode_PWM1; ocInit.TIM_OutputState TIM_OutputState_Enable; ocInit.TIM_OCPolarity TIM_OCPolarity_High; ocInit.TIM_Pulse 6; // 25% 占空比 TIM_OC1Init(TIM1, ocInit); TIM_OC1PreloadConfig(TIM1, TIM_OCPreload_Enable); // 高级定时器必须使能主输出 TIM_CtrlPWMOutputs(TIM1, ENABLE); TIM_ARRPreloadConfig(TIM1, ENABLE); TIM_Cmd(TIM1, ENABLE); }注意我注释里专门写了高级定时器的主输出使能TIM1和TIM8在初始化后并不会立刻输出波形必须调用TIM_CtrlPWMOutputs(TIM1, ENABLE)让主输出使能很多人在这一步漏掉了导致代码检查无数遍就是量不到输出。这是个典型的高级定时器新手坑。4.4 DMA配合PWM动态调速不卡死CPU开环控制里如果只让电机以恒定占空比转代码很简单。但真正到了交互场景比如接收到指令要从10%占空比平滑升到50%你不可能在主循环里频繁调用修改寄存器函数因为系统其他任务会被阻塞。这时候用DMA搬运占空比数据是最干净的方案。思路是把PWM的比较寄存器地址作为DMA外设目标周期性从内存数组里搬运新的比较值。每个PWM周期或每几个周期更新一次就能实现占空比的平滑变化而不占用CPU。热词里出现“pwm dma”和“pwm dma hal”说明大家都意识到这在电机工程里是个刚需。HAL库下配置DMA只要注意一点DMA搬运的数据宽度要和定时器比较寄存器一致一般用HalfWord否则高字节会覆盖其他寄存器位导致输出乱跳。5. 开环实测中的坑启动冲击、PWM故障保护与电源纹波5.1 启动瞬间的“地板油”软启动该怎么设计直流电机堵转和启动瞬间的电流可以达到额定电流的5到8倍。开环控制下你直接给一个50%占空比这个瞬间会产生非常大的电流冲击电源被拉低驱动芯片可能进入过流保护或过热保护。处理办法是软启动让占空比从0逐步爬升到目标值。软启动的爬坡时间和电流上升斜率要匹配你的机械负载特性。负载惯性大启动时间拉长负载轻可以快速爬升。我经验上的默认参数是每10ms增加1%到2%的占空比然后根据实测启动时间微调。实现方式最方便的仍然是DMA自动修改比较值或者用一个定时中断按步进修改占空比。注意事项软启动并不是越多越好太慢的斜坡会让电机长时间处于低速大电流状态同样会过热。你要去感觉或者测量电机是“平稳启动”还是“干磨硬推”这个手感只有亲自上电才知道。5.2 PWM故障保护不只是过热断电那么简单开环PWM控制阶段最容易犯的错误是“只想着怎么让电机转不考虑让电机停”。从P0阶段起就应该把故障保护通路放在设计里。常见的故障保护包含过流保护驱动芯片的IPROG引脚或电流采样电阻反馈信号越过阈值立即关闭PWM过温保护芯片结温超过阈值强制拉高使能/STBY引脚关断输出欠压保护供电电压跌落及时停PWM并置故障标志位。以TB6612为例STBY引脚是待机控制拉低时全部输出关闭这是一个天然的故障停车信号。你可以把MCU的一个GPIO接到STBY上发生过流或过温时直接拉低STBY同时把PWM输出清空。不要只想着在代码里把PWM占空比改成0因为PWM引脚如果因为程序跑飞卡在某个状态线上电平可能仍然是高电平电机照样通着电。5.3 续流与电源纹波电机产生的反电动势会反噬系统电机本质上是感性负载PWM关断瞬间会产生反向感应电动势电流必须有个回路。如果你的驱动芯片没有内置续流二极管或者你外部的续流电路设计不当反电动势会直接冲击电源产生电压尖峰。示波器上你会看到VCC的纹波从几十毫伏瞬间跳到几百毫伏严重的时候能把单片机复位掉。这类问题排查起来特别费时间。最有效的手段是从源头堵驱动模块选自带续流感应的集成芯片至少确认内部有体二极管在电机两端并联一个反向二极管或RC吸收电路给反电动势提供低阻抗泄放路径电源输入端放置大容量电解电容比如470μF以上和0.1μF陶瓷电容就近吸收高频脉冲。有些自动控制项目的电源和主控共用一路电机一启动主控就重启这种故障我处理过很多次根子都在电源和续流上而不在代码。这个坑不要等到代码写完再排查P0阶段先用示波器看清电源波形。5.4 频率过高过低都有信号怎么用耳朵和示波器预判开环实测时会遇到一些有趣而被忽视的现象。占空比保持不变的前提下把PWM频率调到8kHz以下你能听到电机里有明显的啸叫调到100kHz以上电机噪声几乎听不见但驱动芯片温度上升明显因为开关损耗上去了。如果电机在高频PWM下反而发出间歇性的“扣扣”声检查一下占空比是否处在极小或极大的区间。占空比太小时宽度1%以下的极窄脉冲可能窄到驱动芯片无法响应输出直接丢失占空比太大时关闭时间过短续流电路可能来不及泄放能量。这些极值区间的非线性在开环调试阶段就该摸清楚。最基本的办法就是写个上位机或者按键程序让占空比从0扫描到100%一边看示波器一边记录电机的异常表现——这份表格在后续标定闭环参数时非常好用。6. 然后是闭环P0阶段为后续扩展预留的接口与数据6.1 反馈元件带来的新信息编码器和电流采样开环PWM做到位之后你手头会积累一份“占空比-实测转速/电流”对照表。你会发现一个现象同一占空比下电机空载和带载的转速差别很大。这就是开环最大的缺陷。要做闭环你需要新增两类反馈速度或位置反馈通常是编码器、霍尔传感器安装在电机轴或减速器输出端电流反馈采样电阻串在电机回路里或者用驱动芯片提供的电流镜像引脚。P0阶段做的PWM通道、驱动极性方向、使能逻辑在闭环阶段全部复用。唯一要注意的是反馈信号的地线要与功率地严格单点连接否则PWM开关噪声会串进编码器信号里导致速度读数跳变。布线时把信号地和功率地分开走在电源接入点会合是这个阶段就要养成的习惯。6.2 从开环到PID只用编码器做速度闭环的最简起步很多教程一上来就让你调三个参数Kp、Ki、Kd。实际上从开环切到闭环第一步只需要做P控制甚至可以说第一步是“把开环输出当作前馈往上面叠一个P修正项”。比如你在50%占空比下测到空载转速是3000rpm但目标转速是2800rpm那先用占空比减一点看看转速下落到多少这个“占空比-转速”的增量关系就是你Kp的经验标定起点。我的做法是先记录开环曲线再让Kp只做小幅修正初始值设为“占空比变化量/转速误差量”的倒数左右然后逐步加大。比例系数调稳之后再加积分消除静差最后才考虑微分项。顺序反了你会被P和I之间的震荡折磨到怀疑人生。开环阶段积累的数据在这里就是最宝贵的启动参照这也是为什么我坚持P0不能只是“随便转转”的原因。6.3 开环阶段的代码结构把调速、换向和保护拆开写最后给一个非常实用的建议开环PWM控制的代码虽然简单但模块划分会直接影响后续闭环的改动量。我建议至少拆成三个模块PWM输出模块只负责把“目标占空比”写到定时器比较寄存器不关心占空比怎么来控制决策模块负责软启动、换向、刹车逻辑内部定义最高层级的“动作指令”故障保护模块独立于控制决策模块拥有最高优先级只要触发条件必然拉低使能。这样划分之后将来你写速度闭环时只需要在“控制决策模块”前面加一个误差计算器把PID输出结果转成目标占空比传给PWM模块。故障保护模块和PWM输出模块不需要动整个软件架构的迁移成本会大大降低。实际跑过几轮电机项目后你会有一个体会开环控制看起来是最简单廉价的起点但它决定了整个电机控制项目的地基水平。PWM的占空比算得准不准、频率选择是否匹配电机特性、驱动电路死区和续流是否可靠这些基础项做扎实了后面叠加什么控制算法都顺理成章。反过来地基没打好就急着上闭环最后排查出来的问题大概率都还是开环阶段就该解决的事情。如果你正在做自己的电机项目把这篇文章里的几个参数记在笔记本上去示波器上一个个验证过去你的P0才算是真正磨出来了。