ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

51单片机驱动28BYJ-48步进电机:ULN2003接线与8拍控制程序详解

51单片机驱动28BYJ-48步进电机:ULN2003接线与8拍控制程序详解 简介基于51单片机的28BYJ-48步进电机驱动工程面向单片机入门与电机控制学习者解决四相五线步进电机正反转与调速控制问题工程以K1正转、K2反转的按键方式演示脉冲序列切换逻辑并兼顾ULN2003驱动芯片的典型接法。压缩包共13个文件、约26KB包含main.c源码、Keil工程文件uvproj/uvopt、可直接烧录的hex文件以及lst、m51、obj等编译中间文件整体结构清晰适合对照学习点动控制和编译流程。已有2099人学习使用资源体积小、内容聚焦适合快速上手。通过该工程可掌握步进电机四相绕组励磁顺序的编程实现理解正反转时脉冲相位变化还能借鉴延时参数调整转速并了解Keil下51单片机项目的组织方式为后续接入实际设备打下基础。1. 28BYJ-48的“慢”一步一步数得清的位置精度从哪来让28BYJ-48第一次转起来的人印象最深的不是转动而是它的慢。满速运行也像在一格一格地爬行肉眼都能看清每拍之间的停顿。这份慢来自两个数字的乘积减速比1/64、每拍标称步距角5.625°两个数相除之后输出轴每拍只走0.08789°。它和42、57步进电机那种“全速跑到丢步”的风格完全不同代价是高转速无缘换来的是开环就能守住接近一度分辨率的位置以及减速齿轮放大后的保持力矩正好适合51单片机的入门课程设计电磁摆、云台、小传送带都是它的主场。驱动它也犯不上上DRV8825这类细分驱动芯片四根相线、一块ULN2003加上51的四个IO就能跑起来。剩下要解决的就是一件事让这四根线按正确的顺序、按稳定的节拍通电这就是标题里说的“驱动程序”的本质。2. 4相8拍励磁把28BYJ-48的转动翻译成GPIO电平2.1 五根线里只有四根干活公共端决定电流方向28BYJ-48是四相永磁式步进电机引线却是五根。其中红线是公共端接电源正极剩下橙、黄、粉、蓝四根分别对应A、B、C、D四相绕组的引出端。任意时刻要让某一相“有电”电流路径都是电源正极到公共端再进绕组从该相引线流出最后被外部开关管拉到地。51的IO直接提供不了这条路径需要的吸流能力因此中间要插ULN2003它的核心是一排达林顿管输入接51的IO输出接电机相线IO输出高电平时内部导通把对应相线拉低到GND绕组才真正建成回路。这也是“灌电流”接法的由来——51高电平驱动能力弱让它去拉低远比让它推高可靠。这里必须明确一个容易混淆的电平约定在ULN2003这种接法里IO给1表示该相被接通到地相线上有电流IO给0表示该相断开。后面整张节拍表都按这个逻辑写。不少初学者把“IO1就是给绕组高电平”套进去结果电机纹丝不动其实绕组不需要IO去推IO只负责驱动ULN2003的输入端。2.2 8拍表的物理含义单相通电与双相通电交替四相电机最基本的转法叫单四拍按A到B到C到D到A的顺序每次只给一相通电每换一次相转子转一个整步。只做四拍也能转但每拍之间转矩波动大低速抖得厉害。更常见的做法是1-2相励磁也叫8拍或半步模式单相通电和双相通电交替出现顺序如下表。拍序橙A黄B粉C蓝D写入P1低四位(十六进制)110000x01211000x03301000x02401100x06500100x04600110x0C700010x08810010x09表里的1代表该相导通。第1拍单A第2拍A和B同时通第3拍单B第4拍B和C同时通……交替下来每换一拍转子只前进半个整步而且换向过渡时至少保持一相通电转矩曲线比四拍平滑得多。这八拍走完后转子位置正好比起点前进一个整步对应的角度循环执行这八拍就是连续转动。2.3 反转就是反着查表要反转不需要换硬件、不需要改接线只要把查表方向倒过来。正转按索引0、1、2……7顺序取反转按0、7、6……1顺序取物理上等价于把A到B到C到D改成A到D到C到B。这也顺手回答了一个高频疑问“步进电机回原方向怎么调”绝大多数情况下软件里改一个dir变量就能换向如果程序本身没问题直接把橙、黄、粉、蓝里任意两根相线对调方向也会翻转。两个手段都等价于改变励磁序列的行进方向选哪个都不会伤电机。2.4 从表到程序把8拍存成查找表表格落到C51里就是8字节的无符号数组表里存的不是四组独立的高低电平而是低四位打包后的IO状态一次写P1口就能换一拍。这种查表法比逐位sbit赋值少写二十行代码也避免了“在中断里连续写四个端口导致中间状态出错”的问题。数组本身没有复杂度真正的复杂度在后面什么时候换下一拍以及换了几拍。下一章用定时器中断来解决这两个问题。3. 51单片机直驱ULN200328BYJ-48驱动程序的完整C51实现3.1 接线与最小硬件清单51单片机引脚ULN2003模块28BYJ-48电机引线P1.0IN1橙P1.1IN2黄P1.2IN3粉P1.3IN4蓝外接5V/模块VCC公共端红硬件上有三个注意点。第一51和ULN2003必须共地否则输入电平没有参考点达林顿管可能一直误导通。第二28BYJ-48额定5V启动瞬间电流能到300mA以上不要从开发板的USB口取电还指望带负载USB口带几个LED发光管能亮带电机就可能把供电电压拉低导致单片机反复复位。常见做法是给电机单独接一路5V电源和开发板共地。第三部分ULN2003模块上有两个电源端子一个给逻辑电路一个给电机如果丝印区分两个都接5V即可。3.2 用Timer0产生节拍而不是用delay()驱动步进电机最直接的想法是for循环里换相加延时。delay在单任务演示里没问题但转90度停住这种动作会占死CPU。用中断方式换拍由定时器触发主程序可以在等待节拍的间隙轮询按键、刷新数码管这是“驱动程序”和“演示程序”的分界线。完整程序如下MCU按AT89C52或STC89C52编写晶振12MHz。#include REG52.H #define MOTOR_PORT P1 // 1-2相励磁8拍表1表示该相接GND绕组通电 unsigned char code STEP_TABLE[8] { 0x01, 0x03, 0x02, 0x06, 0x04, 0x0C, 0x08, 0x09 }; bit motor_running 0; // 1:正在转动 bit motor_dir 1; // 1:正转 0:反转 unsigned char step_index 0; // 当前拍索引 0~7 unsigned int step_count 0; // 已走拍数 unsigned int step_target 0; // 目标拍数 void Timer0_Init(void) { TMOD 0xF0; TMOD | 0x01; // Timer0方式116位 TH0 (65536 - 2000) 8; // 2000个机器周期 2ms TL0 (65536 - 2000) 0xFF; ET0 1; TR0 1; EA 1; } void Timer0_ISR(void) interrupt 1 { TH0 (65536 - 2000) 8; TL0 (65536 - 2000) 0xFF; if (!motor_running) return; if (motor_dir) step_index (step_index 1) 0x07; // 正转索引1 else step_index (step_index 7) 0x07; // 反转索引-1 MOTOR_PORT (MOTOR_PORT 0xF0) | STEP_TABLE[step_index]; step_count; if (step_count step_target) { motor_running 0; // 到位停止换相 MOTOR_PORT 0xF0; // 四相全断释放转子 } } void Motor_Run(unsigned int steps, bit dir) { step_target steps; step_count 0; step_index 0; // 从第1拍开始 motor_dir dir; motor_running 1; } void Delay_ms(unsigned int ms) { unsigned int i, j; for (i 0; i ms; i) for (j 0; j 123; j); } void main(void) { Timer0_Init(); while (1) { Motor_Run(4096, 1); // 正转一整圈 while (motor_running); Delay_ms(500); Motor_Run(2048, 0); // 反转半圈 while (motor_running); Delay_ms(500); } }代码做了几件关键的事。定时器重装值2000对应2ms节拍12MHz晶振下机器周期1us2000个周期正好2ms若换11.0592MHz晶振重装值要按“晶振频率除以12再乘以节拍秒数”换算。正转用step_index加1再与0x07按位与等价于对8取模反转用step_index加7等价于减1后取模。写入端口时先保留P1高四位不变只改低四位避免影响P1口上可能的LED或按键。最重要的一处是停止动作MOTOR_PORT 0xF0把四根相线全部置0等效于断开所有绕组。新手最容易踩的坑是把停止写成MOTOR_PORT | 0x0F以为“全给高电平就是全通”结果四相同时导通电流很大电机却因为四相合成力矩为零而死死卡住摸上去烫手逻辑上还查不出问题。3.3 把“转多少”翻译成“走多少拍”调用Motor_Run之前第一步做角度到拍数的换算。28BYJ-48在8拍驱动下输出轴一圈4096拍所以拍数等于角度除以0.08789近似等于角度乘4096再除以360。主程序里不堆浮点常用做法是预计算常用角度对应的拍数360度是4096拍90度是1024拍45度是512拍。要求更灵活就写一个整数换算函数把角度放大10倍传入避免小数运算。// 角度转拍数angle_x103600 表示360.0度 unsigned int Angle_To_Steps(unsigned int angle_x10) { unsigned long tmp; tmp (unsigned long)angle_x10 * 4096UL / 3600UL; return (unsigned int)tmp; }这个函数的参数说明很简单3600对应360度900对应90度。用无符号长整型做中间计算防止16位乘法溢出返回时截断到unsigned int因为4096最大值远小于65535。调用时写成Motor_Run(Angle_To_Steps(900), 1)代码语义就变成“正转90度”比直接填1024可读性好得多。3.4 回原方向与限位回零的软件做法“回原方向怎么调”落到工程上不只是换方向还包括回原点。开环步进系统没有位置检测上电后机械零点未知常见做法是装一个行程开关或光电开关上电后朝固定方向走直到IO读到开关置位这个位置就是机械零点。sbit LIMIT_SW P3^2; // 限位开关默认高电平碰到后拉低 void Motor_Home(void) { while (LIMIT_SW) { // 没碰到限位 Motor_Run(1, 0); // 反向走1拍 while (motor_running); } Motor_Run(50, 1); // 正向离开限位一小段 while (motor_running); step_count 0; // 以此为机械零点 }这是最简单的减速扫描式回零适合对回零时间不敏感的小机构。如果机构行程长希望快速回零需要双速方案先快速反转接近限位检测到信号后慢速正转脱离再慢速反压一次消除齿轮背隙。51上做成状态机也不复杂核心仍是这个Motor_Run单拍接口。4. 28BYJ-48的转速计算与脉冲当量节拍时间和位移怎么对上4.1 先算清楚一圈多少拍先固定前提8拍半步模式、减速比1/64、电机标称步距角5.625度。输出轴每拍角度是5.625除以64约0.087890625度那么一整圈拍数就是360除以这个值得到4096。这也是多数资料里28BYJ-48最常用的“一圈步数”。部分卖家参数页或教程写2048多半是把驱动模式理解成4拍或把减速比记成了1/32。到手后建议拿输出轴对准一个刻度程序慢速走一圈数拍子以实测为准。4.2 节拍时间与RPM的换算表节拍时间T就是中断里那个2000对应的毫秒数。转一圈拍数固定4096转速算法是每分钟60000毫秒除以“一圈拍数乘以节拍毫秒数”即RPM等于60000除以128? 不对是60000除以(4096乘T)。T为2ms时60000除以8192约7.3转每分钟。不同节拍的转速和适用场景如下。节拍T(ms)一圈耗时(s)转速(RPM)适用场景14.1014.6空载可跑带负载易失步28.197.3常用兼顾速度与扭矩520.482.9稳定适合课程演示1040.961.5几乎可逐拍观察定时器重装值与节拍的换算也要同步重装计数值等于晶振频率除以12再乘以节拍秒数。12MHz、2ms节拍就是20005ms节拍就是5000把代码里的宏或常量改掉即可。4.3 节拍不是越短越好失步边界与加减速28BYJ-48是永磁式减速步进电机转子本身惯量加上减速齿轮决定了它启停不能太急。节拍从2ms降到1ms转速翻倍但每拍给转子的建立时间也减半负载稍大就会丢步表现为电机“嗡嗡”响甚至只抖不转。这是失步不是故障。工程上的标准做法是梯形加减速启动时用长节拍比如2ms运行若干拍后逐步缩短到目标节拍快到位前再逐步加长。代码里把定时器重装值做成变量就行。unsigned int step_delay 2000; // 当前节拍计数值 // 每走64拍把节拍缩短50us直到下限1000(约1ms) void Speed_Update(void) { if (step_count 64) return; // 起步阶段保持2ms if (step_delay 1000) step_delay - 50; // 加速段 if (step_count step_target - 64) step_delay 50; // 减速段 }这里step_delay要参与定时器重装中断里不能再写死2000。起步保持长节拍中间逐渐加速接近目标再减速这样能把丢步概率降到最低。加减速曲线按项目负载调整50us这个步进量适合空载云台带重负载时改小到10us。4.4 脉冲当量把拍数换算成毫米位移脉冲当量指一个脉冲也就是一拍执行机构末端走过的距离。在51加28BYJ-48加同步轮的组合里答案等于同步轮周长除以4096。假设同步轮直径20mm周长约62.83mm每拍位移约0.01534mm想让负载移动100mm就是100除以0.01534约6518拍。写成函数如下。// 目标位移mm - 拍数wheel_diameter为同步轮直径mm unsigned long Distance_To_Steps(unsigned int mm, unsigned int wheel_diameter) { float circumference 3.14159f * (float)wheel_diameter; float step_distance circumference / 4096.0f; return (unsigned long)((float)mm / step_distance); }这个函数用浮点方便人读在51上会引入浮点库实际项目中如果只用到固定直径直接把step_distance算成常量比如0.01534用整数乘除替代浮点更省ROM。脉冲当量这个概念在连PLC、连上位机的场合尤其重要上位机下发“走100mm”下位机先算出6518拍再交给Motor_Run执行。4.5 这类小电机不需要碰FOC闭环步进、FOC这类词通常出现在57、86大扭矩电机的场景里。28BYJ-48做FOC没有现实意义它力矩小、减速齿轮背隙明显、机械响应带宽低FOC换回来的动态性能被齿轮和背隙吃掉大半。对课程设计开环加限位回零已经能满足绝大多数要求真要闭环在输出轴上加一个反射式光电编码器做位置校正即可不必上电流环。5. 四个验证技巧数拍、回零、消抖、保持5.1 用LED和示波器数出4096拍程序写没写对不要只看电机转不转。把P1.0到P1.3接四个LED转动时能看到LED按走马灯方式循环点亮这是最廉价的8拍时序验证。要更精确在中断里加一个标志位让P1.7每8拍翻转一次sbit P1_7 P1^7; bit sync_flag 0; if (step_index 0) sync_flag ~sync_flag; P1_7 sync_flag;P1.7每8拍翻转一次示波器上每16拍出现一个完整方波周期输出轴转一圈共4096拍所以能看到256个完整方波周期。数方波永远比数脉冲容易。Proteus仿真51单片机也能搭这个电路虚拟示波器挂P1.7先确认波形周期对再上实物能省掉大量排查硬件的时间。5.2 回零消抖两次读IO确认限位开关在机械触点上会产生抖动程序只读一次IO就判零回零位置可能每次差几拍。常见做法是连续读两次配合短暂延时bit Home_Detected(void) { if (LIMIT_SW 0) { Delay_ms(5); if (LIMIT_SW 0) return 1; } return 0; }5ms延时对弹簧片限位足够消除触点抖动对光电开关可以适当缩短到1ms。回零完成后把step_count清零后续正转反转都以这个点为参考。方向调反了也不用改硬件把Motor_Run的dir参数取反或将相线任意对调两根。5.3 停止保持的两种策略与发热判断程序停转后四相全断是省电做法但转子失去保持力外力能拨动输出轴。如果机构要求停止时锁住位置不要一直给同一拍通电那样绕组持续发热30分钟表面温度就会明显升高。工程做法是周期性刷新每20ms重新写一次当前拍IO大部分时间处于断开状态保持力在温升可控。void Hold_Position(void) { // 每20ms调用一次保持当前拍 MOTOR_PORT (MOTOR_PORT 0xF0) | STEP_TABLE[step_index]; }28BYJ-48本身是长期通电会有温升的电机正常运行五六十度不算异常手摸上去热但不烫。如果烫到不能持续握住说明节拍太快或保持电流太大把节拍从2ms放宽到5ms或者改用电平保持再试。验证完整套驱动逻辑分析仪抓P1.0到P1.3波形看到0001、0011、0010、0110、0100、1100、1000、1001严格循环4096拍对应一圈程序就算真跑通了。本文还有配套的精品资源点击获取
返回列表