ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

51单片机驱动步进电机:从选型到稳定运行的完整实践

51单片机驱动步进电机:从选型到稳定运行的完整实践 1. 为什么现在还有人用51单片机驱动步进电机很多人第一次接触步进电机控制脑子里蹦出来的方案要么是STM32加驱动器要么直接上PLC。但如果你去翻一些小型自动化设备、教学实验平台或者老式仪表的电控部分会发现51单片机出现的频率依然很高。原因不复杂便宜、够用、生态老但稳。一片STC89C52几块钱加上一个ULN2003或者L298N模块再配一个28BYJ-48步进电机整套成本能压到二十块以内。对于只需要控制一个轴、转速要求不高、不需要复杂算法的场景这套组合的性价比几乎没有对手。我最初接触这个组合是在一个模拟项目里需要做一个能按固定角度旋转的展示转台。当时试过用Arduino开发确实快但批量做的时候成本压不下来。后来换回51单片机用C语言直接操作IO口反而把整个控制逻辑摸得更透了。这篇文章就是把这套东西从选型、接线、驱动原理到代码实现再到实际调试中会遇到的坑完整地梳理一遍。不管你是电子类专业的学生要做课程设计还是想给某个小设备加一个简单的定位功能这套方案都值得花时间搞明白。步进电机的核心特点是“给一个脉冲转一个固定角度”不像普通直流电机那样通电就狂转。这个特性决定了它天生适合做位置控制。51单片机要做的就是按一定的时间间隔依次给电机的各相绕组通电让电机一步一步转起来。听起来简单但实际做的时候脉冲时序怎么排、转速怎么调、怎么让电机不丢步每一个都是需要动手试过才知道的细节。2. 步进电机和51单片机之间的那点事2.1 步进电机的类型选择与驱动逻辑常见的步进电机分两类单极性和双极性。单极性电机每个绕组有一个中心抽头驱动电路简单用ULN2003这种达林顿管阵列就能直接推。双极性电机没有中心抽头需要H桥电路来改变电流方向扭矩更大但驱动复杂一些。对于51单片机入门来说28BYJ-48是最常见的选择它内部就是单极性结构减速比通常是1:64输出轴转一圈需要内部转子转64圈。28BYJ-48的步进角是5.625度这是内部转子的步进角。经过1:64的减速齿轮后输出轴的实际步进角是5.625/640.0879度。也就是说输出轴转一圈需要360/0.0879≈4096步。这个数字很关键后面写代码算角度的时候要用到。很多人第一次用的时候以为给4096个脉冲就能转一圈结果发现转多了或者转少了问题往往出在驱动方式上。驱动方式有四种单相励磁、双相励磁、单双相八拍、以及更复杂的细分驱动。单相励磁每次只给一相通电扭矩小但省电双相励磁每次给两相通电扭矩大但功耗高八拍是单相和双相交替步进角减半运行更平滑。对于28BYJ-48最常用的是八拍模式因为它在扭矩和平滑度之间取得了比较好的平衡。2.2 51单片机的IO口驱动能力与扩展方案51单片机的IO口是准双向口每个引脚拉电流能力大概在10mA到20mA之间灌电流能力稍强一些。而步进电机每相绕组的工作电流通常在100mA以上直接驱动是不可能的。所以必须加驱动芯片。ULN2003是七路达林顿管阵列每路能承受500mA电流输入兼容TTL电平正好用来做51单片机和步进电机之间的缓冲。ULN2003的接法很简单输入端接51单片机的四个IO口输出端接电机的四相线公共端接电源正极。电机电源和单片机电源要分开供电但共地。这一点很重要如果共用一组电源电机启动时的电流波动会拉低单片机电压导致复位或者程序跑飞。我试过用同一组5V电源电机一转单片机就重启后来加了独立的5V电源给电机问题立刻消失。如果要用更大功率的步进电机比如42步进电机ULN2003就不够了需要换成L298N或者专用的步进电机驱动器。L298N是双H桥能驱动双极性电机每路电流能到2A。但L298N的发热比较大需要加散热片。再往上就是TB6600这类专用驱动器支持细分和更高电压但成本也上去了。对于51单片机来说ULN2003加28BYJ-48是最匹配的组合简单、便宜、够用。2.3 脉冲时序与转速计算的实际关系步进电机的转速取决于脉冲频率。假设用八拍模式每一步的时间间隔是T那么输出轴转一圈需要4096步总时间就是4096×T。转速n转/分钟60/(4096×T)。如果T1ms那么n60/(4096×0.001)≈14.6转/分钟。这个速度对于展示转台来说差不多但对于需要快速定位的场景就太慢了。要提高转速就得缩短T。但T不能无限缩短因为电机有响应时间。28BYJ-48的响应频率大概在1000Hz左右也就是说T最小不能小于1ms。如果强行给更快的脉冲电机会发出嗡嗡声但不转这就是丢步。实际调试的时候我一般从2ms开始试逐步减小找到电机能稳定运行的最小T。这个值因电机个体差异而不同需要实测。还有一个细节加减速。如果电机从静止直接跳到最高速很容易丢步。正确的做法是让脉冲间隔从大到小逐渐变化给电机一个加速过程。停止的时候反过来从快到慢。这个在51单片机上用定时器中断实现比较方便每次中断改变定时器的重装值就行。3. 硬件搭建从原理图到面包板的完整过程3.1 元器件清单与选型理由先列一下我实际用的东西元器件型号数量备注单片机STC89C52RC1经典51内核资源够用晶振11.0592MHz1方便串口通信纯控制用12MHz也行驱动芯片ULN20031或者直接买ULN2003模块步进电机28BYJ-4815V供电带减速箱电源5V/2A2单片机一路电机一路电容100uF2电源滤波电阻1kΩ4IO口限流面包板1或者洞洞板焊接选STC89C52是因为它支持串口下载不用拔插芯片调试方便。晶振用11.0592MHz是为了以后如果要加串口通信波特率能算得比较准。ULN2003我直接买的模块上面已经集成了续流二极管和指示灯省得自己搭。28BYJ-48买的时候注意看减速比有的是1:16有的是1:64步进角不一样代码里的参数要对应改。电源这块要重点说一下。我一开始图省事用一个5V/2A的电源同时给单片机和电机供电结果电机一转单片机就复位。后来用示波器看电机启动瞬间电源电压跌到了3.8V单片机当然扛不住。换成两个独立电源后电压稳在5.1V问题解决。如果没有两个电源至少要在电机电源端并一个大电容比如470uF能缓解一下。3.2 接线步骤与常见错误接线顺序很重要先接低压控制部分再接高压驱动部分最后接电机。具体步骤把单片机插在面包板上接好晶振和复位电路。晶振两个引脚各接一个30pF电容到地复位引脚接10uF电容到VCC再接10k电阻到地。ULN2003模块的输入端IN1到IN4分别接单片机P1.0到P1.3。注意模块上一般有VCC和GNDVCC接5VGND接单片机地。电机插头插到模块的输出端。28BYJ-48的插头有五个引脚中间是公共端接5V正极另外四个是相线对应接OUT1到OUT4。电机电源正极接模块的公共端负极接单片机地。这里要注意模块上的公共端通常标着VCC或者5V但它是给电机绕组供电的不是给ULN2003芯片供电的。ULN2003芯片的VCC是另一个引脚别搞混。常见错误有这么几个一是电机插头插反了导致相序不对电机只抖动不转。这时候把插头反过来插就行。二是ULN2003的公共端没接电源电机完全没反应。三是单片机IO口和ULN2003输入之间没接限流电阻虽然ULN2003输入阻抗高但为了保险还是串一个1k电阻。四是忘了共地单片机和电机电源的地必须连在一起否则控制信号没有参考电平。3.3 用万用表快速验证接线是否正确接完线别急着上电先用万用表测一下。把万用表调到二极管档测电机插头的四个相线对公共端的正向压降应该都在0.6V左右。如果某个相线测出来是开路或者短路说明电机内部有问题。然后测ULN2003输出端对地的电阻应该是无穷大如果有阻值说明有短路。上电之后先不写程序用手动方式给ULN2003的输入端依次加5V信号看电机能不能一步一步转。具体做法是用一根杜邦线一头接5V另一头依次碰IN1、IN2、IN3、IN4每碰一次电机应该走一步。如果电机不转或者只抖动检查相序和电源。这个方法能快速定位是硬件问题还是软件问题。4. 代码实现从点亮一个IO口到电机转起来4.1 八拍时序表的推导与代码映射八拍模式的时序表是这样的步序IN1IN2IN3IN4说明11000A相通电21100AB相通电30100B相通电40110BC相通电50010C相通电60011CD相通电70001D相通电81001DA相通电这个表对应到51单片机的P1口就是八个字节0x01, 0x03, 0x02, 0x06, 0x04, 0x0C, 0x08, 0x09。把这八个值存在一个数组里循环取出来输出到P1口电机就会连续转动。正转就是按数组顺序取反转就是倒序取。这里有个细节P1口的高四位可能接了其他东西所以输出的时候最好用位操作只改变低四位。比如P1 (P1 0xF0) | phase[i]这样不会影响高四位。如果整个P1口都空着直接赋值也行。4.2 定时器中断产生脉冲的完整代码用定时器0产生脉冲工作方式116位定时。假设晶振11.0592MHz机器周期是12/11.0592≈1.085微秒。要产生1ms的定时需要计数1ms/1.085us≈921个机器周期。定时器初值就是65536-92164615即0xFC67。代码框架#include reg52.h unsigned char code phase[] {0x01,0x03,0x02,0x06,0x04,0x0C,0x08,0x09}; unsigned char step_index 0; unsigned int speed_delay 921; // 对应1ms void Timer0_Init() { TMOD 0xF0; TMOD | 0x01; // 定时器0方式1 TH0 (65536 - speed_delay) / 256; TL0 (65536 - speed_delay) % 256; ET0 1; EA 1; TR0 1; } void Timer0_ISR() interrupt 1 { TH0 (65536 - speed_delay) / 256; TL0 (65536 - speed_delay) % 256; P1 (P1 0xF0) | phase[step_index]; step_index; if(step_index 8) step_index 0; } void main() { Timer0_Init(); while(1); }这段代码烧进去电机就会以大约14.6转/分钟的速度连续转动。如果要调速改变speed_delay的值就行。值越小转速越快但不要小于921对应1ms否则可能丢步。4.3 正反转与角度控制的实现思路正反转很简单加一个方向标志位。在中断里根据标志位决定step_index是加还是减bit direction 1; // 1正转0反转 void Timer0_ISR() interrupt 1 { TH0 (65536 - speed_delay) / 256; TL0 (65536 - speed_delay) % 256; P1 (P1 0xF0) | phase[step_index]; if(direction) { step_index; if(step_index 8) step_index 0; } else { if(step_index 0) step_index 7; else step_index--; } }角度控制需要知道转了多少步。输出轴转一圈是4096步那么转90度就是1024步。可以设一个目标步数变量每中断一次减一减到零就停止定时器unsigned int target_steps 0; void Timer0_ISR() interrupt 1 { if(target_steps 0) { TR0 0; return; } TH0 (65536 - speed_delay) / 256; TL0 (65536 - speed_delay) % 256; P1 (P1 0xF0) | phase[step_index]; if(direction) { step_index; if(step_index 8) step_index 0; } else { if(step_index 0) step_index 7; else step_index--; } target_steps--; }要转90度就在主程序里设target_steps 1024; direction 1; TR0 1;。转完自动停。这个方法简单直接适合单次定位。如果要连续转不同角度可以在停止后重新设target_steps再启动。4.4 加减速曲线的简易实现直接全速启动容易丢步尤其是带负载的时候。简易的加减速思路是启动时speed_delay从大变小停止时从小变大。可以在中断里根据剩余步数动态调整unsigned int accel_steps 200; // 加速阶段步数 void Timer0_ISR() interrupt 1 { // ... 输出脉冲 ... if(target_steps accel_steps) { if(speed_delay 921) speed_delay - 2; // 加速 } else { if(speed_delay 2000) speed_delay 2; // 减速 } TH0 (65536 - speed_delay) / 256; TL0 (65536 - speed_delay) % 256; target_steps--; }这个逻辑比较粗糙但实测效果还行。加速阶段每步减少2个计数减速阶段每步增加2个。accel_steps设成总步数的一半左右比较合适。如果电机负载轻可以不用加减速直接全速跑也没问题。5. 调试过程中那些让人抓狂的瞬间5.1 电机只抖动不转的排查链路第一次接线通电电机发出“嗡嗡”声但就是不转这是最典型的问题。排查顺序如下先查相序。用万用表测电机插头的四个相线两两之间的电阻应该差不多。如果某两个之间电阻明显不同说明电机内部绕组有问题。然后查ULN2003输出端的相序确认IN1对应OUT1没有交叉。最后查代码里的时序表确认八个状态没有写错。我遇到过把0x02写成0x20的情况结果电机就是抖。再查电源。用万用表测电机公共端的电压应该在5V左右。如果跌到4V以下说明电源功率不够。28BYJ-48每相电流大概100mA四相全开就是400mA加上ULN2003的损耗电源至少要有1A的余量。最后查脉冲频率。如果speed_delay设得太小比如小于500电机也会只抖不转。把speed_delay改回2000试试如果转了说明是频率太高。慢慢减小找到临界值。5.2 丢步和失步的典型原因丢步是指电机应该转4096步转一圈结果只转了4000步。失步是指电机完全跟不上脉冲转着转着就停了。这两个问题的原因不太一样。丢步通常是负载太大或者加速度太快。28BYJ-48的保持扭矩大概300gf·cm如果转台上放了重物扭矩不够就会丢步。解决办法是降低转速或者加减速曲线。另外如果电源电压不足扭矩也会下降。我试过用4.5V供电丢步明显比5V严重。失步通常是脉冲频率超过了电机的响应极限。28BYJ-48的空载响应频率大概1000Hz带负载会降到600Hz左右。如果speed_delay设成500对应2kHz电机肯定失步。这时候需要降低频率或者换响应更快的电机。还有一个隐蔽的原因中断被其他高优先级中断打断。如果系统里还有串口中断或者外部中断可能会延迟定时器中断的响应导致脉冲间隔不均匀。解决办法是把定时器中断设为高优先级或者关掉其他不必要的中断。5.3 电源干扰导致单片机复位的解决办法电机启动瞬间电流突变会在电源线上产生尖峰。如果单片机和电机共用电源这个尖峰可能触发单片机的复位电路。表现就是电机一转单片机就重启串口不断输出启动信息。解决办法有三个层次。最简单的在电机电源端并一个470uF电解电容和一个0.1uF陶瓷电容吸收尖峰。中等方案单片机和电机用两个独立的5V电源只共地。彻底方案在单片机电源端加LC滤波电感用100uH电容用100uF能把干扰压到很低。我一开始用第一种方案电容并了之后复位次数明显减少但偶尔还会发生。后来换成独立电源彻底解决。如果做产品建议直接上独立电源省心。5.4 用示波器看脉冲波形的实际经验有条件的话用示波器看一下ULN2003输入端的波形。正常的八拍波形应该是四路方波相位依次错开。如果某一路波形上升沿很缓说明IO口驱动能力不足需要加缓冲。如果波形上有毛刺说明电源干扰大需要滤波。我还遇到过一种情况示波器上看波形完全正常但电机就是不转。后来发现是ULN2003模块上的指示灯坏了实际上芯片已经烧了。所以看波形只能确认控制信号没问题不能确认驱动芯片是好的。判断驱动芯片好坏的方法是用万用表测输出端对地的导通情况正常应该是截止的。6. 从能转到好用几个提升稳定性的细节6.1 电源分离供电的具体接法独立供电不是简单地把两个电源的正负极分别接上就行地必须共。具体接法单片机电源的GND和电机电源的GND用一根粗线连在一起越短越好。电机电源的正极只接ULN2003的公共端不接单片机。单片机电源的正极只接单片机和ULN2003的VCC不接电机。如果只有一个电源可以用一个DC-DC模块分出两路或者用两个7805稳压芯片输入并联输出分开。7805的输入输出都要加电容输入0.33uF输出0.1uF。这样两路电源之间有一定的隔离效果。6.2 光电隔离在低成本方案中的取舍光电隔离能彻底切断单片机和电机之间的电气连接抗干扰能力最强。但成本会增加一个6N137光耦大概两块钱四路就是八块比ULN2003还贵。对于28BYJ-48这种小电机干扰本身不大光电隔离有点杀鸡用牛刀。我的建议是如果电机功率小于5W电源分离就够了不用光耦。如果电机功率大于10W或者电源环境特别恶劣比如旁边有大功率设备那就上光耦。光耦的接法是单片机IO口接光耦的阳极阴极接地光耦的集电极接ULN2003输入发射极接地。这样单片机和ULN2003之间就没有直接电气连接了。6.3 程序上的看门狗与异常恢复51单片机内部有看门狗定时器启用之后如果程序跑飞看门狗会复位单片机。对于步进电机控制来说复位意味着电机停止位置丢失。所以看门狗要慎用。如果一定要用需要在复位后重新初始化位置或者加一个绝对位置传感器。更实用的异常恢复方法是在定时器中断里加一个计数器如果连续多次中断没有输出脉冲比如因为干扰导致IO口状态错误就强制重新初始化IO口和定时器。这个逻辑很简单但能解决大部分偶发故障。6.4 长时间运行的发热与散热处理ULN2003在四相全开的时候每路电流100mA压降大概1V功耗就是0.1W四路0.4W。这个功耗不大芯片摸上去温温的不用散热片。但如果驱动更大电流的电机比如每路500mA功耗就是2W芯片会很烫需要加散热片或者降低占空比。28BYJ-48本身发热很小连续转几个小时也只是微温。但如果环境温度高或者电机堵转温度会上升。堵转的时候电流会增大到正常值的两倍以上时间长了可能烧绕组。所以程序里最好加一个堵转检测如果电机应该转但实际没转通过检测脉冲反馈或者电流检测就停止输出并报警。7. 这套方案还能怎么玩几个扩展方向7.1 多轴联动的资源分配51单片机有四个IO口P0到P3。一个步进电机占四个引脚理论上可以控制八个电机。但定时器只有两个如果每个电机都要独立调速就需要多个定时器。解决办法是用一个定时器产生基准时间然后在中断里用软件计数器给每个电机分频。比如基准中断是100us电机1每10次中断走一步1ms电机2每20次中断走一步2ms。这样就能用两个定时器控制多个电机。多轴联动的时候要注意IO口的分配。P1口通常用来接电机因为P1口内部有上拉电阻驱动能力强一些。P0口是开漏输出需要外接上拉电阻才能输出高电平不太适合直接驱动ULN2003。P2和P3口可以备用但P3口有第二功能串口、中断等用的时候要避开。7.2 加一个串口指令控制给系统加一个串口接收功能就能用电脑或者手机发指令控制电机。51单片机的串口用定时器1做波特率发生器11.0592MHz晶振下9600波特率的初值是0xFD。串口中断里接收指令比如“F1000”表示正转1000步“R500”表示反转500步。解析指令后设置target_steps和direction启动定时器。串口指令的格式可以自己定简单点用单字符加数字复杂点用JSON。对于51单片机来说内存有限指令越简单越好。我一般用两个字节第一个字节是命令0x01正转0x02反转0x03停止后面跟两个字节的步数高字节在前。这样一帧就三个字节处理起来快。7.3 用限位开关做位置闭环开环控制最大的问题是丢步之后位置就错了。加一个限位开关或者光电传感器就能做简单的闭环。比如在转台的零位装一个光电开关每次上电先让电机往零位方向转直到触发开关然后把step_index清零作为绝对零位。之后所有的角度都相对于这个零位计算。限位开关接在单片机的外部中断引脚上比如P3.2。触发时产生中断在中断里停止定时器并清零step_index。注意开关信号要加去抖硬件上用RC滤波软件上在中断里延时10ms再确认。如果开关是机械式的抖动比较严重软件去抖是必须的。7.4 从51到STM32的迁移思路如果项目要求变高比如需要更高的转速、更细的细分、更多的轴51单片机就不够用了。这时候可以考虑迁移到STM32。迁移的核心变化是定时器更强大有PWM模式和编码器接口IO口更多驱动能力更强主频更高能跑更复杂的算法。但迁移不是重写很多逻辑是相通的。八拍时序表可以直接用只是输出方式从直接赋值IO口变成操作寄存器或者用库函数。定时器中断的框架也类似只是STM32的定时器配置更复杂一些。如果51上的代码已经调通了迁移到STM32主要是改底层驱动上层逻辑基本不用动。8. 一些个人体会这套51单片机加ULN2003加28BYJ-48的组合我前后用了大概两年做过转台、推杆、小型传送带几个模拟项目。最大的感受是简单的东西不一定好做把简单的东西做稳定了才是本事。步进电机控制的理论不复杂但实际调试中遇到的电源干扰、丢步、复位这些问题每一个都需要动手试才能找到原因。另外不要迷信代码。很多时候问题出在硬件上比如一根杜邦线接触不良或者电源功率不够。我遇到过调了一下午代码最后发现是ULN2003模块的电源线没插紧。所以调试的时候先查硬件再查软件能省很多时间。最后说一个实用技巧如果电机转动时有明显的噪音可以在每个脉冲之间加一个短暂的延时让电流有一个建立的过程。具体做法是在输出脉冲后延时10us再进入下一次中断。这个延时不影响转速但能明显降低噪音。我试过效果不错。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进