零知开源——基于STM32F407VET6实现ULN2003AN驱动28BYJ-48步进电机控制系统

✔零知IDE 是一个真正属于国人自己的开源软件平台,在开发效率上超越了Arduino平台并且更加容易上手,大大降低了开发难度。零知开源在软件方面提供了完整的学习教程和丰富示例代码,让不懂程序的工程师也能非常轻而易举的搭建电路来创作产品,测试产品。快来动手试试吧!

✔访问零知实验室,获取更多实战项目和教程资源吧!

www.lingzhilab.com

目录

一、硬件系统设计

[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_VOLTAGEPOT_MAX_VOLTAGE参数,匹配实际电压范围。

Q2: 显示屏显示异常怎么办?

A:检查SPI接线是否正确,确认CS、DC、RST引脚定义与代码一致。

Q3: 按键无响应如何解决?

A:确认按键是否正确连接到GND和引脚2,检查内部上拉电阻是否启用。

Q4: 电机振动大且噪音明显?

A:降低MAX_SPEED值,或增加加速度参数,使速度变化更平滑。

项目资源:

步进电机数据表:28BYJ-48 数据表

库文件依赖:AccelStepper 库

本项目成功实现了一套基于零知标准板的智能步进电机控制系统,希望能为您的嵌入式学习和开发提供有价值的参考!

相关推荐
kaikaile19951 小时前
GY-BMP280压强传感器完整工程stm32控制
stm32·单片机·嵌入式硬件
智能物联实验室1 小时前
屏随人动+视觉魔方+多样主题+智能留言,涂鸦Wukong AI 2.0助力打造爆款带屏云台相机
图像处理·人工智能·嵌入式硬件·数码相机·智能硬件
GodKK老神灭2 小时前
STM32 定时器(互补输出+刹车)
stm32·单片机·嵌入式硬件
JasmineX-17 小时前
直流电机驱动与TB6612
c语言·stm32·单片机·嵌入式硬件
hahaha601617 小时前
模拟电路中什么时候适合使用电流传递信号,什么时候合适使用电压传递信号
stm32·单片机·嵌入式硬件
小小少年12318 小时前
基于蓝牙的stm32智能火灾烟雾报警系统设计
stm32·单片机·嵌入式硬件
点灯小铭20 小时前
基于51单片机红外避障车辆高速汽车测速仪表设计
单片机·嵌入式硬件·汽车·毕业设计·51单片机·课程设计
猫猫的小茶馆1 天前
【STM32】将 FreeRTOS移植到STM32F103RCT6 详细流程
stm32·单片机·嵌入式硬件·mcu·智能硬件
智驾1 天前
MCU平台化实践方案
单片机·嵌入式硬件·mcu·嵌入式