STM32驱动直流有刷电机:H桥电路设计、PWM调速与闭环控制
2026/9/17 2:43:14 网站建设 项目流程

简介:面向嵌入式初学者的STM32直流有刷电机驱动完整工程资源,基于HAL库与定时器PWM输出,覆盖单路电机的正反转、调速及安全保护逻辑,适合正在学习电机控制或需要快速搭建驱动框架的开发者。压缩包共236个文件,含121个.h头文件、107个.c源文件,另附uvprojx工程文件、hex/bin固件与SCVD调试描述文件;源文件包含STM32F4 HAL驱动库和应用层代码,固件可直接烧录验证,整体仅1.67MB。已有321人学习下载。工程对定时器通道、GPIO配置、PWM占空比调节和电机状态检测等关键环节均有对应实现,并保留工程配置与调试辅助文件,便于对照原理图理解硬件连接。通过查阅该工程,可快速掌握STM32输出PWM控制直流有刷电机的方法,并能借鉴过流、过热保护思路,为后续闭环调速或双极性驱动开发打下基础。

1. 为什么直接驱动直流有刷电机还需要单独做一块驱动板

很多刚接触 STM32 的工程师会先点亮 LED,然后顺手把电机接到 GPIO 上,发现电机不转,或者一转就把单片机复位了。这不是单片机的问题,而是直流有刷电机的驱动逻辑和单片机管脚电平之间隔着电流放大和续流保护两层硬需求。GPIO 只能提供几毫安的电流,而一个普通 370 电机堵转时电流轻易超过 1A,12V 供电下瞬间反电动势还能把管脚打坏。所谓「STM32 驱动单路直流有刷电机」,真正要解决的不是写几句翻转电平的代码,而是设计一套可靠的功率接口:方向控制、PWM 调速、过流保护、续流回路缺一不可。

这篇文章以单路直流有刷电机为载体,从驱动电路选型讲起,给出 STM32 侧的完整配置代码(基于 HAL 库),再落到带载调试和常见坑的排查方法,最后补一个闭环测速的思路。适合正在做小车、云台、水泵、风扇这类项目的开发者,也适合想把电机驱动从「能转」做到「转得稳」的人。下面所有电路和参数都按最常见的有刷电机驱动方案展开,不涉及具体开发板型号,代码可以直接移植到 F1 和 F4 系列。

2. 驱动电路选型:H 桥、半桥和集成驱动芯片的取舍

2.1 单路有刷电机为什么需要 H 桥而不是单个 MOS 管

直流有刷电机需要双向转动时,必须能够反转电枢两端的电压极性。单个 MOS 管只能实现通断,无法换向,所以单路可正反转的有刷电机驱动最常用的是 H 桥结构。H 桥由四个开关管组成,电机接在桥臂中间,通过控制 Q1/Q4 导通实现正转,Q2/Q3 导通实现反转。如果电机只需要单向转动,可以用单管加续流二极管的低边驱动,但考虑到实际项目里正反转几乎是刚需,直接做成 H 桥更省事。

H 桥的四个开关管可以用分立 MOS 管搭建,也可以直接用集成驱动芯片。分立方案的优点是成本低、电流上限可以做得很大,缺点是死区时间和逻辑互锁需要自己处理,一旦上下桥臂同时导通就是短路。集成方案把逻辑、死区、电流采样都封装好了,适合快速出原型,但选型时要留足电流裕量并关注散热。

2.2 三款常见方案的参数对比与选型逻辑

方案典型型号最大持续电流逻辑电压适合场景缺点
分立 N-MOS H 桥IRF540N + 半桥驱动 IR210420A+10-20V 栅压大电流、低成本量产电路复杂,需自举电容和死区
集成 H 桥DRV8870 / TB66121.5-3.5A2.7-5.5V小型电机、电池供电耐压和电流有限,容易过热
智能电机驱动BTN7970 / BTN898230-50A3-5.5V 逻辑大功率、带电流反馈价格高,需要散热片

对大多数 STM32 学习者来说,DRV8870 和 TB6612 是性价比最高的入门选择,前者单芯片内置电流调节功能,后者适合 3V 逻辑电平单片机。DRV8870 的输入逻辑可以直接兼容 3.3V,不需要电平转换,而 TB6612 虽然标称逻辑电压低至 2.7V,但 PWM 输入频率高时建议用 5V 供电保证逻辑电平稳定。

选型时最容易被忽略的是电机的堵转电流。标称额定电流 1A 的电机,堵转电流可能是额定值的 5 到 8 倍,如果芯片选型和散热没有余量,启动瞬间就可能触发芯片内部过流保护,表现为电机一启动就停。稳妥的做法是驱动芯片的持续电流余量至少是电机额定电流的 3 倍以上。

2.3 续流二极管和电容布局的落地细节

H 桥驱动感性负载时,MOS 管关断瞬间电机会产生反向电动势,如果不提供续流通路,尖峰电压会击穿开关管。集成驱动芯片内部已经集成续流二极管,但 PCB 布局时仍然要在电机端子附近并联一个 100nF 陶瓷电容吸收高频噪声,再并一个 100uF 电解电容平抑电流波动。

分立 MOS 方案里,续流二极管的选型要看反向恢复时间和峰值电流承受能力,常见的做法是用快恢复二极管或者直接用 MOS 管的体二极管。低边 MOS 管栅极串联电阻建议取 10 到 22 欧姆,用来限制栅极充放电电流,避免开关瞬间振铃。驱动板的电源输入端必须加一个大容量电解电容,位置尽量靠近 H 桥,否则电源线较长时会因为线路电感导致电压跌落,甚至引起单片机复位。

3. STM32 侧 GPIO 与 PWM 生成:CubeMX 配置和 HAL 库代码解析

3.1 用 CubeMX 分配 PWM 通道和方向控制引脚

打开 STM32CubeMX,先确定用哪个定时器输出 PWM。常见的做法是用高级定时器 TIM1 或通用定时器 TIM3 的某个通道,两者对单路电机来说区别不大,但 TIM1 是高级定时器,输出极性配置和刹车功能更丰富,适合后续需要做硬件保护的应用。

假设用 TIM3 的 CH1 作为 PWM 输出,方向控制用两个普通 GPIO。具体引脚分配建议这样规划:

功能引脚示例CubeMX 配置
PWM 输出PA6 (TIM3_CH1)复用推挽输出
方向 APB0GPIO Output
方向 BPB1GPIO Output

这里方向 A 和方向 B 接 H 桥的两个输入引脚,当 A=1、B=0 时正转,A=0、B=1 时反转,A=B 时电机制动。这种控制逻辑和 DRV8870 的 IN1/IN2 完全一致,如果用的是 TB6612,还要额外分配一个 STBY 引脚使能芯片,不使用时拉低可以省电。

HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_1)启动 PWM 输出后,电机并不会转,因为初始占空比为 0,同时两个方向引脚默认都是低电平,驱动芯片处于制动状态。这样的默认状态是安全的,不会在上电瞬间让电机猛转。

3.2 最小可运行的驱动代码框架

// 电机方向枚举 typedef enum { MOTOR_STOP = 0, MOTOR_FORWARD, MOTOR_BACKWARD } MotorDir; // 方向引脚控制 void Motor_SetDir(MotorDir dir) { HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_RESET); HAL_GPIO_WritePin(GPIOB, GPIO_PIN_1, GPIO_PIN_RESET); if (dir == MOTOR_FORWARD) { HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_SET); } else if (dir == MOTOR_BACKWARD) { HAL_GPIO_WritePin(GPIOB, GPIO_PIN_1, GPIO_PIN_SET); } } // 设置占空比,speed 范围 0-100 void Motor_SetSpeed(uint8_t speed) { uint16_t ccr = (uint16_t)(speed * 1000 / 100); __HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_1, ccr); } // 完整启动流程 void Motor_Start(void) { Motor_SetDir(MOTOR_FORWARD); Motor_SetSpeed(50); HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_1); }

这套代码里 PWM 的自动重载值 ARR 设置为 1000,所以__HAL_TIM_SET_COMPARE写入的 ccr 值除以 10 就是百分比,0 对应占空比 0%,1000 对应 100%。方向控制函数里先拉低两个引脚再置位某一个引脚,这样可以避免代码执行顺序出错导致两个方向同时使能。

实际项目里一般不会在第一版就同时输出全占空比,应该先启动 PWM 设置一个较小的占空比(比如 20%),确认转向正确后再慢慢加大。如果电机反转了,只需要交换方向引脚的逻辑关系,不需要改动电路。

3.3 PWM 频率选择的两个极端

直流有刷电机的 PWM 驱动频率不是随便选的。频率太低会听到明显的电机啸叫,而且电流纹波大;频率太高则开关损耗增大,驱动芯片发热严重。常见的经验范围是 10kHz 到 20kHz,高于人耳听觉上限同时又不会让 MOS 管开关损耗过大。

对带减速箱的直流电机,还要注意齿轮箱的共振频率。如果某个转速下出现异常噪声,可以试着微调 PWM 频率避开共振点。还有一种情况是电机带了霍尔编码器,此时 PWM 频率太高会通过空间电磁耦合干扰编码器信号,这时要在编码器供电端加滤波电容,并考虑把 PWM 频率降到 10kHz 以下。

htim3.Init.Period = 1000 - 1;对应 PWM 频率可以由定时器时钟 / ((Prescaler+1) * (Period+1))计算,例如定时器时钟 72MHz,预分频 71,重载值 999,则 PWM 频率为 72MHz / (72 * 1000) = 1kHz,这个频率偏低,铜损大。工程上推荐预分频 1-1 即 0 分频,重载值 999,72MHz / 1000 = 72kHz,频率偏高但开关损耗可接受;折中做法是预分频 71、重载值 99,得到 10kHz。

4. 带载调试和参数整定:从空载到带载的三大必调参数

4.1 启动电流冲击不是靠减小占空比就能解决的

电机从静止到转动需要克服静摩擦力,启动瞬间电流通常是额定电流的 3 到 5 倍。如果程序一上来就给一个固定的中等占空比,有可能电流太大导致驱动芯片保护或电源电压跌落。常见做法是软启动,也就是占空比按时间线性爬升。

uint16_t target_speed = 80; for (uint16_t i = 0; i <= target_speed; i++) { Motor_SetSpeed(i); HAL_Delay(10); }

这段代码让占空比从 0 逐步爬到 80%,每步间隔 10ms,整个过程 800ms,启动电流被摊平。步进间隔要按电机惯量调整,惯量大的电机需要更长的爬升时间,否则依然会有明显冲击。比如带了大减速比齿轮箱或者负载是水泵叶轮时,间隔可以放宽到 30ms 以上。

软启动在某些场景下不适用。比如门锁电机需要在 100ms 内快速推开锁舌,此时软启动会导致力矩不足。这类应用应该反过来做:先短时间施加一个大占空比克服静摩擦,然后回落到维持电压。

4.2 死区补偿和占空比与电压的非线性关系

低速时电机容易抖动或者不转,一个原因是死区时间的存在使得低速占空比区域出现非线性。H 桥上下桥臂切换时需要插入死区,防止瞬时直通,但死区时间在低占空比时占比很大,导致实际输出电压低于理论值。

以 DRV8870 为例,死区时间典型值是几百纳秒,PWM 周期 100us(10kHz)时占比约 1%,看起来不大,但在 5% 占空比时死区占了有效时间的 20% 左右。补偿方法是对小占空比做非线性映射,把占空比 0-10% 区间做一段指数或者查表映射。

uint16_t Motor_DeadBandCompensation(uint8_t speed) { // speed 0-100, 输出 0-1000 if (speed < 10) { return (uint16_t)(speed * speed * 10); } return (uint16_t)(speed * 10); }

这个映射在 5% 输入时输出 250,也就是 25% 占空比,补偿了死区造成的电压损失。实际需要的补偿曲线跟电机内阻、供电电压、PWM 频率都有关系,可以在示波器上测电枢两端电压波形来标定。

4.3 供电电压跌落和电流纹波的排查清单

带载调试时最容易遇到的状况是电机一转单片机就复位。先用万用表测电机启动瞬间的电源电压,如果跌落到单片机最低工作电压以下,说明供电不足。正确的检查顺序是:第一检查电源输出能力,第二检查电源到驱动板的走线长度和线径,第三检查驱动板电源输入端电容容量。

现象可能原因排查方法
电机启动即复位电源跌落示波器抓电机启动瞬间的 3.3V 波形
电流大但力矩小PWM 频率/占空比异常用示波器测电机两端有效值电压
电机发热严重死区不足或频率太高检查 H 桥波形是否有直通
低速抖动死区未补偿/占空比过低提高 PWM 分辨率,做非线性映射

供电问题的一个隐蔽来源是面包板。接触电阻在毫欧级别,但大电流通过时会产生压降,而且弹簧片的老化会引入间歇性接触不良。做电机驱动验证时建议直接焊洞洞板或者用转接板,不要在面包板上跑超过 1A 的电流。

5. 闭环进阶:用编码器测速并把 PID 输出映射到 PWM 占空比

5.1 单通道和双通道编码器的接线差异

很多减速电机自带霍尔编码器,输出是两路相位差 90 度的方波信号。如果只是测速,接一路信号到定时器的输入捕获通道就能采集频率;如果还要判断方向,就必须接两路并用编码器模式。

STM32 的定时器编码器模式可以同时采样两路信号并自动完成倍频计数。在 CubeMX 中把 TIM2 的 CH1 和 CH2 都配置为编码器模式,定时器计数方向就是电机方向,计数速率就是转速。四倍频模式读到的数值是实际线数的 4 倍,用这个数去做速度反馈时不需要关心具体是几倍频,只要和 PID 参数配套标定即可。

uint16_t encoder_val = TIM2->CNT; // 计算速度:每 10ms 读取一次差值,除以时间得到增量计数 static uint16_t last_cnt = 0; uint16_t current_cnt = TIM2->CNT; int16_t delta = (int16_t)(current_cnt - last_cnt); last_cnt = current_cnt; // delta 除以 10ms 就是增量计数/秒

编码器计数溢出是容易踩的坑。TIM2 是 16 位定时器,四倍频模式下每分钟几千转的电机在 10ms 内的增量计数不会溢出,但长时间累计不处理,计数回绕时直接相减可能有符号问题。代码里用了强制转换成 int16_t 的方式,利用有符号数溢出回绕来自动处理反向差值,比判断溢出标志更简洁,但前提是两次采样间隔内的最大增量不能超过 32767。

5.2 PID 输出限幅和电机死区的配合

PID 控制器输出是一个 0 到 100 的占空比数值,但直接把这个数值丢给Motor_SetSpeed会有一个问题:占空比很小的时候电机不转,PID 的积分项会一直累积,导致电机突然猛转。解决办法是在 PID 输出前做一个最小占空比门限判断。

int pid_output; int final_speed; if (pid_output > 0 && pid_output < 5) { final_speed = 0; // 低于死区不输出 } else { final_speed = pid_output; } if (final_speed > 95) final_speed = 95; Motor_SetSpeed((uint8_t)final_speed);

这个 5% 的门限不是随意取的,要根据 4.2 节的死区补偿曲线来定。如果有补偿函数,PID 输出可以直接进入补偿函数再映射到占空比。另一个细节是 PID 输出限幅不要设到 100%,留出 5% 的余量给电机停止时切入制动状态,避免 PWM 全开时方向切换产生过大的电流冲击。

5.3 无编码器时的电流采样做堵转保护

有些项目没有编码器,但需要保护电机不被堵转烧毁,常见做法是通过驱动芯片的电流检测引脚配合 ADC 实现。DRV8870 的 IPROPI 引脚输出电流比例信号,串联一个电阻接地后用 STM32 的 ADC 采样,堵转时电压升高,超过阈值就切断 PWM。

uint32_t adc_val = HAL_ADC_GetValue(&hadc1); if (adc_val > THRESHOLD_BLOCK) { Motor_SetSpeed(0); // 记录故障标志,等待外部复位或延时后重新尝试 motor_fault = 1; }

电流阈值要留出启动瞬间的余量,不能在电机正常启动时误触发。常见的标定方法是在空载高速运行时记下 ADC 读数的 1.5 倍作为堵转阈值。如果读数和电流的换算关系不清楚,用示波器勾 IPROPI 引脚电压,在堵转瞬间记录峰值,然后留 20% 余量。

5.4 多路 PWM 共用定时器时要注意的同频限制

单路电机驱动可能以后会扩展到双路,如果两个电机共用一个定时器,只能选择不同的通道但没有独立的频率。此时如果两个电机的额定参数差异较大,没办法为每路单独设置最优 PWM 频率。常见的妥协方案是用两个定时器分频,或者所有电机统一用同一个折中频率,比如常见的 15kHz,兼顾听觉噪声和开关损耗。

另一个容易踩的坑是关闭 PWM 输出时,有的 HAL 库版本释放定时器通道后,引脚电平状态不确定,可能导致电机在停止瞬间产生一次意外的抖动。稳妥的做法是先把占空比写 0,延时几个毫秒,再调用HAL_TIM_PWM_Stop。对要求严格的应用,停止后把 PWM 引脚重新配置为 GPIO 输出并拉低,彻底避免浮空电平。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询