STM32与TB6612实战:PWM精准调速的双直流电机驱动方案
1. 从零开始:为什么选择STM32+TB6612驱动直流电机?
如果你正在捣鼓一个智能小车、机器人手臂,或者任何需要让轮子、关节动起来的项目,那么“如何让电机听话地转起来”就是你遇到的第一个硬核问题。直接拿单片机引脚去接电机?电流太小,根本带不动,还可能烧芯片。这时候,你就需要一个“大力士”——电机驱动芯片。在众多驱动芯片里,TB6612 是我个人非常偏爱的一款,尤其是和 STM32 搭档时,简直是创客和嵌入式新手的黄金组合。
为什么这么说呢?我踩过不少坑,用过经典的L298N。那家伙驱动能力是强,但发热也厉害,体积还大,需要外接散热片,电路板上总得给它留出一大块“豪宅”。后来换到TB6612,感觉世界都清净了。它体积小巧,效率高,发热量小,最关键的是逻辑电压和电机电压可以分开供电,这让用3.3V的STM32去控制12V甚至更高电压的电机变得非常轻松安全。而且,它内置了保护电路,新手不小心接错线或者短路,芯片也不那么容易“牺牲”,容错率更高。
这套方案的核心,就是利用STM32产生PWM(脉冲宽度调制) 信号来控制TB6612,从而实现对直流电机的精准调速。你可以把PWM想象成一种“开关水龙头”的技术:不是一直开着大水,也不是一直关着,而是以极高的频率快速地开、关。开的时间长,关的时间短,平均水流就大(电机转得快);开的时间短,关的时间长,平均水流就小(电机转得慢)。STM32就像一个精准的计时员,负责控制这个“开关”的节奏,而TB6612则是一个强力的执行者,把微弱的控制信号放大成足以驱动电机的大电流。
所以,无论你是想做一个能灵活变速的循迹小车,还是一个需要平稳启停的机械臂,掌握STM32与TB6612的配合,都是你嵌入式开发路上非常实用且关键的一步。接下来,我就带你从硬件连接到软件编程,一步步实现这个“精准调速”的驱动方案。
2. 硬件连接:把你的STM32和TB6612“手拉手”连起来
动手之前,我们先得把“舞台”搭好。硬件连接是基础,连错了,代码写得再漂亮也没用。这里我以最常用的 STM32F103C8T6(也就是常说的Blue Pill核心板) 和TB6612模块为例。别担心,其他STM32型号原理完全一样,只是引脚名字可能不同,你需要根据自己芯片的数据手册做对应调整。
首先,我们得认识一下TB6612模块上的几个关键引脚。别看它引脚多,其实就分三类:电源类、控制信号类和电机输出类。
-
电源类:
- VM:这是电机的“加油站”,接你的电机电源。比如你的直流电机是12V的,这里就接12V。注意电流要足够,建议使用独立的电源或稳压模块供电。
- VCC:这是芯片的“大脑”供电,接逻辑电平,通常是3.3V或5V。因为我们的STM32是3.3V逻辑,所以这里接3.3V最稳妥,可以确保控制信号电平匹配。
- GND:接地,这个不用多说,所有地(STM32的地、电机电源的地、逻辑电源的地)最终都要连在一起。
-
控制信号类(来自STM32):
- AIN1/AIN2:这组引脚控制电机A的转向。通过给它们不同的高低电平组合,来决定电机是正转、反转还是刹车。
- BIN1/BIN2:同理,控制电机B的转向。
- PWMA/PWMB:这是调速的关键!分别接STM32的PWM输出引脚。STM32通过改变输出到这个引脚的PWM波的占空比(高电平时间占整个周期的比例),来无级调节电机的速度。
- STBY:待机引脚。高电平时芯片正常工作,低电平时芯片进入待机模式,所有输出关闭,电机停止。通常我们直接接高电平(VCC)让它一直工作就行,但用STM32的一个GPIO控制会更灵活,可以软件控制启停。
-
电机输出类:
- AO1/AO2:接电机A的两根线。
- BO1/BO2:接电机B的两根线。
下面这张表格是我在实际项目中常用的连接方式,清晰明了,你可以直接照着接:
| STM32F103C8T6 引脚 | TB6612 引脚 | 功能说明 |
|---|---|---|
| PA0 (或任意TIM2_CH1) | PWMA | 电机A的PWM调速信号 |
| PA1 | AIN1 | 电机A方向控制信号1 |
| PA2 | AIN2 | 电机A方向控制信号2 |
| PA3 (或任意TIM2_CH2) | PWMB | 电机B的PWM调速信号 |
| PA4 | BIN1 | 电机B方向控制信号1 |
| PA5 | BIN2 | 电机B方向控制信号2 |
| PA6 | STBY | 待机控制(高电平有效) |
| 3.3V | VCC | 逻辑电源(3.3V) |
| 电机电源正极 (如12V) | VM | 电机动力电源 |
| GND | GND | 公共地 |
注意:电机电源(VM)一定要和STM32的电源分开!特别是当电机功率较大时,电机启动和停止的瞬间会产生很大的电压电流波动,如果共用电源,很容易导致STM32复位甚至损坏。我建议用两节18650电池串联(约7.4V-8.4V)或者一个12V的电源适配器单独给VM供电。STM32和TB6612的VCC则可以从你的开发板的3.3V输出引脚取电。
连接好之后,硬件部分就准备就绪了。记得在通电前,务必再三检查接线,尤其是电源正负极不要接反。接下来,我们就可以进入激动人心的软件编程环节了。
3. 软件编程:用HAL库让PWM“动”起来
硬件是躯体,软件是灵魂。要让电机按照我们的想法转动,我们需要用STM32的定时器产生PWM波,并用GPIO输出方向控制信号。现在STM32开发最主流的方式就是使用ST官方推出的HAL库,它封装了很多底层操作,让我们能更关注逻辑本身。这里我使用STM32CubeIDE来演示,它是ST官方的免费集成开发环境,集成了CubeMX图形化配置工具,非常方便。
3.1 使用STM32CubeMX进行图形化配置
首先,新建一个工程,选择你的芯片型号(STM32F103C8Tx)。然后我们一步步配置:
-
配置时钟:在“Pinout & Configuration”标签页的“System Core”里,点开“RCC”。将“High Speed Clock (HSE)”选择为“Crystal/Ceramic Resonator”。这样我们使用外部8MHz晶振,系统时钟会更稳定。
-
配置PWM引脚(以TIM2为例):
- 在左侧“Timers”里找到“TIM2”。
- 将“Clock Source”选为“Internal Clock”(内部时钟)。
- 然后,将“Channel1”和“Channel2”的模式都设置为“PWM Generation CHx”(PWM生成通道)。这时,芯片图上PA0和PA1(TIM2_CH1和TIM2_CH2的默认引脚)会被自动标记为绿色。
- 接下来是关键参数设置。点击“Parameter Settings”选项卡:
- Prescaler (PSC - 预分频器):这个值决定了定时器的计数时钟频率。系统时钟是72MHz,如果我们想让PWM频率为1kHz,可以这样算:
PSC = 72MHz / (ARR+1) / 期望频率 - 1。我们先设PSC = 71。 - Counter Period (ARR - 自动重装载值):这个值决定了PWM的周期。设
ARR = 999。 - Pulse (CCR - 捕获/比较寄存器值):这个就是初始的占空比,我们先设为0。这样,PWM频率 = 72MHz / (71+1) / (999+1) = 1000Hz = 1kHz。这是一个很常用的电机PWM频率,既能保证调速平滑,又不会让开关损耗太大。
- Prescaler (PSC - 预分频器):这个值决定了定时器的计数时钟频率。系统时钟是72MHz,如果我们想让PWM频率为1kHz,可以这样算:
- 保持其他参数默认即可。
-
配置GPIO方向控制引脚:
- 在芯片图上找到PA1, PA2, PA4, PA5, PA6这几个我们计划使用的引脚。
- 分别点击它们,选择“GPIO_Output”。这样就把它们配置成了普通的推挽输出模式。
- 你还可以在右侧的“GPIO”设置里,给这些引脚起个有意义的“User Label”,比如把PA1标签为“AIN1”,PA2为“AIN2”,以此类推。这样在代码里就能用
AIN1_GPIO_Port和AIN1_Pin来引用了,非常直观。
-
生成代码:点击右上角的“GENERATE CODE”,选择你的IDE和工程路径,CubeMX就会自动生成一个完整的、包含所有初始化代码的工程。
3.2 编写电机控制函数:让代码更优雅
打开生成的工程,进入main.c文件。CubeMX已经把定时器和GPIO的初始化代码都写好了(在MX_TIM2_Init()和MX_GPIO_Init()函数里)。我们只需要在/* USER CODE BEGIN 2 */和/* USER CODE END 2 */之间添加启动PWM输出的代码。
/* USER CODE BEGIN 2 */
// 启动TIM2的通道1和通道2输出PWM
HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_1);
HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_2);
// 将TB6612的STBY引脚置高,使能芯片
HAL_GPIO_WritePin(STBY_GPIO_Port, STBY_Pin, GPIO_PIN_SET);
/* USER CODE END 2 */
但直接在主循环里操作寄存器控制电机会让代码很乱。最好的做法是封装成独立的、易用的函数。我在main.c文件末尾的/* USER CODE BEGIN 4 */区域,或者单独新建一个motor.c文件里,编写了两个电机控制函数:
// 控制电机A:direction=1正转,-1反转,0刹车/停止;speed为PWM比较值(0-ARR,即0-999)
void MotorA_Control(int8_t direction, uint16_t speed)
{
switch(direction)
{
case 1: // 正转
HAL_GPIO_WritePin(AIN1_GPIO_Port, AIN1_Pin, GPIO_PIN_SET);
HAL_GPIO_WritePin(AIN2_GPIO_Port, AIN2_Pin, GPIO_PIN_RESET);
break;
case -1: // 反转
HAL_GPIO_WritePin(AIN1_GPIO_Port, AIN1_Pin, GPIO_PIN_RESET);
HAL_GPIO_WritePin(AIN2_GPIO_Port, AIN2_Pin, GPIO_PIN_SET);
break;
case 0: // 刹车(短路刹车,快速停止)
default:
HAL_GPIO_WritePin(AIN1_GPIO_Port, AIN1_Pin, GPIO_PIN_SET);
HAL_GPIO_WritePin(AIN2_GPIO_Port, AIN2_Pin, GPIO_PIN_SET);
// 另一种停止方式是都置低(高阻态,惯性停止),根据需求选择
// HAL_GPIO_WritePin(AIN1_GPIO_Port, AIN1_Pin, GPIO_PIN_RESET);
// HAL_GPIO_WritePin(AIN2_GPIO_Port, AIN2_Pin, GPIO_PIN_RESET);
break;
}
// 设置PWM占空比,speed越大,转速越高
__HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_1, speed);
}
// 控制电机B,逻辑同电机A
void MotorB_Control(int8_t direction, uint16_t speed)
{
switch(direction)
{
case 1:
HAL_GPIO_WritePin(BIN1_GPIO_Port, BIN1_Pin, GPIO_PIN_SET);
HAL_GPIO_WritePin(BIN2_GPIO_Port, BIN2_Pin, GPIO_PIN_RESET);
break;
case -1:
HAL_GPIO_WritePin(BIN1_GPIO_Port, BIN1_Pin, GPIO_PIN_RESET);
HAL_GPIO_WritePin(BIN2_GPIO_Port, BIN2_Pin, GPIO_PIN_SET);
break;
case 0:
default:
HAL_GPIO_WritePin(BIN1_GPIO_Port, BIN1_Pin, GPIO_PIN_SET);
HAL_GPIO_WritePin(BIN2_GPIO_Port, BIN2_Pin, GPIO_PIN_SET);
break;
}
__HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_2, speed);
}
这样封装之后,控制电机就变得极其简单。你只需要关心两个参数:往哪转(direction)和转多快(speed)。
3.3 在主循环中实现复杂动作
有了好用的函数,我们就可以在main函数的while(1)循环里,像搭积木一样组合出各种电机动作了。下面是一个简单的测试程序,让两个电机交替正反转,并且速度渐变:
int main(void)
{
// ... CubeMX生成的初始化代码(HAL_Init, 时钟配置,外设初始化等)
/* USER CODE BEGIN 2 */
HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_1);
HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_2);
HAL_GPIO_WritePin(STBY_GPIO_Port, STBY_Pin, GPIO_PIN_SET);
/* USER CODE END 2 */
uint16_t speed = 0;
uint8_t dir = 1;
while (1)
{
/* USER CODE BEGIN 3 */
// 示例1:电机A正转加速,电机B反转加速
for(speed=0; speed<800; speed+=50)
{
MotorA_Control(1, speed); // 电机A正转,速度递增
MotorB_Control(-1, speed); // 电机B反转,速度递增
HAL_Delay(100); // 每100ms加速一次
}
HAL_Delay(1000); // 全速运行1秒
// 示例2:电机A刹车,电机B减速停止
MotorA_Control(0, 0); // 电机A快速刹车
for(speed=800; speed>0; speed-=100)
{
MotorB_Control(-1, speed); // 电机B减速
HAL_Delay(100);
}
MotorB_Control(0, 0); // 电机B最终停止
HAL_Delay(1000);
// 示例3:两电机反向运行
dir = -dir; // 改变方向
MotorA_Control(dir, 600);
MotorB_Control(dir, 600);
HAL_Delay(2000);
}
}
下载程序到你的STM32开发板,接通电机电源,你应该就能看到两个电机按照代码逻辑开始运转了!通过调整MotorA_Control和MotorB_Control函数中的speed参数(范围0-999),你可以直观地感受到PWM占空比对电机速度的线性控制效果。
4. 进阶技巧与避坑指南:让控制更稳、更智能
基本的驱动和调速实现了,但要想在实际项目(比如平衡车、竞速小车)中用好,还有一些进阶的细节和常见的“坑”需要注意。这些都是我实际项目中积累的经验。
4.1 PWM频率的选择:不是越高越好
前面我们设置了1kHz的PWM频率,这是一个安全的起点。但PWM频率的选择其实有讲究:
- 频率太低(如几十Hz):电机会听到明显的“滋滋”啸叫声,转动不连续,有顿挫感。因为每次脉冲的间隔时间太长,电机线圈的电流断续太明显。
- 频率太高(如20kHz以上):超出了人耳听觉范围,电机运行非常安静平滑。但是,TB6612这类MOSFET桥式驱动芯片,其内部的功率管在开关时会有损耗(开关损耗)。频率越高,单位时间内开关次数越多,总损耗越大,芯片发热就越严重。同时,过高的频率也可能超过芯片允许的最高开关频率。
- 推荐范围:对于一般的直流有刷电机,1kHz到10kHz是一个比较理想的区间。我个人的经验是,小型玩具电机用1-5kHz,模型车用的130、TT马达用5-10kHz效果都不错。你可以在这个范围内微调,找到一个兼顾静音、平滑和低发热的平衡点。
在代码中调整频率很简单,就是修改CubeMX里定时器的Prescaler (PSC)和Counter Period (ARR)这两个参数。记住公式:PWM频率 = 定时器时钟源 / (PSC+1) / (ARR+1)。比如,想要5kHz,在72MHz系统时钟下,可以设PSC=71,ARR=199,计算得 72M / 72 / 200 = 5kHz。
4.2 电机的启动、停止与刹车策略
直接给电机全速PWM信号,有时会导致启动电流过大,或者停止时因惯性滑行太远。更好的做法是加入软启动和软停止。
-
软启动:不要一下子将PWM占空比从0跳到最大值。可以设计一个循环,让
speed值在几十毫秒内从0线性增加到目标值。这能有效减小启动冲击,对齿轮箱和传动结构也是一种保护。void Motor_SoftStart(int8_t dir, uint16_t target_speed, uint16_t duration_ms) { uint16_t steps = duration_ms / 10; // 假设每10ms增加一次 uint16_t step_inc = target_speed / steps; for(uint16_t s=0; s<target_speed; s+=step_inc) { MotorA_Control(dir, s); HAL_Delay(10); } MotorA_Control(dir, target_speed); // 确保达到目标值 } -
刹车模式选择:在
Motor_Control函数里,我写了两种停止方式。- 短路刹车:将电机的两个输入端都置为高电平(或都置低,取决于芯片内部逻辑)。这相当于把电机的两个端子短接,电机转动产生的反电动势会形成一个制动电流,让电机快速停止。优点是制动快;缺点是刹车电流大,发热也大。
- 惯性停止(高阻态):将电机的两个输入端都置为低电平(或浮空)。电机依靠自身摩擦和负载惯性慢慢停下。优点是平滑无冲击;缺点是停止位置不精确。 根据你的应用场景选择。比如循迹小车需要快速响应,可能用刹车模式;而机械臂的关节可能需要平滑停止,用惯性模式更好。
4.3 电源与抗干扰:稳定性的基石
电机,尤其是直流有刷电机,是一个巨大的干扰源。电刷在换向时会产生强烈的火花和电磁噪声。这些问题如果处理不好,会导致STM32程序跑飞、复位,或者传感器读数异常。
- 电源隔离与滤波:务必为电机驱动部分(VM)和单片机控制部分(VCC)使用独立的电源或通过DC-DC隔离模块供电。至少要在VM电源入口处并联一个大容量电解电容(如100uF-470uF)和一个小容量陶瓷电容(如0.1uF),用于吸收电机启停时产生的大电流脉冲和高频噪声。
- 信号隔离:在要求高的场合,可以在STM32的PWM和方向控制引脚与TB6612的输入引脚之间,加入光耦进行隔离,彻底切断电气上的干扰路径。
- PCB布局与布线:如果自己画板子,电机的大电流走线要尽量粗、短,并且远离STM32的晶振、复位电路以及模拟信号线(如ADC采样线)。数字地(DGND)和功率地(PGND)可以单点连接。
- 软件看门狗:开启STM32的独立看门狗(IWDG),当程序因干扰跑飞时,能自动复位系统,提高可靠性。
4.4 引入编码器实现闭环速度控制
开环PWM调速有一个问题:同样的PWM占空比,在不同负载、不同电池电压下,电机的实际转速是不一样的。要想实现精准的定速控制(比如让两个轮子严格保持相同转速),就需要引入编码器进行速度反馈,形成闭环控制。
编码器可以安装在电机轴上,输出两路相位差90度的脉冲(A相和B相)。STM32的定时器有专门的编码器接口模式,可以硬件计数这些脉冲。通过定时(比如每100ms)读取一次脉冲数,就能计算出电机的实时转速。
然后,你就可以使用经典的PID控制算法了。PID控制器会比较“目标转速”和“编码器测得的实际转速”,计算出误差,并自动调整输出给TB6612的PWM值,使得实际转速紧紧跟随目标转速,不受负载变化的影响。这才是真正意义上的“精准调速”。虽然PID调参需要一些经验,但一旦调好,系统的稳定性和响应速度会有质的飞跃。这通常是智能小车、平衡车项目从入门走向精通的必经之路。
5. 实战项目构想:从单一驱动到综合应用
掌握了基础驱动和进阶技巧后,你就可以把这些知识组合起来,完成一些有趣且实用的项目了。这里给你几个思路:
项目一:可调速智能小风扇 就像网络资料里那个“暴力小风扇”一样,你可以用STM32+TB6612驱动一个直流电机带动风扇叶片。通过按键或旋钮电位器(接STM32的ADC)来无极调节风扇转速。还可以加上温湿度传感器,做一个自动根据环境温度调整风量的智能风扇。这个项目涵盖了GPIO、PWM、ADC、传感器通信等多个知识点,综合性很强。
项目二:双电机差分驱动智能小车 这是最经典的应用。两个电机分别驱动左右轮。通过让两个电机以相同速度正转实现前进,相同速度反转实现后退。通过让两个电机速度不同(差速),就能实现转向。比如左轮慢、右轮快,小车就向左转。结合超声波或红外传感器,就可以实现避障、跟随。结合灰度传感器或摄像头,就能实现循迹。在这个项目中,你之前学的双电机独立控制、PWM调速、以及未来可能加入的编码器PID调速,全部都能用上。
项目三:简易机械臂关节控制 用TB6612驱动小型直流减速电机,配合电位器作为位置反馈,可以控制一个机械臂关节的转动角度。STM32读取电位器的电压(ADC)来获取当前角度,与目标角度比较,通过PID算法计算出控制电机的PWM输出,驱动关节转动到指定位置。虽然精度不如步进电机或伺服舵机,但对于一些对精度要求不高的演示或教育项目来说,成本更低,也更适合理解闭环控制的概念。
在我自己的开发经历中,第一次用STM32成功驱动电机让小车跑起来时,那种成就感是无与伦比的。从点亮LED到控制一个物理实体运动,是嵌入式学习路上一个重要的里程碑。TB6612是一个可靠且高效的伙伴,而STM32强大的定时器资源让PWM控制变得轻而易举。希望这篇详细的指南能帮你绕过我当年踩过的那些坑,快速上手,并把重点放在如何用这项技术去实现你更酷的创意上。记住,硬件连接仔细,电源处理干净,代码模块清晰,剩下的就是尽情发挥你的想象力了。
更多推荐
所有评论(0)