免费获取学习方案
ARTICLE DETAIL

资讯详情

深耕编程基础知识与建站技术分享的一线实战洞察。

STM32驱动直流有刷电机:从H桥原理到PWM闭环控制

STM32驱动直流有刷电机:从H桥原理到PWM闭环控制 简介面向嵌入式开发者与电机控制初学者的STM32单路直流有刷电机驱动工程基于STM32F4系列HAL库系统讲解PWM脉宽调制调速、正反转控制以及过流、过热、短路等保护策略并结合直流有刷电机基础、硬件接口和软件实现流程从原理到代码引导读者搭建稳定电机控制系统。压缩包共236个文件以121个h头文件和107个c源文件为主体便于阅读和移植另含Keil工程文件、hex与bin固件可编译烧录验证整体仅1.67MB轻量易用。目前已有321人浏览学习。借助该工程可快速获得定时器PWM配置示例、HAL库驱动调用流程、正反转控制及保护逻辑实现还涉及PWM波形观测与编码器闭环调试思路目录结构清晰可直接在STM32F407等开发板上运行适用于课程设计、毕业设计或电机控制项目二次开发能明显缩短动手实践的学习曲线。1. 用 STM32 驱动直流有刷电机先想清楚功率这一层很多做 stm32 项目的工程师第一次接触直流电机都是直接把电机两根线怼到单片机 GPIO 上然后发现电机纹丝不动拿万用表一量引脚电压又正常。原因不复杂直流有刷电机的启动电流是额定电流的好几倍而 STM32 的 GPIO 只能输出毫安级电流真正的电机驱动需要功率放大电路、换向电路和调速电路三样东西叠加。下面这套方案围绕标题里的单路直流有刷电机驱动需求按 H 桥选型、STM32 引脚分配、HAL 库 PWM 调速、示波器排错到编码器闭环的顺序展开适合正在做智能小车、鱼缸水泵或毕业设计的工程师也适合想搞明白「电机驱动」这一层到底在解决什么的人。全程以 STM32F103C8T6 为例换用其它型号时只有引脚和定时器号需要调整。2. STM32 驱动直流有刷电机从 H 桥原理到驱动芯片选型2.1 为什么 GPIO 直连转不动直流电机是功率负载直流有刷电机的等效模型是绕组电阻串联电感再串联一个反电动势。电机静止时反电动势为零电源电压几乎全加在绕组电阻上所以启动瞬间电流最大。一颗额定 12V、内阻 3 欧的电机堵转电流就是 4A 左右。STM32 的 GPIO 推挽输出能力大约 20mA连电机空载电流的零头都不到强行驱动的结果只会是芯片发热、复位甚至烧引脚。所以电机驱动的第一层是把控制信号放大成功率信号第二层是支持正反转和调速。正反转靠 H 桥电路完成四个开关管组成两个桥臂对角管 Q1 和 Q4 同时导通时电机正转Q2 和 Q3 同时导通时反转同一桥臂的上下两个管子不能同时导通否则电源直通短路。第三层是 PWM 调速通过改变占空比让电机平均电压变化从而改变转速。PWM 频率常见取 10kHz 到 20kHz既能避开人耳可闻的电机啸叫又不会让驱动芯片的开关损耗过高。这三层就是「电机驱动」原理图里最常见的结构。实际工程很少用分立元件搭 H 桥因为四个管子的驱动时序、死区补偿和续流二极管处理起来很麻烦。常见做法是直接用集成 H 桥驱动芯片内部控制逻辑已经做了直通保护外部只需要接 PWM、方向和电源三组信号。2.2 单路驱动芯片的选型对比TB6612、L298N 与 DRV8833芯片连续电流逻辑电压特点适合场景TB6612FNG1.2A2.7V~5.5V压降小、体积极小、支持高频 PWM智能小车、电池供电设备L298N2A峰值更高4.5V~7V压降大、发热大、封装大教学实验、大功率电机DRV88331.5A/通道2.7V~10.8V内置电流限制、过温保护玩具电机、3D 打印挤出机标题要求的是单路直流有刷电机我一般优先选 TB6612FNG。它的逻辑电压由 STM32 的 3.3V 提供不需要额外电平转换VM 可以直接接 12V 以内的电机电源外围电路很干净。L298N 虽然模块便宜但两个桥臂的饱和压降接近 2V12V 供电时电机实际只能拿到 10V小功率场景发热还明显。DRV8833 的电流限制功能对实验室电源很友好但单路使用会浪费一半通道。如果只是临时验证电机能不能转L298N 模块最省事要把驱动固化到自己画的 PCB 上TB6612 的 SMD 封装和外围电路更合适。模块版和裸芯片版的接法略有不同但核心逻辑一致VM 接电机电源VCC 接 STM32 的 3.3VGND 与 STM32 共地IN1、IN2 接两个 GPIOPWM 接定时器通道。下面以 STM32F103C8T6 为例给出一组实际可用的引脚分配。2.3 引脚分配、接线与最小验证代码STM32F103C8T6 上我常用 PB0 作为 PWM 输出PA1 和 PA2 作为方向控制接线如下表。注意 PA1、PA2 要选支持推挽输出的普通 GPIOPB0 要确认映射到定时器通道避免使用已被调试器占用的 PA13、PA14。STM32 引脚驱动板引脚说明PB0PWM / PWMA定时器 TIM3 通道 3 输出10kHz PWMPA1IN1方向控制 1PA2IN2方向控制 23.3VVCC / 逻辑电源给驱动芯片逻辑电路供电GNDGND必须和电机电源共地外部电源 6V~12VVM / 电机电源给电机本体供电电机电源和 STM32 的供电建议分开不要用同一条 USB 线同时给开发板和电机供电。电机启动瞬间的大电流会让 USB 电压跌落轻则 STM32 复位重则电脑 USB 口直接断开。我一般会在电机电源输入处并联一个 470uF 电解电容和 0.1uF 陶瓷电容电解电容吸收启动浪涌陶瓷电容滤高频干扰。提示驱动板 GND 必须同时连接 STM32 GND 和电机电源负极三者在同一点共地。只接逻辑地不接电机地时PWM 信号没有参考回路电机会出现抖动或完全不转。接线完成后先不急着写 PWM用下面这段代码验证方向控制是否正常。代码把占空比固定为 50%每 2 秒切换一次方向如果电机能正反转说明 H 桥、共地和电源都正常接下来再调 PWM 调速。// 简单方向测试固定占空比每 2 秒切换正反转 void Direction_Test(void) { HAL_TIM_PWM_Start(htim3, TIM_CHANNEL_3); // 启动 PWM 输出 __HAL_TIM_SET_COMPARE(htim3, TIM_CHANNEL_3, 50); // 占空比 50% while (1) { // 正转IN1 高、IN2 低 HAL_GPIO_WritePin(GPIOA, GPIO_PIN_1, GPIO_PIN_SET); HAL_GPIO_WritePin(GPIOA, GPIO_PIN_2, GPIO_PIN_RESET); HAL_Delay(2000); // 反转IN1 低、IN2 高 HAL_GPIO_WritePin(GPIOA, GPIO_PIN_1, GPIO_PIN_RESET); HAL_GPIO_WritePin(GPIOA, GPIO_PIN_2, GPIO_PIN_SET); HAL_Delay(2000); } }这段代码的逻辑很直接先启动定时器 PWM 输出把比较寄存器设为 50让电机先转起来然后在循环里交替翻转两个方向引脚。设置占空比用了__HAL_TIM_SET_COMPARE而不是普通赋值因为这个宏直接操作寄存器实时性好适合在控制循环里频繁调用。占空比 50 对应 50%如果电机只抖动不转可能是占空比太低或负载太重可以先把 50 改成 80 再试。2.4 开发环境快速就绪芯片包与 ST-Link 驱动如果新装的 Keil 5 打开工程时报找不到器件那就是 STM32 芯片包没装。常见做法是打开 Pack Installer搜索 STM32F1找到 STM32F1xx_DFP 安装包点击 Install。网络不好的环境可以下载离线 DFP 包通过 Pack Installer 的 File/Import 导入版本选 2.x 即可不必追最新。这里关联一个常见报错设备管理器里 ST-Link 显示未知设备说明 stlink 驱动没装好装完驱动重新插拔确认枚举出 ST-Link 调试器再打开 Keil。芯片包和调试器驱动都确认后再检查工程选项里的 Debug 设置选择 ST-Link Debugger并在 SW Device 页面确认能读到目标芯片 ID。读不到 ID 时常见的报错就是 error: no stm32 target found这个问题放到第 4 章专门讲。环境准备花不了多少时间但大部分下载失败都栽在这一层。3. STM32 HAL 库配置 PWM定时器参数、占空比与正反转封装3.1 用 CubeMX 配置定时器和 GPIO 的参数表PWM 的生成放在定时器上不占 CPU。CubeMX 里选择 TIM3时钟源选 Internal Clock通道 3 选 PWM Generation CH3其余参数按下表设置。这个配置在 72MHz 主频下把 PWM 频率固定为 10kHz占空比分辨率 1%。配置项值计算说明Prescaler72-172MHz 分频后计数时钟为 1MHzCounter Period100-1计数到 99 清零周期 100 个计数点Pulse50初始占空比 50%CH PolarityHigh高电平为有效占空比PWM 频率 72MHz / (72 × 100) 10kHz。Prescaler 决定计数时钟频率Counter Period 决定一个 PWM 周期占多少个计数点两者都要减 1因为芯片里的计数器从 0 开始计。Pulse 小于 Counter Period 时它就是普通占空比等于 Counter Period 时输出 100% 占空比逻辑上等同直通。这个配置下 Pulse 范围是 0~99一个脉冲对应 1% 占空比。为什么把 PWM 频率定在 10kHz10kHz 以下电机的绕组电流纹波明显铁芯会发出尖锐的磁噪声20kHz 以上虽然人耳听不见但驱动芯片的开关损耗和 PCB 走线的高频干扰都会上升。TB6612 手册支持最高 100kHz实际工程里 10kHz 到 20kHz 最舒服这个范围也是直流有刷电机驱动里最常见的经验值。如果你的占空比需要更细的梯度可以把 Counter Period 改成 1000Prescaler 改成 7频率降为 9kHz占空比分辨率提升到 0.1%代价是频率略降。GPIO 方面IN1、IN2 配置为推挽输出、无上下拉、最大速度 50MHz。PB0 由定时器复用功能自动配置不需要手动改 GPIO 模式。CubeMX 生成代码后在 main 函数里添加一个Motor_Init()调用即可。3.2 最小驱动代码初始化、调速与方向切换下面是去掉无关代码后的电机驱动实现。注意初始化时先把方向引脚全部拉低再用 PWM 占空比 0 启动避免上电瞬间电机猛转撞到机构限位。// motor.cPWM 方向控制的最小驱动 #define PWM_MAX 99 // 0~99 对应 0%~99% 占空比 void Motor_Init(void) { HAL_TIM_PWM_Start(htim3, TIM_CHANNEL_3); Motor_SetSpeed(0); // 先把占空比清零防止上电转动 Motor_SetDirection(0); // 默认正转 } void Motor_SetSpeed(uint8_t duty) { if (duty PWM_MAX) { duty PWM_MAX; // 限幅防止越界写坏定时器寄存器 } __HAL_TIM_SET_COMPARE(htim3, TIM_CHANNEL_3, duty); } void Motor_SetDirection(uint8_t dir) { if (dir 0) { // 正转 HAL_GPIO_WritePin(GPIOA, GPIO_PIN_1, GPIO_PIN_SET); HAL_GPIO_WritePin(GPIOA, GPIO_PIN_2, GPIO_PIN_RESET); } else { // 反转 HAL_GPIO_WritePin(GPIOA, GPIO_PIN_1, GPIO_PIN_RESET); HAL_GPIO_WritePin(GPIOA, GPIO_PIN_2, GPIO_PIN_SET); } } void Motor_Brake(void) { HAL_GPIO_WritePin(GPIOA, GPIO_PIN_1, GPIO_PIN_SET); HAL_GPIO_WritePin(GPIOA, GPIO_PIN_2, GPIO_PIN_SET); }调用方式是在主循环里按业务逻辑组合需要前进就执行Motor_SetDirection(0)和Motor_SetSpeed(60)需要停止先Motor_SetSpeed(0)再Motor_Brake()。注意Motor_SetSpeed只改比较寄存器不会立即刹停电机电机会因为机械惯性继续转动所以紧急停止要配合刹车引脚逻辑。方向切换时IN1 和 IN2 的变化要和 PWM 占空比变化错开至少一个 PWM 周期否则桥臂对管可能出现极短时间的直通。3.3 换向死区与软启动让电机不抖、不烧驱动直流电机正反转切换不能直接翻转 IN1、IN2。以正转切反转为例正转时停在 IN1 高、IN2 低直接改成 IN1 低、IN2 高中间会经过两个引脚同为高或同为低的瞬间如果此时 PWM 还处于导通状态同一桥臂的上下管就可能同时导通。集成驱动芯片内部有防直通逻辑但外部逻辑顺序不好时仍然存在隐患尤其 L298N 这种老结构。稳妥的做法是切换方向前先把 PWM 占空比置 0延时 1ms 到 2ms再改 IN1、IN2最后恢复需要的占空比。提示无论驱动芯片是否自带直通保护换向前都要先将占空比降到 0等待 1ms再切换方向引脚最后重新加载占空比。软启动是另一个实用细节电机从 0 直接上 99% 占空比时启动电流通常达到额定电流的 5 倍以上减速齿箱会「哐」地一声驱动芯片承受的冲击也大。常见做法是让占空比按斜坡逼近目标值比如每 10ms 加 1100ms 内从 0 升到 10%。如果你的项目对启动时间没有硬性要求可以用一个简单的循环实现// 软启动示例500ms 内从 0 升到目标占空比 void Motor_SoftStart(uint8_t target) { for (uint8_t i 0; i target; i) { __HAL_TIM_SET_COMPARE(htim3, TIM_CHANNEL_3, i); HAL_Delay(10); // 每 10ms 增加 1% 占空比 } }每隔 10ms 把占空比增加 1100 步后稳定在 target。软启动对减速电机尤其重要减速比大的电机惯性大突然上电容易扫齿也会让电源瞬间跌压导致 STM32 复位。实际项目里可以把软启动合并进Motor_SetSpeed内部但要注意HAL_Delay是阻塞函数中断或实时控制循环里不能这么写。4. 电机不转或下载失败PWM 波形、供电共地与 target found 报错4.1 用逻辑分析仪确认 PWM 波形先隔离 STM32 侧问题电机不转时最常见的反应是反复改代码但先分清故障在哪一层更高效。用逻辑分析仪抓 PB0 对地的波形占空比为 0% 时电平恒为低50% 时是标准方波。如果没有逻辑分析仪可以先把占空比固定成 50用万用表量 PB0 的平均电压应该接近 1.65V也就是 3.3V 的一半。这个值偏离太多基本可以确认 PWM 没有输出。更直观的方法是让占空比周期性变化用耳朵和眼睛判断定时器在不在工作。下面这段斜坡测试代码会每 500ms 把占空比提高 10%如果示波器上的波形宽度随之变化说明 STM32 侧没问题// 占空比斜坡测试每 500ms 增加 10%用来确认 PWM 输出 void PWM_Ramp_Test(void) { for (uint8_t duty 0; duty 99; duty 10) { __HAL_TIM_SET_COMPARE(htim3, TIM_CHANNEL_3, duty); HAL_Delay(500); } while (1) { __NOP(); } // 测试结束停在最后占空比 }如果看到 PWM 宽度随 duty 增加而变宽说明 STM32 侧没有问题把排查重心放到驱动板和电机。如果波形根本没有检查 CubeMX 里 TIM3 的通道有没有配置成 PWM Generation、HAL_TIM_PWM_Start是否真的执行到了、以及初始化顺序有没有被其它代码覆盖。这一步能把「代码问题」和「硬件问题」快速切开省去大量盲改代码的时间。4.2 电机侧排查共地、使能脚与电源回路的常见坑PWM 正常但电机不动按概率排下来是这样现象最常见原因处理方式电机完全不转驱动板指示灯亮使能脚没拉高TB6612 的 STBY 脚接 3.3V电机只抖动不转占空比太低或 PWM 频率不对占空比提高到 30% 以上上电后 STM32 复位电机电源与逻辑电源共用一个电源分两路供电加大电解电容转速明显低于预期驱动板压降大或供电不足换 TB6612或提高电源电压使能脚是最容易忽略的。TB6612 的 STBY 是待机控制低电平时整个芯片不工作有的模块出厂已经拉高有的模块留出引脚需要自己接。第二种常见问题是没有共地驱动板 GND、STM32 GND、电机电源负极三者必须同点相连很多串口供电的开发板只有一个 GND 出口容易漏接。第三种是 PWM 频率和占空比不匹配10kHz 下占空比 5% 对大多数电机来说转矩不足带负载时只会嗡嗡响提高到 30% 再观察。电流测量在这种场景下很有用。把万用表串联在电机电源回路里测空载电流正常情况下空载电流远小于额定电流。如果电流超过额定值检查有没有机械卡死或轴承问题。堵转电流达到额定电流 5 倍以上时故障链路通常是驱动芯片先过热接着 STM32 复位最后电源塌陷这条链路经常被误报成「代码写错了」。4.3 下载报错 error: no stm32 target found 的处理步骤下载程序时报这个错本质是调试器没和 STM32 建立 SWD 连接。按顺序排查先看设备管理器里有没有 ST-Link 设备没有就装 stlink 驱动装完还有感叹号重装一次驱动并换 USB 线。设备管理器正常但 Keil 里报 target not found最可能的是 SWD 接线接反或接触不良SWDIO、SWCLK、GND 三根线必须逐一确认导通3V3 也不能少。另一个容易翻车的情况是程序把 SWD 引脚重映射成了普通 GPIO。解决办法是按住开发板复位键不放点击 Keil 的 Download 按钮然后立刻松开复位键。这时程序会短暂进入调试模式利用这个窗口擦除 flash。如果这个方法也不行把 BOOT0 拉高、BOOT1 拉低用串口 ISP 擦除芯片后再恢复默认配置。提示USB 转串口芯片显示感叹号比如 CH340、CP2102、FT232 驱动异常影响的是串口通信和 SWD 下载是两条独立通路。串口报错不影响 ST-Link 下载反之亦然别在下载失败时先去重装串口驱动。这个报错在 stm32 项目里很常见绝大多数来自驱动、接线、BOOT 三个因素之一按顺序排查通常十分钟内解决。如果用了 J-Link报错形态类似把调试器驱动换成 jlink 驱动再在 Keil 里把 Debug 选项切到 J-Link 即可。5. 编码器测速与 PID 闭环STM32 直流电机驱动的进阶改造5.1 编码器测速与最小 PID 实现前面的方案是开环控制占空比固定转速随负载波动。要稳定转速最常见的做法是给直流减速电机加编码器用 STM32 的定时器编码器模式测速再跑 PID 闭环。六线制的电机多出两根编码器输出线通常接在 TIM4 的编码器模式上PB6、PB7 分别是 CH1、CH2。这样 AB 相正交信号由硬件计数读__HAL_TIM_GET_COUNTER就能得到脉冲数再除以单位时间即可换算成 RPM。有了转速反馈后增量式 PID 的代码量其实很小// 转速闭环返回 PID 输出叠加到 PWM 占空比 int Motor_PID(int16_t target_rpm, int16_t current_rpm) { static int16_t last_err, integral; int16_t err target_rpm - current_rpm; integral err; // 积分限幅防止长时间堵转积分饱和 if (integral 200) integral 200; if (integral -200) integral -200; int16_t out kp * err ki * integral kd * (err - last_err); last_err err; return out; }使用的时候每个固定周期比如 10ms读一次编码器计数算出当前转速调用Motor_PID得到输出值再调用Motor_SetSpeed把占空比更新为基础占空比加 PID 输出。kp、ki、kd 的参数要统一量纲占空比范围是 0~99转速单位是 RPM比例系数先取 0.2 到 0.5再根据实际响应慢慢调。5.2 调参顺序与验证方法调参顺序建议从 P 开始只保留 Kp让电机在空载下接近目标转速但存在稳态误差接着加 Ki 消除误差最后加入很小的 Kd 抑制过冲。如果转速在目标值附近来回震荡说明 Kp 过大直接减半再试。编码器计数抖动明显时可以在读计数器前做软件滤波对连续三次读数取平均避免 PID 输出跟着毛刺抖动。验证环节用串口最直接把目标转速和当前转速通过 UART 每秒打印一次观察两者差值。带载后转速如果能在 1 秒内拉回目标值说明闭环已经调好。调参时如果发现 I 项在小负载下震荡严重可以先把 Ki 关掉只保留 P 和 D等稳态误差可接受再一点点加回 Ki这个顺序对大惯量直流减速电机尤其有效。本文还有配套的精品资源点击获取
返回列表