본문 바로가기
아두이노 관련/NodeMCU

NodeMCU - 웹서버 웹페이지에서 NodeMCU 서보모터 제어하기

by Bugwhale 2020. 2. 18.

1.개요

이전 글에서 LED 를 제어하는 웹페이지를 만들고 NodeMCU를 웹서버로 구동하는 코드를 업로드하여 LED 는 ON/OFF 하는 작업을 해보았다. 이번 글에서는 응용 과정으로 웹페이지에서 NodeMCU 웹서버를 기반으로 서보모터 각도를 조절하는 작업을 해볼 것이다. 제어하는 부분(요청/응답)은 LED ON/OFF 과 거의 비슷하여 오히려 슬라이더와 슬라이더에 의해 변경된 값을 구현하는데 JavaScript, Jquery 라이브러리 문법을 설명하는데 중점을 두겠다.

1.1 요약 정리

1. HTML 언어로 웹페이지 구성 (아두이노 IDE)
2. 구성한 웹페이지를 NodeMCU 보드에 업로드 (아두이노 IDE)
3. NodeMCU 보드를 Wi-Fi에 연결 (NodeMcu 보드)
4. NodeMCU 보드를 웹서버로 구동 (NodeMcu 보드)
5. 웹클라이언트로 NodeMCU 보드 웹서버에 웹페이지 요청 (크롬, 파이어폭스 등의 웹브라우저)
6. 웹클라이언트로 요청받은 웹페이지 전송 (NodeMcu 보드)
7. 웹페이즈에서 슬라이더를 움직여 슬라이더 값을 웹서버로 데이터 전송 (크롬, 파이어폭스 등의 웹브라우저)
8. 웹서버에서 슬라이더 값을 확인 후 서보모터 이동 (NodeMcu 보드)

1.2 준비물

  • NodeMCU 개발보드(이 글에서는 NodeMCU v3)
  • Micro 5pin USB
  • LED 1개
  • 서보모터 (SG90)

2. 웹페이지 구성

<!DOCTYPE html>
<html>
<head>
	<meta charset="UTF-8">
	<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
</head>
<body>
	<h2>Servo Control Webpage</h2>
	Servo position : <span id="servoPosText">90</span>
	<br>
	<input type="range" min="0" max="180" id="servoSlider" onchange="servoWrite(this.value)">
	<script>
		var slider = document.getElementById("servoSlider");
		var servoPos = document.getElementById("servoPosText");
		slider.oninput = function() { 
			slider.value = this.value;
			servoPos.innerHTML = this.value;
		}
		$.ajaxSetup({timeout:1000});
		function servoWrite(pos) {
			$.get("/pos=" + pos + "d");
			{Connection: close};
		}
	</script>
</body>
</html>

JQuery 라이브러리 사용을 위해 불러드리는 부분입니다.

	<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>

슬라이더 값을 표시해주는 부분입니다. 초기값은 90 으로 설정되어있습니다.

	Servo position : <span id="servoPosText">90</span>

type 를 range 설정하여 슬라이더를 만들어주는 부분이며 최소, 최대 값은 서보모터 최소, 최대값인 0 과 180 으로 설정하였습니다. onchange 속성은 값이 바뀌었을 때 동작하는 이벤트입니다.

	<input type="range" min="0" max="180" id="servoSlider" onchange="servoWrite(this.value)">

3. 하드웨어 구성

이 글에서 사용한 서보모터는 SG90 모델인데 정격전압이 4.7 ~ 5.0 VDC 이며 NodeMCU 의 전원전압은 3.3 VDC 보니 서보모터가 정상적으로 동작하지 않을 수 있습니다. NodeMCU 애는 VU(V USB)가 있어 USB 로부터 들어오는 전원을 사용해도 되지만 그마저도 서보모터가 요구하는 정격 전류가 높다면 동작에 문제가 생길 수 있습니다.

그래서 아래 하드웨어 구성에는 보조배터리 또는 건전지를 사용한 외부전원을 서보모터에 넣어주었습니다.

 

4. 소스코드

4.1 전체 소스코드

#include <ESP8266WiFi.h>
#include <Servo.h>
 
#define PIN_SERVO D0

const char* ssid = "Wi-Fi 아이디";
const char* password = "Wi-Fi 패스워드";
 
WiFiServer server(80);
Servo myServo;

void setup() {
	Serial.begin(115200);
	myServo.attach(PIN_SERVO);

	WiFi.mode(WIFI_STA);
	WiFi.begin(ssid, password);

	while (WiFi.status() != WL_CONNECTED) {
		delay(500);
		Serial.print(".");
	}
	Serial.println("");
	Serial.print("Connected to ");
	Serial.println(ssid);
	Serial.print("IP address: ");
	Serial.println(WiFi.localIP());

	server.begin();
	Serial.println("Server started");
}

void loop() {
	WiFiClient client = server.available();
	if(!client) return;

	Serial.println("새로운 클라이언트");
	client.setTimeout(5000);

	String request = client.readStringUntil('\r');
	Serial.println("request: ");
	Serial.println(request);

	while(client.available()) {
		client.read();
	}

	if(request.indexOf("/pos=") >= 0) {
		int pos1 = request.indexOf('=');
		int pos2 = request.indexOf('d');
		String servoPos = request.substring(pos1+1, pos2);

		myServo.write(servoPos.toInt());
		Serial.println(servoPos); 
	}

	client.print("<!DOCTYPE HTML>");
	client.print("<html>");
	client.print("<head>");
	client.print("<meta charset=\"UTF-8\">");
	client.print("<script src=\"https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js\"></script>");
	client.print("</head>");
	client.print("<body>");
	client.print("<h2>Servo Control Webpage</h2>");
	client.print("Servo position : <span id=\"servoPos\"></span>");
	client.print("<br>");
	client.print("<input type=\"range\" min=\"0\" max=\"180\" id=\"servoSlider\" onchange=\"servoWrite(this.value)\"/>");
	client.print("<script>");
	client.print("var slider = document.getElementById(\"servoSlider\");");
	client.print("var servoPos = document.getElementById(\"servoPos\");");
	client.print("slider.oninput = function() {");
	client.print("slider.value = this.value;");
	client.print("servoPos.innerHTML = this.value;}");
	client.print("\n");
	client.print("$.ajaxSetup({timeout:1000});");
	client.print("function servoWrite(pos) {");
	client.print("$.get(\"/pos=\" + pos + \"d\");");
	client.print("{Connection: close};}");
	client.print("</script>");
	client.print("</body>");
	client.print("</html>");

	Serial.println("클라이언트 연결 해제");
}

4.2 소스코드 설명

4.2.1 선언 코드
#include <ESP8266WiFi.h>
#include <Servo.h>

ESP8266WiFi 와 Servo 라이브러리 사용합니다.

#define PIN_SERVO D0

const char* ssid = "Wi-Fi 아이디";
const char* password = "Wi-Fi 패스워드";

D0 핀 번호 설정을 해주고 5, 6 번줄은 본인의 Wi-Fi 아이디와 패스워드를 하나도 틀리지 않고 작성해줍니다.

WiFiServer server(80);
Servo myServo;

server 이름으로 WiFiServer 객체를 선언합니다. 여기서 매개변수 값인 80 은 내부에서 사용될 포트번호이며 http 프로토콜은 80번 포트를 사용하니 특별한 이유가 없다면 변경하지 않습니다. 그 아래는 서보 모터를 위한 객체를 선언부입니다.

4.2.2 setup() 코드
	Serial.begin(115200);
	myServo.attach(PIN_SERVO);

시리얼 모니터 초기화 시켜주고 그 아래 서보모터를 PIN_SERVO 핀에 연결하고 초기화 설정을 해줍니다.

	WiFi.mode(WIFI_STA);
	WiFi.begin(ssid, password);

mode 메서드로 ESP8266 모듈의 모드를 WIFI_STA 모드(다른 기기가 이 모듈을 통하여 인터넷 접속 금지)로 설정하며 begin 메서드로 Wi-Fi 연결을 시도합니다.

	while (WiFi.status() != WL_CONNECTED) {
		delay(500);
		Serial.print(".");
	}

Wi-Fi 접속상태를 계속하여 확인하며 접속될때까지 반복합니다.

	Serial.print("Connecting to ");
	Serial.println(ssid);
	Serial.print("IP address: ");
	Serial.println(WiFi.localIP());

접속된 Wi-Fi 아이디와 IP 주소를 시리얼 모니터에 출력시켜준다. 웹브라우저를 통하여 접속할때를 대비하여 IP 주소를 기억합니다.

	server.begin();

server.begin() 로 서버를 시작합니다.

4.2.3 loop() 코드
	WiFiClient client = server.available();
	if(!client) return;

클라이언트가 접속하였는지 확인하며 접속이 되어있지 않다면 return 으로 종료시킵니다. 여기서 클라이언트란 웹브라우저를 통해 접속한 유저를 의미합니다.

	client.setTimeout(5000);

클라이언트에게 HTTP를 전송을 시도하며 5000 ms(5초) 초과 시 타임아웃으로 넘어갑니다.

	String request = client.readStringUntil('\r');
	Serial.println("request: ");
	Serial.println(request);

전송받은 데이터를 \r 까지 (HTTP/1.1 까지) 클라이언트가 서버에 요청한 정보. 여기서는 URL 을 알기위해 사용되었습니다.

	while(client.available()) {
		client.read();
	}

그 이외에 request 정보는 필요없으니 수신된 request 데이터를 전부 읽어버립니다.(버퍼 비움 효과)

	if(request.indexOf("/pos=") >= 0) {
		int pos1 = request.indexOf('=');
		int pos2 = request.indexOf('d');
		String servoPos = request.substring(pos1+1, pos2);
 
		myServo.write(servoPos.toInt());
		Serial.println(servoPos); 
	}

요청한 정보에 /pos= 가 포함되어 있다면 = 뒤부터 d 사이의 값을 servoPos 문자열에 저장합니다. myServo.write() 의 매개변수는 int 자료형으로 받아들이니 servoPos.toInt() 로 자료형을 변환하여 넣어줍니다.

	client.print("<!DOCTYPE HTML>");
	client.print("<html>");
	client.print("<head>");
	client.print("<meta charset=\"UTF-8\">");
	client.print("<script src=\"https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js\"></script>");
	client.print("</head>");
	client.print("<body>");
	client.print("<h2>Servo Control Webpage</h2>");
	client.print("Servo position : <span id=\"servoPos\"></span>");
	client.print("<br>");
	client.print("<input type=\"range\" min=\"0\" max=\"180\" id=\"servoSlider\" onchange=\"servoWrite(this.value)\"/>");
	client.print("<script>");
	client.print("var slider = document.getElementById(\"servoSlider\");");
	client.print("var servoPos = document.getElementById(\"servoPos\");");
	client.print("slider.oninput = function() {");
	client.print("slider.value = this.value;");
	client.print("servoPos.innerHTML = this.value;}");
	client.print("\n");
	client.print("$.ajaxSetup({timeout:1000});");
	client.print("function servoWrite(pos) {");
	client.print("$.get(\"/pos=\" + pos + \"d\");");
	client.print("{Connection: close};}");
	client.print("</script>");
	client.print("</body>");
	client.print("</html>");

위에서 구상한 웹페이지를 NodeMcu 에서 사용하기 위한 코드로 바꿔 작성하였습니다.

4. 동작 결과

5. 참조

-

댓글