아두이노 - 웹을 통해 자동차 제어 | Arduino - Control Car via Web

이 튜토리얼은 WiFi를 통해 스마트폰이나 PC의 웹 브라우저에서 Arduino를 사용하여 로봇 자동차를 무선으로 제어하는 방법을 안내합니다. 제어는 WebSocket 기술을 활용하는 그래픽 웹 사용자 인터페이스를 통해 관리되어 자동차의 원활하고 동적인 작동을 가능하게 합니다.

Arduino controls robot car via Web

준비물

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
공개: 이 섹션에서 제공된 링크 중 일부는 제휴 링크입니다. 이 링크를 통해 구매한 경우 추가 비용없이 수수료를 받을 수 있습니다. 지원해 주셔서 감사합니다.

2WD RC 자동차와 웹소켓에 대하여

이제 왜 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

2WD RC 자동차와 아두이노 간의 배선도

Arduino 2WD RC Car Wiring Diagram

이 이미지는 Fritzing을 사용하여 만들어졌습니다. 이미지를 확대하려면 클릭하세요.

일반적으로 두 개의 별도 전원이 필요합니다:

  • 모터용 하나.
  • 아두이노 보드와 L298N 모듈(모터 드라이버 역할)용 또 하나.

그러나 이 설정을 단순화할 수 있습니다. 단 한 개의 전원원 – 1.5V 배터리 4개를 사용해 총 6V를 만듭니다. 방법은 다음과 같습니다:

  • 지시된 대로 L298N 모듈에 배터리를 연결하세요.
  • L298N 모듈의 ENA와 ENB 핀에서 5볼트에 연결된 두 개의 점퍼를 제거하세요.
  • 점퍼에 '5VEN'이라고 표시되어 있으며 다이어그램에서 노란색 원으로 표시된 점퍼를 삽입하세요.
  • L298N 모듈의 12V 핀을 아두이노의 Vin 핀에 연결하세요. 이 연결을 통해 배터리에서 아두이노로 직접 전력을 공급합니다.

2WD RC 자동차에는 on/off 스위치가 포함되어 있어, 스위치를 통해 배터리를 연결할 수 있는 옵션이 제공됩니다. 이 설정을 통해 필요에 따라 자동차의 전원을 켜고 끌 수 있습니다. 더 간단한 배치를 선호하는 경우, 스위치를 완전히 우회하여 사용할 수도 있습니다.

아두이노 코드

웹 페이지의 콘텐츠(HTML, CSS, JavaScript)는 index.h 파일에 별도로 저장됩니다. 따라서 우리는 Arduino IDE에서 두 개의 코드 파일을 가지게 될 것입니다:

  • 아두이노 코드인 .ino 파일로, 웹 서버와 웹소켓 서버를 생성하고 자동차를 제어합니다.
  • 웹페이지의 내용을 포함한 .h 파일입니다.

사용 방법

  • 아두이노 우노 R4를 처음 사용하는 경우, 아두이노 우노 R4를 위한 환경 설정 방법을 보십시오.
  • 위 이미지와 같이 배선하세요.
  • 마이크로 USB 케이블을 사용하여 PC에 아두이노 보드를 연결하세요.
  • PC에서 아두이노 IDE를 엽니다.
  • 올바른 아두이노 보드(아두이노 우노 R4 와이파이)와 COM 포트를 선택하세요.
  • 아두이노 IDE의 왼쪽 탐색 바에 있는 Library Manager 아이콘을 클릭하여 라이브러리 관리자를 엽니다.
  • “mWebSockets”를 검색하고, Dawid Kurek이 만든 mWebSockets를 찾습니다.
  • Install 버튼을 클릭하여 mWebSockets 라이브러리를 설치하세요.
Arduino mWebSockets library
  • Arduino IDE에서 새 스케치를 생성하고, 이름을 지정하세요. 예를 들어, ArduinoGetStarted.com.ino로 지정합니다.
  • 아래 코드를 복사한 후 Arduino IDE로 열어주세요.
/* * 이 Arduino 코드는 newbiely.kr 에서 개발되었습니다 * 이 Arduino 코드는 어떠한 제한 없이 공개 사용을 위해 제공됩니다. * 상세한 지침 및 연결도에 대해서는 다음을 방문하세요: * https://newbiely.kr/tutorials/arduino/arduino-controls-car-via-web */ #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 // The Arduino pin connected to the ENA pin L298N #define IN1_PIN 6 // The Arduino pin connected to the IN1 pin L298N #define IN2_PIN 5 // The Arduino pin connected to the IN2 pin L298N #define IN3_PIN 4 // The Arduino pin connected to the IN3 pin L298N #define IN4_PIN 3 // The Arduino pin connected to the IN4 pin L298N #define ENB_PIN 2 // The Arduino pin connected to the ENB pin L298N const char *ssid = "YOUR_WIFI_SSID"; // CHANGE IT const char *password = "YOUR_WIFI_PASSWORD"; // CHANGE IT 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); // set full speed digitalWrite(ENB_PIN, HIGH); // set full speed //Initialize serial and wait for port to open: Serial.begin(9600); String fv = WiFi.firmwareVersion(); if (fv < WIFI_FIRMWARE_LATEST_VERSION) Serial.println("Please upgrade the firmware"); // attempt to connect to WiFi network: while (status != WL_CONNECTED) { Serial.print("Attempting to connect to SSID: "); Serial.println(password); // Connect to WPA/WPA2 network. Change this line if using open or WEP network: status = WiFi.begin(ssid, password); // wait 4 seconds for connection: delay(4000); } // print your board's IP address: 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(); // listen for incoming clients WiFiClient client = server.available(); if (client) { // read the HTTP request header line by line while (client.connected()) { if (client.available()) { String HTTP_header = client.readStringUntil('\n'); // read the header line of HTTP request if (HTTP_header.equals("\r")) // the end of HTTP request break; Serial.print("<< "); Serial.println(HTTP_header); // print HTTP request to Serial Monitor } } // send the HTTP response header client.println("HTTP/1.1 200 OK"); client.println("Content-Type: text/html"); client.println("Connection: close"); // the connection will be closed after completion of the response client.println(); // the separator between HTTP header and body String html = String(HTML_CONTENT); client.println(html); client.flush(); // give the web browser time to receive the data delay(100); // close the connection: 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 파일을 생성하려면:
    • 직렬 모니터 아이콘 바로 아래에 있는 버튼을 클릭하고 새 탭을 선택하거나, Ctrl+Shift+N 키를 사용하세요.
    Arduino IDE 2 adds file

    파일 이름을 index.h로 지정하고 OK 버튼을 클릭하세요.

    Arduino IDE 2 adds file index.h

    아래 코드를 복사하여 index.h 에 붙여넣으세요.

    /* * 이 Arduino 코드는 newbiely.kr 에서 개발되었습니다 * 이 Arduino 코드는 어떠한 제한 없이 공개 사용을 위해 제공됩니다. * 상세한 지침 및 연결도에 대해서는 다음을 방문하세요: * https://newbiely.kr/tutorials/arduino/arduino-controls-car-via-web */ 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; // MessageEvent //alert("msg : " + e_msg.data); } 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://" + window.location.host + ":81"); 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> )=====";
    • 이제 코드가 두 파일에 있습니다: ArduinoGetStarted.com.inoindex.h
    • 아두이노 IDE에서 Upload 버튼을 클릭하여 코드를 아두이노에 업로드하세요.

    아래와 같은 오류가 표시됩니다:

    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
    • Arduino IDE에서 Upload 버튼을 클릭하여 아두이노에 코드를 업로드하세요.
    • 시리얼 모니터를 엽니다.
    • 시리얼 모니터에서 결과를 확인하세요.
    COM6
    Send
    Connecting to WiFi... Connected to WiFi Arduino Web Server's IP address IP address: 192.168.0.2
    Autoscroll Show timestamp
    Clear output
    9600 baud  
    Newline  
    • 표시된 IP 주소를 기록하고, 이 주소를 스마트폰이나 PC의 웹 브라우저 주소 창에 입력하세요.
    • 아래와 같은 웹페이지가 표시될 것입니다:
    Arduino controls car via web browser
    • 웹소켓을 통해 웹페이지를 아두이노에 연결하려면 CONNECT 버튼을 클릭하세요.
    • 이제 웹 인터페이스를 통해 자동차를 좌/우로 회전시키거나 전진/후진하도록 제어할 수 있습니다.

    아두이노의 메모리를 절약하기 위해, 컨트롤 버튼의 이미지는 아두이노에 저장되지 않습니다. 대신, 그것들은 인터넷에 저장되어 있으므로, 웹 컨트롤 페이지의 이미지를 로드하려면 귀하의 휴대폰이나 PC가 인터넷 연결을 필요로 합니다.

    ※ NOTE THAT:

    • 만약 index.h 파일에서 HTML 내용을 수정하고 ArduinoGetStarted.com.ino 파일은 건드리지 않는다면, 코드를 컴파일하고 Arduino에 업로드할 때 Arduino IDE는 HTML 내용을 업데이트하지 않습니다.
    • 이 경우에 Arduino IDE가 HTML 내용을 업데이트하게 하려면, ArduinoGetStarted.com.ino 파일에서 변경을 하세요 (예를 들어, 빈 줄을 추가하거나 주석을 추가하는 등).

    코드 줄별 설명

    위의 아두이노 코드에는 줄마다 설명이 포함되어 있습니다. 코드에 있는 주석을 읽어주세요!

※ OUR MESSAGES

  • Please feel free to share the link of this tutorial. However, Please do not use our content on any other websites. We invested a lot of effort and time to create the content, please respect our work!