基于FZ114单片机与ULN2003的步进电机控制系统设计实战
2026/9/8 4:50:14 网站建设 项目流程

大家好,分享一个很经典的机电控制实战项目——步进电机控制系统设计。

之前在做自动定位平台时,步进电机的控制始终是绕不开的核心环节。网上关于步进电机的资料很零散,要么只讲原理,要么只贴代码,很难把硬件接线、控制逻辑、软件实现串成一套完整方案。本文以 FZ114 控制板为基础,完整拆解一个步进电机控制系统的设计过程,从概念原理到电路连接,再到代码实现、运行调试,力求一篇文章讲透。

如果你正在做课程设计、毕业设计,或者刚开始接触步进电机控制,这篇教程可以直接对照实操,帮你少走弯路。

1. 认识步进电机控制系统

1.1 什么是步进电机

步进电机是一种将电脉冲信号转换为角位移或线位移的电动机。每接收一个脉冲,电机就旋转一个固定的角度,这个角度称为“步距角”。常见的两相混合式步进电机步距角为 1.8°,四相五线步进电机步距角通常为 5.625° 或 1.8° 不等。

步进电机的核心特点是:

  • 开环控制即可精确定位:在不丢步的情况下,旋转角度只与脉冲个数有关。
  • 速度可调:脉冲频率越高,转速越快。
  • 启停方便:只需控制脉冲的有无和方向电平,就能精确启动和停止。
  • 结构简单、维护方便:没有电刷,可靠性高。

这些特点使步进电机广泛应用于打印机、雕刻机、3D 打印机、小型机械臂、自动化产线、档案室智能环境控制设备等场景。

1.2 什么是控制系统

“步进电机控制系统”指的是由控制器(MCU)、驱动器、步进电机、反馈或辅助电路、人机交互模块组成的闭环或开环系统。控制系统的核心任务是:

  1. 生成规定频率和数量的脉冲信号。
  2. 控制电机旋转方向。
  3. 根据需求调节转速,并保证不丢步、不失步。
  4. 提供启动、停止、换向、调速等人机交互接口。

用 FZ114 控制板来做系统设计,本质上就是实现“MCU 基于定时器产生脉冲序列 → 驱动器放大信号 → 电机按相序旋转”的三级控制链路。

1.3 FZ114 控制板在系统中的角色

FZ114 是常见的 51 单片机学习/开发板,核心通常为 STC89C52RC 或 AT89C51 系列芯片。其片上资源包括 GPIO、定时器、UART、中断等,足以驱动四相步进电机。

在本文项目中,FZ114 的作用是:

  • 通过 P1 口或 P2 口输出电机相序控制信号。
  • 通过 ULN2003 芯片放大电流驱动五线四相步进电机。
  • 通过按键输入控制启停、方向、速度。
  • 通过 LED 或数码管显示当前状态。
  • 使用定时器中断实现精确的脉冲间隔控制。

整体系统结构可以概括为:

按键输入 → FZ114 单片机 → ULN2003 驱动板 → 步进电机 ↓ LED/数码管显示

2. 系统整体方案设计

2.1 功能需求分析

在设计控制系统之前,先明确系统需要实现的功能。本文项目定位为基础综合型控制实验,包含以下功能:

功能编号功能名称功能说明
F1电机启停控制按键控制电机转动或停止
F2正反转控制按键切换电机旋转方向
F3速度调节多档速度切换,脉冲频率可调
F4状态显示通过 LED 指示运行状态和档位
F5步进角度控制可设置转过的步数,实现精确角度定位

其中 F1~F4 为核心基本功能,F5 为扩展应用功能,体现“控制系统”的完整度。

2.2 方案选型与模块划分

系统从硬件角度分为四个模块:

  1. 主控模块:FZ114 单片机的 GPIO 和定时器,负责计算和时序输出。
  2. 驱动模块:ULN2003 达林顿管驱动板,负责电流放大。五线四相步进电机的公共端接 5V 电源,四相线圈分别接 ULN2003 的四个输出通道。
  3. 电机执行模块:28BYJ-48 型五线四相步进电机,减速比 1:64,内部步距角 5.625°,输出轴步距角约 0.088°。
  4. 人机交互模块:按键输入与 LED 状态指示。

软件部分采用模块化设计,后期方便移植和扩展:

main.c 主循环与控制逻辑 delay.c 定时/软件延时 motor.c 步进电机相序输出与控制 key.c 按键扫描与去抖 display.c LED/数码管显示 timer.c 定时器初始化与中断服务

2.3 控制方式选择

项目采用“查表法”驱动步进电机。将电机四相绕组的导通顺序整理为一张状态表,程序每延时一段时间,就将状态表的指针移向下一个位置,并把对应相序写到 GPIO。

这种方式相比直接计算脉宽更直观、更稳定,适合单片机资源有限的情况。本文使用八拍驱动方式,四相导通顺序为:

A → AB → B → BC → C → CD → D → DA

八拍方式比四拍方式转动手感更平滑,转矩波动更小。

3. 硬件设计与电路连接

3.1 元器件清单

本系统需要准备的硬件如下:

元器件型号/规格数量说明
主控板FZ114 单片机开发板1基于 STC89C52RC
步进电机28BYJ-48 五线四相1减速步进电机
驱动模块ULN2003 驱动板1可直接插接 28BYJ-48
按键轻触开关4启停、正反转、加速、减速
LED5mm 发光二极管4状态显示
电阻330Ω / 1kΩ若干LED 限流、上拉
电源USB 5V / 电源适配器1给 FZ114 和电机供电
连接线杜邦线/排线若干电路连接

FZ114 板上通常自带按键和 LED,可以直接复用,省去外部搭建。

3.2 FZ114 与 ULN2003 接线方法

28BYJ-48 步进电机自带的排线可以直插 ULN2003 驱动板。驱动板与单片机之间需要连接 6 根信号线:

  • IN1 → 单片机 P1.0(接 A 相)
  • IN2 → 单片机 P1.1(接 B 相)
  • IN3 → 单片机 P1.2(接 C 相)
  • IN4 → 单片机 P1.3(接 D 相)
  • 驱动板 VCC → 5V 电源正极
  • 驱动板 GND → 电源负极(与单片机共地)

接线顺序需要和电机相序表保持一致。如果接反,电机可能不转或振动,此时只需交换 IN1 与 IN4,或者将相序表倒序使用。

3.3 电源与共地注意事项

在步进电机控制系统中,电源设计非常重要。电机启动瞬间电流较大,如果使用 FZ114 板载电源,可能导致单片机复位。因此建议:

  • 单片机与电机驱动板使用独立 5V 电源,或者选用输出电流 1A 以上的 5V 适配器。
  • 所有模块必须共地,否则控制信号不存在回路,电机无法正常工作。
  • ULN2003 驱动板上的 VCC 接电机电源,不可接信号电源。
  • 单片机 GPIO 输出高电平约 3.3~5V,可以直接驱动 ULN2003 的输入端,无需额外放大电路。

在接线时务必断开电源,确认无误后再通电。避免短路和反接,这是硬件调试阶段最常见的事故原因。

4. 步进电机控制核心原理

4.1 相序与旋转方向

以五线四相步进电机为例,四相线圈分别命名为 A、B、C、D。八拍驱动时序真值表如下:

步骤A 相B 相C 相D 相GPIO 值
110000x01
211000x03
301000x02
401100x06
500100x04
600110x0C
700010x08
810010x09

如果按顺序输出这些状态,电机正转;如果按倒序输出,电机反转。

这里需要特别注意,表格中的 GPIO 值取决于接线顺序。如果你的电路接线是 IN1=P1.0,IN2=P1.1,IN3=P1.2,IN4=P1.3,那么上面表格可以直接使用。接线不同时,要根据实际硬件修改相序表。

4.2 转速计算与定时器设计

步进电机的转速由脉冲频率决定。假设 28BYJ-48 电机内部步距角为 5.625°,减速比为 1:64,那么输出轴旋转一圈需要:

(360° / 5.625°) × 64 = 4096 个八拍步骤

也就是说,每输出 4096 个相序状态,电机输出轴旋转一圈。如果每个状态的持续时间为 T 秒,那么:

转速 R(转/分钟) = 60 / (4096 × T)

例如,每个八拍步骤持续 2ms,则:

R = 60 / (4096 × 0.002) ≈ 7.32 转/分钟

如果要达到更高的速度,可以缩短每个状态的保持时间。但是,步进电机有响应频率上限,频率过高时电机会丢步甚至停转。因此调速时要兼顾平稳性和精度。

在 FZ114 单片机中,T 的精确控制建议使用定时器中断,而不是纯软件delay(),因为中断方式可以保证时序精度,同时主循环还能处理按键和显示任务。

4.3 加减速控制思路

步进电机启动时如果直接从零跳到高速,很容易失步。改进方法是采用“梯形加减速”曲线:

  • 启动时低频输出脉冲,电机逐步加速。
  • 运行中维持目标速度。
  • 接近目标位置时逐步减速,最后低速停止。

在实现中,每发送一定数量脉冲后,修改定时器重载值即可改变脉冲频率。虽然本文基础版没有加入完整加减速算法,但代码中预留了速度档位切换,扩展时只需要增加频率渐变逻辑即可。

5. 软件功能规划与开发环境

5.1 开发环境与工具链

本项目使用 Keil C51 作为编译器,配合 STC-ISP 烧录工具或开发板自带下载器。

工具说明
Keil uVision5C51 单片机集成开发环境
STC-ISP程序烧录工具
FZ114 开发板硬件测试平台
逻辑分析仪/示波器观察脉冲波形(可选)

版本需要根据你的实际开发板型号调整,本文使用 STC89C52RC 作为示例,重点演示控制思路,代码稍作修改即可移植到其他 51 单片机。

5.2 软件整体流程

系统上电后,程序执行如下流程:

  1. 初始化定时器、GPIO、变量。
  2. 检测按键状态,更新启动标志、方向标志、速度档位。
  3. 根据当前状态将相序数据写入 P1 口。
  4. 调用延时函数或等待定时器中断,实现脉冲间隔控制。
  5. 循环执行以上步骤。

使用定时器中断时,中断服务函数中翻转相序状态和 GPIO,主程序只负责按键扫描和目标参数更新。

5.3 程序文件划分

为了便于工程组织和后续移植,建议将程序拆分为多个文件:

FZ114_Motor/ ├── main.c ├── motor.c ├── motor.h ├── key.c ├── key.h ├── delay.c ├── delay.h └── timer.c

头文件负责声明函数和常量,源文件负责具体实现。这样后续添加脉冲计数、数码管显示、串口通讯等模块时,不会把主函数写成一堆混杂代码。

6. 完整代码实现

6.1 主程序 main.c

主程序完成系统初始化,并循环扫描按键,根据系统状态控制电机。

// 文件路径:main.c #include <REGX52.H> #include "motor.h" #include "key.h" #include "delay.h" sbit KEY_START = P3^0; // 启动/停止按键 sbit KEY_DIR = P3^1; // 正反转切换按键 sbit KEY_ADD = P3^2; // 加速按键 sbit KEY_SUB = P3^3; // 减速按键 unsigned char motor_run = 0; // 运行标志:1-运行,0-停止 unsigned char motor_dir = 1; // 方向标志:1-正转,0-反转 unsigned char speed_level = 3; // 速度档位:1-5 unsigned int step_delay = 1800; // 步与步之间的延时时间 void System_Init(void) { P1 = 0x00; // 初始关闭所有相 motor_init(); // 初始化电机 GPIO Timer0_Init(); // 初始化定时器 } void Key_Scan(void) { if(KEY_START == 0) { delay_ms(10); // 软件去抖 if(KEY_START == 0) { motor_run = !motor_run; if(motor_run == 0) { P1 = 0x00; // 停止时关闭所有相输出 } while(KEY_START == 0); // 等待按键释放 } } if(KEY_DIR == 0) { delay_ms(10); if(KEY_DIR == 0) { motor_dir = !motor_dir; while(KEY_DIR == 0); } } if(KEY_ADD == 0) { delay_ms(10); if(KEY_ADD == 0) { if(speed_level < 5) { speed_level++; step_delay -= 300; // 提高速度 } while(KEY_ADD == 0); } } if(KEY_SUB == 0) { delay_ms(10); if(KEY_SUB == 0) { if(speed_level > 1) { speed_level--; step_delay += 300; // 降低速度 } while(KEY_SUB == 0); } } } void main(void) { System_Init(); while(1) { Key_Scan(); // 定时器中断中根据 motor_run 和 motor_dir 自动输出脉冲 } }

这个主循环看似很短,但实际上电机相序输出已经放在定时器中断服务中,主循环只负责响应按键和更新参数。这样做的好处是电机控制不受按键扫描阻塞,转速更稳定。

6.2 电机控制模块 motor.c

电机模块的核心是根据运行标志和方向标志,控制相序状态表的指针移动。

// 文件路径:motor.c #include <REGX52.H> #include "motor.h" // 八拍驱动相序表 // 正转:A->AB->B->BC->C->CD->D->DA // 反转:DA->D->CD->C->BC->B->AB->A unsigned char code motor_phase_table[8] = { 0x01, // 0001 A 0x03, // 0011 AB 0x02, // 0010 B 0x06, // 0110 BC 0x04, // 0100 C 0x0C, // 1100 CD 0x08, // 1000 D 0x09 // 1001 DA }; unsigned char phase_index = 0; // 当前相序指针 bit motor_running = 0; // 运行标志 bit motor_direction = 1; // 方向标志 void motor_init(void) { P1 = 0x00; phase_index = 0; motor_running = 0; motor_direction = 1; } void motor_step(void) { if(motor_running == 1) { // 根据方向移动相序指针 if(motor_direction == 1) { phase_index++; if(phase_index >= 8) { phase_index = 0; } } else { if(phase_index == 0) { phase_index = 7; } else { phase_index--; } } // 输出当前相序到 P1 口 P1 = (P1 & 0xF0) | motor_phase_table[phase_index]; } } // 供外部设置运行状态 void motor_set_run(bit run) { motor_running = run; if(run == 0) { P1 = 0x00; // 停止时释放线圈 } } void motor_set_dir(bit dir) { motor_direction = dir; }

模块接口设计为motor_set_run()motor_set_dir(),主程序和按键模块通过这两个函数控制电机,不直接操作底层寄存器,代码组织结构更清晰,也更方便后期替换为步进电机驱动芯片的 API。

6.3 定时器模块 timer.c

定时器是保证脉冲精度的关键。本文采用定时器 0 工作方式 1,产生固定时间间隔中断。每次中断调用一次motor_step(),移动一个相序状态。

// 文件路径:timer.c #include <REGX52.H> #include "motor.h" extern unsigned int step_delay; // 步间延时(重载值) void Timer0_Init(void) { TMOD &= 0xF0; // 清空 T0 模式位 TMOD |= 0x01; // 定时器0,工作方式1(16位定时) TH0 = (65536 - step_delay) / 256; TL0 = (65536 - step_delay) % 256; ET0 = 1; // 使能 T0 中断 EA = 1; // 使能总中断 TR0 = 1; // 启动定时器 } void Timer0_ISR(void) interrupt 1 { // 重新装载定时初值 TH0 = (65536 - step_delay) / 256; TL0 = (65536 - step_delay) % 256; // 调用电机步进函数 motor_step(); }

定时器中断频率与速度档位直接相关。step_delay越大,定时器溢出越慢,电机转速越低。由于step_delay在 main.c 中定义并在按键扫描中修改,因此实现了速度调整。

需要注意的是,step_delay的值不能太小。如果小于电机启动频率对应的时间,电机会失步。如果小于中断服务函数执行时间,则系统无法正常工作,这是实际调试中需要重点关注的值。

6.4 数码管显示模块 display.c(扩展功能)

为了让系统状态更直观,可以增加 2 位数码管显示当前速度档位。使用定时器实现动态扫描,不需要额外 IC。

// 文件路径:display.c #include <REGX52.H> // 共阴数码管段码:0-9 unsigned char code seg_table[10] = { 0x3F, // 0 0x06, // 1 0x5B, // 2 0x4F, // 3 0x66, // 4 0x6D, // 5 0x7D, // 6 0x07, // 7 0x7F, // 8 0x6F // 9 }; sbit SEG_WE1 = P2^0; // 十位位选 sbit SEG_WE2 = P2^1; // 个位位选 unsigned char display_value = 0; void Display_SetValue(unsigned char val) { display_value = val; } void Display_Scan(void) { static unsigned char scan_index = 0; if(scan_index == 0) { SEG_WE1 = 1; SEG_WE2 = 0; P0 = seg_table[display_value / 10]; } else { SEG_WE1 = 0; SEG_WE2 = 1; P0 = seg_table[display_value % 10]; } scan_index = !scan_index; }

Display_Scan()需要放到定时器中断或者主循环中周期调用。需要注意的是,位选和段选引脚以实际开发板原理图为准,不同型号的 FZ114 板卡的数码管引脚定义可能存在差异。

7. 运行与调试验证

7.1 编译与烧录流程

在 Keil 中新建工程后:

  1. 选择芯片型号 STC89C52RC。
  2. 将上述源文件加入工程。
  3. 设置 Output 选项卡勾选 "Create HEX File"。
  4. 编译,生成 .hex 文件。
  5. 使用 STC-ISP 软件连接开发板,选择对应 COM 口。
  6. 烧录程序,复位开发板,开始调试。

7.2 功能验证步骤

烧录成功后,按下列步骤验证系统:

  1. 静态检查:上电后电机不转,P1 口输出 0x00,LED 无异常。
  2. 启动测试:按下启动按键,电机开始旋转,八拍相序表按顺序输出。
  3. 方向切换:按下方向按键,电机反转。
  4. 调速测试:按下加速按键,电机转速逐步提升;按下减速按键,转速降低。
  5. 停止测试:再次按下启动按键,电机停止,P1 口全部输出低电平。

如果电机不转,首先检查接线和相序表方向。用手轻轻触碰电机轴,如果感觉到有明显顿挫感但转不起来,说明相序输出不对,需要调整相序表或交换 IN1 与 IN4。

7.3 使用逻辑分析仪观察脉冲

高级调试可以使用逻辑分析仪观察 P1.0~P1.3 的波形,验证八拍时序是否正确。正常的波形应该是一组依次移位的方波,顺序为 1000 → 1100 → 0100 → 0110 → 0010 → 0011 → 0001 → 1001。如果波形顺序不一致,说明相序表或接线需要调整。

8. 常见问题与排查思路

在实际调试步进电机控制系统的过程中,遇到的问题往往集中在接线、相序、电流和软件时序几个方面。下面整理一张常见问题排查表:

问题现象常见原因解决思路
电机完全不动电源未接/共地不良检查 5V 电源,确认驱动板与单片机共地
电机嗡嗡响但不转相序表与接线不匹配交换 IN1/IN4,或修改相序表方向
电机能转但方向反了方向标志初始值或按键逻辑错误检查 motor_dir 初始值,或者交换相序表
电机转速不均匀定时器中断被主程序阻塞把相序输出放在中断里,不要用 delay 长延时
电机发热严重停止时线圈仍通电检查停止逻辑,确保 P1 输出 0x00
电机失步/抖动速度档位太高,超过启动频率降低最高速度档位,或加入加减速算法
程序烧录失败串口选择错误或开发板未断电复位检查 COM 口,烧录时按开发板复位键
单片机复位电机启动电流拉低电源电压使用独立电源,或加大电源电流

8.1 电机不转的通用排查流程

当电机不转时,建议按以下顺序排查:

  1. 看电源:万用表测量驱动板 VCC 与 GND 之间电压是否为 5V。
  2. 听声音:电机通电后是否有“嗡嗡”声或轻微振动,若有说明相序有输出。
  3. 测引脚:用万用表测 P1.0~P1.3 电压,启动后应该有 0V/5V 交替变化。
  4. 看波形:用逻辑分析仪确认相序表是否正确输出。
  5. 查程序:检查 motor_run 是否为 1,phase_index 是否正常递增。

8.2 注意单片机引脚驱动能力

51 单片机的 GPIO 直接驱动电流有限,虽然 AT89C51 或 STC89C52 的输出可以点亮 LED,但不足以直接驱动步进电机线圈,必须使用 ULN2003 之类的驱动芯片。如果试图直接接线,不仅电机不转,还可能损坏单片机端口。

9. 最佳实践与工程建议

9.1 硬件接线规范

  • 使用不同颜色杜邦线区分信号线和电源线,方便排错。
  • 电机驱动板的电源线尽量短,减少线路压降。
  • 电源输入端口可以并联一个 100μF 电解电容和 104 陶瓷电容,滤除高频干扰。
  • 不要在带电状态下插拔电机排线,否则容易烧毁驱动芯片。
  • 实验完成后,先关闭电源,再拆线。

9.2 软件健壮性设计

  • 按键去抖:机械按键在按下和释放时会产生抖动,必须做软件去抖,否则会误触发多次操作。
  • 边界条件:速度档位加减时要限制上下限,避免数组越界或定时器重载值过小。
  • 异常保护:如果电机堵转,程序无法直接感知,但可以通过电流采样或位置反馈实现堵转检测。对于开环系统,建议结合限位开关,避免机械结构超出边界。
  • 代码可移植性:不要直接在 main.c 中写死寄存器地址,建议封装为motor_set_run()motor_set_dir()这类接口函数。

9.3 生产环境注意事项

如果是小型自动化设备或课程设计之外的真实项目,建议注意以下几点:

  • 使用独立的电机驱动电源,与逻辑电源隔离。
  • 在驱动板和电源之间增加保险丝或自恢复保险丝,防止短路损坏。
  • 需要考虑电磁干扰,信号线使用双绞线或屏蔽线。
  • 长距离走线时,驱动信号建议使用光耦隔离。
  • 如果需要精确定位,建议改用带编码器的闭环步进电机驱动方案,例如常用 STM32 控制闭环步进电机驱动器的方案,别在开环上盲目追求高精度。

9.4 从基础实验到实际项目的升级路径

当基于 FZ114 的基础驱动实验成功后,可以沿以下方向继续提升:

  1. 使用专用驱动芯片:如 A4988、DRV8825,支持细分控制,可以显著降低振动和噪声。
  2. 使用定时器 PWM 方式:通过硬件 PWM 输出脉冲,减少 CPU 开销。
  3. 加入位置闭环:增加编码器反馈,实现精确的位置和速度闭环控制。
  4. 增加串口上位机:通过串口发送指令控制电机,替代按键输入。
  5. 切换至 STM32 平台:使用 TIM 输出比较功能输出脉冲,配合 DMA 和 HAL 库,扩展性更强。

10. 总结与后续学习路线

通过本文,我们完成了一个基于 FZ114 单片机与 ULN2003 驱动板的步进电机控制系统设计。从系统需求分析、硬件接线、驱动原理到代码实现和调试排错,已经形成一条完整的实操链路。你实际动手做一遍之后,对“单片机生成相序、驱动器放大电流、步进电机精确转动”这条控制链路的理解会变得非常具体。

如果再往深走,建议优先做三件事:

  • 学习细分的原理:为什么驱动器细分后振动降低、精度提升,这在机械结构中非常重要。
  • 动手测一遍步距角:在电机轴上贴指针,记录不同脉冲数对应的角度,验证 4096 步对应一圈的关系。
  • 研究加减速算法:特别是快速定位时的梯形或 S 形加减速,这是步进电机控制系统从玩具走向工业应用的关键环节。

如果本文对你有帮助,可以收藏备用。后续我也会继续分享基于 STM32 的闭环步进电机控制实战,欢迎持续关注。有问题欢迎在评论区交流,一起进步。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询