4. ULN2003驱动:让STM32轻松驱动步进电机

说实话,我第一次用STM32的GPIO直接驱动28BYJ-48时,差点把芯片烧了。那时候我刚从51单片机转过来,心想GPIO输出电流应该差不多吧?结果一接上,电机纹丝不动,STM32倒是烫得厉害。后来才明白——STM32的GPIO驱动能力太弱了,根本带不动这种四相五线步进电机。

所以,ULN2003这个芯片就派上用场了。它本质上就是个「电流放大器」,专门解决单片机IO口「有心无力」的问题。

4.1 ULN2003芯片介绍

ULN2003是个达林顿晶体管阵列芯片。说白了,里面集成了7个达林顿对,每个都能把输入的小电流放大成500mA的大电流。我习惯把它理解成「7个电子开关」——你给它一个TTL电平信号(比如3.3V或5V),它就能控制一个最大500mA的负载。

关键参数我列一下:

参数 数值 说明
最大输出电流 500mA/通道 驱动28BYJ-48绰绰有余
最大输出电压 50V 一般电机用5V-12V
输入电平 TTL兼容 3.3V或5V都能识别
内部续流二极管 保护电路,很重要

嗯,这里要注意——ULN2003内部已经集成了续流二极管。这意味着驱动感性负载(比如电机线圈)时,你不需要额外加二极管来吸收反向电动势。我在项目中吃过这个亏,早期用分立元件搭驱动电路,忘了加续流二极管,结果MOS管烧了好几个。ULN2003把这个坑直接填平了。

4.2 电路连接:STM32 + ULN2003 + 28BYJ-48

28BYJ-48是四相五线步进电机。五根线分别是:一根公共电源线(通常是红色),四根相线(橙、黄、粉、蓝)。公共线接VCC(5V),四根相线分别接ULN2003的四个输出通道。

连接方式其实很简单:

  1. STM32 GPIO → ULN2003输入:GPIO输出3.3V电平,直接连到ULN2003的IN1~IN4
  2. ULN2003输出 → 电机相线:OUT1~OUT4分别接电机的橙、黄、粉、蓝
  3. 电机公共线 → 5V电源:红色线接5V正极
  4. ULN2003的COM引脚 → 5V电源:这个引脚是内部续流二极管的公共端
  5. GND共地:STM32、ULN2003、5V电源的GND全部连在一起

核心要点:ULN2003的COM引脚必须接电机电源正极(5V),否则续流二极管不起作用。我曾经见过有人把COM悬空,结果电机一转,ULN2003就冒烟了。

下面是我画的一个连接示意图:

STM32 GPIO ULN2003 28BYJ-48 5V电源 IN1 IN2 IN3 IN4 VCC 红线(5V) COM→5V GND共地

我的经验:实际接线时,我习惯在ULN2003的每个输出通道和电机相线之间串一个100Ω的限流电阻。虽然ULN2003本身有电流限制,但加个电阻能让电机运行更平稳,发热也小一些。

4.3 驱动原理:四相八拍

28BYJ-48是四相步进电机,驱动方式有单四拍、双四拍和八拍三种。我推荐用八拍方式——步距角更小,运行更平滑。

八拍的相序是这样的:

拍数 橙(A相) 黄(B相) 粉(C相) 蓝(D相)
11000
21100
30100
40110
50010
60011
70001
81001

你想想看,这个序列其实就是「单相→双相→单相→双相」交替。每次只改变一个相的状态,所以电机运行特别平稳,几乎没有共振点。

4.4 STM32代码实现

下面是我写的一个驱动函数。我用的是HAL库,GPIO直接控制四个引脚。

// 定义四个相位的GPIO引脚
#define MOTOR_A_PIN GPIO_PIN_0  // PA0 - 橙色
#define MOTOR_B_PIN GPIO_PIN_1  // PA1 - 黄色
#define MOTOR_C_PIN GPIO_PIN_2  // PA2 - 粉色
#define MOTOR_D_PIN GPIO_PIN_3  // PA3 - 蓝色

#define MOTOR_PORT GPIOA

// 八拍相序表
const uint8_t step_sequence[8][4] = {
    {1, 0, 0, 0},  // 第1拍
    {1, 1, 0, 0},  // 第2拍
    {0, 1, 0, 0},  // 第3拍
    {0, 1, 1, 0},  // 第4拍
    {0, 0, 1, 0},  // 第5拍
    {0, 0, 1, 1},  // 第6拍
    {0, 0, 0, 1},  // 第7拍
    {1, 0, 0, 1}   // 第8拍
};

// 步进电机驱动函数
// direction: 1=正转, 0=反转
// steps: 步数
// speed: 速度(毫秒, 每拍延时)
void Stepper_Move(uint8_t direction, uint16_t steps, uint16_t speed)
{
    static uint8_t current_step = 0;
    
    for(uint16_t i = 0; i < steps; i++)
    {
        // 根据方向更新当前拍数
        if(direction)
        {
            current_step++;
            if(current_step > 7) current_step = 0;
        }
        else
        {
            if(current_step == 0) current_step = 7;
            else current_step--;
        }
        
        // 输出相序
        HAL_GPIO_WritePin(MOTOR_PORT, MOTOR_A_PIN, 
                          step_sequence[current_step][0] ? GPIO_PIN_SET : GPIO_PIN_RESET);
        HAL_GPIO_WritePin(MOTOR_PORT, MOTOR_B_PIN, 
                          step_sequence[current_step][1] ? GPIO_PIN_SET : GPIO_PIN_RESET);
        HAL_GPIO_WritePin(MOTOR_PORT, MOTOR_C_PIN, 
                          step_sequence[current_step][2] ? GPIO_PIN_SET : GPIO_PIN_RESET);
        HAL_GPIO_WritePin(MOTOR_PORT, MOTOR_D_PIN, 
                          step_sequence[current_step][3] ? GPIO_PIN_SET : GPIO_PIN_RESET);
        
        // 延时控制速度
        HAL_Delay(speed);
    }
}

注意:28BYJ-48的减速比是1:64。也就是说,电机内部转了64圈,输出轴才转1圈。而电机本身步距角是5.625°,八拍模式下每拍走5.625°/2 = 2.8125°。所以输出轴转一圈需要的拍数是:64 × (360° / 2.8125°) = 8192拍。

我曾经犯过一个错——以为4096拍就能转一圈,结果电机只转了半圈。后来查资料才发现忘了算减速比。

4.5 实际调试经验

调试的时候,我建议你先用单步模式测试。就是每次只走一拍,看看电机是不是按预期方向转动。如果方向反了,交换任意两根相线就行。

还有几个坑我帮你提前踩了:

  • 电源要稳:28BYJ-48额定电流约200mA,但启动瞬间可能冲到300mA。如果用STM32开发板的3.3V供电,肯定带不动。我习惯用独立的5V电源。
  • 速度别太快:speed参数建议从10ms开始调。太快了电机丢步,太慢了听着像拖拉机。我一般用2ms~5ms,运行比较顺畅。
  • 别忘了共地:STM32的GND和电机电源的GND必须连在一起。不共地的话,信号电平参考点不同,电机根本不会动。

小技巧:如果你发现电机运行时抖动厉害,可以试试在每拍之间加一个「全关断」状态——就是把所有相都置0,保持1ms再进入下一拍。这样能有效减少相间切换时的电流冲击。

好了,ULN2003驱动这部分就讲到这里。你只要按我说的电路接好,代码烧进去,电机应该就能转起来了。如果转不起来,先检查共地,再检查电源,最后看相序对不对——这三个地方出问题的概率占了九成。


公众号:蓝海资料掘金营,微信deep3321