步进电机点动控制增强版(没有角度控制)
我将为您的步进电机控制器添加点动功能,并美化网页界面。点动功能允许用户点击按钮一次,电机就转动一步,同时可以调整点动速度。
以下是完整的实现代码:
#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
存储点动方向 - 添加
jogSpeedPercent
和jogStepDelay
控制点动速度 - 创建
performJog()
函数处理点动操作 - 新增
setJogSpeed()
函数设置点动速度 - 扩展
handleControl()
支持点动命令
3. 网页界面美化
- 现代化UI设计:
- 渐变背景和卡片式布局
- 半透明效果和模糊背景
- 响应式设计适配各种设备
- 直观的控制面板:
- 连续运行控制区(蓝色)
- 点动控制区(紫色)
- 状态显示区
- 视觉反馈:
- 按钮悬停动画
- 点动操作状态提示
- 当前速度实时显示
- Font Awesome图标:
- 为每个功能添加直观图标
- 增强用户体验
4. 使用说明
-
连续运行模式:
- 点击"正转"、"反转"或"停止"按钮控制电机持续运行
- 使用速度滑块调节运行速度
-
点动模式:
- 点击"正点动"或"反点动"按钮,每次点击电机转动一步
- 使用点动速度滑块调节点动响应速度
- 点动操作后状态栏会显示"点动完成"提示
-
状态显示:
- 顶部显示设备IP地址
- 状态栏显示当前运行模式
- 分别显示运行速度和点动速度
5. 接线说明
ESP8266 → ULN2003
D1 (GPIO5) → IN1
D2 (GPIO4) → IN2
D3 (GPIO0) → IN3
D4 (GPIO2) → IN4
GND → GND (共地)
注意事项
- 点动速度设置不影响连续运行速度,两者独立控制
- 点动操作会中断连续运行模式
- 点动速度设置会影响点动响应时间,但不改变步进角度
- 高速点动时,请确保电源供应稳定
这个增强版添加了点动控制功能并大幅美化了界面,使步进电机控制更加精确和直观。点动功能特别适合需要精确定位的应用场景。
评论区