侧边栏壁纸
  • 累计撰写 8 篇文章
  • 累计创建 2 个标签
  • 累计收到 2 条评论

目 录CONTENT

文章目录

ESP8266控制步进电机

波波
2025-07-08 / 0 评论 / 0 点赞 / 0 阅读 / 0 字 / 正在检测是否收录...

步进电机点动控制增强版(没有角度控制)

我将为您的步进电机控制器添加点动功能,并美化网页界面。点动功能允许用户点击按钮一次,电机就转动一步,同时可以调整点动速度。

以下是完整的实现代码:

#include <Arduino.h>
#include <ESP8266WiFi.h>
#include <ESP8266WebServer.h>

// 定义ULN2003引脚
#define IN1 D1  // GPIO5
#define IN2 D2  // GPIO4
#define IN3 D3  // GPIO0
#define IN4 D4  // GPIO2

// 28BYJ-48步进序列 (8步半驱动)
const uint8_t stepSequence[8][4] = {
  {1, 0, 0, 0},  // 步1
  {1, 1, 0, 0},  // 步2
  {0, 1, 0, 0},  // 步3
  {0, 1, 1, 0},  // 步4
  {0, 0, 1, 0},  // 步5
  {0, 0, 1, 1},  // 步6
  {0, 0, 0, 1},  // 步7
  {1, 0, 0, 1}   // 步8
};

// WiFi设置
const char* ssid = "Your_WiFi_SSID";      // 修改为你的WiFi名称
const char* password = "Your_WiFi_Pass";  // 修改为你的WiFi密码

ESP8266WebServer server(80);

// 电机控制变量
int motorDirection = 0;           // 0=停止, 1=正转, -1=反转
int currentStep = 0;              // 当前步进位置
unsigned long stepDelay = 2000;   // 步进延迟(微秒),默认=2000μs(慢速)
unsigned long lastStepTime = 0;   // 上次步进时间

// 点动控制变量
bool jogMode = false;             // 点动模式标志
int jogDirection = 0;             // 点动方向
int jogSpeedPercent = 50;         // 点动速度百分比
unsigned long jogStepDelay = 2000;// 点动步进延迟

void setup() {
  // 设置引脚输出
  pinMode(IN1, OUTPUT);
  pinMode(IN2, OUTPUT);
  pinMode(IN3, OUTPUT);
  pinMode(IN4, OUTPUT);
  
  Serial.begin(115200);
  
  // 连接WiFi
  WiFi.begin(ssid, password);
  Serial.print("Connecting to WiFi");
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("\nConnected! IP address: ");
  Serial.println(WiFi.localIP());

  // 设置Web服务器路由
  server.on("/", handleRoot);
  server.on("/control", handleControl);
  
  server.begin();
  Serial.println("HTTP server started");
}

void loop() {
  server.handleClient();  // 处理客户端请求
  controlMotor();         // 控制电机运动
  controlJog();           // 控制点动运动
}

// 非阻塞式电机控制
void controlMotor() {
  if (motorDirection == 0) return; // 如果停止状态,不执行动作
  
  unsigned long currentTime = micros();
  
  // 检查是否到达步进时间
  if (currentTime - lastStepTime > stepDelay) {
    // 更新步进位置
    currentStep = (currentStep + motorDirection + 8) % 8;
    
    // 输出步进信号
    digitalWrite(IN1, stepSequence[currentStep][0]);
    digitalWrite(IN2, stepSequence[currentStep][1]);
    digitalWrite(IN3, stepSequence[currentStep][2]);
    digitalWrite(IN4, stepSequence[currentStep][3]);
    
    lastStepTime = currentTime;
  }
}

// 点动控制
void controlJog() {
  if (!jogMode) return; // 如果不是点动模式,不执行动作
  
  unsigned long currentTime = micros();
  
  // 检查是否到达步进时间
  if (currentTime - lastStepTime > jogStepDelay) {
    // 更新步进位置
    currentStep = (currentStep + jogDirection + 8) % 8;
    
    // 输出步进信号
    digitalWrite(IN1, stepSequence[currentStep][0]);
    digitalWrite(IN2, stepSequence[currentStep][1]);
    digitalWrite(IN3, stepSequence[currentStep][2]);
    digitalWrite(IN4, stepSequence[currentStep][3]);
    
    lastStepTime = currentTime;
    jogMode = false; // 完成一次点动
  }
}

// 设置电机速度 (0-100)
void setMotorSpeed(int speedPercent) {
  // 将速度百分比转换为步进延迟
  if (speedPercent <= 0) {
    stepDelay = 0;
    motorDirection = 0;
    // 停止时断电
    digitalWrite(IN1, LOW);
    digitalWrite(IN2, LOW);
    digitalWrite(IN3, LOW);
    digitalWrite(IN4, LOW);
  } else {
    // 速度范围:500μs(最快)到5000μs(最慢)
    stepDelay = map(speedPercent, 1, 100, 500, 5000);
  }
  Serial.print("Speed set to: ");
  Serial.print(speedPercent);
  Serial.print("% (Delay: ");
  Serial.print(stepDelay);
  Serial.println("μs)");
}

// 设置点动速度 (0-100)
void setJogSpeed(int speedPercent) {
  // 将速度百分比转换为步进延迟
  if (speedPercent <= 0) {
    jogStepDelay = 5000; // 最慢速度
  } else {
    // 速度范围:500μs(最快)到5000μs(最慢)
    jogStepDelay = map(speedPercent, 1, 100, 500, 5000);
  }
  jogSpeedPercent = speedPercent;
  Serial.print("Jog speed set to: ");
  Serial.print(speedPercent);
  Serial.print("% (Delay: ");
  Serial.print(jogStepDelay);
  Serial.println("μs)");
}

// 执行点动操作
void performJog(int direction) {
  jogMode = true;
  jogDirection = direction;
  lastStepTime = micros(); // 重置计时器
}

// 处理控制命令
void handleControl() {
  if (server.hasArg("cmd")) {
    String command = server.arg("cmd");
    
    if (command == "forward") {
      motorDirection = 1;
      server.send(200, "text/plain", "FORWARD");
      Serial.println("Direction: FORWARD");
    } 
    else if (command == "reverse") {
      motorDirection = -1;
      server.send(200, "text/plain", "REVERSE");
      Serial.println("Direction: REVERSE");
    } 
    else if (command == "stop") {
      motorDirection = 0;
      // 停止时断电
      digitalWrite(IN1, LOW);
      digitalWrite(IN2, LOW);
      digitalWrite(IN3, LOW);
      digitalWrite(IN4, LOW);
      server.send(200, "text/plain", "STOP");
      Serial.println("Motor STOPPED");
    }
    else if (command == "jog_forward") {
      performJog(1);
      server.send(200, "text/plain", "JOG_FORWARD");
      Serial.println("Jog FORWARD");
    }
    else if (command == "jog_reverse") {
      performJog(-1);
      server.send(200, "text/plain", "JOG_REVERSE");
      Serial.println("Jog REVERSE");
    }
    else {
      server.send(400, "text/plain", "Invalid command");
    }
  }
  
  if (server.hasArg("speed")) {
    int speedVal = server.arg("speed").toInt();
    setMotorSpeed(speedVal);
    server.send(200, "text/plain", "Speed set");
  }
  
  if (server.hasArg("jog_speed")) {
    int speedVal = server.arg("jog_speed").toInt();
    setJogSpeed(speedVal);
    server.send(200, "text/plain", "Jog speed set");
  }
}

// 网页界面 - 添加点动控制功能
void handleRoot() {
  String html = R"=====(
<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>步进电机控制</title>
  <style>
    * {
      box-sizing: border-box;
      margin: 0;
      padding: 0;
      font-family: 'Segoe UI', 'Microsoft YaHei', sans-serif;
    }
    
    body {
      background: linear-gradient(135deg, #6a11cb 0%, #2575fc 100%);
      color: #333;
      min-height: 100vh;
      padding: 20px;
      display: flex;
      justify-content: center;
      align-items: center;
    }
    
    .container {
      max-width: 800px;
      width: 100%;
      background: rgba(255, 255, 255, 0.95);
      border-radius: 20px;
      padding: 30px;
      box-shadow: 0 15px 30px rgba(0, 0, 0, 0.2);
      backdrop-filter: blur(10px);
    }
    
    header {
      text-align: center;
      margin-bottom: 30px;
      padding-bottom: 20px;
      border-bottom: 1px solid #eee;
    }
    
    h1 {
      font-size: 2.5rem;
      color: #2c3e50;
      margin-bottom: 10px;
      background: linear-gradient(45deg, #2575fc, #6a11cb);
      -webkit-background-clip: text;
      -webkit-text-fill-color: transparent;
    }
    
    .subtitle {
      color: #7f8c8d;
      font-size: 1.1rem;
    }
    
    .panel {
      background: white;
      border-radius: 15px;
      padding: 25px;
      margin-bottom: 25px;
      box-shadow: 0 5px 15px rgba(0, 0, 0, 0.05);
      transition: transform 0.3s ease;
    }
    
    .panel:hover {
      transform: translateY(-5px);
    }
    
    .panel-title {
      font-size: 1.5rem;
      margin-bottom: 20px;
      color: #2c3e50;
      display: flex;
      align-items: center;
    }
    
    .panel-title i {
      margin-right: 10px;
      color: #3498db;
    }
    
    .control-row {
      display: flex;
      justify-content: space-between;
      flex-wrap: wrap;
      gap: 15px;
      margin-bottom: 20px;
    }
    
    .control-group {
      flex: 1;
      min-width: 250px;
    }
    
    .btn-group {
      display: flex;
      gap: 15px;
      margin-top: 15px;
      flex-wrap: wrap;
    }
    
    .btn {
      flex: 1;
      padding: 18px 10px;
      font-size: 18px;
      font-weight: 600;
      border: none;
      border-radius: 12px;
      cursor: pointer;
      transition: all 0.3s ease;
      display: flex;
      justify-content: center;
      align-items: center;
      min-width: 120px;
      box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
    }
    
    .btn i {
      margin-right: 8px;
      font-size: 20px;
    }
    
    .btn:hover {
      transform: translateY(-3px);
      box-shadow: 0 6px 8px rgba(0, 0, 0, 0.15);
    }
    
    .btn:active {
      transform: translateY(1px);
    }
    
    #forwardBtn {
      background: linear-gradient(to right, #00b09b, #96c93d);
      color: white;
    }
    
    #stopBtn {
      background: linear-gradient(to right, #8e2de2, #4a00e0);
      color: white;
    }
    
    #reverseBtn {
      background: linear-gradient(to right, #ff416c, #ff4b2b);
      color: white;
    }
    
    .jog-btn {
      background: linear-gradient(to right, #3498db, #2c3e50);
      color: white;
      padding: 15px 10px;
    }
    
    .slider-container {
      margin: 25px 0;
    }
    
    .slider-header {
      display: flex;
      justify-content: space-between;
      margin-bottom: 10px;
    }
    
    .slider-label {
      font-size: 1.1rem;
      font-weight: 600;
      color: #2c3e50;
    }
    
    .slider-value {
      font-size: 1.2rem;
      font-weight: bold;
      color: #e74c3c;
      min-width: 60px;
      text-align: right;
    }
    
    .slider {
      width: 100%;
      height: 25px;
      background: #e0e0e0;
      border-radius: 12px;
      outline: none;
      -webkit-appearance: none;
    }
    
    .slider::-webkit-slider-thumb {
      width: 35px;
      height: 35px;
      background: #3498db;
      border-radius: 50%;
      cursor: pointer;
      box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2);
      -webkit-appearance: none;
    }
    
    .status {
      background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
      border-radius: 12px;
      padding: 20px;
      margin-top: 25px;
      text-align: center;
    }
    
    .status-item {
      margin: 10px 0;
      font-size: 1.1rem;
      display: flex;
      justify-content: space-between;
    }
    
    .status-label {
      font-weight: 600;
      color: #2c3e50;
    }
    
    .status-value {
      font-weight: 600;
      color: #e74c3c;
    }
    
    .jog-control {
      display: flex;
      justify-content: space-between;
      gap: 15px;
      margin-top: 15px;
    }
    
    .jog-slider {
      flex: 3;
    }
    
    .jog-speed-value {
      flex: 1;
      display: flex;
      align-items: center;
      justify-content: center;
      background: #f8f9fa;
      border-radius: 10px;
      font-weight: bold;
      font-size: 1.2rem;
      color: #3498db;
    }
    
    @media (max-width: 600px) {
      .control-row {
        flex-direction: column;
      }
      
      .btn {
        min-width: 100%;
      }
      
      .jog-control {
        flex-direction: column;
      }
    }
  </style>
  <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet">
</head>
<body>
  <div class="container">
    <header>
      <h1><i class="fas fa-motorcycle"></i> 步进电机控制系统</h1>
      <p class="subtitle">28BYJ-48 步进电机控制 | 连续运行与点动模式</p>
    </header>
    
    <div class="panel">
      <h2 class="panel-title"><i class="fas fa-cogs"></i> 连续运行控制</h2>
      
      <div class="control-row">
        <div class="control-group">
          <div class="btn-group">
            <button class="btn" id="forwardBtn" onclick="sendCommand('forward')">
              <i class="fas fa-forward"></i> 正转
            </button>
            <button class="btn" id="stopBtn" onclick="sendCommand('stop')">
              <i class="fas fa-stop"></i> 停止
            </button>
            <button class="btn" id="reverseBtn" onclick="sendCommand('reverse')">
              <i class="fas fa-backward"></i> 反转
            </button>
          </div>
        </div>
        
        <div class="control-group">
          <div class="slider-container">
            <div class="slider-header">
              <span class="slider-label">运行速度控制</span>
              <span class="slider-value" id="speedValue">50%</span>
            </div>
            <input type="range" min="0" max="100" value="50" class="slider" id="speedSlider" oninput="updateSpeed(this.value)">
          </div>
        </div>
      </div>
    </div>
    
    <div class="panel">
      <h2 class="panel-title"><i class="fas fa-tap"></i> 点动控制模式</h2>
      
      <div class="control-row">
        <div class="control-group">
          <div class="btn-group">
            <button class="btn jog-btn" onclick="sendCommand('jog_forward')">
              <i class="fas fa-arrow-up"></i> 正点动
            </button>
            <button class="btn jog-btn" onclick="sendCommand('jog_reverse')">
              <i class="fas fa-arrow-down"></i> 反点动
            </button>
          </div>
        </div>
        
        <div class="control-group">
          <div class="jog-control">
            <div class="jog-slider">
              <div class="slider-header">
                <span class="slider-label">点动速度</span>
              </div>
              <input type="range" min="0" max="100" value="50" class="slider" id="jogSpeedSlider" oninput="updateJogSpeed(this.value)">
            </div>
            <div class="jog-speed-value">
              <span id="jogSpeedValue">50%</span>
            </div>
          </div>
        </div>
      </div>
      
      <div class="info" style="margin-top: 20px; background: #e3f2fd; padding: 15px; border-radius: 10px;">
        <p style="color: #1565c0;"><i class="fas fa-info-circle"></i> 点动模式说明:点击一次点动按钮,电机将精确转动一步。可调节点动速度控制电机响应时间。</p>
      </div>
    </div>
    
    <div class="status">
      <div class="status-item">
        <span class="status-label">设备IP地址:</span>
        <span class="status-value">)=====";
  html += WiFi.localIP().toString();
  html += R"=====(</span>
      </div>
      <div class="status-item">
        <span class="status-label">当前状态:</span>
        <span class="status-value" id="statusText">已停止</span>
      </div>
      <div class="status-item">
        <span class="status-label">运行速度:</span>
        <span class="status-value" id="currentSpeed">50%</span>
      </div>
      <div class="status-item">
        <span class="status-label">点动速度:</span>
        <span class="status-value" id="currentJogSpeed">50%</span>
      </div>
    </div>
  </div>
  
  <script>
    function sendCommand(cmd) {
      // 更新状态文本
      if (cmd === 'stop') {
        document.getElementById('statusText').textContent = '已停止';
      } else if (cmd === 'forward') {
        document.getElementById('statusText').textContent = '正转中...';
      } else if (cmd === 'reverse') {
        document.getElementById('statusText').textContent = '反转中...';
      } else if (cmd === 'jog_forward') {
        document.getElementById('statusText').textContent = '正点动...';
        // 1秒后恢复状态
        setTimeout(() => {
          if (document.getElementById('statusText').textContent === '正点动...') {
            document.getElementById('statusText').textContent = '点动完成';
            setTimeout(() => {
              document.getElementById('statusText').textContent = '已停止';
            }, 1000);
          }
        }, 500);
      } else if (cmd === 'jog_reverse') {
        document.getElementById('statusText').textContent = '反点动...';
        // 1秒后恢复状态
        setTimeout(() => {
          if (document.getElementById('statusText').textContent === '反点动...') {
            document.getElementById('statusText').textContent = '点动完成';
            setTimeout(() => {
              document.getElementById('statusText').textContent = '已停止';
            }, 1000);
          }
        }, 500);
      }
      
      fetch('/control?cmd=' + cmd)
        .then(response => response.text())
        .then(data => console.log('Command:', data));
    }
    
    function updateSpeed(value) {
      document.getElementById('speedValue').textContent = value + '%';
      document.getElementById('currentSpeed').textContent = value + '%';
      fetch('/control?speed=' + value)
        .then(response => response.text())
        .then(data => console.log('Speed:', data));
    }
    
    function updateJogSpeed(value) {
      document.getElementById('jogSpeedValue').textContent = value + '%';
      document.getElementById('currentJogSpeed').textContent = value + '%';
      fetch('/control?jog_speed=' + value)
        .then(response => response.text())
        .then(data => console.log('Jog Speed:', data));
    }
    
    // 初始化显示
    document.addEventListener('DOMContentLoaded', function() {
      updateSpeed(50);
      updateJogSpeed(50);
    });
  </script>
</body>
</html>
)=====";
  
  server.send(200, "text/html", html);
}

新增功能说明

1. 点动控制功能

  • 正点动按钮:点击一次,电机正向转动一步
  • 反点动按钮:点击一次,电机反向转动一步
  • 点动速度控制:独立滑块控制点动速度(0-100%)

2. 技术实现

  • 添加了jogMode标志区分点动模式
  • 新增jogDirection存储点动方向
  • 添加jogSpeedPercentjogStepDelay控制点动速度
  • 创建performJog()函数处理点动操作
  • 新增setJogSpeed()函数设置点动速度
  • 扩展handleControl()支持点动命令

3. 网页界面美化

  • 现代化UI设计
    • 渐变背景和卡片式布局
    • 半透明效果和模糊背景
    • 响应式设计适配各种设备
  • 直观的控制面板
    • 连续运行控制区(蓝色)
    • 点动控制区(紫色)
    • 状态显示区
  • 视觉反馈
    • 按钮悬停动画
    • 点动操作状态提示
    • 当前速度实时显示
  • Font Awesome图标
    • 为每个功能添加直观图标
    • 增强用户体验

4. 使用说明

  1. 连续运行模式

    • 点击"正转"、"反转"或"停止"按钮控制电机持续运行
    • 使用速度滑块调节运行速度
  2. 点动模式

    • 点击"正点动"或"反点动"按钮,每次点击电机转动一步
    • 使用点动速度滑块调节点动响应速度
    • 点动操作后状态栏会显示"点动完成"提示
  3. 状态显示

    • 顶部显示设备IP地址
    • 状态栏显示当前运行模式
    • 分别显示运行速度和点动速度

5. 接线说明

ESP8266 → ULN2003
D1 (GPIO5) → IN1
D2 (GPIO4) → IN2
D3 (GPIO0) → IN3
D4 (GPIO2) → IN4
GND → GND (共地)

注意事项

  1. 点动速度设置不影响连续运行速度,两者独立控制
  2. 点动操作会中断连续运行模式
  3. 点动速度设置会影响点动响应时间,但不改变步进角度
  4. 高速点动时,请确保电源供应稳定

这个增强版添加了点动控制功能并大幅美化了界面,使步进电机控制更加精确和直观。点动功能特别适合需要精确定位的应用场景。

0

评论区