
不需要额外说明以下是正文。1. 项目概述与整体思路拆解1.1 这个P0到底在做什么PWM电机开环控制基本是所有做嵌入式、做机器人、做驱动控制的开发者绕不开的第一个里程碑。标题里带着P0意思很明确这是整个电机控制项目的第一优先级任务先让电机按照预期转起来不谈精度、不谈闭环、不谈FOC核心就一句话用STM32输出一路可调占空比的PWM信号经过驱动芯片放大后让直流电机以期望的转速转动。很多人觉得这个太基础了不就一个PWM嘛定时器配一下占空比改一下电机就转了。但实际做下来你会发现从“PWM波形能出来”到“电机稳定可靠地转”中间隔着不少坑频率选多少、占空比跟电压什么关系、为什么电机转起来有尖锐的啸叫、为什么一上电单片机就复位、为什么驱动芯片烫得能煎鸡蛋。这些坑不踩一遍后面做PID闭环、做FOC、做机械臂关节控制的时候都会被回头清算。这个P0项目我选用的硬件是STM32F103ZET6定时器用TIM3驱动模块用TB6612电机是普通的N20减速直流电机和370空心杯电机各一只。选STM32F103ZET6的原因很简单资料多、定时器资源丰富、有重映射功能而且CubeMX配置起来效率高。TB6612相比老掉牙的L298N内阻小、频率响应好、逻辑电平兼容3.3V特别适合跟STM32直连不用做电平转换。这些选型决策在后面的章节里我会逐个展开。1.2 开环控制的本质不测量就发令开环控制的英文是open-loop control翻译过来就是“开着环跑”。所谓环指的是反馈回路。开环的意思就是输出端对输入端没有影响控制器发出指令后不管电机实际跑到什么状态不采样、不修正、不补偿。用大白话解释就像你让一个外卖小哥骑车去3公里外送餐你只告诉他“出发”然后不再给他打电话确认路线。他可能准时到也可能路上爆胎、迷路你都无从知晓。PWM开环控制电机就是这样占空比给50%电机理论上应该达到额定电压50%对应的空载转速但实际转速会受到负载扭矩、电压跌落、电机温度、轴承摩擦等多方面因素影响。既然这样为什么还要先做开环因为开环是闭环的基础。你连PWM的占空比和电机转速的对应关系都没摸清楚直接上PID那调参的时候连“给多少占空比能到多快”的基准都没有鬼知道P、I、D三个参数该从哪个量级开始试。而且机械臂、云台、小车这些项目里很多场景并不需要精密的速度控制开环加上合适的加速曲线已经能覆盖相当多的需求了。P0的意义就是把底层的驱动链路打通、把坐标系建立起来。2. PWM与电机驱动的核心原理2.1 PWM的本质用开关换平均电压PWM的全称是脉冲宽度调制Pulse Width Modulation。它的本质不是“调电压”而是“调导通时间”。对直流电机来说在一个周期T内电源电压Vcc不是持续施加在电机两端的而是以固定频率反复接通和断开。接通的时间是Ton断开的时间是Toff那么占空比D Ton / T。电机在这个PWM信号下承受的平均电压是\[ V_{avg} D \times V_{cc} \]这句话好理解但背后的物理机制值得展开一下。为什么不是直接给电机降电压而是用开关的方式因为有三大好处第一效率高。开关管工作在饱和导通和完全截止两个状态饱和导通时管压降极小截止时漏电流极小两种状态下管子本身的功耗都很低。而线性调压是把多余的电压“烧”在调整管上能量全变成热量。对电池供电的设备来说线性调压的续航能力就是灾难。第二控制精度容易做。数字控制器输出高低电平是最拿手的占空比的精度取决于定时器的计数精度。16位定时器满量程0到65535理论上精度能到1/65535。想让电机精确跑在某一转速区间数字调占空比远比模拟调电压稳定。第三响应速度快。电机的机械时间常数通常在几十毫秒量级而PWM的周期通常在微秒到几百微秒量级。电机对PWM波形的响应相当于一个低通滤波器高频开关信号经过绕组电感后变成平滑的电流所以电机实际感受到的是平均电压。2.2 频率怎么选20kHz不是随便定的PWM频率的选择是第一个容易翻车的细节。频率太低了电机会发出明显的“嘶嘶”声这个声音频率落在人耳最敏感的2kHz到5kHz区域听起来就像指甲刮黑板。更关键的是频率低意味着电流纹波大电机的有效转矩波动也大低速时会出现一顿一顿的爬行现象。频率选高了也有问题。开关损耗和频率成正比频率越高MOS管或驱动芯片的开关损耗越大芯片就越烫。而且很多驱动芯片对PWM频率有上限要求TB6612的datasheet上建议PWM频率在100kHz以内超过之后波形畸变明显驱动能力反而下降。实际工程里常用的电机PWM频率大概是这些区间直流有刷电机10kHz到20kHz航模无刷电调通常用16kHz到32kHz舵机是50Hz周期20ms但是这是周波性质的PWM属于伺服控制范畴不是调速范畴。我习惯先用20kHz起步理由是超过人耳听觉上限没有噪声同时还没有逼近TB6612的上限。具体的计算过程是这样的STM32F103的TIM3挂载在APB1总线上APB1时钟默认36MHz但定时器时钟是72MHzAPB1分频系数为2时定时器时钟频率是APB1的两倍。那么要得到20kHz的PWM频率自动重装载值ARR和预分频系数PSC的关系是\[ f_{PWM} \frac{T_{clk}}{(PSC 1) \times (ARR 1)} \]如果PSC设为0那么72MHz直接除以20kHz等于3600ARR设为3599即可。此时计数精度是1/3600最低可以从约0.03%的占空比起调够用了。2.3 占空比的“100%”陷阱占空比调节范围也是新手很容易踩坑的地方。很多人把CCR直接赋成ARR1以为输出100%占空比但实测会发现波形异常——输出变成了常高电平没错但这个“常高”在STM32的PWM模式下可能并不是你想要的占空比0%逻辑反过来。坑点在于STM32的PWM模式1是向上计数时CNT小于CCR输出有效电平模式2是CNT大于CCR输出有效电平。如果在极端情况下CCR等于ARR1输出确实恒为有效电平看起来像100%占空比但CCR等于0的时候输出恒为无效电平0%也没问题。问题出在中间状态的一点计数器从0数到ARR是3600个状态CCR能取0到3600共3601个值因此CCR3600时占空比是3600/3600100%CCR3599是99.97%。这里有个物理概念要区分清楚——占空比达到100%时已经没有PWM开关动作了驱动芯片的输入引脚一直处于高电平此时芯片内部的H桥上下桥臂切换逻辑也停止工作电机直接接入电源。这种情况通常没问题但如果你用的是自带使能脚的驱动芯片要确认使能引脚和PWM引脚的逻辑配合是否正确。还需要提一句有些应用场景需要刻意限制最大占空比不超过95%比如电动车控制器、某些BUCK电路是为了给低边MOS的电荷泵自举电容留出充电时间。电机驱动上虽然没有这么严格但在做电源相关的PWM控制时这个习惯要提前种下。3. 硬件选型与电路设计3.1 驱动芯片TB6612为什么比L298N舒服硬件选型上驱动芯片一定不能省。我见过很多新手拿L298N来驱动小电机也不是不能用但体验是真的差。L298N用的是双极性三极管饱和压降高达2V以上5V供电的电机实际到电机两端的电压可能只有2.8V左右转速根本起不来芯片加上散热片还是烫得厉害。关键是L298N的PWM频率上限只有10kHz左右再高波形就变形了。TB6612FNG是东芝出的MOSFET H桥驱动芯片内阻典型值0.5Ω逻辑供电2.7V到5.5V电机驱动供电最高13.5V连续输出电流标称1.2A峰值3.2A。它最方便的地方是逻辑引脚兼容2.5V到5V电平STM32的3.3V GPIO可以直接驱动两边不需要电平转换。三路控制输入分别是AIN1、AIN2、PWMA正转一个引脚高一个引脚低反转反过来PWM引脚控制速度。还有一个STBY待机引脚拉低后整个芯片进入休眠常用于整机低功耗控制。再往下走就是DRV8833、A4950这些芯片驱动能力相近引脚排列略有差异。如果控制的电机功率再大一点比如几十瓦的直流减速电机就要上分立MOS方案或者BTS7960这种半桥驱动模块。做P0阶段TB6612是性价比和容错率都最舒服的选择。3.2 电源系统共地问题比你想的严重电机驱动最容易被忽略的问题不是芯片本身而是电源和逻辑电路之间的参考点。电机启动瞬间的电流冲击很大N20电机堵转电流能到1A以上370电机空载电流约200mA堵转能到2到3A。如果电机电源和MCU电源不共地或者共地回路阻抗太大电机电流波动会在地线上产生压降导致MCU参考地电位跳动直接表现为一启动电机单片机就复位LCD屏幕闪雪花串口打印乱码。我刚开始做的时候用的是实验室直流稳压电源一路输出5V给MCU一路输出5V给电机驱动结果两者负极没有连在一起电路怎么调都不工作。把两个电源的负极用粗杜邦线短接之后一切正常。后来画PCB的时候我把电机电源和逻辑电源之间的地做成了单点连接靠近驱动芯片底部覆铜汇合再用0欧电阻或磁珠隔开模拟地和功率地实测EMI也好很多。还有一点电机会反电动势。减速的时候或者外力带动电机旋转时电机会变成发电机在电源端产生高于供电电压的尖峰。不加续流二极管或者驱动芯片内部没有体二极管的话这个尖峰轻则损坏驱动芯片重则顺着电源线干扰MCU。TB6612内部已经集成了续流二极管外部不需要额外加。但如果你用的是大功率电机和分立驱动电路续流二极管一定要加而且要用快恢复二极管或者肖特基二极管紧贴MOS管摆放。3.3 面包板开发与PCB直连的区别P0阶段用面包板快速验证完全没问题但要注意几个细节。面包板的寄生电容和接触电阻都比较大PWM频率在20kHz时还勉强能接受往上到50kHz波形就会明显变钝。电机的两根线尽量用短粗的导线不要用细长的杜邦线绕来绕去电感跟导线长度成正比线越长反电动势尖峰越猛。如果是做PCB布局上的优先级是驱动芯片靠近电机连接器电源去耦电容100nF陶瓷电容10uF电解电容紧贴驱动芯片电源引脚MCU和驱动芯片之间的信号线尽量短且远离电机线。电机线不要跟PWM信号线并行走线否则PWM信号被耦合干扰容易出现电机转速抖动的现象。这些经验看着小但在工程调试中能省出大把时间。4. STM32生成PWM的实现过程4.1 CubeMX配置从引脚重映射到定时器参数我用的开发环境是STM32CubeMX生成初始化代码再用Keil MDK写业务逻辑。F103ZET6的TIM3默认通道1映射在PA6但我的板子上PA6被其他外设占了所以要用重映射功能。STM32F103的部分定时器引脚支持重映射通过设置AFIOAlternate Function IO重映射寄存器可以把TIM3_CH1从PA6挪到PB4、PB5这些引脚上。CubeMX里的操作路径Pinout Configuration → Timers → TIM3 → Mode选择Internal ClockChannel1选择PWM Generation CH1然后在Pinout视图中找到TIM3_CH1右键选择Remap功能CubeMX新版会自动在芯片引脚图上显示可选的重映射引脚直接选中PB4即可参数配置里Prescaler设为0Counter Period设为3599Pulse设为0Polarity选High最后在GPIO设置里确认PB4的复用功能为AF2_TIM3。这里有个容易被忽略的点重映射后必须检查GPIO的GPIO_Speed设置。PWM输出的翻转频率是20kHzGPIO翻转速率当然绰绰有余但GPIO_Speed设置太低会导致上升沿变缓输出波形边沿变圆。我的经验是设置成GPIO_SPEED_FREQ_HIGH即50MHz档位不要用LOW档。4.2 启动与动态调占空比的正确写法初始化完成后启动PWM输出和修改占空比的代码并不复杂。核心逻辑如下// 启动TIM3通道1的PWM输出 HAL_TIM_PWM_Start(htim3, TIM_CHANNEL_1); // 修改占空比假设CCR的范围是0到3599 __HAL_TIM_SET_COMPARE(htim3, TIM_CHANNEL_1, 1800); // 约50%占空比占空比的计算关系是占空比百分比 CCR / (ARR 1) × 100%。所以CCR1800时占空比约50%。实际使用中有一个细节值得记一下如果你在一个PRD中断里频繁调用HAL_TIM_PWM_Start或者__HAL_TIM_SET_COMPARE建议用宏关闭中断保护或者确保中断优先级安排合理否则进入中断频繁操作定时器寄存器可能导致PWM波形出现偶发抖动。再补充一个调试技巧用Keil的Simulator仿真查看PWM波形时往往只能看逻辑电平变化看不出真正的电压平均值和占空比细节。更好的办法是用逻辑分析仪哪怕是几十块钱那种8通道的把CH1接到PB4低电平触发采一段就能清楚看到周期、脉宽、占空比。淘宝上几十块的逻辑分析仪配合PulseView软件最大采样率24MHz分析20kHz的PWM绰绰有余。4.3 引脚重映射的寄存器级说明CubeMX虽然把重映射的活都干了但明白底层在做什么的好处是当你换平台或者遇到CubeMX生成的代码不生效时自己能排查。TIM3_CH1的默认映射是PA6重映射到PB4需要两步第一步开启AFIO时钟第二步设置AFIO_MAPR寄存器的TIM3_REMAP位。具体寄存器操作如下__HAL_RCC_AFIO_CLK_ENABLE(); __HAL_RCC_GPIOB_CLK_ENABLE(); __HAL_RCC_TIM3_CLK_ENABLE(); GPIO_InitTypeDef GPIO_InitStruct {0}; GPIO_InitStruct.Pin GPIO_PIN_4; GPIO_InitStruct.Mode GPIO_MODE_AF_PP; GPIO_InitStruct.Speed GPIO_SPEED_FREQ_HIGH; HAL_GPIO_Init(GPIOB, GPIO_InitStruct); __HAL_AFIO_REMAP_TIM3_ENABLE();这里__HAL_AFIO_REMAP_TIM3_ENABLE()对应的是将AFIO_MAPR的TIM3_REMAP[1:0]位置为10即部分重映射把CH1挪到PB4CH2挪到PB5。全部重映射则是TIM3_REMAP[1:0]11CH1到PC6CH2到PC7CH3到PC8CH4到PC9。明白这点之后再看CubeMX的图形界面你会对引脚复用关系一目了然。5. 开环控制的核心细节换向逻辑与时序5.1 正反转和刹车的控制逻辑TB6612每个通道的输入逻辑如下表AIN1和AIN2是方向输入PWMA是速度PWM输入AIN1AIN2PWMA电机状态00X刹车/停止短接01PWM反转10PWM正转11X刹车/停止短接注意AIN1和AIN2同时为1也是刹车状态这是TB6612内部H桥上下桥臂同时短接的逻辑不是烧毁状态但一般不建议长期使用。正常停止时可以把PWMA设为0或者把STBY拉低。一个常见问题正转反转切换时要不要先停再切答案是最好先停。因为如果你在电机高速正转时突然把方向引脚翻转电机绕组中的电流方向瞬间反转会产生极大的反向冲击电流轻则导致电源电压跌落让MCU复位重则烧驱动芯片。安全的切换流程是先把占空比降到0延时几毫秒实测N20电机50ms足够大电机要更久再切换方向引脚然后重新抬升占空比。5.2 为什么需要加速曲线开环启动的堵转真相开环控制最典型的翻车现场就是一上电直接给100%占空比电机要么不动要么猛冲一下。很多人在这一步怀疑是硬件问题但其实原理上就能解释。电机启动瞬间绕组中还没有建立起反电动势此时电流只受绕组电阻限制启动电流是额定电流的好几倍。如果占空比给得很高瞬时电流可能超过驱动芯片的峰值电流能力触发过流保护或者直接烧芯片。更重要的是如果负载是减速齿轮箱齿轮之间存在齿隙瞬时的冲击扭矩会加剧齿轮磨损甚至在负载惯量较大的情况下直接“撞”坏减速箱。解决的方法是做一个斜坡启动。用定时器中断或者延时循环把占空比从0逐步增加到目标值每次增加的量可以是固定的也可以遵循S型曲线。小电机上最简单的做法是每10ms增加CCR加50也就是大概0.7%的占空比步进总共720ms到达目标速度。我实测下来N20电机用这种斜坡方式启动电流冲击比直接满占空比启动低了大约40%而且很安静。代码示例void motor_ramp_up(uint16_t target_ccr, uint16_t step, uint16_t interval_ms) { uint16_t ccr 0; while (ccr target_ccr) { ccr step; if (ccr target_ccr) { ccr target_ccr; } __HAL_TIM_SET_COMPARE(htim3, TIM_CHANNEL_1, ccr); HAL_Delay(interval_ms); } }5.3 低速时的抖动问题开环的死穴开环PWM控制在低速状态下表现很差这是物理规律决定不是代码写得不好。低速时电机的反电动势很小绕组的电感效应在低占空比下导致电流断续电机的输出转矩就是脉动的转速波动非常明显严重时甚至能看到电机一顿一顿地转。缓解的办法有几个一是提高PWM频率让电流续流更充分。20kHz基础上可以测试一下如果你的驱动芯片支持提升到50kHz脉动会小一些但开关损耗随之增大。二是降低占空比步长的粒度。ARR设大一点比如ARR7199这样同样的占空比下CCR的数值范围更大可以更精细地控制低速区间。代价是PWM频率降为10kHz噪声会明显一点。三是从控制策略上改变最直接的是切到闭环PID引入编码器测速反馈。这就是P0之后下一步的工作了。6. 常见问题排查与实测经验6.1 电机不转时的排查顺序我把实际调试中遇到的问题整理成了一个排查清单每次遇到电机不转都按这个顺序逐项检查能省很多时间现象可能原因排查方法电机完全不动GPIO复用功能没配置对用逻辑分析仪检查PB4有无波形电机完全不动驱动芯片STBY脚被拉低确认STBY引脚接高电平电机完全不动电源未共地万用表量驱动芯片GND和MCU GND是否导通电机有微弱振动但不转占空比太小初始CCR设到10%以上试试电机转但转速抖动电源功率不足换稳压电源大电流档位测试电机吱吱叫PWM频率落在可听声区把PWM频率调高到20kHz以上电机反转方向引脚逻辑接反交换AIN1和AIN2接线6.2 电源电压跌落导致的复位问题我遇到最典型的一个案例用USB供电跑P0程序空载时一切正常手一捏电机轴电机转速下降、电流增大然后开发板直接重启。用示波器测5V电源轨发现瞬间跌到了3.2V。原因是USB口供电能力只有500mA左右电机瞬时电流超过供电能力电压被拉垮MCU的供电电压低于复位阈值就重启了。解决方法是电机电源和逻辑电源分开电机用外部稳压电源或电池供电MCU用USB或者单独的3.3V LDO供电两个电源共地但功率路径不重叠。如果是电池供电还需要注意一点电池内阻会随电量变化满电和快没电的时候同样占空比下电机转速差不少。开环控制下这是“正常现象”但如果你做的是对转速一致性要求高的项目就需要在软件里做电压补偿或者直接上闭环。6.3 电机本身好坏怎么判断有一类故障排查到最后发现不是驱动问题是电机自己坏了。热词里提到了用万用表测台扇电机绕组的场景红兰线之间没有阻值黑兰线之间有阻值怀疑电机坏了。家用异步电机的绕组结构跟直流电机不完全一样但排查思路是相通的用万用表电阻档测绕组正常情况下绕组电阻应该在几欧到几十欧之间如果四个端子之间有两组完全不导通说明绕组有开路如果阻值为零说明绕组短路。直流电机的绕组阻值很小N20电机额定电压6V额定电流约50mA算下来绕组电阻约120欧实际上因为电刷接触电阻和绕组铜阻加起来用万用表测大概几十到一百多欧都正常。关键是量的时候要转动电机轴再量一下看阻值有没有明显波动。如果转动时阻值变化剧烈或者从某个角度开始彻底不通了大概率是碳刷磨损或者换向器脏了。6.4 从开环到闭环的延伸思考P0阶段把开环PWM控制跑通之后最顺理成章的下一步是加编码器做PID速度闭环。开环控制的所有局限——负载变化导致转速波动、低速抖动、电源电压变化影响速度——闭环都能解决因为闭环的核心理念就是“测量实际值跟目标值比较用差值去修正输出”。我个人的建议是不要着急在P0阶段就把PID调得很完美而是先做一个简单的数据采集把电机在不同占空比下的空载转速读出来画一条占空比-转速曲线。这条曲线是你后面所有调试的基准有了它PID的初始参数估算就不再是瞎猜了。闭环改造的基本思路TIM3_CH1继续输出PWMTIM2或TIM4配置成编码器模式采集AB相脉冲再用一个定时器做1ms或10ms的周期中断在中断里算速度、跑PID、更新CCR。STM32F103ZET6的资源完全够用。6.5 一个容易被忽略的安全设置最后分享一个我踩过坑之后形成的习惯在MCU代码里加一个PWM输出使能的全局开关。初始化时PWM输出默认关闭收到特定的运行指令后才开启。这样做的原因是在调试中如果MCU跑飞了PWM输出可能停在任意状态如果是100%占空比电机不受控地全速运转非常危险。加一个安全开关配合看门狗MCU跑飞后看门狗复位PWM重新回到初始化的关闭状态整机安全性提升一个量级。// 在main函数中的初始化 HAL_TIM_PWM_Stop(htim3, TIM_CHANNEL_1); __HAL_TIM_SET_COMPARE(htim3, TIM_CHANNEL_1, 0); // 需要开始运动时 motor_safety_enable(); HAL_TIM_PWM_Start(htim3, TIM_CHANNEL_1);这个习惯在做机械臂、无人机、小车这些带动力装置的项目时尤其重要。硬件上再做一层保护在电源输入端串一个自恢复保险丝或者做过流采样电路即使软件全崩硬件也能把电机驱动力限制在安全范围内。开环控制虽然简单但做工程安全和可靠永远是最先要立的规矩。