ULN2003 步进电机驱动 :从原理、电路到 Arduino、51 与 STM32 实战
1. 为什么需要 ULN2003 驱动步进电机
很多初学者第一次接触步进电机时,经常会直接把电机线圈接到单片机 IO 口上,结果发现电机要么完全不转,要么只有很微弱的抖动,甚至把单片机烧坏。出现这个问题的核心原因在于,步进电机的每一相线圈都是低阻感性负载。以最常见的小型四相五线步进电机 28BYJ-48 为例,单相线圈直流电阻大约在几十欧姆到一百多欧姆之间,如果工作电压选择 5V,单相电流可能在几十毫安到一百毫安左右。这样的电流虽然看起来不大,但对单片机的 GPIO 口来说已经很勉强,而且更重要的是,电感断电瞬间会产生反向电动势,这个反向电压可能远高于电源电压,很容易击穿单片机引脚内部的 MOS 管。
所以,工程上通常不会用单片机引脚直接驱动步进电机,而是会在单片机和电机之间增加一级驱动电路。驱动电路的任务主要有三个:第一,提供足够大的电流驱动能力;第二,承受并泄放线圈关断瞬间的反向电动势;第三,把单片机的弱信号控制电平转换为电机线圈需要的功率信号。
ULN2003 正是完成这三项任务的一款经典高耐压大电流达林顿晶体管阵列。它由 7 组高压大电流达林顿管构成,每一路都可以看作一个反相放大开关。对于驱动四相步进电机来说,只需要使用其中 4 路即可,剩下的 3 路还可以留给 LED、继电器或蜂鸣器等其它负载。ULN2003 价格便宜、货源充足、电路简单、可靠性高,几乎所有 28BYJ-48 步进电机模块都以它作为核心驱动芯片。因此,理解 ULN2003 的原理和用法,是入门步进电机控制非常重要的一步。
本文将以 ULN2003 芯片为核心,从步进电机基本原理讲起,逐步展开 ULN2003 内部结构、引脚功能、电气参数、典型驱动电路、励磁时序、转速控制,再到基于 Arduino、51 单片机和 STM32 的完整代码实现,最后补充常见故障排查和实际项目案例,力求让读者对 ULN2003 驱动步进电机形成系统完整的认识。
2. 步进电机基础
2.1 步进电机是什么
步进电机是一种将电脉冲信号转换为固定角度位移的执行电机。普通直流电机只要通电就会连续旋转,转速和电流大小有关;而步进电机每收到一个脉冲,转子就会转过一个固定的角度,这个角度称为步距角。只要控制脉冲的数量,就可以精确控制电机转过的总角度;只要控制脉冲的频率,就可以控制电机的转速;只要改变各相线圈通电的顺序,就可以改变电机的旋转方向。
步进电机这种“给一个脉冲就走一步”的特性,使得它在不需要编码器反馈的情况下也能实现相对精确的开环位置控制,非常适合打印机、扫描仪、摄像头云台、3D 打印机、小型机器人、自动窗帘、机械臂等需要精确定位或匀速转动的场景。
2.2 常见小型步进电机 28BYJ-48
在学习 ULN2003 驱动时,最常搭配的电机是 28BYJ-48 四相五线步进电机。它的命名中,28 表示安装法兰直径约为 28 毫米,B 表示步进电机,Y 表示永磁式,J 表示减速型,48 表示四相八拍。该电机内部是永磁转子,定子上有四组线圈,四组线圈的一端公共连接,形成一个公共端,因此引出线共有 5 根,通常称为四相五线制。
28BYJ-48 最关键的参数有两个:步距角和减速比。它的整步步距角通常为 5.625 度,也就是每 64 个整步脉冲转子转过一圈。但该电机内部还带有一个大约 1/64 的减速齿轮组,因此输出轴的最终步距角大约是 5.625 度除以 64,约等于 0.0879 度。也就是说,输出轴转一圈大约需要 4096 个整步脉冲,或者 2048 个半步脉冲。对于实际使用,这个减速比意味着电机输出轴转速很慢但转矩较大,非常适合云台、阀门、小型机械臂等低速大扭矩场合。
值得注意的是,不同厂家生产的 28BYJ-48 减速比可能略有差异,有的标称为 1/64,有的标称为 1/63.68395,还有的使用八拍驱动时输出轴一圈约为 2048 步。因此在精确位置控制中,最好先通过实验标定实际步数,而不要盲目相信数据手册中的标称值。
2.3 四相五线的接线结构
28BYJ-48 的 5 根线中,通常有 4 根同色线对应四个相,另一根不同颜色的线是公共端。以常见的红、橙、黄、粉、蓝五色线为例,红色线通常是公共端,应接到电源正极;橙、黄、粉、蓝四根线分别对应 A、B、C、D 四个相。不同批次颜色可能不同,因此最可靠的方法是使用万用表测量公共端和各相之间的电阻,通常公共端与任意一相之间的电阻都相等,而任意两相之间的电阻约为单相电阻的两倍。
在 ULN2003 驱动电路中,公共端接到电源正极,四个相分别接到 ULN2003 的四个输出端。当 ULN2003 某一路导通时,对应相的线圈被拉到地,形成电流回路。由于 ULN2003 的输出是开集电极结构,导通时输出端近似接地,截止时输出端悬空,因此当公共端接正电源,输出端接线圈后,线圈是否通电就完全由 ULN2003 输入端的电平决定。
2.4 步距角、拍数与分辨率
步进电机的控制精度由步距角和驱动拍数共同决定。以 28BYJ-48 为例,若采用单相励磁或双相励磁方式,每走一个整步旋转 5.625 度,电机转子转一圈需要 64 步,经过 1/64 减速后输出轴转一圈需要 4096 步;若采用半步驱动,即八拍方式,每走一拍转子旋转 2.8125 度,转子转一圈需要 128 步,输出轴转一圈需要 2048 步。显然,半步驱动的角度分辨率提高了一倍,转动也更加平滑,但控制程序需要维护一个更长的八拍状态表。
需要理解的是,步进电机的“步”是指电脉冲序列中的一拍,而不是输出轴的实际机械角度。由于减速齿轮的存在,电机内部转子和输出轴的步数之间存在倍率关系。因此在计算位置时,要区分“电机转子步数”和“输出轴步数”。如果应用只关心输出轴转了多少圈,则直接使用输出轴步数即可。
3. ULN2003 芯片详解
3.1 ULN2003 是什么
ULN2003 是 Texas Instruments 以及众多兼容厂商生产的一款高电压、大电流达林顿晶体管阵列。它内部包含 7 个独立的达林顿管,每个达林顿管都由两级 NPN 晶体管级联而成,因此具有很高的电流增益。ULN2003 的常用封装有 DIP-16、SOP-16、TSSOP-16 等,其中 DIP-16 直插封装在实验板上使用非常方便。
每个达林顿通道包括输入端、输出端以及一个连接在输出端和公共续流二极管正极之间的钳位二极管。ULN2003 内部还集成了输入基极串联电阻,因此输入端可以直接由 5V 逻辑电路驱动,无需额外限流电阻。输出端采用开集电极结构,负载一端接正电源,另一端接 ULN2003 输出端。当输入为高电平时,达林顿管导通,输出端被拉到接近地;当输入为低电平时,达林顿管截止,输出端呈现高阻状态。
3.2 为什么是“达林顿阵列”
达林顿结构是指把两个或多个晶体管级联,使总的电流增益等于各级增益的乘积。ULN2003 中每个通道都采用类似结构,因此即使输入电流很小,也能在输出端控制较大的负载电流。对于步进电机这类需要几百毫安电流的负载来说,达林顿结构可以提供足够的驱动能力。
使用达林顿结构的一个代价是导通压降略有增加。普通单个 NPN 晶体管的饱和压降可能在 0.1V 到 0.3V,而达林顿管的饱和压降通常在 1V 左右,有的甚至更高。这意味着当电机线圈工作电流较大时,ULN2003 内部会消耗一部分功率并发热。不过对于 28BYJ-48 这种低功耗小型电机,电流通常只有几十毫安到一百多毫安,发热并不严重,通常不需要额外散热片。
3.3 引脚功能
以 DIP-16 封装为例,ULN2003 的 16 个引脚按逆时针排列。其中引脚 1 至引脚 7 是七个输入 IN1 到 IN7,引脚 10 至引脚 16 是七个输出 OUT1 到 OUT7。引脚 8 为地 GND,引脚 9 为公共续流二极管正极 COM。每个输入和对应的输出按顺序一一对应:IN1 对应 OUT1,IN2 对应 OUT2,以此类推。
以常见的 28BYJ-48 驱动模块为例,模块上通常会引出 IN1、IN2、IN3、IN4 四个输入,分别由单片机的四个 IO 口控制,输出端接电机的 A、B、C、D 四相,公共端 COM 接电机外接电源正极。模块上还会自带电源指示灯、接线端子和若干电阻,但核心仍是 ULN2003 芯片。
3.4 电气参数
ULN2003 的典型电气参数如下:单通道输出端最高可承受 50V 的电压,单通道最大集电极电流典型值为 500mA,七路同时导通时总电流应控制在合理范围内,否则芯片发热会明显增加。输入高电平电压通常为 2.4V 到 5V,因此可以直接由 3.3V 或 5V 单片机 IO 驱动。输入低电平通常低于 0.7V 即可关断对应通道。输入电流通常只有几毫安,因此单片机引脚负载很轻。
对于 28BYJ-48 而言,推荐工作电压一般是 5V,单相电流约几十毫安,因此 ULN2003 单路 500mA 的驱动能力完全足够。即使接多个电机,只要每个电机分别使用一个 ULN2003,或使用多个未使用的通道,通常也不会超出芯片能力。需要注意的是,如果电机采用 12V 版本,虽然仍在 ULN2003 的 50V 耐压范围内,但电流可能增大,需要关注芯片温升和外接电源的稳定性。
3.5 内部续流二极管的作用
步进电机线圈属于电感负载,电感中的电流不能突变。当某一相从通电状态切换到断电状态时,线圈会试图维持原有电流,从而在两端产生一个与电源方向相反的反向电动势。如果没有泄放路径,这个反向电压可能叠加到输出引脚上,造成 ULN2003 输出管被击穿。
ULN2003 内部在每个输出端和 COM 公共端之间都集成了一个续流二极管,二极管的负极接 COM,正极接输出端。当 COM 接到电机电源正极时,线圈断电瞬间的反向电动势会通过该二极管续流到电源正极,从而把电压钳位在安全范围内。这样,ULN2003 自身就完成了对感性负载的保护,不需要像使用普通三极管时那样额外焊接续流二极管,这是它非常实用的一个特点。
3.6 输入输出逻辑关系
ULN2003 的本质是一个反相器。当输入 IN1 为高电平时,对应输出 OUT1 的达林顿管导通,OUT1 被拉到低电平;当 IN1 为低电平时,达林顿管截止,OUT1 变为高阻。对于公共端接正电源的步进电机来说,输出端被拉低就意味着对应相线圈得电,输出端高阻就意味着对应相线圈断电。因此,写程序时让某一个控制引脚输出高电平,对应的那一相就会通电。
这个逻辑关系虽然简单,但初学者经常在接线和代码之间混淆。记住一句话即可:在典型 ULN2003 步进电机模块中,单片机电平高,对应电机相导通;电平低,对应电机相关断。代码中的励磁序列,本质上就是控制哪些引脚同时输出高电平。
4. 驱动电路设计
4.1 典型接线方式
使用 ULN2003 驱动 28BYJ-48 的典型接线如下:电机公共端红色线接外接电源正极,通常为 5V。电机四相线分别接 ULN2003 的四个输出端。ULN2003 的 COM 端与电机公共端合并后接到同一正电源。ULN2003 的 GND 与单片机的 GND 共地。ULN2003 的四个输入 IN1 到 IN4 分别接单片机的四个普通 IO 口,比如 Arduino 的 D8、D9、D10、D11,或 51 单片机的 P1.0 到 P1.3,或 STM32 的任意 GPIO。
如果使用已经集成 ULN2003 的蓝色小模块,则接线更加简单。模块上通常有四个输入引脚、一个 5 至 12V 电源输入、一个 GND,以及一个五线电机插座。只要把电机插到对应插座,电源接到模块供电端,四个输入接到单片机即可。这类模块一般还带有四个状态 LED,可以直观看到当前哪些相导通,非常有利于学习和调试。
4.2 电源选择
ULN2003 本身只是开关器件,真正的能量来自电机外接电源。对于标准 5V 版本的 28BYJ-48,外接电源可以选择 5V。需要特别注意的是,不建议直接用单片机的 5V 引脚给电机供电,因为步进电机工作时电流波动较大,特别是在频繁换相时,瞬时电流会给单片机供电总线带来干扰,严重时可能导致单片机复位或程序跑飞。正确做法是使用独立的 5V 电源,或至少使用能提供足够电流且带滤波的稳压电源,并且让单片机地和电机地共地。
如果使用 12V 版本的步进电机,则可以搭配 12V 电源。此时 ULN2003 输出耐压没有问题,但需要注意电机电流和芯片发热。外接电源的额定电流应至少为四相总电流的 1.5 倍以上,并预留一定余量。例如单相电流 100mA,由于电机通常同一时间有一相或两相导通,平均电流不会超过 200mA,电源额定电流选择 500mA 到 1A 就足够稳定。
4.3 去耦电容与抗干扰
在电机驱动电路中,建议在 ULN2003 模块的电源输入端并联一个 100uF 电解电容和一个 0.1uF 陶瓷电容。较大的电解电容可以缓冲电机换相时产生的瞬时电流需求,较小的陶瓷电容可以滤除高频噪声。电容应尽量靠近模块电源引脚放置。同时,电机引线应尽量短,必要时可以使用双绞线,减小对外辐射的电磁干扰。
另外,单片机控制引脚与 ULN2003 输入之间如果走线较长,可以在输入端串一个小电阻或在引脚到地之间加一个小电容,以抑制反射和尖峰。一般实验板上的短距离连接不需要这些措施,但当需要制作稳定长期运行的产品时,抗干扰设计不能忽略。
4.4 是否给限流电阻
ULN2003 输入内部已经串联了基极电阻,因此输入端不需要再串联限流电阻。线圈回路中,电机自身电阻就起到限流作用。对于 5V 版本的 28BYJ-48,一般不需要在电机回路中额外串联限流电阻。对于 12V 版本,如果实测单相电流过大、电机或芯片过热,可以在公共端串联一个合适的功率电阻来降低工作电流,但这会牺牲一部分转矩,需要根据具体电机参数权衡。
4.5 模块与分立元件对比
对初学者来说,直接购买集成模块最省事,不容易出错。对希望深入理解电路的读者,也可以使用 ULN2003 裸芯片自行搭建。自制电路时,需要准备 ULN2003、16 脚 IC 座、几根杜邦线,接线端子,以及必要的去耦电容。自建电路的好处是可以更清楚地理解 IN、OUT、GND、COM 之间的关系,也能方便地把剩余三路用于其它负载。
不论使用模块还是自制电路,都应该养成公共地和电源先接好,再接入信号的顺序习惯。特别是在带电插拔时,先断电源,避免热插拔造成电机反转电动势损坏芯片或单片机。
5. 步进电机驱动时序
5.1 励磁方式概述
四相步进电机的转动,本质上是让 A、B、C、D 四相按特定顺序通电。四相线圈在定子上空间排布,每一次通电组合都会产生一个合成磁场,转子会吸引到与该磁场对齐的位置。当通电组合按一定顺序切换时,磁场旋转,转子也跟着一步一步转动。
常见的励磁方式有四种:单相励磁整步驱动、双相励磁整步驱动、单双相混合的半步驱动(八拍驱动),以及用于某些特殊电机的细分驱动。对于 ULN2003 和 28BYJ-48,前三者最为常用,下面分别介绍。
5.2 单相励磁整步驱动
单相励磁是指任意时刻只有一相线圈通电。其通电顺序为 A、B、C、D、A……每一次切换,转子转动一个整步,步距角为 5.625 度。单相励磁的优点是控制逻辑简单,每个状态只需要一个 IO 口输出高电平。缺点是转矩较小,因为同一时刻只有一组线圈产生磁场,而且换相过程中容易出现振动和失步,尤其是低速时抖动比较明显。
单相励磁四拍的状态表如下:第一拍 IN1 高,IN2 低,IN3 低,IN4 低;第二拍 IN1 低,IN2 高,IN3 低,IN4 低;第三拍 IN1 低,IN2 低,IN3 高,IN4 低;第四拍 IN1 低,IN2 低,IN3 低,IN4 高。这样循环,电机向一个方向转动;把顺序反过来,电机就反向转动。
5.3 双相励磁整步驱动
双相励磁是指任意时刻有两相线圈同时通电。其通电顺序通常为 AB、BC、CD、DA、AB……每一步也有两相导通,磁场更强,因此转矩比单相励磁大,转动也更平稳。双相励磁的步距角同样为 5.625 度,每四拍转子转一圈的整步数与单相励磁相同。大多数实际应用更推荐使用双相励磁,因为它用几乎相同的复杂度换来了更大的转矩和更少的振动。
双相励磁四拍的状态表如下:第一拍 IN1 高,IN2 高,IN3 低,IN4 低,即 A、B 通电;第二拍 IN1 低,IN2 高,IN3 高,IN4 低,即 B、C 通电;第三拍 IN1 低,IN2 低,IN3 高,IN4 高,即 C、D 通电;第四拍 IN1 高,IN2 低,IN3 低,IN4 高,即 D、A 通电。
5.4 半步驱动(八拍)
半步驱动交替使用单相和双相励磁,将整步拆分为两个半步。通电顺序为 A、AB、B、BC、C、CD、D、DA、A……每切换一拍,转子转动 2.8125 度,即半个整步。半步驱动的优点是角度分辨率提高一倍,运行更加平滑,噪声和振动明显降低。因此在云台、3D 打印机等对运动平稳性要求较高的场合,通常使用半步驱动甚至更细分的驱动方式。
半步驱动八拍的状态表如下:第一拍 A 通电;第二拍 AB 通电;第三拍 B 通电;第四拍 BC 通电;第五拍 C 通电;第六拍 CD 通电;第七拍 D 通电;第八拍 DA 通电。然后循环。反向转动时把序列倒过来即可。
5.5 拍序与反转的实现
无论是四拍还是八拍,反转都只需要把拍序数组倒序使用。在程序中,常见的做法是定义一个正向拍序数组和一个反向拍序数组,或者定义一个数组后通过索引递增做正转、索引递减做反转。对于简单的演示程序,可以只定义正向数组,反转时把索引从末尾开始递减,从而减少代码重复。
需要注意的是,切换拍序的速度决定了电机转速。每拍之间的延时越短,单位时间内完成的拍数越多,电机转动越快。但延时不能无限短,因为当换相速度快到超过转子机械响应速度时,转子来不及跟随磁场旋转,就会发生失步,表现为电机嗡嗡响但不转,或者转得很慢却剧烈抖动。
6. 基于 Arduino 的驱动实现
6.1 硬件连接
使用 Arduino UNO 与 ULN2003 模块驱动 28BYJ-48 时,先将电机插到 ULN2003 模块的五线插座上。模块的电源正极接外部 5V,GND 与 Arduino 的 GND 相连。模块的 IN1、IN2、IN3、IN4 分别接到 Arduino 的数字引脚 8、9、10、11。为了避免电机电流干扰 Arduino 电源,建议使用外部独立 5V 供电,或将 USB 供电能力较强的口与电容滤波结合使用。对于短期演示,从 Arduino 5V 引脚供电也能工作,但不建议长时间高负载运行。
6.2 使用 Arduino Stepper 库
Arduino IDE 自带了 Stepper 库,可以快速驱动四线或五线步进电机。对于 28BYJ-48,使用 Stepper 库时需要通过 ULN2003 驱动,并采用四拍整步方式。下面给出一个基本示例:
#include <Stepper.h>
const int stepsPerRevolution = 2048;
Stepper myStepper(stepsPerRevolution, 8, 10, 9, 11);
void setup() {
myStepper.setSpeed(10);
}
void loop() {
myStepper.step(stepsPerRevolution);
delay(2000);
myStepper.step(-stepsPerRevolution);
delay(2000);
}
在 Stepper 构造函数中,参数依次是每圈步数、线圈 1 引脚、线圈 2 引脚、线圈 3 引脚、线圈 4 引脚。对于双相励磁四拍驱动,输出轴每圈约 2048 步,因此 stepsPerRevolution 可以近似设置为 2048。setSpeed 的单位是每分钟转数,step 函数会阻塞运行,直到走完指定步数。这个库使用简单,但阻塞式运行会占用主循环,不适合需要同时处理按键、显示或通信的复杂任务。
6.3 手动实现四拍单双相控制
为了深入理解时序,也可以不用库,手动定义拍序表。下面给出一个双相励磁四拍的实现:
const int in1 = 8;
const int in2 = 9;
const int in3 = 10;
const int in4 = 11;
const int stepCount = 4;
const int phases[4][4] = {
{1, 1, 0, 0},
{0, 1, 1, 0},
{0, 0, 1, 1},
{1, 0, 0, 1}
};
void setPhase(int index) {
digitalWrite(in1, phases[index][0]);
digitalWrite(in2, phases[index][1]);
digitalWrite(in3, phases[index][2]);
digitalWrite(in4, phases[index][3]);
}
void setup() {
pinMode(in1, OUTPUT);
pinMode(in2, OUTPUT);
pinMode(in3, OUTPUT);
pinMode(in4, OUTPUT);
for (int i = 0; i < stepCount; i++) {
setPhase(i);
delayMicroseconds(1500);
}
}
void loop() {}
这段代码把四组通电状态写成一个二维数组,每行表示一拍中四个引脚的电平。delayMicroseconds 控制每拍停留时间,1500 微秒即 1.5 毫秒,此时电机转速约为每秒 166 拍,输出轴转速大约每秒 166 除以 2048 圈。通过改变延时值可以调整转速。
6.4 手动实现八拍半步控制
八拍半步驱动需要维护一个 8 行 4 列的拍序表:
const int in1 = 8;
const int in2 = 9;
const int in3 = 10;
const int in4 = 11;
const int stepCount = 8;
const int phases[8][4] = {
{1, 0, 0, 0},
{1, 1, 0, 0},
{0, 1, 0, 0},
{0, 1, 1, 0},
{0, 0, 1, 0},
{0, 0, 1, 1},
{0, 0, 0, 1},
{1, 0, 0, 1}
};
void setPhase(int index) {
digitalWrite(in1, phases[index][0]);
digitalWrite(in2, phases[index][1]);
digitalWrite(in3, phases[index][2]);
digitalWrite(in4, phases[index][3]);
}
void step(int steps, unsigned long stepDelay) {
int index = 0;
if (steps < 0) {
for (int i = 0; i < -steps; i++) {
index = 7 - (i % 8);
setPhase(index);
delayMicroseconds(stepDelay);
}
} else {
for (int i = 0; i < steps; i++) {
index = i % 8;
setPhase(index);
delayMicroseconds(stepDelay);
}
}
}
void setup() {
pinMode(in1, OUTPUT);
pinMode(in2, OUTPUT);
pinMode(in3, OUTPUT);
pinMode(in4, OUTPUT);
}
void loop() {
step(2048, 2000);
delay(1000);
step(-2048, 2000);
delay(1000);
}
在这个实现中,step 函数可以指定步数和每步延时。步数为正时顺时针旋转,步数为负时通过反向索引实现逆时针旋转。使用八拍半步驱动后,输出轴一圈约为 2048 步,因此 step(2048) 表示输出轴转一圈。由于使用 delayMicroseconds 阻塞等待,这段代码适合没有其它实时任务的场景。若需要非阻塞控制,可以把换相逻辑放入定时器中断或使用 millis 记录时间。
6.5 非阻塞控制与多任务
在许多实际项目中,CPU 不能一直被步进电机的延时等待占用。例如,系统还需要处理串口、显示、按键或其它传感器。此时可以采用非阻塞方式:记录上一次换相时间,每次主循环比较当前时间,到达换相间隔后再切换下一拍。下面给出一个基于 millis 的非阻塞示例:
const int in1 = 8;
const int in2 = 9;
const int in3 = 10;
const int in4 = 11;
const int phases[8][4] = {
{1, 0, 0, 0},
{1, 1, 0, 0},
{0, 1, 0, 0},
{0, 1, 1, 0},
{0, 0, 1, 0},
{0, 0, 1, 1},
{0, 0, 0, 1},
{1, 0, 0, 1}
};
unsigned long previousMicros = 0;
int index = 0;
unsigned long interval = 2000;
void setPhase(int i) {
digitalWrite(in1, phases[i][0]);
digitalWrite(in2, phases[i][1]);
digitalWrite(in3, phases[i][2]);
digitalWrite(in4, phases[i][3]);
}
void setup() {
pinMode(in1, OUTPUT);
pinMode(in2, OUTPUT);
pinMode(in3, OUTPUT);
pinMode(in4, OUTPUT);
}
void loop() {
unsigned long now = micros();
if (now - previousMicros >= interval) {
previousMicros = now;
index = (index + 1) % 8;
setPhase(index);
}
}
这种方式让主循环可以同时执行其它任务,而步进电机按照固定间隔持续推进。可以通过修改变量 index 的增减方向实现正反转,通过修改 interval 实现调速。需要注意 interval 不能小于电机能响应的最小周期,否则会失步。
7. 基于 51 单片机的驱动实现
7.1 硬件连接
51 单片机与 ULN2003 模块连接时,通常使用 P1 口的四个引脚,例如 P1.0、P1.1、P1.2、P1.3 分别接模块 IN1 到 IN4。电机公共端和模块电源接 5V,模块 GND 与 51 单片机 GND 共地。51 单片机工作电压通常为 5V,与 ULN2003 输入电平匹配,不需要电平转换。
由于 51 单片机的高电平驱动能力较弱,尤其传统 8051 的端口高电平输出电流很小,但在接入 ULN2003 后,由于 ULN2003 输入只需要几毫安电流,51 的准双向口也基本可以驱动。为了提高驱动可靠性,可以在软件中把相应端口写 1 后配合上拉电阻,或使用强推挽输出的增强型 51,例如 STC89C52 的部分 IO 模式配置。
7.2 单双相四拍程序
下面以 STC89C52 为例,使用双相励磁四拍驱动 28BYJ-48。程序使用一个无符号字符数组保存四个相位状态,通过循环延时控制转速:
#include <reg52.h>
sbit IN1 = P1 ^ 0;
sbit IN2 = P1 ^ 1;
sbit IN3 = P1 ^ 2;
sbit IN4 = P1 ^ 3;
unsigned char code phase[4] = {
0x03, /* AB 通电,IN1 IN2 高 */
0x06, /* BC 通电,IN2 IN3 高 */
0x0C, /* CD 通电,IN3 IN4 高 */
0x09 /* DA 通电,IN1 IN4 高 */
};
void delay_ms(unsigned int ms) {
unsigned int i, j;
for (i = 0; i < ms; i++) {
for (j = 0; j < 120; j++);
}
}
void main(void) {
unsigned char i = 0;
while (1) {
for (i = 0; i < 4; i++) {
P1 = (P1 & 0xF0) | phase[i];
delay_ms(2);
}
}
}
这段代码把 P1 的低四位作为控制输出。phase 数组以十六进制表示四个相位的电平组合。通过对 P1 的高四位进行掩码保留,并把低四位替换为 phase 值,避免影响 P1 其它引脚。delay_ms 的延时参数决定速度。由于 51 单片机指令周期与晶振频率有关,具体延时时间需要根据实际晶振调整。
7.3 八拍半步程序
八拍驱动同样可以使用一个 8 个元素的数组。下面是完整的正反转示例:
#include <reg52.h>
sbit IN1 = P1 ^ 0;
sbit IN2 = P1 ^ 1;
sbit IN3 = P1 ^ 2;
sbit IN4 = P1 ^ 3;
unsigned char code phase[8] = {
0x01,
0x03,
0x02,
0x06,
0x04,
0x0C,
0x08,
0x09
};
void delay_ms(unsigned int ms) {
unsigned int i, j;
for (i = 0; i < ms; i++) {
for (j = 0; j < 120; j++);
}
}
void rotate_forward(unsigned int steps) {
unsigned int i;
for (i = 0; i < steps; i++) {
P1 = (P1 & 0xF0) | phase[i % 8];
delay_ms(2);
}
}
void rotate_backward(unsigned int steps) {
unsigned int i;
for (i = 0; i < steps; i++) {
P1 = (P1 & 0xF0) | phase[(7 - (i % 8))];
delay_ms(2);
}
}
void main(void) {
while (1) {
rotate_forward(2048);
delay_ms(1000);
rotate_backward(2048);
delay_ms(1000);
}
}
这里使用 i 一直递增,正转时对 8 取余得到 0 到 7 的正向序列,反转时用 7 减去 i 对 8 的余数得到反向序列。由于 28BYJ-48 采用半步驱动时输出轴一圈约 2048 步,所以 rotate_forward(2048) 表示输出轴转一圈。
7.4 定时器精确延时
使用软件延时虽然简单,但延时时间受晶振和编译器优化影响,不够精确。如果需要精确控制转速,可以改用 51 单片机的定时器中断。在定时器中断服务函数中切换下一拍,主循环则继续处理其它任务。以定时器 0 模式 1 为例,可以这样安排:
#include <reg52.h>
unsigned char code phase[8] = {
0x01, 0x03, 0x02, 0x06,
0x04, 0x0C, 0x08, 0x09
};
unsigned char index = 0;
bit direction = 1;
void timer0_init(void) {
TMOD = 0x01;
TH0 = 0xFC;
TL0 = 0x18;
ET0 = 1;
TR0 = 1;
EA = 1;
}
void timer0_isr(void) interrupt 1 {
TH0 = 0xFC;
TL0 = 0x18;
if (direction) {
index = (index + 1) % 8;
} else {
index = (index + 7) % 8;
}
P1 = (P1 & 0xF0) | phase[index];
}
void main(void) {
timer0_init();
while (1) {
/* 主循环可以处理按键、显示等其它任务 */
}
}
定时器初值的计算方法是:先确定换相周期,再根据晶振频率和定时器计数频率反推初值。例如 11.0592MHz 晶振下,定时器按 12 分频后计数频率为 921600Hz,如果希望每 1 毫秒换相一次,则计数初值应使定时器在 1ms 后溢出。通过调整初值即可改变转速。定时器方式让电机控制与主任务解耦,适合稍微复杂的 51 项目。
8. 基于 STM32 的驱动实现
8.1 硬件连接与 GPIO 配置
STM32 的 GPIO 输出能力较强,但依然不适合直接驱动步进电机,仍需要通过 ULN2003 进行功率放大。以 STM32F103C8T6 为例,可以使用 PB0、PB1、PB2、PB3 四个引脚接 ULN2003 模块的 IN1 到 IN4。STM32 的供电电压为 3.3V,而 ULN2003 输入高电平最低约 2.4V,因此 3.3V 逻辑电平可以直接驱动 ULN2003。若使用 5V 输入要求的特殊模块,则需要加电平转换,但市面上大多数 ULN2003 模块都兼容 3.3V 输入。
使用 STM32 HAL 库时,首先在 CubeMX 中配置四个 GPIO 为输出模式,输出速度选择 Low 即可,因为步进电机换相频率通常不高。需要注意的是,PB3、PB4 等引脚默认可能作为 JTAG 功能使用,如果使用这些引脚,需要在 CubeMX 中关闭相应调试功能或将引脚映射为普通 GPIO。
8.2 基于 HAL 的四拍控制
下面给出基于 HAL 库的双相励磁四拍程序:
#include "main.h"
#define IN1_GPIO_Port GPIOB
#define IN1_Pin GPIO_PIN_0
#define IN2_GPIO_Port GPIOB
#define IN2_Pin GPIO_PIN_1
#define IN3_GPIO_Port GPIOB
#define IN3_Pin GPIO_PIN_2
#define IN4_GPIO_Port GPIOB
#define IN4_Pin GPIO_PIN_3
void set_phase(uint8_t phase) {
HAL_GPIO_WritePin(IN1_GPIO_Port, IN1_Pin, (phase & 0x01) ? GPIO_PIN_SET : GPIO_PIN_RESET);
HAL_GPIO_WritePin(IN2_GPIO_Port, IN2_Pin, (phase & 0x02) ? GPIO_PIN_SET : GPIO_PIN_RESET);
HAL_GPIO_WritePin(IN3_GPIO_Port, IN3_Pin, (phase & 0x04) ? GPIO_PIN_SET : GPIO_PIN_RESET);
HAL_GPIO_WritePin(IN4_GPIO_Port, IN4_Pin, (phase & 0x08) ? GPIO_PIN_SET : GPIO_PIN_RESET);
}
void step_4phase(uint16_t steps, uint32_t delay_us) {
const uint8_t phases[4] = {0x03, 0x06, 0x0C, 0x09};
uint16_t i;
for (i = 0; i < steps; i++) {
set_phase(phases[i % 4]);
HAL_Delay(delay_us / 1000);
}
}
int main(void) {
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
while (1) {
step_4phase(2048, 2000);
HAL_Delay(1000);
}
}
set_phase 函数把四个引脚的电平按相位值的低四位设置。step_4phase 函数按正向序列循环走指定步数。由于 HAL_Delay 的单位是毫秒,如果需要微秒级精确延时,可以改用 DWT 延时或定时器。
8.3 基于定时器中断的精确换相
在 STM32 中,HAL_Delay 会阻塞 CPU,不适合实时控制系统。推荐使用定时器产生固定周期中断,在中断中换相。比如使用 TIM2 产生一个 1kHz 的中断,每次中断切换一拍。下面给出关键代码:
volatile uint8_t phase_index = 0;
const uint8_t phase_table[8] = {
0x01, 0x03, 0x02, 0x06,
0x04, 0x0C, 0x08, 0x09
};
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) {
if (htim->Instance == TIM2) {
phase_index = (phase_index + 1) % 8;
set_phase(phase_table[phase_index]);
}
}
void motor_init(void) {
HAL_TIM_Base_Start_IT(&htim2);
}
这样,定时器每隔设定的周期触发一次中断,完成一个半步。转速由定时器周期决定。通过修改定时器自动重装载值或预分频值,可以动态调速。定时器中断方式让步进电机运行完全在后台完成,主循环可以处理按键、屏幕、通信等其它逻辑。
8.4 加减速控制简介
在实际工程中,如果要求电机快速启动并高速运行,直接给定一个很高的换相频率,电机可能因为惯性无法跟上磁场变化而失步。解决方法是进行加减速控制。常见做法是使用梯形速度曲线:开始以较低频率启动,逐步提高频率到目标速度,保持一段时间,再逐步降低频率直到停止。这样可以减少失步和机械冲击。
在定时器中断方案中,可以通过函数在每次中断后逐步修改定时器周期。例如定义一个函数 motor_set_speed,根据加减速规划计算出当前期望周期,并写入定时器寄存器。对于 28BYJ-48 这种减速比很大、负载较慢的电机,加减速要求相对宽松,但如果在输出轴上加装了较重的负载,加减速控制仍然很有必要。
9. 转速、转矩与定时控制
9.1 转速计算方法
步进电机的转速由两个因素决定:驱动拍数和换相周期。对于四拍整步驱动,输出轴转一圈需要约 2048 步;对于八拍半步驱动,输出轴转一圈约需 2048 步,但每一步对应电机转子半个整步。设每拍延时为 T 秒,则输出轴每秒转数为 1 除以每圈步数与 T 的乘积。用公式表示,若每圈步数为 S,每拍间隔为 T 秒,则输出轴转速 R 转每秒等于 1 除以 S 与 T 的乘积。
例如,八拍驱动下 S 取 2048,如果每拍间隔 2 毫秒,即 0.002 秒,则输出轴每秒转动约 0.244 圈,每分钟约 14.6 圈。这个转速对于 28BYJ-48 来说已经属于较快状态。若把间隔缩短到 1 毫秒,转速约增加一倍,但也更容易出现失步。因此调速时应在不丢步的前提下逐渐缩短间隔。
9.2 最高转速与失步
步进电机存在最高启动转速和最高运行转速。最高启动转速是指电机在静止状态下能够直接启动而不丢步的最大速度;最高运行转速是在已经转动起来后,通过逐渐加速能够达到的最大速度。直接以过高的频率启动,会导致转子无法同步,出现原地抖动或反转。因此,需要高速运行时必须采用渐进加速。
28BYJ-48 由于自带减速箱,输出轴速度并不高,但其内部转子转速已经很高。实验时可以从每拍 3 至 5 毫秒开始,逐步缩短到 1 至 2 毫秒,观察电机是否正常。在最低速区,由于单相或双相励磁的磁势不连续,电机还可能出现明显的齿槽振动,这是正常现象。
9.3 转矩特性
步进电机在低速时转矩较大,随着转速升高,由于电感限制电流上升时间,线圈电流来不及达到额定值,转矩会明显下降。此外,双相励磁比单相励磁能产生更大的转矩,半步驱动则在平滑性和转矩之间取得平衡。对于 28BYJ-48 来说,内部减速齿轮进一步放大了输出转矩,适合驱动云台、阀门、小型机械臂等负载。
如果负载过重,电机会出现堵转。堵转时,线圈电流最大,ULN2003 和电机发热都会加剧。长时间堵转可能烧毁电机或芯片,因此在设计中应确保负载在电机额定转矩范围内,并避免机械卡死。对于需要堵转保护的项目,可以通过检测电流或检测速度下降来判断堵转。
9.4 定时参数与计算
在 51 或 STM32 中,定时器初值可以把“换相周期”精确转换为计数器的重载值。以 STM32 定时器为例,假设定时器时钟为 72MHz,经过 7199 分频后计数频率为 10kHz,即每个计数周期 100 微秒。若希望每 2 毫秒换相一次,则自动重装载值应为 20。这样通过修改分频系数和重装载值,可以灵活得到各种换相频率。
在计算时,要留出中断服务程序的执行时间。对于简单项目,中断服务只做查表和 GPIO 写,耗时很短,可以忽略;但如果中断中还包含复杂计算或有阻塞操作,则会干扰换相周期,引起转速抖动。因此中断服务函数应尽量简短。
10. 常见问题排查
10.1 电机完全不动
电机完全不动时,首先要检查电源是否在线,是否有共地。用万用表测量电机公共端到 GND 之间的电压,确认供电正常。然后检查 ULN2003 模块电源灯是否点亮。接下来检查四相接线顺序是否与代码一致,尤其是 IN1 到 IN4 是否按 A、B、C、D 的顺序连接。若接线混乱,电机虽然可能也会动,但会发生乱转或抖动。
还要确认代码是否真正把引脚配置为输出,并且在循环中不断切换相位。如果所有引脚长期保持同一个电平,电机只会锁定在某个位置,不会转动。可以用示波器或万用表测量四个输入引脚,观察电平是否按拍序变化。如果没有变化,问题在程序;如果输入变化但输出不变,问题在芯片或其供电。
10.2 电机嗡嗡响但不转
电机嗡嗡响但转子不转,通常是换相频率过高,导致失步;也可能是负载过重或电压不足,使转子跟不上磁场。解决方法是先增大每拍延时,从 5 毫秒开始尝试,确认能正常转动后再逐步减小延时。还要检查外接电源电流是否足够,如果使用 USB 或开发板 5V 供电,供电能力不足也会导致转不起来。建议使用独立电源,并且确保电源地和单片机地共地。
此外,如果四根相线中有一根接触不良或断线,电机也会出现断续转动、抖动或锁定异常。可以用万用表逐相测量公共端到各相之间的电阻,确认四相阻值一致。
10.3 电机发热
电机发热通常是因为长时间大电流工作或堵转。对于 28BYJ-48,正常空载运行时温度不会太高,但如果频繁正反转、快速启停或机械卡死,线圈持续通过较大电流,温度会明显上升。解决方法是降低工作电压、缩短重载运行时间、降低换相频率,或者在停止时让所有相关断,以减小待机电流。
ULN2003 芯片发热则通常与电流和导通时间有关。如果使用 12V 电机且电流较大,芯片可能明显发热。此时应检查是否有过多通道同时长时间导通,必要时在公共端串联限流电阻,或者更换更大电流等级的兼容芯片。对 5V 小电机而言,ULN2003 通常不会过热。
10.4 转动方向与预期相反
方向相反通常有两种原因:一是接线顺序与代码拍序不匹配,二是正反转逻辑写反。最简单的方法是交换两个相的接线,例如把 A 相和 C 相对调,或者把 B 相和 D 相对调;也可以在程序中把正向和反向数组互换。注意不要只交换一个相,那样会打乱磁场旋转顺序,导致电机抖动或无法正常转动。
10.5 转动时抖动明显
低速抖动在单相励磁下尤其明显,因为单相驱动转矩较小且磁场切换不连续。改用双相励磁或八拍半步驱动通常能明显改善平滑性。如果已经使用八拍驱动仍抖动,可以尝试少量调整每拍延时,避开与机械结构发生共振的速度点。另外,机械安装同心度差、齿轮间隙大也会造成抖动。
10.6 单片机复位或程序跑飞
电机频繁换相时,电源上的浪涌会通过电源线传递到单片机,可能导致复位。解决方法是电机使用独立电源,并在电机电源和逻辑电源之间做好去耦。必要时在单片机复位脚电容、电源端增加滤波,缩短电机引线,并避免电机线与信号线平行走线。对稳定性要求高的项目,还可以用光耦隔离电机电源和逻辑电源。
11. 进阶:多电机控制、位置控制与闭环
11.1 同时控制多个步进电机
一个 ULN2003 有 7 个通道,因此理论上可以驱动一个四相电机,并剩余 3 个通道。若要同时控制多个四相电机,最直接的方法是每个电机使用一个独立的 ULN2003 模块。例如两个电机需要 8 个控制 IO 口。对于 IO 口紧张的场合,可以使用移位寄存器拓展输出,例如 74HC595 串行转并行,通过 3 个 IO 即可控制多个 ULN2003 的输入,但软件复杂度会增加。
多电机控制时,关键是要避免同时大量换相造成瞬时电流过大。如果多个电机共用一个电源,电源的额定电流必须按所有同时导通相的总电流峰值再预留一定余量。软件上可以使用定时器轮询或分时换相,避免所有电机在同一时刻同时切换。
11.2 位置控制与步数统计
步进电机非常适合开环位置控制。程序只需维护一个步数计数器,每执行一拍就更新计数器。正转时计数器增加,反转时计数器减少。需要移动到指定角度时,把目标角度换算成步数,再让电机走到该步数即可。对于 28BYJ-48 八拍驱动,输出轴一圈约 2048 步,因此 90 度约为 512 步,180 度约为 1024 步。
位置控制程序可以限制电机在一个范围内摆动,例如 0 到 90 度之间往返。当步数达到上限时改变方向,达到下限时再改变方向。这种方式可以应用于小型机械臂、云台、门闸等场景。需要注意,由于开环控制没有反馈,一旦外力导致电机丢步,位置就会产生累积误差,因此需要定期归零或加装限位开关。
11.3 限位开关与原点校准
在实际设备中,通常会在运动范围的两端安装微动开关或光电开关作为限位。上电后先让电机缓慢朝原点方向运行,直到触发原点开关,然后把当前步数清零,作为位置基准。之后所有运动都相对于原点计算。限位开关直接并联在 ULN2003 输入上并接到单片机 IO 即可,程序需要读取开关状态并在触发时停止电机。
以云台为例,可以设置左右两个限位开关。当从左向右旋转到达右限位时,停止向右,只允许向左运动;反之亦然。这样可以避免机械结构越过安全范围损坏电机或机构。
11.4 引入编码器做闭环
步进电机虽然常用于开环,但在高可靠性系统中也可以加入编码器进行闭环控制。常见做法是在电机输出轴或末端机构上安装旋转编码器,实时读取实际角度,并与目标角度比较。如果误差超过阈值,则发出额外的补偿脉冲进行修正;如果实际速度明显低于设定速度,则判断为堵转并停机。
对于 28BYJ-48 这类成本敏感的小型电机,加编码器会显著增加成本和复杂度,因此通常只在必要场合使用。大多数简单项目依靠限位开关和合理的安全余量即可满足要求。
12. 实践项目:小型云台控制器
12.1 项目目标
下面设计一个利用 ULN2003 和 28BYJ-48 的小型云台控制器。云台能够左右往复转动,并可以通过按键改变转动范围。为了简化,假设使用 Arduino UNO,两个按键用于增大和缩小摆动角度,一个电位器用于调节速度。云台在 0 到设定角度之间往复摆动,接近两端时自动减速以减少冲击。
12.2 电路连接
电机通过 ULN2003 模块连接,IN1 到 IN4 接 Arduino 数字口 4、5、6、7。两个按键分别接数字口 2 和 3,并通过内部上拉电阻接到地,按下时读取到低电平。电位器中点接 A0,用于调节速度。电机电源使用独立 5V,GND 与 Arduino 共地。两个按键如果条件允许,可以换成安装在机械结构两端的限位开关,用于实际限位。
12.3 程序设计思路
程序使用八拍半步驱动,以非阻塞方式换相。在每次换相时更新步数计数器,并根据当前步数和目标角度判断是否反向。当接近端点时,动态增大换相间隔,实现减速;在中间区域使用由电位器决定的基准速度。按键用于增加或减小目标摆动角度,限制在 0 到 2048 步之间。
以下给出核心代码:
const int IN1 = 4;
const int IN2 = 5;
const int IN3 = 6;
const int IN4 = 7;
const int butPlus = 2;
const int butMinus = 3;
const int potPin = A0;
const int phases[8][4] = {
{1, 0, 0, 0},
{1, 1, 0, 0},
{0, 1, 0, 0},
{0, 1, 1, 0},
{0, 0, 1, 0},
{0, 0, 1, 1},
{0, 0, 0, 1},
{1, 0, 0, 1}
};
int phaseIndex = 0;
int targetSteps = 512;
int currentSteps = 0;
int direction = 1;
unsigned long lastStepMicros = 0;
void setPhase(int i) {
digitalWrite(IN1, phases[i][0]);
digitalWrite(IN2, phases[i][1]);
digitalWrite(IN3, phases[i][2]);
digitalWrite(IN4, phases[i][3]);
}
int readSpeed() {
int raw = analogRead(potPin);
return map(raw, 0, 1023, 3000, 800);
}
void setup() {
pinMode(IN1, OUTPUT);
pinMode(IN2, OUTPUT);
pinMode(IN3, OUTPUT);
pinMode(IN4, OUTPUT);
pinMode(butPlus, INPUT_PULLUP);
pinMode(butMinus, INPUT_PULLUP);
setPhase(phaseIndex);
}
void loop() {
if (digitalRead(butPlus) == LOW) {
targetSteps += 16;
if (targetSteps > 2048) targetSteps = 2048;
delay(80);
}
if (digitalRead(butMinus) == LOW) {
targetSteps -= 16;
if (targetSteps < 16) targetSteps = 16;
delay(80);
}
if (currentSteps >= targetSteps) direction = -1;
if (currentSteps <= 0) direction = 1;
int baseInterval = readSpeed();
int distanceToEnd = direction == 1 ? targetSteps - currentSteps : currentSteps;
if (distanceToEnd < 100) {
baseInterval = baseInterval + 800;
}
unsigned long now = micros();
if (now - lastStepMicros >= (unsigned long)baseInterval) {
lastStepMicros = now;
phaseIndex = (phaseIndex + direction + 8) % 8;
setPhase(phaseIndex);
currentSteps += direction;
}
}
该程序通过 buttons 调整摆动范围,通过电位器调节速度,并在接近两端时自动减速。由于使用了非阻塞换相,主循环可以继续扩展更多功能。这种结构也可以迁移到 51 或 STM32 上。
13. 实践项目:自动门或自动窗帘
13.1 项目背景
自动窗帘和自动门是步进电机非常典型的应用。通过 28BYJ-48 的减速输出轴带动绕线轮或齿轮,可以驱动窗帘开合或门板移动。相比普通直流电机,步进电机可以在没有编码器的情况下按设定步数走到指定位置,控制简单可靠,非常适合慢速平移机构。
13.2 机械结构建议
对于小型窗帘模型,可以使用 28BYJ-48 输出轴安装一个绕线轮,用线绳牵引窗帘。电机正转时窗帘打开,反转时关闭。通过限位开关或步数计数确定全开和全关位置。如果负载较重,可以增加齿轮组降低速度、增大扭矩,但要注意输出轴的径向受力不宜过大,必要时加装轴承支撑。
对于自动门模型,电机可以通过齿轮驱动齿条或皮带。左右两端各安装一个限位开关,程序在触发限位后停止,并记录当前位置作为基准。这样即使步进电机因外力丢步,也能在每次到达限位时自动校准,消除累计误差。
13.3 软件设计
软件可以采用“命令式”结构,定义 open、close、stop 三个状态。上电后先执行归零动作:缓慢关闭直到触发全关限位,然后清零步数。收到打开命令后,电机正向运行,直到触发全开限位或达到设定步数。到达端点后停止并切断所有相电流,以降低待机功耗和发热。
停止时切断所有相的做法是让四个输入全部输出低电平,使 ULN2003 全部截止。电机转子会停留在当前齿槽位置,但对于带减速箱的 28BYJ-48 来说,输出轴在无保持电流时仍可能被外力推动,因此如果需要严格保持位置,可以保留一相保持通电,这会消耗电流并产生发热,需要根据实际需求选择。
在代码中,使用非阻塞换相配合状态机,可以让控制器同时处理开关门命令、限位信号和电流保护。相比简单的延时循环,状态机结构更清晰,也更容易后续扩展为遥控或联网控制。
14. 进阶话题:细分驱动与替代芯片
14.1 ULN2003 的局限性
ULN2003 驱动步进电机虽然简单,但本质上是恒压驱动,无法控制线圈电流。随着换相频率提高,线圈电感使电流来不及建立,电机转矩迅速下降。此外,ULN2003 只能实现整步或半步驱动,不能实现更细的微步细分,因此对运动平滑度要求较高的应用,ULN2003 不是最佳选择。
对于 3D 打印机、数控设备等场合,通常使用专用的步进电机驱动芯片,例如 A4988、DRV8825、TMC2209 等。这类芯片采用恒流斩波技术,可以实现 1/16、1/32 甚至更高的细分,运转非常平滑,转矩也更大。但它们的成本、复杂度和电源要求都高于 ULN2003,不适合纯入门项目。
14.2 专用驱动芯片与 ULN2003 的对比
A4988 是双极步进电机驱动,需要两相四线或四线六线步进电机,电压范围约 8 至 35V,最大电流约 2A。DRV8825 支持更高的电压和电流,细分更多。TMC2209 则以静音和智能诊断著称。这些芯片通常需要方向引脚和脉冲引脚两个控制信号,协议与 ULN2003 的相序控制完全不同。
对初学者来说,从 ULN2003 入门理解步进电机原理非常合适,因为相序、磁场换相、失步等概念都可以直观观察。掌握这些基础后,再迁移到细分驱动芯片就会非常容易。反之,如果一开始就使用封装完善的高级驱动模块,反而容易忽略底层原理。
14.3 为什么入门仍推荐 ULN2003
ULN2003 加 28BYJ-48 的组合成本极低,接线简单,几乎不依赖外部复杂电路,代码可读性强。更重要的是,学习者可以通过 LED 状态或示波器直观看到四相依次通电的过程,真正理解“给一个脉冲走一步”的含义。这种对底层原理的直观理解,是后续学习高级驱动和闭环控制的坚实基础。
15. 总结与学习路径
本文从步进电机基本原理出发,详细介绍了 ULN2003 芯片的内部结构、引脚功能、电气参数和续流保护,随后分析了单相励磁、双相励磁和八拍半步驱动的时序,并给出了基于 Arduino、51 单片机和 STM32 的完整代码实现。文章还讨论了转速转矩计算、加减速控制、多电机协同、位置控制、限位开关和闭环控制,并通过云台和自动门两个项目展示了实际应用方法。
对刚入门的读者,建议按以下路径学习:第一步,只使用 Arduino 和 ULN2003 模块,跑通最简单的四拍程序,观察电机转动方向与拍序的关系。第二步,修改延时观察转速变化,体会最高启动频率和失步现象。第三步,把单相改成双相、四拍改成八拍,对比振动、噪声和转矩的差异。第四步,把代码迁移到 51 或 STM32,尝试用定时器中断实现非阻塞换相。第五步,完成云台或窗帘等带有位置控制的完整项目。第六步,根据项目需求,了解 A4988、DRV8825 等细分驱动芯片,向更高级的控制系统过渡。
ULN2003 驱动步进电机看似简单,却涵盖了功率驱动、感性负载保护、时序控制、定时器、位置控制和状态机等大量嵌入式基础知识。把这个经典组合彻底吃透,对后续学习电机控制、机械电子和机器人技术都会有很大帮助。希望本文能够为读者在步进电机控制方向的学习和实践提供一份系统而实用的参考。
更多推荐



所有评论(0)