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

发布于:2025-08-31 ⋅ 阅读:(18) ⋅ 点赞:(0)

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

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

www.lingzhilab.com

目录

一、硬件系统设计

1.1 器件清单

1.2 接线方案

1.3 硬件连接图

1.4 实物接线图

二、软件架构设计

2.1 库文件及初始化

2.2 电压校准算法

2.3 状态机按键处理

2.4 电位器模拟值处理

2.5 完整代码

三、操作过程及展示

3.1 系统连接

3.2 校准测试

3.3 功能验证

四、控制系统模块详解

4.1 ULN2003AN驱动芯片

4.2 28BYJ-48步进电机

五、常见问题解答

Q1: 电位器控制不线性如何调整?

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

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

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 库文件及初始化

// 主要库文件引入
#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 电压校准算法

// 电压校准参数 - 关键创新点
#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 状态机按键处理

// 按键状态枚举
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 电位器模拟值处理

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 完整代码

// 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 库

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


网站公告

今日签到

点亮在社区的每一天
去签到