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调速信号
PA1AIN1电机A方向控制信号1
PA2AIN2电机A方向控制信号2
PA3 (或任意TIM2_CH2)PWMB电机B的PWM调速信号
PA4BIN1电机B方向控制信号1
PA5BIN2电机B方向控制信号2
PA6STBY待机控制(高电平有效)
3.3VVCC逻辑电源(3.3V)
电机电源正极 (如12V)VM电机动力电源
GNDGND公共地

注意:电机电源(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)。然后我们一步步配置:

  1. 配置时钟:在“Pinout & Configuration”标签页的“System Core”里,点开“RCC”。将“High Speed Clock (HSE)”选择为“Crystal/Ceramic Resonator”。这样我们使用外部8MHz晶振,系统时钟会更稳定。

  2. 配置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频率,既能保证调速平滑,又不会让开关损耗太大。
    • 保持其他参数默认即可。
  3. 配置GPIO方向控制引脚:

    • 在芯片图上找到PA1, PA2, PA4, PA5, PA6这几个我们计划使用的引脚。
    • 分别点击它们,选择“GPIO_Output”。这样就把它们配置成了普通的推挽输出模式。
    • 你还可以在右侧的“GPIO”设置里,给这些引脚起个有意义的“User Label”,比如把PA1标签为“AIN1”,PA2为“AIN2”,以此类推。这样在代码里就能用AIN1_GPIO_Port和AIN1_Pin来引用了,非常直观。
  4. 生成代码:点击右上角的“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程序跑飞、复位,或者传感器读数异常。

  1. 电源隔离与滤波:务必为电机驱动部分(VM)和单片机控制部分(VCC)使用独立的电源或通过DC-DC隔离模块供电。至少要在VM电源入口处并联一个大容量电解电容(如100uF-470uF)和一个小容量陶瓷电容(如0.1uF),用于吸收电机启停时产生的大电流脉冲和高频噪声。
  2. 信号隔离:在要求高的场合,可以在STM32的PWM和方向控制引脚与TB6612的输入引脚之间,加入光耦进行隔离,彻底切断电气上的干扰路径。
  3. PCB布局与布线:如果自己画板子,电机的大电流走线要尽量粗、短,并且远离STM32的晶振、复位电路以及模拟信号线(如ADC采样线)。数字地(DGND)和功率地(PGND)可以单点连接。
  4. 软件看门狗:开启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控制变得轻而易举。希望这篇详细的指南能帮你绕过我当年踩过的那些坑,快速上手,并把重点放在如何用这项技术去实现你更酷的创意上。记住,硬件连接仔细,电源处理干净,代码模块清晰,剩下的就是尽情发挥你的想象力了。

Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐