아두이노 - 웹을 통해 자동차 제어 | Arduino - Control Car via Web
이 튜토리얼은 WiFi를 통해 스마트폰이나 PC의 웹 브라우저에서 Arduino를 사용하여 로봇 자동차를 무선으로 제어하는 방법을 안내합니다. 제어는 WebSocket 기술을 활용하는 그래픽 웹 사용자 인터페이스를 통해 관리되어 자동차의 원활하고 동적인 작동을 가능하게 합니다.
1 | × | Arduino UNO R4 WiFi | Amazon | |
1 | × | USB Cable Type-C | 쿠팡 | Amazon | |
1 | × | 2WD RC Car | 쿠팡 | Amazon | |
1 | × | L298N Motor Driver Module | 쿠팡 | Amazon | |
1 | × | IR Remote Controller Kit | Amazon | |
1 | × | CR2025 Battery (for IR Remote controller) | Amazon | |
1 | × | 1.5V AA Battery (for Arduino and Car) | Amazon | |
1 | × | Jumper Wires | Amazon | |
1 | × | Breadboard | 쿠팡 | Amazon | |
1 | × | (Recommended) Screw Terminal Block Shield for Arduino UNO R4 | 쿠팡 | Amazon | |
1 | × | (Recommended) Breadboard Shield For Arduino UNO R4 | 쿠팡 | Amazon | |
1 | × | (Recommended) Enclosure For Arduino UNO R4 | Amazon | |
공개: 이 섹션에서 제공된 링크 중 일부는 제휴 링크입니다. 이 링크를 통해 구매한 경우 추가 비용없이 수수료를 받을 수 있습니다. 지원해 주셔서 감사합니다.
이제 왜 WebSocket을 선택해야 할까요? 여기 이유가 있습니다:
WebSocket이 없다면, 자동차의 방향을 바꿀 때마다 페이지를 새로고침해야 합니다. 이는 효율적이지 않습니다!
WebSocket을 사용하면, 웹페이지와 아두이노 사이에 특별한 연결이 만들어집니다. 이를 통해 페이지를 새로고침할 필요 없이 배경에서 아두이노에 명령을 보낼 수 있습니다. 결과는 어떻게 되나요? 로봇 자동차가 매끄럽고 즉시 반응합니다. 꽤 인상적이지 않나요?
요약하자면, WebSocket은 로봇의 원활하고 실시간 제어를 가능하게 합니다.
우리는 2WD RC 자동차와 웹소켓에 대한 특정 튜토리얼을 가지고 있습니다. 각 튜토리얼에는 하드웨어 핀 배치, 작동 원리, 아두이노와의 연결 배선, 아두이노 코드 등에 대한 자세한 정보와 단계별 지침이 포함되어 있습니다. 다음 링크에서 자세히 알아보세요:
아두이노 코드는 웹 서버와 웹소켓 서버를 모두 설정합니다. 그것이 작동하는 방식은 다음과 같습니다:
아두이노의 IP 주소를 웹 브라우저에 입력하면, 웹 페이지(사용자 인터페이스)에 대한 요청을 아두이노에 보냅니다.
아두이노의 웹 서버는 웹 페이지의 내용(HTML, CSS, JavaScript)을 전송하여 응답합니다.
그러면 웹 브라우저가 이 웹 페이지를 표시합니다.
웹 페이지 내부에서 JavaScript 코드가 아두이노에 있는 WebSocket 서버와 WebSocket 연결을 시작합니다.
이 WebSocket 연결이 활성화되면, 웹 페이지에서 버튼을 누르거나 놓으면 JavaScript 코드가 이 연결을 통해 아두이노에 명령을 조용히 전송합니다.
아두이노의 WebSocket 서버는 이 명령들을 받고 로봇 자동차를 그에 맞게 조종합니다.
아래 표는 사용자의 조작에 따라 웹페이지가 아두이노에 보내는 명령어 목록을 보여줍니다:
User's Action | Button | Command | Car Action |
PRESS | UP | 1 | MOVE FORWARD |
PRESS | DOWN | 2 | MOVE BACKWARD |
PRESS | LEFT | 4 | TURN LEFT |
PRESS | RIGHT | 8 | TURN RIGHT |
PRESS | STOP | 0 | STOP |
RELEASE | UP | 0 | STOP |
RELEASE | DOWN | 0 | STOP |
RELEASE | LEFT | 0 | STOP |
RELEASE | RIGHT | 0 | STOP |
RELEASE | STOP | 0 | STOP |
이 이미지는 Fritzing을 사용하여 만들어졌습니다. 이미지를 확대하려면 클릭하세요.
일반적으로 두 개의 별도 전원이 필요합니다:
그러나 이 설정을 단순화할 수 있습니다. 단 한 개의 전원원 – 1.5V 배터리 4개를 사용해 총 6V를 만듭니다. 방법은 다음과 같습니다:
지시된 대로 L298N 모듈에 배터리를 연결하세요.
L298N 모듈의 ENA와 ENB 핀에서 5볼트에 연결된 두 개의 점퍼를 제거하세요.
점퍼에 '5VEN'이라고 표시되어 있으며 다이어그램에서 노란색 원으로 표시된 점퍼를 삽입하세요.
L298N 모듈의 12V 핀을 아두이노의 Vin 핀에 연결하세요. 이 연결을 통해 배터리에서 아두이노로 직접 전력을 공급합니다.
2WD RC 자동차에는 on/off 스위치가 포함되어 있어, 스위치를 통해 배터리를 연결할 수 있는 옵션이 제공됩니다. 이 설정을 통해 필요에 따라 자동차의 전원을 켜고 끌 수 있습니다. 더 간단한 배치를 선호하는 경우, 스위치를 완전히 우회하여 사용할 수도 있습니다.
웹 페이지의 콘텐츠(HTML, CSS, JavaScript)는 index.h 파일에 별도로 저장됩니다. 따라서 우리는 Arduino IDE에서 두 개의 코드 파일을 가지게 될 것입니다:
위 이미지와 같이 배선하세요.
마이크로 USB 케이블을 사용하여 PC에 아두이노 보드를 연결하세요.
PC에서 아두이노 IDE를 엽니다.
올바른 아두이노 보드(아두이노 우노 R4 와이파이)와 COM 포트를 선택하세요.
아두이노 IDE의 왼쪽 탐색 바에 있는 Library Manager 아이콘을 클릭하여 라이브러리 관리자를 엽니다.
“mWebSockets”를 검색하고, Dawid Kurek이 만든 mWebSockets를 찾습니다.
Install 버튼을 클릭하여 mWebSockets 라이브러리를 설치하세요.
Arduino IDE에서 새 스케치를 생성하고, 이름을 지정하세요. 예를 들어, ArduinoGetStarted.com.ino로 지정합니다.
아래 코드를 복사한 후 Arduino IDE로 열어주세요.
#include <WiFiS3.h>
#include <WebSocketServer.h>
#include "index.h"
#define CMD_STOP 0
#define CMD_FORWARD 1
#define CMD_BACKWARD 2
#define CMD_LEFT 4
#define CMD_RIGHT 8
#define ENA_PIN 7
#define IN1_PIN 6
#define IN2_PIN 5
#define IN3_PIN 4
#define IN4_PIN 3
#define ENB_PIN 2
const char *ssid = "YOUR_WIFI_SSID";
const char *password = "YOUR_WIFI_PASSWORD";
using namespace net;
WebSocketServer webSocket(81);
WiFiServer server(80);
int status = WL_IDLE_STATUS;
void setup() {
Serial.begin(9600);
pinMode(ENA_PIN, OUTPUT);
pinMode(IN1_PIN, OUTPUT);
pinMode(IN2_PIN, OUTPUT);
pinMode(IN3_PIN, OUTPUT);
pinMode(IN4_PIN, OUTPUT);
pinMode(ENB_PIN, OUTPUT);
digitalWrite(ENA_PIN, HIGH);
digitalWrite(ENB_PIN, HIGH);
Serial.begin(9600);
String fv = WiFi.firmwareVersion();
if (fv < WIFI_FIRMWARE_LATEST_VERSION)
Serial.println("Please upgrade the firmware");
while (status != WL_CONNECTED) {
Serial.print("Attempting to connect to SSID: ");
Serial.println(password);
status = WiFi.begin(ssid, password);
delay(4000);
}
Serial.print("IP Address: ");
Serial.println(WiFi.localIP());
server.begin();
webSocket.onConnection([](WebSocket &ws) {
const auto protocol = ws.getProtocol();
if (protocol) {
Serial.print(F("Client protocol: "));
Serial.println(protocol);
}
ws.onMessage([](WebSocket &ws, const WebSocket::DataType dataType,
const char *message, uint16_t length) {
String cmd_str = String((char *)message);
int command = cmd_str.toInt();
Serial.print("command: ");
Serial.println(command);
switch (dataType) {
case WebSocket::DataType::TEXT:
switch (command) {
case CMD_STOP:
Serial.println("Stop");
CAR_stop();
break;
case CMD_FORWARD:
Serial.println("Move Forward");
CAR_moveForward();
break;
case CMD_BACKWARD:
Serial.println("Move Backward");
CAR_moveBackward();
break;
case CMD_LEFT:
Serial.println("Turn Left");
CAR_turnLeft();
break;
case CMD_RIGHT:
Serial.println("Turn Right");
CAR_turnRight();
break;
default:
Serial.println("Unknown command");
}
break;
case WebSocket::DataType::BINARY:
Serial.println(F("Received binary data"));
break;
}
});
ws.onClose([](WebSocket &, const WebSocket::CloseCode, const char *,
uint16_t) {
Serial.println(F("Disconnected"));
});
Serial.print(F("New WebSocket Connnection from client: "));
Serial.println(ws.getRemoteIP());
});
webSocket.begin();
}
void loop() {
webSocket.listen();
WiFiClient client = server.available();
if (client) {
while (client.connected()) {
if (client.available()) {
String HTTP_header = client.readStringUntil('\n');
if (HTTP_header.equals("\r"))
break;
Serial.print("<< ");
Serial.println(HTTP_header);
}
}
client.println("HTTP/1.1 200 OK");
client.println("Content-Type: text/html");
client.println("Connection: close");
client.println();
String html = String(HTML_CONTENT);
client.println(html);
client.flush();
delay(100);
client.stop();
}
}
void CAR_moveForward() {
digitalWrite(IN1_PIN, HIGH);
digitalWrite(IN2_PIN, LOW);
digitalWrite(IN3_PIN, HIGH);
digitalWrite(IN4_PIN, LOW);
}
void CAR_moveBackward() {
digitalWrite(IN1_PIN, LOW);
digitalWrite(IN2_PIN, HIGH);
digitalWrite(IN3_PIN, LOW);
digitalWrite(IN4_PIN, HIGH);
}
void CAR_turnLeft() {
digitalWrite(IN1_PIN, HIGH);
digitalWrite(IN2_PIN, LOW);
digitalWrite(IN3_PIN, LOW);
digitalWrite(IN4_PIN, LOW);
}
void CAR_turnRight() {
digitalWrite(IN1_PIN, LOW);
digitalWrite(IN2_PIN, LOW);
digitalWrite(IN3_PIN, HIGH);
digitalWrite(IN4_PIN, LOW);
}
void CAR_stop() {
digitalWrite(IN1_PIN, LOW);
digitalWrite(IN2_PIN, LOW);
digitalWrite(IN3_PIN, LOW);
digitalWrite(IN4_PIN, LOW);
}
코드에서 WiFi 정보(SSID 및 비밀번호)를 자신의 네트워크 자격 증명과 일치하도록 수정하세요.
아두이노 IDE에서 index.h 파일을 생성하려면:
파일 이름을 index.h로 지정하고 OK 버튼을 클릭하세요.
아래 코드를 복사하여 index.h 에 붙여넣으세요.
const char *HTML_CONTENT = R"=====(
<!DOCTYPE html>
<html>
<head>
<title>Arduino Control Car via Web</title>
<meta name="viewport" content="width=device-width, initial-scale=0.7, maximum-scale=1, user-scalable=no">
<style type="text/css">
body { text-align: center; font-size: 24px;}
button { text-align: center; font-size: 24px;}
#container {
margin-right: auto;
margin-left: auto;
width: 400px;
height: 400px;
position: relative;
margin-bottom: 10px;
}
div[class^='button'] { position: absolute; }
.button_up, .button_down { width:214px; height:104px;}
.button_left, .button_right { width:104px; height:214px;}
.button_stop { width:178px; height:178px;}
.button_up {
background: url('https://esp32io.com/images/tutorial/up_inactive.png') no-repeat;
background-size: contain;
left: 200px;
top: 0px;
transform: translateX(-50%);
}
.button_down {
background: url('https://esp32io.com/images/tutorial/down_inactive.png') no-repeat;
background-size: contain;
left:200px;
bottom: 0px;
transform: translateX(-50%);
}
.button_right {
background: url('https://esp32io.com/images/tutorial/right_inactive.png') no-repeat;
background-size: contain;
right: 0px;
top: 200px;
transform: translateY(-50%);
}
.button_left {
background: url('https://esp32io.com/images/tutorial/left_inactive.png') no-repeat;
background-size: contain;
left:0px;
top: 200px;
transform: translateY(-50%);
}
.button_stop {
background: url('https://esp32io.com/images/tutorial/stop_inactive.png') no-repeat;
background-size: contain;
left:200px;
top: 200px;
transform: translate(-50%, -50%);
}
</style>
<script>
var CMD_STOP = 0;
var CMD_FORWARD = 1;
var CMD_BACKWARD = 2;
var CMD_LEFT = 4;
var CMD_RIGHT = 8;
var img_name_lookup = {
[CMD_STOP]: "stop",
[CMD_FORWARD]: "up",
[CMD_BACKWARD]: "down",
[CMD_LEFT]: "left",
[CMD_RIGHT]: "right"
}
var ws = null;
function init()
{
var container = document.querySelector("#container");
container.addEventListener("touchstart", mouse_down);
container.addEventListener("touchend", mouse_up);
container.addEventListener("touchcancel", mouse_up);
container.addEventListener("mousedown", mouse_down);
container.addEventListener("mouseup", mouse_up);
container.addEventListener("mouseout", mouse_up);
}
function ws_onmessage(e_msg)
{
e_msg = e_msg || window.event;
}
function ws_onopen()
{
document.getElementById("ws_state").innerHTML = "OPEN";
document.getElementById("wc_conn").innerHTML = "Disconnect";
}
function ws_onclose()
{
document.getElementById("ws_state").innerHTML = "CLOSED";
document.getElementById("wc_conn").innerHTML = "Connect";
console.log("socket was closed");
ws.onopen = null;
ws.onclose = null;
ws.onmessage = null;
ws = null;
}
function wc_onclick()
{
if(ws == null)
{
ws = new WebSocket("ws:
document.getElementById("ws_state").innerHTML = "CONNECTING";
ws.onopen = ws_onopen;
ws.onclose = ws_onclose;
ws.onmessage = ws_onmessage;
}
else
ws.close();
}
function mouse_down(event)
{
if (event.target !== event.currentTarget)
{
var id = event.target.id;
send_command(id);
event.target.style.backgroundImage = "url('https://esp32io.com/images/tutorial/" + img_name_lookup[id] + "_active.png')";
}
event.stopPropagation();
event.preventDefault();
}
function mouse_up(event)
{
if (event.target !== event.currentTarget)
{
var id = event.target.id;
send_command(CMD_STOP);
event.target.style.backgroundImage = "url('https://esp32io.com/images/tutorial/" + img_name_lookup[id] + "_inactive.png')";
}
event.stopPropagation();
event.preventDefault();
}
function send_command(cmd)
{
if(ws != null)
if(ws.readyState == 1)
ws.send(cmd + "\r\n");
}
window.onload = init;
</script>
</head>
<body>
<h2>Arduino - RC Car via Web</h2>
<div id="container">
<div id="0" class="button_stop"></div>
<div id="1" class="button_up"></div>
<div id="2" class="button_down"></div>
<div id="8" class="button_right"></div>
<div id="4" class="button_left"></div>
</div>
<p>
WebSocket : <span id="ws_state" style="color:blue">closed</span><br>
</p>
<button id="wc_conn" type="button" onclick="wc_onclick();">Connect</button>
<br>
<br>
<div class="sponsor">Sponsored by <a href="https://amazon.com/diyables">DIYables</a></div>
</body>
</html>
)=====";
아래와 같은 오류가 표시됩니다:
In file included from c:\Users\YOU_ACCOUNT\Documents\Arduino\libraries\mWebSockets\src/utility.h:3:0,
from c:\Users\YOU_ACCOUNT\Documents\Arduino\libraries\mWebSockets\src/WebSocket.h:5,
from c:\Users\YOU_ACCOUNT\Documents\Arduino\libraries\mWebSockets\src/WebSocketServer.h:5,
from C:\Users\YOU_ACCOUNT\Documents\Arduino\ArduinoGetStarted.com\ArduinoGetStarted.com.ino:2:
C:\Users\YOU_ACCOUNT\Documents\Arduino\libraries\mWebSockets\src/platform.h:54:12: fatal error: Ethernet.h: No such file or directory
# include <Ethernet.h>
^~~~~~~~~~~~
compilation terminated.
exit status 1
이 오류를 수정하려면:
C:\Users\YOU_ACCOUNT\Documents\Arduino\libraries\mWebSockets\src/ 디렉토리로 이동하세요.
Config.h 파일을 찾아 텍스트 편집기로 열어주세요.
26번째 줄을 봐주세요, 아래와 같이 보일 겁니다:
#define NETWORK_CONTROLLER ETHERNET_CONTROLLER_W5X00
아래와 같이 이 줄을 변경하고 저장하세요:
#define NETWORK_CONTROLLER NETWORK_CONTROLLER_WIFI
Connecting to WiFi...
Connected to WiFi
Arduino Web Server's IP address IP address: 192.168.0.2
아두이노의 메모리를 절약하기 위해, 컨트롤 버튼의 이미지는 아두이노에 저장되지 않습니다. 대신, 그것들은 인터넷에 저장되어 있으므로, 웹 컨트롤 페이지의 이미지를 로드하려면 귀하의 휴대폰이나 PC가 인터넷 연결을 필요로 합니다.
※ NOTE THAT:
만약 index.h 파일에서 HTML 내용을 수정하고 ArduinoGetStarted.com.ino 파일은 건드리지 않는다면, 코드를 컴파일하고 Arduino에 업로드할 때 Arduino IDE는 HTML 내용을 업데이트하지 않습니다.
이 경우에 Arduino IDE가 HTML 내용을 업데이트하게 하려면, ArduinoGetStarted.com.ino 파일에서 변경을 하세요 (예를 들어, 빈 줄을 추가하거나 주석을 추가하는 등).
위의 아두이노 코드에는 줄마다 설명이 포함되어 있습니다. 코드에 있는 주석을 읽어주세요!