✔零知IDE 是一个真正属于国人自己的开源软件平台,在开发效率上超越了Arduino平台并且更加容易上手,大大降低了开发难度。零知开源在软件方面提供了完整的学习教程和丰富示例代码,让不懂程序的工程师也能非常轻而易举的搭建电路来创作产品,测试产品。快来动手试试吧!
✔访问零知实验室,获取更多实战项目和教程资源吧!
目录
[1.1 器件清单](#1.1 器件清单)
[1.2 接线方案](#1.2 接线方案)
[1.3 硬件连接图](#1.3 硬件连接图)
[1.4 实物接线图](#1.4 实物接线图)
[2.1 库文件及初始化](#2.1 库文件及初始化)
[2.2 电压校准算法](#2.2 电压校准算法)
[2.3 状态机按键处理](#2.3 状态机按键处理)
[2.4 电位器模拟值处理](#2.4 电位器模拟值处理)
[2.5 完整代码](#2.5 完整代码)
[3.1 系统连接](#3.1 系统连接)
[3.2 校准测试](#3.2 校准测试)
[3.3 功能验证](#3.3 功能验证)
[4.1 ULN2003AN驱动芯片](#4.1 ULN2003AN驱动芯片)
[4.2 28BYJ-48步进电机](#4.2 28BYJ-48步进电机)
[Q1: 电位器控制不线性如何调整?](#Q1: 电位器控制不线性如何调整?)
[Q2: 显示屏显示异常怎么办?](#Q2: 显示屏显示异常怎么办?)
[Q3: 按键无响应如何解决?](#Q3: 按键无响应如何解决?)
[Q4: 电机振动大且噪音明显?](#Q4: 电机振动大且噪音明显?)
(1)项目概述
本项目基于STM32F407VET6零知标准板开发了一套完整的步进电机智能控制系统,通过电位器实现精确的速度控制,按键控制方向切换,并配备1.3寸TFT显示屏实时显示系统状态。系统采用ULN2003AN驱动芯片控制28BYJ-48步进电机,实现了平滑的速度调节和精准的位置控制。
(2)项目亮点
>自动识别电位器有效电压范围(3.4V-4.0V),实现线性速度映射
>采用状态机设计,确保按键响应稳定可靠
>1.3寸TFT显示屏提供丰富的状态信息显示
>指数曲线速度映射算法,低速控制更精准
(3)项目难点及解决方案
问题描述:电位器在3.4-4.0V范围内电机才转动
解决方案:
实现电压范围重新映射算法,通过校准参数自适应不同电位器特性
问题描述:28BYJ-48步进电机在低速时扭矩不足
解决方案:
设置最小启动速度(MIN_SPEED),并采用指数曲线速度映射
一、硬件系统设计
1.1 器件清单
规格型号 | 数量 | 备注 |
---|---|---|
零知增强板 | 1 | STM32F407VET6主控芯片 |
28BYJ-48 | 1 | 5V减速步进电机 |
ULN2003AN驱动板 | 1 | 达林顿管阵列驱动 |
1.3寸TFT LCD (ST7789) | 1 | 240×240分辨率 |
10KΩ旋转电位器 | 1 | 线性电位器 |
6×6mm轻触开关 | 1 | 常开型 |
杜邦线 | 若干 | 母对母、母对公 |
1.2 接线方案
根据代码定义进行以下接线操作、如需定义为其他引脚请自行修改代码:
(1)电机驱动部分接线
零知增强板引脚 | ULN2003AN驱动板 | 28BYJ-48步进电机 | 功能 |
---|---|---|---|
8 | IN1 | / | 电机控制 |
9 | IN2 | / | 电机控制 |
10 | IN3 | / | 电机控制 |
11 | IN4 | / | 电机控制 |
/ | 排线连接 | 排线连接 | 电机驱动 |
5V(外部) | + | / | 电机电源 |
GND | - | / | 电机地线 |
(2)ST7789显示屏接线
| 零知增强板引脚 | ST7789引脚 | 功能 |
| 3.3V | VCC | 电源 |
| GND | GND | 地 |
| 7 | CS | 片选 |
| 6 | DC | 数据/命令控制 |
| 5 | RES | 复位 |
| 12 | SDA | SPI数据线 |
13 | SCL | SPI时钟线 |
---|
(3)电机操作部分接线
零知增强板引脚 | 电位器 | 按键 | 功能 |
---|---|---|---|
5V | 左侧引脚 | / | 供电 |
GND | 右侧引脚 | / | 地线 |
A0 | 中间引脚 | / | 信号输入 |
2 | / | 高电平一端 | 信号输入,内部上拉 |
ps:电机需要消耗大量电力,最好直接从外部 5V 电源供电
1.3 硬件连接图

1.4 实物接线图

二、软件架构设计
2.1 库文件及初始化
cpp
// 主要库文件引入
#include <AccelStepper.h> // 步进电机控制库
#include <Adafruit_GFX.h> // 图形显示库
#include <Adafruit_ST7789.h> // ST7789显示屏驱动
#include <SPI.h> // SPI通信库
#define TFT_CS 7 // 显示屏片选
#define TFT_SCL 13 // 显示屏时钟
#define TFT_SDA 12 // 显示屏数据
#define TFT_DC 6 // 显示屏数据/命令
#define TFT_RST 5 // 显示屏复位
#define BUTTON_PIN 2 // 按键引脚(内部上拉)
#define POT_PIN A0 // 电位器模拟输入
2.2 电压校准算法
cpp
// 电压校准参数 - 关键创新点
#define POT_MIN_VOLTAGE 3.4 // 电机启动最小电压
#define POT_MAX_VOLTAGE 4.0 // 电机最大速度电压
// 计算对应的ADC值范围
#define POT_MIN_ADC (int)((POT_MIN_VOLTAGE / 5.0) * 1023)
#define POT_MAX_ADC (int)((POT_MAX_VOLTAGE / 5.0) * 1023)
// 速度映射函数
if (potValue <= POT_MIN_ADC) {
targetSpeed = 0; // 低于最小电压停止
} else if (potValue >= POT_MAX_ADC) {
targetSpeed = MAX_SPEED; // 高于最大电压全速
} else {
// 线性映射计算
targetSpeed = map(potValue, POT_MIN_ADC, POT_MAX_ADC, MIN_SPEED, MAX_SPEED);
}
2.3 状态机按键处理
cpp
// 按键状态枚举
enum ButtonState { IDLE, PRESSED, DEBOUNCING };
ButtonState buttonState = IDLE;
void handleButton() {
int buttonReading = digitalRead(BUTTON_PIN);
switch (buttonState) {
case IDLE:
if (buttonReading == LOW) {
buttonState = DEBOUNCING;
lastButtonTime = millis();
}
break;
// ... 状态处理逻辑
}
}
2.4 电位器模拟值处理
cpp
void handlePotentiometer() {
// 读取电位器值
int potValue = analogRead(POT_PIN);
// 只有当电位器值显著变化时才处理
if (abs(potValue - lastPotValue) > 3) {
lastPotValue = potValue;
if (DEBUG_MODE && millis() % 500 < 10) {
float voltage = (float)potValue / 1023.0 * ADC_REF_VOLTAGE;
Serial.print("Pot ADC: ");
Serial.print(potValue);
Serial.print(" | Voltage: ");
Serial.print(voltage);
Serial.print("V");
}
// 重新映射电位器值到速度范围
if (potValue <= POT_MIN_ADC) {
// 低于最小电压,电机停止
targetSpeed = 0;
} else if (potValue >= POT_MAX_ADC) {
// 高于最大电压,电机全速运行
targetSpeed = MAX_SPEED;
} else {
// 在有效范围内线性映射
targetSpeed = map(potValue, POT_MIN_ADC, POT_MAX_ADC, MIN_SPEED, MAX_SPEED);
}
if (DEBUG_MODE && millis() % 500 < 10) {
Serial.print(" | Target Speed: ");
Serial.println(targetSpeed);
}
// 平滑调整当前速度到目标速度
if (abs(targetSpeed - currentSpeed) > SPEED_RAMP_RATE) {
if (targetSpeed > currentSpeed) {
currentSpeed += SPEED_RAMP_RATE;
} else {
currentSpeed -= SPEED_RAMP_RATE;
}
} else {
currentSpeed = targetSpeed;
}
// 更新速度显示
updateSpeedDisplay();
}
}
2.5 完整代码
cpp
// Include the necessary libraries
#include <AccelStepper.h>
#include <Adafruit_GFX.h>
#include <Adafruit_ST7789.h>
#include <SPI.h>
// Define step constant
#define MotorInterfaceType 8
// Display pins
#define TFT_CS 7
#define TFT_SCL 13
#define TFT_SDA 12
#define TFT_DC 6
#define TFT_RST 5
// Button pin
#define BUTTON_PIN 2
// Potentiometer pin
#define POT_PIN A0
// Creates motor instance
AccelStepper myStepper(MotorInterfaceType, 8, 10, 9, 11);
// Creates display instance with updated pin configuration
Adafruit_ST7789 tft = Adafruit_ST7789(TFT_CS, TFT_DC, TFT_SDA, TFT_SCL, TFT_RST);
// Variables for control
int motorDirection = 1; // 1 for forward, -1 for reverse
int currentSpeed = 0;
int targetSpeed = 0;
bool motorRunning = false;
int lastPotValue = 0;
// Button state machine variables
enum ButtonState { IDLE, PRESSED, DEBOUNCING };
ButtonState buttonState = IDLE;
unsigned long lastButtonTime = 0;
#define DEBOUNCE_DELAY 50
// UI layout constants
#define TITLE_Y 15
#define DIRECTION_Y 60
#define SPEED_Y 90
#define POSITION_Y 120
#define STATE_Y 150
#define INFO_Y 180
#define VALUE_X 130
// Speed control parameters
#define MIN_SPEED 0 // 最小速度
#define MAX_SPEED 800 // 最大速度
#define SPEED_RAMP_RATE 10 // 速度变化率
// 电位器校准参数 - 根据您的测量结果调整这些值
#define POT_MIN_VOLTAGE 3.4 // 电机开始转动的最小电压
#define POT_MAX_VOLTAGE 4.0 // 电机达到最大速度的电压
#define ADC_REF_VOLTAGE 5.0 // ADC参考电压
// 计算对应的模拟值范围
#define POT_MIN_ADC (int)((POT_MIN_VOLTAGE / ADC_REF_VOLTAGE) * 1023)
#define POT_MAX_ADC (int)((POT_MAX_VOLTAGE / ADC_REF_VOLTAGE) * 1023)
// 调试模式
#define DEBUG_MODE true
void setup() {
// 初始化串口(用于调试)
if (DEBUG_MODE) {
Serial.begin(115200);
Serial.println("System initialized");
Serial.print("POT_MIN_ADC: ");
Serial.println(POT_MIN_ADC);
Serial.print("POT_MAX_ADC: ");
Serial.println(POT_MAX_ADC);
}
// 初始化显示屏
tft.init(240, 240);
tft.setRotation(1);
tft.fillScreen(ST77XX_BLACK);
// 初始化按钮
pinMode(BUTTON_PIN, INPUT_PULLUP);
// 初始化电机
myStepper.setMaxSpeed(MAX_SPEED);
myStepper.setAcceleration(500.0);
myStepper.setSpeed(0);
// 绘制UI
drawUI();
}
void loop() {
// 处理按钮状态机
handleButton();
// 读取电位器值并设置速度
handlePotentiometer();
// 运行电机
runMotor();
// 定期更新显示
static unsigned long lastUpdate = 0;
if (millis() - lastUpdate > 100) {
updatePositionDisplay();
updateStateDisplay();
lastUpdate = millis();
}
}
void handleButton() {
int buttonReading = digitalRead(BUTTON_PIN);
switch (buttonState) {
case IDLE:
if (buttonReading == LOW) {
buttonState = DEBOUNCING;
lastButtonTime = millis();
}
break;
case DEBOUNCING:
if (millis() - lastButtonTime > DEBOUNCE_DELAY) {
if (digitalRead(BUTTON_PIN) == LOW) {
buttonState = PRESSED;
// 按钮动作 - 改变方向
motorDirection = -motorDirection;
updateDirectionDisplay();
if (DEBUG_MODE) {
Serial.print("Direction changed to: ");
Serial.println(motorDirection > 0 ? "FORWARD" : "REVERSE");
}
} else {
buttonState = IDLE;
}
}
break;
case PRESSED:
if (buttonReading == HIGH) {
buttonState = IDLE;
}
break;
}
}
void handlePotentiometer() {
// 读取电位器值
int potValue = analogRead(POT_PIN);
// 只有当电位器值显著变化时才处理
if (abs(potValue - lastPotValue) > 3) {
lastPotValue = potValue;
if (DEBUG_MODE && millis() % 500 < 10) {
float voltage = (float)potValue / 1023.0 * ADC_REF_VOLTAGE;
Serial.print("Pot ADC: ");
Serial.print(potValue);
Serial.print(" | Voltage: ");
Serial.print(voltage);
Serial.print("V");
}
// 重新映射电位器值到速度范围
if (potValue <= POT_MIN_ADC) {
// 低于最小电压,电机停止
targetSpeed = 0;
} else if (potValue >= POT_MAX_ADC) {
// 高于最大电压,电机全速运行
targetSpeed = MAX_SPEED;
} else {
// 在有效范围内线性映射
targetSpeed = map(potValue, POT_MIN_ADC, POT_MAX_ADC, MIN_SPEED, MAX_SPEED);
}
if (DEBUG_MODE && millis() % 500 < 10) {
Serial.print(" | Target Speed: ");
Serial.println(targetSpeed);
}
// 平滑调整当前速度到目标速度
if (abs(targetSpeed - currentSpeed) > SPEED_RAMP_RATE) {
if (targetSpeed > currentSpeed) {
currentSpeed += SPEED_RAMP_RATE;
} else {
currentSpeed -= SPEED_RAMP_RATE;
}
} else {
currentSpeed = targetSpeed;
}
// 更新速度显示
updateSpeedDisplay();
}
}
void runMotor() {
// 设置电机速度和方向
int effectiveSpeed = motorDirection * currentSpeed;
myStepper.setSpeed(effectiveSpeed);
// 运行电机
myStepper.runSpeed();
motorRunning = (currentSpeed > 0);
}
void drawUI() {
// 清屏
tft.fillScreen(ST77XX_BLACK);
// 绘制带装饰线的标题
tft.drawFastHLine(0, 30, 240, ST77XX_WHITE);
tft.setCursor(40, TITLE_Y);
tft.setTextSize(3);
tft.setTextColor(ST77XX_YELLOW);
tft.println("MOTOR CTRL");
// 绘制标签
tft.setTextSize(2);
tft.setTextColor(ST77XX_WHITE);
tft.setCursor(20, DIRECTION_Y);
tft.println("Direction:");
tft.setCursor(20, SPEED_Y);
tft.println("Speed:");
tft.setCursor(20, POSITION_Y);
tft.println("Position:");
tft.setCursor(20, STATE_Y);
tft.println("Status:");
// 绘制校准信息
tft.setTextSize(1);
tft.setCursor(10, INFO_Y);
tft.print("Calib: ");
tft.print(POT_MIN_VOLTAGE, 1);
tft.print("V-");
tft.print(POT_MAX_VOLTAGE, 1);
tft.print("V (");
tft.print(POT_MIN_ADC);
tft.print("-");
tft.print(POT_MAX_ADC);
tft.print(")");
// 绘制控制说明
tft.setCursor(10, INFO_Y + 15);
tft.println("Button: Direction | Pot: Speed");
// 绘制初始值
updateDirectionDisplay();
updateSpeedDisplay();
updatePositionDisplay();
updateStateDisplay();
}
void updateDirectionDisplay() {
tft.fillRect(VALUE_X, DIRECTION_Y, 100, 20, ST77XX_BLACK);
tft.setCursor(VALUE_X, DIRECTION_Y);
tft.setTextSize(2);
if (motorDirection > 0) {
tft.setTextColor(ST77XX_GREEN);
tft.print("FORWARD");
} else {
tft.setTextColor(ST77XX_RED);
tft.print("REVERSE");
}
}
void updateSpeedDisplay() {
tft.fillRect(VALUE_X, SPEED_Y, 100, 20, ST77XX_BLACK);
tft.setCursor(VALUE_X, SPEED_Y);
tft.setTextSize(2);
tft.setTextColor(ST77XX_CYAN);
tft.print(currentSpeed);
tft.print(" step/s");
}
void updatePositionDisplay() {
tft.fillRect(VALUE_X, POSITION_Y, 100, 20, ST77XX_BLACK);
tft.setCursor(VALUE_X, POSITION_Y);
tft.setTextSize(2);
tft.setTextColor(ST77XX_WHITE);
tft.print(myStepper.currentPosition());
}
void updateStateDisplay() {
tft.fillRect(VALUE_X, STATE_Y, 100, 20, ST77XX_BLACK);
tft.setCursor(VALUE_X, STATE_Y);
tft.setTextSize(2);
if (motorRunning) {
tft.setTextColor(ST77XX_GREEN);
tft.print("RUNNING");
} else {
tft.setTextColor(ST77XX_RED);
tft.print("STOPPED");
}
}

如图所示,步进电机控制系统的工作流程图
三、操作过程及展示
3.1 系统连接
将ULN2003AN驱动板接入5~12V外部电源,连接28BYJ-48电机到驱动板输出端,按照接线表连接零知增强板与各模块,确保所有电源正确连接后上电
3.2 校准测试
(1)上传代码到零知IDE的零知增强板

(2)旋转电位器,观察电机响应
(3)根据实际电压范围调整校准参数

(4)测试按键功能,确认方向切换正常
3.3 视频演示验证
ULN2003AN驱动28BYJ-48步进电机控制系统
缓慢旋转电位器,观察速度线性增加。按下按键,验证方向切换功能。电位器逆时针旋转到底,确认电机停止
四、控制系统模块详解
4.1 ULN2003AN驱动芯片
(1)功能说明
ULN2003AN是高压大电流达林顿晶体管阵列,包含7个NPN达林顿对管,每个可驱动500mA和50V负载。在本项目中用于驱动28BYJ-48步进电机的四相绕组。

该板具有四个控制输入接口和一个电源接口,还配备了一个与电机连接器相兼容的 Molex 连接器,可直接插入电机。同时,板上设有四个 LED,用于显示四条控制输入线上的活动状态,并且带有一个开 / 关跳线,以便在需要时禁用步进电机。
(2)工作流程
接收MCU发出的控制信号、通过达林顿管放大电流、驱动步进电机各相绕组、提供反向电动势保护二极管
4.2 28BYJ-48步进电机
(1)工作原理
基于齿轮与电磁铁的协同作用,通过一次推动轮子一个 "步" 来实现运动

向线圈发送高脉冲时,线圈会通电,进而吸引最靠近齿轮的齿,使电机以精确且固定的角度增量(即步长)旋转。其 360 度旋转的步数取决于齿轮上的齿数
(2)引脚排列
28BYJ-48 步进电机有五根线。引脚排列如下:

28BYJ-48 包含两个线圈,每个线圈都设有一个中心抽头,这两个中心抽头在电机内部相连,并通过红线引出。
而线圈的一端与中心抽头共同构成一相,所以 28BYJ-48 总共有四相:

红线始终拉高,因此当另一根引线拉低时,该相通电。
仅当各相按称为步进顺序的逻辑顺序通电时,步进电机才会旋转。
(3)齿轮减速比
依据数据表,28BYJ-48 步进电机在全步模式下运行时,每一步对应的旋转角度为 11.25°,由此可算出每完成 360° 旋转需要 32 步(360°÷11.25°=32)

该电机配备了 1/64 的减速齿轮组(实际减速比为 1/63.68395,不过在多数情况下,1/64 的近似值已能满足需求)。
这也就意味着,电机实际完成一圈旋转需要约 2038 步(32 步 / 转 ×63.68395≈2037.8864 步,近似为 2038 步)。
(4)能量消耗
28BYJ-48 步进电机的典型电流消耗约为 240mA。

由于其耗电量较大,因此建议直接采用外部 5V 电源供电,而非通过 零知增强板供电。
需要注意的是,即便处于静止状态,该电机也会消耗功率以维持当前位置。
(5)技术规格
|-------|-------------------|
| 工作电压 | 5VDC |
| 工作电流 | 240mA(典型值) |
| 相数 | 4 |
| 齿轮减速比 | 64:1 |
| 步距角 | 5.625°/64 |
| 频率 | 100赫兹 |
| 牵引扭矩 | >34.3mN.m(120Hz) |
| 自定位扭矩 | >34.3mN.m |
| 摩擦力矩 | 600-1200 克力·厘米 |
| 拉入扭矩 | 300 克力.厘米 |
五、常见问题解答
Q1: 电位器控制不线性如何调整?
A:修改代码中的
POT_MIN_VOLTAGE
和POT_MAX_VOLTAGE
参数,匹配实际电压范围。
Q2: 显示屏显示异常怎么办?
A:检查SPI接线是否正确,确认CS、DC、RST引脚定义与代码一致。
Q3: 按键无响应如何解决?
A:确认按键是否正确连接到GND和引脚2,检查内部上拉电阻是否启用。
Q4: 电机振动大且噪音明显?
A:降低MAX_SPEED值,或增加加速度参数,使速度变化更平滑。
项目资源:
步进电机数据表:28BYJ-48 数据表
库文件依赖:AccelStepper 库
本项目成功实现了一套基于零知标准板的智能步进电机控制系统,希望能为您的嵌入式学习和开发提供有价值的参考!