ESP8266-Arduino网络编程实例-Web页面控制步进电机

Web页面控制步进电机

本文将演示如何通过Web页面控制步进电机步数及方向。

在这里插入图片描述

客户端与服务器通过WebSocket协议通信。示例实现了如下功能:

  • Web页面显示一个表格,可以在其中输入希望电机移动的步数并选择方向:顺时针或逆时针。
  • 显示电机状态:电机旋转或电机停止。 此外,只要电机旋转,就会有一个齿轮图标旋转。 齿轮根据所选方向顺时针或逆时针方向旋转。

1、硬件准备

  • ESP8266 NodeMCU开发板一块
  • 数据线一条
  • UL2003N步进电机驱动模块
  • 28BYJ-48 Stepper Motor步进电机
  • 杜邦线若干
  • 5V电源

硬件接线如下:

在这里插入图片描述

Motor Driver ESP8266
IN1 GPIO 5
IN2 GPIO 4
IN3 GPIO 14
IN4 GPIO 12

2、软件准备

  • Arduino IDE或VSCode + PlatformIO

在前面的文章中,对如何搭建ESP8266开发环境做了详细的介绍,请参考:

ESP8266 NodeMCU的引脚介绍在前面的文章中做了详细的介绍,请参考:

3、代码实现

1)ESP8266服务器

本次实例使用到如下驱动库:

1)ESP8266服务器实现代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
#include <Arduino.h>
#include <ESP8266WiFi.h>
#include <ESPAsyncWebServer.h>
#include "LittleFS.h"
#include <AccelStepper.h>

#define IN1 5
#define IN2 4
#define IN3 14
#define IN4 12
AccelStepper stepper(AccelStepper::HALF4WIRE, IN1, IN3, IN2, IN4);

String message = "";


const char* ssid = "****";
const char* ssid_pwd = "****";

// Create AsyncWebServer object on port 80
AsyncWebServer server(80);

// Create a WebSocket object
AsyncWebSocket ws("/ws");

//Variables to save values from HTML form
String direction ="STOP";
String steps;

bool notifyStop = false;

// Initialize LittleFS
void initFS() {
if (!LittleFS.begin()) {
Serial.println("An error has occurred while mounting LittleFS");
}
else{
Serial.println("LittleFS mounted successfully");
}
}

// Initialize WiFi
void initWiFi() {
WiFi.mode(WIFI_STA);
WiFi.begin(ssid, password);
Serial.print("Connecting to WiFi ..");
while (WiFi.status() != WL_CONNECTED) {
Serial.print('.');
delay(1000);
}
Serial.println(WiFi.localIP());
}

void notifyClients(String state) {
ws.textAll(state);
}

void handleWebSocketMessage(void *arg, uint8_t *data, size_t len) {
AwsFrameInfo *info = (AwsFrameInfo*)arg;
if (info->final && info->index == 0 && info->len == len && info->opcode == WS_TEXT) {
data[len] = 0;
message = (char*)data;
steps = message.substring(0, message.indexOf("&"));
direction = message.substring(message.indexOf("&")+1, message.length());
Serial.print("steps");
Serial.println(steps);
Serial.print("direction");
Serial.println(direction);
notifyClients(direction);
notifyStop = true;
if (direction == "CW"){
Serial.print("CW");
stepper.move(steps.toInt());
}
else{
Serial.print("CCW");
stepper.move(-steps.toInt());
}
}
}

void onEvent(AsyncWebSocket *server, AsyncWebSocketClient *client, AwsEventType type, void *arg, uint8_t *data, size_t len) {
switch (type) {
case WS_EVT_CONNECT:
Serial.printf("WebSocket client #%u connected from %s\n", client->id(), client->remoteIP().toString().c_str());
//Notify client of motor current state when it first connects
notifyClients(direction);
break;
case WS_EVT_DISCONNECT:
Serial.printf("WebSocket client #%u disconnected\n", client->id());
break;
case WS_EVT_DATA:
handleWebSocketMessage(arg, data, len);
break;
case WS_EVT_PONG:
case WS_EVT_ERROR:
break;
}
}

void initWebSocket() {
ws.onEvent(onEvent);
server.addHandler(&ws);
}

void setup() {
// Serial port for debugging purposes

Serial.begin(115200);
initWiFi();
initWebSocket();
initFS();
stepper.setMaxSpeed(1000);
stepper.setAcceleration(100);

// Web Server Root URL
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
request->send(LittleFS, "/index.html", "text/html");
});

server.serveStatic("/", LittleFS, "/");

server.begin();
}

void loop() {
if (stepper.distanceToGo() == 0 && notifyStop == true){
direction = "stop";
notifyClients(direction);
notifyStop = false;
}
ws.cleanupClients();
stepper.run();
}

2)Web页面(index.html)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<!DOCTYPE html>
<html>
<head>
<title>Stepper Motor</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" type="text/css" href="style.css">
<link rel="icon" type="image/png" href="favicon.png">
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.7.2/css/all.css" integrity="sha384-fnmOCqbTlWIlj8LyTjo7mOUStjsKC4pOpQbqyi7RrhN7udi9RwhKkMHpvLbHG9Sr" crossorigin="anonymous">
</head>
<body>
<div class="topnav">
<h1>Stepper Motor Control <i class="fas fa-cogs"></i></h1>
</div>
<div class="content">
<form>
<input type="radio" id="CW" name="direction" value="CW" checked>
<label for="CW">Clockwise</label>
<input type="radio" id="CCW" name="direction" value="CCW">
<label for="CW">Counterclockwise</label><br><br><br>
<label for="steps">Number of steps:</label>
<input type="number" id="steps" name="steps">
</form>
<button onclick="submitForm()">GO!</button>
<p>Motor state: <span id="motor-state">Stopped</span></p>
<p><i id="gear" class="fas fa-cog"></i> </p>

</div>
</body>
<script src="script.js"></script>
</html>

3)Web页面样式文件(style.css)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
html {
font-family: Arial, Helvetica, sans-serif;
}

h1 {
font-size: 1.8rem;
color: white;
}

p{
font-size: 20px;
text-align: center;
}

.topnav {
overflow: hidden;
background-color: #0A1128;
text-align: center;
}

body {
margin: 0;
}

.content {
padding: 20px;
max-width: max-content;
margin: 0 auto;
}

input[type=number], select {
width: 100%;
padding: 12px 20px;
margin: 8px 0;
display: inline-block;
border: 1px solid #ccc;
border-radius: 4px;
box-sizing: border-box;
}

form{
border-radius: 5px;
background-color: #f2f2f2;
padding: 20px;
}

button {
background-color: #034078;
border: none;
padding: 14px 20px;
text-align: center;
font-size: 20px;
border-radius: 4px;
transition-duration: 0.4s;
width: 100%;
color: white;
cursor: pointer;
}

button:hover {
background-color: #1282A2;
}

input[type="radio"] {
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
border-radius: 50%;
width: 16px;
height: 16px;
border: 2px solid #999;
transition: 0.2s all linear;
margin-right: 5px;
position: relative;
top: 4px;
}

input[type="radio"]:checked{
border: 6px solid #1282A2;
}

#motor-state{
font-weight: bold;
color: red;
}

#gear{
font-size:100px;
color:#2d3031cb;
}

.spin {
-webkit-animation:spin 4s linear infinite;
-moz-animation:spin 4s linear infinite;
animation:spin 4s linear infinite;
}

.spin-back {
-webkit-animation:spin-back 4s linear infinite;
-moz-animation:spin-back 4s linear infinite;
animation:spin-back 4s linear infinite;
}

@-moz-keyframes spin { 100% { -moz-transform: rotate(360deg); } }
@-webkit-keyframes spin { 100% { -webkit-transform: rotate(360deg); } }
@keyframes spin { 100% { -webkit-transform: rotate(360deg); transform:rotate(360deg); } }

@-moz-keyframes spin-back { 100% { -moz-transform: rotate(-360deg); } }
@-webkit-keyframes spin-back { 100% { -webkit-transform: rotate(-360deg); } }
@keyframes spin-back { 100% { -webkit-transform: rotate(-360deg); transform:rotate(-360deg); } }

4)Web页面逻辑控制(script.js)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
var gateway = `ws://${window.location.hostname}/ws`;
var websocket;
window.addEventListener('load', onload);
var direction;

function onload(event) {
initWebSocket();
}

function initWebSocket() {
console.log('Trying to open a WebSocket connection…');
websocket = new WebSocket(gateway);
websocket.onopen = onOpen;
websocket.onclose = onClose;
websocket.onmessage = onMessage;
}

function onOpen(event) {
console.log('Connection opened');
}

function onClose(event) {
console.log('Connection closed');
document.getElementById("motor-state").innerHTML = "motor stopped"
setTimeout(initWebSocket, 2000);
}

function submitForm(){
const rbs = document.querySelectorAll('input[name="direction"]');
direction;
for (const rb of rbs) {
if (rb.checked) {
direction = rb.value;
break;
}
}

document.getElementById("motor-state").innerHTML = "motor spinning...";
document.getElementById("motor-state").style.color = "blue";
if (direction=="CW"){
document.getElementById("gear").classList.add("spin");
}
else{
document.getElementById("gear").classList.add("spin-back");
}

var steps = document.getElementById("steps").value;
websocket.send(steps+"&"+direction);
}

function onMessage(event) {
console.log(event.data);
direction = event.data;
if (direction=="stop"){
document.getElementById("motor-state").innerHTML = "motor stopped"
document.getElementById("motor-state").style.color = "red";
document.getElementById("gear").classList.remove("spin", "spin-back");
}
else if(direction=="CW" || direction=="CCW"){
document.getElementById("motor-state").innerHTML = "motor spinning...";
document.getElementById("motor-state").style.color = "blue";
if (direction=="CW"){
document.getElementById("gear").classList.add("spin");
}
else{
document.getElementById("gear").classList.add("spin-back");
}
}
}

运行结果:

在这里插入图片描述

文章来源: https://iotsmart.blog.csdn.net/article/details/127481607