[40호]Wi-Fi를 품은 한국형 아두이노 오렌지보드WiFi
Wi-Fi를 품은 한국형 아두이노
오렌지보드WiFi
글 | 금강초롱 blog.naver.com/crucian2k3
아두이노 제품을 조금이라도 다뤄본 독자님들이라면 언젠간 꼭 한번 도전해 보고 싶은 영역이 바로 네트워크일 것입니다. 그러나 막상 시도해 보려하면 어디서부터 어떻게 접근해야 할지 너무 막막하기도 하고 어렵사리 쉴드 보드를 장만하여 테스트 해보려 해도 그리 생각처럼 쉽지 않은 것이 현실입니다.
오렌지보드 시리즈로 유명한 우리나라의 코코아팹에서 따끈한 WiFi 기반의 아두이노 우노 계열의 보드를 내놓았습니다. 이 보드에는 ATmega328P와 USB FTDI 칩이 내장되어 있으므로 여느 아두이노 시리즈와 별반 다를 것은 없으나, 위즈네트에서 출시한 WiZ250 무선통신모듈이 장착되어 있어 좀 더 손쉽게 네트워크에 발을 들여 놓도록 배려해 주고 있습니다. 배려라기보다도 더 정확히는 이 보드의 핵심이라고 볼 수 있겠습니다.
다만 위즈네트 무선랜 모듈이 SPI 채널을 활용하고 있어 일부 핀들이 여기에 할당토록 살짝 제약이 따르긴 합니다만, 아두이노 보드에 쉴드 등을 얹는 경우에도 이와 크게 다르지 않기에 그리 민감하게 반응할 사안은 아닌 듯합니다.
SPI 채널을 매개체로 하여 동작되는 위즈네트 무선랜 모듈은 내부적으로는 AT 커맨드를 사용해 코어와 통신하도록 설계되어 있습니다.
우선 보드가 어떠한 특징들을 포함하고 있는지 쭉~ 살펴본 후에, 코코아팹에서 준비해놓고 있는 튜토리얼과 필자가 랜 통신을 테스트할 때 약방의 감초처럼 여기는 NTP 클라이언트 모듈을 실행해 가며 이 보드를 어떻게 응용할 수 있을지 확인해 보고자 합니다. 이글은 (주)엔티렉스의 지원을 받아 작성하게 되었습니다.
이번 글은 아무래도 난이도가 다소 있을 수밖에 없는 불가피성이 있으며, 적어도 아두이노 우노로 LED나 Serial 모니터, 라이브러리 등은 자유롭게 사용할 줄 안다는 전제하게 글을 써 보고자 합니다.
또 한 가지는 TCP/IP 네트워크, 웹기반 프로그래밍이 등장하게 되는데 이런 부분들까지 일일이 설명하는 것은, 제 지식이 얕기도 하거니와 범위가 너무 넓어질 수도 있으므로, 튜토리얼을 동작하고 이해해 보는 선에서 간략히 정리하고자 합니다.
이더넷통신, IOT 체계 등에 대해서 궁금하신 사항에 대해서는 인터넷상에 충분히 제공되어 있는 정보를 습득해 보는 것도 좋은 방법이 될 것으로 봅니다.
1. 오렌지보드 HW 구성
1.1. 오렌지보드WiFi 상세스펙
오렌지보드에 적용된 MCU가 아두이노 우노(Uno)에도 적용된 ATmeag328P 칩셋이며 원래 제조사는 Atmel사 입니다만, 최근 PIC라는 제품으로 유명한 마이크로칩스에 인수·합병 되었습니다. 따라서 보드의 구성은 위즈네트의 Wi-Fi 모듈을 제외하면 아두이노 우노와 거의 동일하다고 볼 수 있겠습니다. 이런 경우를 일반적으로 동일한 폼펙터를 사용하고 있다고도 이야기하곤 합니다.
필자는 금번에 소개하는 오렌지보드 시리즈를 사용하여 옐로우캣이라는 귀여운 로봇을 아이들과 만들어본 바가 있어 코코아팹 시리즈는 그리 낯설지 않은 편입니다.
보드에 대한 더 상세한 해부는 아래에서 다뤄보기로 하고 WizFi250 Wi-Fi 모듈의 스펙에 대해서 상세히 살펴보면 아래와 같습니다.
1.2. 위즈넷의 대표적인 Wi-Fi 모듈
위즈네트는 사물인터넷(Internet Of Things)을 위한 인터넷 프로세서를 개발·보급하는 팹리스 벤처기업이라고 자신들을 소개하고 있습니다.
필자는 수년 전부터 이더넷에 관심이 있어 마이크로칩사의 PIC18F97J60으로 인터넷 라디오 등 몇 가지 프로젝트를 진행한 바 있었으며 그때 위즈네트 초창기 발매 칩들을 검토해본 바 있습니다. 위즈네트사에서 시판 중인 임베디드형 와이파이 모듈은 현재 5종(WizFi310, WizFi250, WizFi210, WizFi220, WizFi630A)이 있으며 좌측에 이중 3종을 정리해 보았습니다. 개별칩을 구입해서 제작하기에는 이런저런 부담이 있는 경우에, 이러한 모듈형은 상당한 간편함을 제공해줄 수 있다고 봅니다.
WizFi250이 특별히 눈에 띄는 부분이 있다면 제어명령어가 AT 커맨드를 사용한다는 점과 SPI 인터페이스가 구비되어 있다는 점을 들 수 있을 것 같습니다.
이는 사용하기 쉬운 AT 명령어와 시리얼통신에 비해, 보다 고속의 데이터처리가 가능한 SPI를 지원하기에 ‘오렌지보드WiFi’에서 이 모듈을 채용한 것으로 짐작이 됩니다.
데이터 전송 속도로는 801.11n 모드에서는 HT20 MCS0에서 6.5Mbps ~ HT20 MCS7에서 최대 65Mbps로 속도가 나올 수 있다고 하니 나쁘진 않다고 봅니다.
1.3. 제품 핀 구성
다음으로 핀 구성을 살펴보면 USB Type-B로 구성된 아두이노 우노에 비해 MicroUSB 잭을 사용해 보드 공간을 좀 더 효과적으로 사용하려 노력한 점이 돋보입니다.
그 다음으로 눈에 띄는 부분이 있다면 WizFi250과의 연동을 위해 SPI통신을 위한 핀들이 결선되어 있다는 점을 들 수 있을 것 같습니다.
이는 ATmega328이 갖고 있는 한정된 핀 개수에 비춰볼 때 선택의 여지가 없는 사안으로 보이며, 원래 SPI 포트는 보드 하단에 ISP 커넥터와도 연결되어 있어 AVR MK-II 등 프로그래머를 통해 직접 프로그래밍을 하는 용도로도 사용되던 핀이었습니다.
만일 핀 때문이라면 I2C등도 고려해 볼 수도 있을 듯 합니다만, 위즈네트 임베디드 모듈은 I2C를 지원하지 않기에 선택의 여지는 없어 보입니다.
SCK, MISO, MOSI 이 3개의 핀은 다른 SPI 디바이스들과 병렬로 사용하게 되나 SS, DATA_Ready, WIFI_RESET 핀 등 3개는 WizFi250을 사용하는 조건에서는 꼼짝없이 여기에 연결되어야 하는 상황에 놓이게 됩니다. 또한 흔히 On Board LED가 연결되는 D13 핀이 SPI_CLK 핀에 연결되어 있기에 간단한 테스트를 해보고자 해도, 별도의 LED를 연결해야 하는 점이 유의점이라면 유의점에 해당됩니다.
1.4. 회로설계
1.4.1. USB to Serial 부문
PC와 연결되는 마이크로USB 접속부분이 아두이노 우노 R3 와는 다소 다릅니다.
그냥 널리 사용되는 FT232RL로 간단히 USB to Serial을 구현한 것을 알 수 있습니다. 이 칩은 너무나도 유명한 칩으로서 어지간한 USB2SERIAL에는 거의 다 사용되고 있다고 봅니다.
1.4.2. SPI 인터페이스 부문
ATmega328은 5V로 동작되나 WizFi250은 3.3V에서 동작되므로 레벨시프터가 필요합니다. 총 6개 핀이 WizFi250에 연결되어야 하기에 TXB0106PWR을 사용해 정확히 맞춰주도록 설계가 되어 있습니다.
D4, D2, D3, D11, D13, D12 이렇게 핀 6개는 WiZFI250에 연결되어야 하기에, 적어도 오렌지보드WiFi에서는 다른 용도로 사용하지 않는 것이 좋을 것으로 봅니다.
■ SPI 채널 핀 할당
1.4.3. WizFi250 주변부
WiFi Upgrade를 위한 핀은 별도의 커넥터 없이 랜드 4개가 일렬로 되어 있는 부분으로, 이곳이 회로도상 J9에 해당됩니다.
MCU와 인터페이스를 위한 핀 중 UART2는 미사용 상태이며 SPI 측만 연결되도록 설계되어 있음을 알 수 있습니다.
■ 레벨시프터 칩 주변 부
■ WizFi250 칩 주변부
WizFi250 모듈은 필자의 주관적인 생각이긴 합니다만, ARM M3 계열을 코어프로세서로 사용한 것처럼 보입니다.
느낌에 따라서는 ATmega328P와는 주객이 전도된 것 같기도 합니다.
1.4.4. 기타 특수용도 버튼 및 스위치
SW3 Dip 스위치는 오렌지보드 내 WiFi 기능을 꺼버리게 하거나 펌웨어 문제시 초기화하는 기능을 수행 할 수 있도록 설계되어 있습니다.
■ DIP 스위치 기능 정리
오렌지보드WiFi의 HW 부문은 이 정도로 마무리하고 다음은 SW 부문을 살펴보도록 하겠습니다.
2. 오렌지보드WiFi에 WizFi250 라이브러리 설치하기
■ 오렌지보드WiFi HW모델 : 아두이노 IDE > 도구 > 보드 > Arduino/Genuino Uno
■ 라이브러리 확보 : 아두이노 IDE 실행 > 스케치 > 라이브러리추가 > 라이브러리 관리
라이브러리관리자> 검색조건설정 > wizfi250를 입력하면 다음과 같이 WizFi250 by DongEun Koak 가 나옵니다.
설치를 누르면 자동으로 설치가 진행되며 아래와 같은 INSTALLED가 나오는 화면을 볼 수 있습니다.
필자의 경우 라이브러리가 설치된 이후 화면은 아래와 같습니다.
참고로 아두이노 IDE의 환경설정은 아래와 같습니다.
사실 오렌지보드WiFi는 HW적으로나 SW적으로나 워낙 깔끔하고 군더더기가 없어 접근하는데 큰 어려움은 없다고 봅니다.
다만 가장 큰 즐길 거리이자 난관은 SW 부문에서 기다리고 있으며 TCP/IP라고 하는 커다란 언덕을 올라야 뭐라도 하나 해볼 수 있을 것으로 봅니다. 이왕 손에 오렌지보드WiFi를 쥔 만큼 과감히 첫발을 내디뎌보려 합니다.
3. 코코아팹 튜토리얼 따라해 보기
3.1. WebClient
3.1.1. 개요
아두이노 보드를 통해 이제 막 컴퓨터를 배우는 용도 등으로 사용할 것을 생각해 본다면 아두이노 우노나 메가 시리즈 정도는 큰 무리가 없으리라 봅니다.
IDE상에서 digitalWrite() 함수 정도만 다뤄도 뭔가 그럴싸한 일을 해볼 수 있기 때문이고, 이점이 아두이노의 가장 큰 매력이라는 점에 대해서도 인정 안 할 수가 없습니다.
한편 아두이노의 이면에는 이렇게 쉽게 사용할 수 있도록 배려해 놓은 정말 주도면밀한 기술적 완성도가 곳곳에 숨겨져 있습니다.
다시 우리가 다루고 있는 오렌지보드WiFi 보드를 살펴보면, 아두이노 우노의 ATmega328P와는 비교가 불가능한 막강한 성능을 가진 WizFi250 와이파이 모듈이 보드상에 탑재되어 있고, 이 모듈을 통해 인터넷상의 구성원이 되도록 해주고 있는 것입니다.
자 그렇다면 구성원으로서 오렌지보드WiFi가 되기 위해서는 소정의 약속을 지킬 필요가 있고, 이를 코코팹사에서 라이브러리 형태로 배포를 하고 있으며 그 라이브러리 사용법을 예제를 통해 설명하고 있습니다.
우선은 예제대로 실행을 해본 후 어떤 결과가 나오는지를 먼저 보도록 하겠습니다.
우리의 아두이노는 예제부터 일단 돌려보는 것이 최곱니다.
이 예제는 WebClient로 오렌지보드WiFi가 Client가 되어 웹에 있는 서버에서 데이터를 받아 아두이노의 시리얼 모니터창에 뿌리는 것입니다. 처음부터 생소한 용어가 살짝 등장하긴 합니다만, 일단 아래 위치로 가서 WebClient를 내려받아 실행합니다.
이때 SSID와 PASSWORD는 이글을 읽는 독자님들의 무선공유기(AP)에 접근이 될 수 있는 이름으로 올바르게 넣어줍니다. 그러면 3.1.3의 그림과 같은 화면을 볼 수 있게 될 것입니다.
코드를 업로드하기 전에 아래 소스코드에 빨간 색깔로 표시된 부분을 반드시 제대로 입력해야 에러없이 동작될 것입니다. SSID에는 접속할 AP의 ID를 작성하고 PASSWORD부분에는 WPA/PSK 패스워드를 작성하시면 됩니다.
3.1.2. webclient 소스코드
#include <SPI.h>
#include “WizFi250.h”
char ssid[] = “myLivngRoom(2.4GHz)”; // 접속코자하는 무선공유기의 SSID
char pass[] = “??????????”; // 공유기에 설정된 암호
int status = WL_IDLE_STATUS; // 와이파이 상태변수의 초기 값
char targetServer[] = “arduino.cc”;
// 이더넷 클라이언트 오브젝트 초기화
WiFiClient client;
void printWifiStatus();
void setup()
{
Serial.begin(115200);
WiFi.init();
// WizFi250 와이파이모듈이 잘 동작될 수 있는지 검사해 본다.
if (WiFi.status() == WL_NO_SHIELD) {
Serial.println(“WiFi shield not present”);
// 만일 문제가 있다면 여기서 홀딩한다.
while (true);
}
// 와이파이 망으로 연결을 시도한다.
// 연결이 성공적으로 되어야 루프를 벗어날 수 있다.
while ( status != WL_CONNECTED) {
Serial.print(“Attempting to connect to WPA SSID: “);
Serial.println(ssid);
// 암호방식이 WPA/WPA2인 망에 연결이 진행중인 상태값
status = WiFi.begin(ssid, pass);
}
// 와이파이망에 성공적으로 연결이 됨
Serial.println(“You’re connected to the network”);
printWifiStatus();
Serial.println();
Serial.println(“Starting connection to server…”);
// 목적지 서버인 arduino.cc에 80번 포트로 연결을 시도하고 결과를
// 시리얼모니터로 출력한다.
if (client.connect(targetServer, 80)) {
Serial.println(“Connected to server”);
// 나에게 asciilogo.txt를 보내 달라고 요청한다.
client.println(“GET /asciilogo.txt HTTP/1.1″);
client.println(“Host: arduino.cc”);
client.println(“Connection: close”);
client.println();
}
}
void loop()
{
// 타겟서버와 연결이 이뤄졌고 asciilogo.txt을 보내달라고 요청을 setup()에서
// 하였으므로 보내오는 값을 시리얼모니터창에 뿌리는 작업을 한다.
while (client.available()) {
char c = client.read();
Serial.write(c);
}
// if the server’s disconnected, stop the client
// 타켓서버와 연결이 끊어진 상태라면 Disconnecting from server…라는 메시지를 뿌리고 홀딩한다.
if (!client.connected()) {
Serial.println();
Serial.println(“Disconnecting from server…”);
client.stop();
// 여기서 부터는 아무것도 할 수 없는 상태에 접어든다.
while (true);
}
}
void printWifiStatus()
{
// 방금 내가 연결한 와이파이망의 SSID를 읽어와 시리얼 모니터로 출력 한다.
Serial.print(“SSID: “);
Serial.println(WiFi.SSID());
// 방금 내가 연결한 공유기로부터 할당 받은 IP주소를 시리얼 모니터로 출력한다.
IPAddress ip = WiFi.localIP();
Serial.print(“IP Address: “);
Serial.println(ip);
// 나와 공유간의 무선전송구간 수신강도를 표출 한다.
long rssi = WiFi.RSSI();
Serial.print(“Signal strength (RSSI):”);
Serial.print(rssi);
Serial.println(” dBm”);
}
3.1.3. 시리얼모니터창에 출력된 결과 값
3.1.4. 예제 설명
사실 우리는 그냥 예제코드를 복사하여 붙여넣은 후 업로드 시키면 동작되는 정말 편리한 상황에서 이 제품을 사용 중에 있기는 합니다만, 보다 심도있는 학습을 해보고자 한다면 아주 기초적인 것 몇 가지 정도는 짚어볼 필요가 있다고 봅니다.
우선적으로 네트워크가 어떻게 돌아가는지 간단하게 살펴보도록 하겠습니다. 그냥 넘어가려니 너무도 막막해 여기저기 글들을 참고하여 꼭 필요한 정보만 서술해보려 합니다.
■ tcp/ip의 동작과정
아래 그림은 네트웍 관련 서적이나 인터넷상에서 자주 언급되는 흔한 내용입니다.
앞의 예제에서 우리는 「char targetServer[] = “arduino.cc”;」라는 라인을 서두에 적었습니다. 호스트A는 오렌지보드WiFi가 되고 호스트B는 arduino.cc가 된 것입니다.
응용계층에 해당되는 것은 정확히는 「client.println(“GET /asciilogo.txt HTTP/1.1″);」라인에 해당되는 것으로써 호스트B인 arduino.cc라는 서버에게 asciilogo.txt라는 파일을 열어 보내라는 응용프로그램(응용계층 = 소프트웨어)을 돌린 것이 됩니다.
arduino.cc는 잘 아시는 바와 같이 이탈리아에서 운영 중인 서버고 우리는 대한민국에 있으므로, 매우 많은 라우터를 거치고 또 거쳐서 그곳까지 가는 것이며 인터넷 세상이 그러한 연결을 도맡아 해주고 있는 것입니다.
그런데 응용계층 말고도 전송계층이라는 것이 위 그림에 적시되어 있습다. 영어로는 Transport Layer라고 합니다만 TCP 몇 번 포트, UDP 몇 번 포트 등으로 우리는 일상적으로 부르곤 합니다.
위 예제에서 「if (client.connect(targetServer, 80))」라는 라인에서 arduino.cc라는 서버 주소로 연결을 하되 80번 포트번호를 사용하는 의미가 되며, 이 80번 포트를 HTTP(HyperText Transfer Protocol) 서비스라고 부릅니다. 아주 원론적으로 이를 명기한다면 「http://www.arduino.cc:80」이 되는 것이며, 실제로 인터넷익플로러 등 웹브라우저의 주소에 이렇게 쳐도 아무런 에러 없이 페이지가 잘 열리게 됩니다.
네트워크계층은 IP주소와 연관되어 있는 부분입니다.
우리는 이따금씩 언론에서 “중국의 해커, 북한 등으로부터 공격이 있었다.” 라는 말을 듣곤 합니다. 이는 전세계의 IP주소를 할당하고 관리하는 기관이 있으며 이 기관을 통해 각 국가가 사용하는 IP주소가 명확히 설정이 되어 있으며 각 국가는 정부에서 관리하는 자국의 IP주소관리 정책에 따라 사용할 수 있는 IP주소가 제한되게 됩니다.
우선 인터넷 통신이 되게 하기 위해서는 기본적으로 내 IP주소와 상대방의 IP주소가 필요합니다.
내 IP주소를 받는 방법은 몇 가지가 있으나 우리가 테스트하고 있는 WizFi250은 Wi-Fi를 기반으로 동작되므로 접속되는 AP 장치 즉, 공유기로부터 받아야만 합니다. 이때 IP를 받는 방법 중 AP가 일정한 룰에 의해 자동으로 부여해 주는 방식을 DHCP(Dynamic Host Configuration Protocol)라하며, 이렇게 받은 IP주소를 우리의 예제코드에서는 아래와 같이 보여주고 있습니다.
■ IP 주소를 출력하는 소스코드
IPAddress IP = WiFi.localIP(); // IPAddress라는 타입으로 된 변수 IP에 주소값을 담아라
Serial.print(“IP Address: “); // 시리얼모니터 창에 IP Address:를 출력하라
Serial.println(ip); // 시리얼모니터 창에 IP 값을 출력하고 줄바꿈을 하라
■ 위 소스에 대한 실제 출력 값
SSID: myLivngRoom(2.4GHz)
IP Address: 172.30.1.41
Signal strength (RSSI):-32 dBm
□ 웹서버와 통신
위 예제는 Arduino사이트에서 asciilogo.txt라는 데이터를 읽어오는 예제로, 코드를 보시면 Server에 arduino.cc가 작성되어 있는 것을 볼 수 있습니다.
Arduino.cc에 접속한 다음 GET 명령어로 asciilogo.txt라는 데이터를 아두이노로 가져오게 됩니다. 정말로 이런 데이터를 가져오는지 확인하고 싶다면 웹브라우저를 연 후에 http://Arduino.cc/asciilogo.txt를 주소창에 입력하면 똑같은 데이터가 웹브라우저에 출력되는 것을 확인할 수 있습니다.
이렇게 웹서버와 통신하는 가장 간단한 방법이 GET이라는 방법이 있으며 보안성 측면에서는 매우 취약하나 어떻게 동작이 일어나는지 학습해 보는 것이 목적이므로 크게 문제되지는 않는다고 봅니다. 유사한 방법으로 POST라는 방식으로도 원하는 명령을 보낼 수 있습니다.
■ 웹서버와 통신하는 GET 방식과 POST방식의 차이점
어떠신가요? 아마도 이글을 읽는 독자님들은 인터넷에 접속하여 데이터 한 번 받아오기가 이리도 힘든가라고 여길 수도 있을 것입니다. 순전히 제 개인적인 생각입니다만, 오늘날의 인터넷서비스는 그간 인류가 쌓아온 지식, 지혜의 꽃이라고 저는 생각합니다. 그러니 어찌 단순하고 쉽게 돌아가겠습니까?
사실 그런 깊숙한 상황을 잘 몰라도 우리의 WizFi250 제작자들은 매우 쉽게 접할 수 있도록 배려해 놓았으므로 별로 길지 않은 코드로 인터넷에 접속해 데이터를 받아와 시리얼 모니터에 뿌리는 것 까지를 쉽게 해볼 수 있었습니다.
다음으로는 오렌지보드WiFi에 내장된 A/D 변환기능을 활용하여 조도값을 웹상에서 읽어보는 기능을 실험해 보고자 합니다.
이번 예제에서는 WiFi보드를 Client로 사용해봤으니 다음 예제에서는 반대로 WiFi보드를 Server로 사용해보는 예제를 돌려 보겠습니다.
※ 참고 : http://kocoafab.cc/tutorial/view/649
3.2. 조도센서 값을 내PC 웹 브라우저로 출력하기
3.2.1. 개요
코코아팹에서 2번째 튜토리얼로 오렌지보드WiFi에 조도센서를 장착한 후 PC에서 보드에 접근하여 조도 값을 보여주는 예제를 제시하고 있습니다.
첫 번째 예제가 오렌지보드WiFi의 WiFi 기능이 동작되는 것을 간단히 테스트 해보는 것이 목적이라면, 이번 예제는 실제로 아주 간단한 응용을 통해 웹서버의 개념에 대해 이해하는 과정이라고 보면 되겠습니다.
■ 회로도
회로도는 매우 단순해 아두이노의 A0에 CdS와 10K 저항으로 분압 된 결과 값이 들어가도록 구성하면 됩니다.
ATmega328P는 5V에서 동작되는 칩이므로 전원도 10KR의 반대편은 3.3V가 아니라 5V에 꽂아 주어야 합니다.
■ 필요한 부품명 수량 상세 설명
1 오렌지보드WiFi x 1 (WizFi250을 사용한 WiFi보드)
2 조도 센서 x 1 (CdS)
3 저항 10Kohm x 1
3.2.2. 시리얼 모니터 실행
우선 소스코드를 http://kocoafab.cc/tutorial/view/650에서 받아 업로드해 봅니다. 위와 같은 회로를 꾸미지 않아도 동작 되긴 합니다만, 이 경우 제대로 된 조도값을 얻어 올 수는 없게 됩니다. 제대로 업로드가 되었다면 시리얼 모니터를 실행 시켰을 때 아래와 같은 화면을 볼 수가 있을 것입니다.
Opening port
Port open
[WizFi250] Initializing WizFi250
Attempting to connect to WPA SSID: myLivngRoom(2.4GHz)
[WizFi250] Connected to myLivngRoom(2.4GHz)
You’re connected to the network
SSID: myLivngRoom(2.4GHz)
IP Address: 172.30.1.22
To see this page in action, open a browser to http://172.30.1.22
[WizFi250] Server started on port 80[WizFi250] New client
New client Start!—–>
Sending response
<———-Client disconnected
[WizFi250] New client
New client Start!—–>
HTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Encoding: gzip, deflate, sdch
Accept-Language: ko-KR,ko;q=0.8,en-US;q=0.6,en;q=0.4
Sending response
<———-Client disconnected
[WizFi250] New client
New client Start!—–>
Sending response
<———-Client disconnected
[WizFi250] New client
New client Start!—–>
hrome/55.0.2883.87 Safari/537.36
Accept: image/webp,image/*,*/*;q=0.8
Referer: http://172.30.1.22/
Accept-Encoding: gzip, deflate, sdch
Accept-Language: ko-KR,ko;q=0.8,en-US;q=0.6,en;q=0.4
Sending response
<———-Client disconnected
시리얼 모니터를 통해 웹서버의 IP주소가 172.30.1.22란 것을 알 수 있습니다.
크롬 웹브라우저 등을 활용하여 주소창에 HTTP://172.30.1.22를 치면 아래와 같은 결과 화면을 볼 수 있을 것입니다.
3.2.3. 소스코드
#include <SPI.h>
#include “WizFi250.h”
char ssid[] = “myLivngRoom(2.4GHz)”; // 접속코자하는 무선공유기의 SSID
char pass[] = “???????????”; // 공유기에 설정된 암호
int status = WL_IDLE_STATUS; // 와이파이 상태변수의 초기 값
int reqCount = 0; // 수신 재전송 횟수용 카운터
WiFiServer server(80);
void printWifiStatus();
void setup()
{
// 디버깅을 위한 시리얼 모니터포트 초기화
Serial.begin(115200);
// 와이파이모듈 초기화
WiFi.init();
// 와이파이모듈과 통신이 가능한 상태인지 검사
if (WiFi.status() == WL_NO_SHIELD) {
Serial.println(“WiFi shield not present”);
// 통신이 가능한 상태가 아니라면 여기서 홀딩하라
while (true);
}
// 와이파이망에 연결을 시도한다.
while (status != WL_CONNECTED) {
Serial.print(“Attempting to connect to WPA SSID: “);
Serial.println(ssid);
// 연결에 대한 상태값을 받아 온다.
status = WiFi.begin(ssid, pass);
}
Serial.println(“You’re connected to the network”);
printWifiStatus();
// 80번 포트로 리슨하는 웹서버를 구동하라.
server.begin(); // Serial Monitor out: [WizFi250] Server started on port 80
}
void loop()
{
// 클라이언트에서 연결을 해 오는 것에 대해 수신대기를 합니다.
WiFiClient client = server.available();
// Serial Monitor out : [WizFi250] New client
if (client) {
Serial.println(“New client Start!—–>”);
// http 요청은 빈 줄로 끝납니다.
boolean currentLineIsBlank = true;
while (client.connected())
{
if (client.available()) {
// 클라이언트로부터 1문자를 수신합니다.
char c = client.read();
Serial.write(c);
// 줄 바꿈 문자 (줄 바꿈 문자 수신)가 있고 줄이 비어 있으면
//http 요청이 끝났기 때문에 회신을 보낼 수가 있습니다.
if (c == ‘\n’ && currentLineIsBlank) {
Serial.println(“Sending response”);
// 표준 http 응답 헤더를 보내려면 많은 println 문 대신
//\r\n을 사용하여 데이터 전송 속도를 높입니다.
client.print(
“HTTP/1.1 200 OK\r\n”
“Content-Type: text/html\r\n”
“Connection: close\r\n” // 응답이 완료되면 연결이 닫힙니다.
“Refresh: 20\r\n” // 20초마다 자동으로 페이지를 새로 고칩니다.
“\r\n”);
client.print(“<!DOCTYPE HTML>\r\n”);
client.print(“<html>\r\n”);
client.print(“<h1>Hello World!</h1>\r\n”);
client.print(“Requests received: “);
client.print(++reqCount);
client.print(“<br>\r\n”);
client.print(“Analog input A0: “);
client.print(analogRead(0));
client.print(“<br>\r\n”);
client.print(“</html>\r\n”);
break;
}
if (c == ‘\n’) {
// 새라인을 시작한다.
currentLineIsBlank = true;
} else if (c != ‘\r’) {
// 현재 라인에 어떤 문자가 들어 옴
currentLineIsBlank = false;
}
}
} // while
// 웹 브라우저가 데이터를 받을 시간을 줍니다.
delay(10);
// 연결을 종료한다:
client.stop();
Serial.println(“<———-Client disconnected”);
}
}
void printWifiStatus()
{
// 방금 내가 연결한 와이파이망의 SSID를 읽어와 시리얼 모니터로 출력 한다.
Serial.print(“SSID: “);
Serial.println(WiFi.SSID());
// 방금 내가 연결한 공유기로부터 할당 받은 IP주소를 시리얼 모니터로 출력한다.
IPAddress ip = WiFi.localIP();
Serial.print(“IP Address: “);
Serial.println(ip);
// 브라우져에 어디서부터 접속을 시도 했는지를 표출한다.
Serial.println();
Serial.print(“To see this page in action, open a browser to http://”);
Serial.println(ip);
Serial.println();
}
이 부분이 WizFi250 모듈을 웹서버로 만들어주는 부분이 됩니다.
// 80번 포트로 리슨하는 웹서버를 구동하라.
server.begin(); // Serial Monitor out: [WizFi250] Server started on port 80
// 클라이언트에서 연결을 해 오는 것에 대해 수신대기를 합니다.
WiFiClient client = server.available();
// Serial Monitor out : [WizFi250] New client
이 부분은 웹브라우저로부터 접속을 시도해 오면 시리얼 모니터에 [WizFi250] New client를 뿌린 후 실려오는 데이터를 실제로 받을 준비를 합니다.
3.3. OpenWeatherMap API를 사용하여 날씨 데이터 받아오기
3.3.1. 개요
이번에는 난이도를 좀 더 올려서 API를 사용해 WiFi 쉴드로 날씨정보를 가져오는 예제를 실행해봅니다.
이 튜토리얼 역시 코코아팹 웹사이트에 올려져 있는 내용을 참고삼아 진행해 보도록 하겠습니다. 이 튜토리얼을 성공적으로 따라하시면 인터넷 세상이 돌아가는 것에 대해 어렴풋하게나마 이해가 되시리라 봅니다.
코코아팹 웹싸이트에서도 소개하고 있듯이 API란 Application Programming Interface의 약자로 프로그램이나 어플리케이션이 정보처리를 위해 운영체제에 호출하는 함수나 서브루틴의 집합을 말합니다. WebServer는 API를 통해 사용하기 쉬운 인터페이스를 Client에게 제공하고, Client는 API를 가져와서 사용함으로써 손쉽게 원하는 정보에 접근할 수 있습니다.
이번에 실행할 OpenWaetherMap 또한 날씨정보 API를 제공하는 사이트로서, API를 가져온다면 아두이노에서도 WiFi로 날씨정보를 받아올 수 있습니다.
3.3.2. OpenWeatherMap에서 API key받아오기
먼저 http://openweathermap.org/에 접속해 아이디가 없을 경우 회원가입을 하여 로그인을 합니다.
API keys 탭에 들어가면 자신만이 사용가능한 고유한 API key 값을 확인할 수 있습니다. API key 값은 API에서 정보를 가져오기 위한 필수 정보이니 따로 복사를 합니다.
3.3.3. 아두이노 코드 업로드 하기
다시 아두이노로 돌아와서, 아래 코드를 복사 후 오렌지보드WiFi에 업로드시킵니다.
#include <SPI.h>
#include <WizFi250.h>
int getInt(String input);
#define VARID “??????????????????????????????????”
char ssid[] = “myLivngRoom(2.4GHz)”; // 접속코자하는 무선공유기의 SSID
char pass[] = “??????????”; // 공유기에 설정된 암호
int status = WL_IDLE_STATUS; // 와이파이 상태변수의 초기 값
char server[] = “api.openweathermap.org”;
unsigned long lastConnectionTime = 0; // 마지막으로 서버에 연결한 시간
const unsigned long postingInterval = 1000L; // 업데이트 간격 (밀리 초).
boolean readingVal;
boolean getIsConnected = false;
//String valString;
int val, temp;
float tempVal;
String rcvbuf;
// 이더넷 클라이언트 오브젝트를 초기화 한다.
WiFiClient client;
void httpRequest();
void printWifiStatus();
void setup()
{
// 디버깅을 위한 시리얼 포트 초기화
Serial.begin(115200);
Serial.println(F(“\r\nSerial Init”));
WiFi.init();
// 와이파이모듈이 정상인지 확인
if (WiFi.status() == WL_NO_SHIELD) {
Serial.println(“WiFi shield not present”);
// don’t continue
while (true);
}
// 와이파이망에 연결 시도
while (status != WL_CONNECTED) {
Serial.print(“Attempting to connect to WPA SSID: “);
Serial.println(ssid);
// Connect to WPA/WPA2 network
status = WiFi.begin(ssid, pass);
}
Serial.println(“You’re connected to the network”);
printWifiStatus();
}
void loop()
{
// 인터넷 연결에서 들어오는 데이터에 대해 파싱작업을 한다.
String valString;
while (client.available())
{
if (rcvbuf.endsWith(“\”temp\”:”)) { //서버로 부터 보내져 오는 문자열에 temp:을 찾는다.
readingVal = true;
valString = “”;
}
char c = client.read();
if (c != NULL) {
if (rcvbuf.length() > 30)
rcvbuf = “”;
rcvbuf += c;
Serial.write(c);// DEBUG
}
if (readingVal) {
if (c != ‘,’) {
valString += c; // ‘temp:’ 다음에 콤마(,)가 나타날때 까지 valstring에 담는다.
Serial.write(c); // DEBUG
}
else {
readingVal = false;
//Serial.println(valString); // DEBUG
// 절대온도를 상대온도로 변환시키기 위해 273을 뺌
tempVal = valString.toFloat() – 273.0;
//Serial.println(tempVal); // DEBUG
}
}
}
if (millis() – lastConnectionTime > (postingInterval * 5))
{
if (getIsConnected) {
Serial.println(valString);
Serial.println(F(“===================”));
Serial.print(F(“Temperature : “));
Serial.println(tempVal);
Serial.println(F(“===================”));
}
httpRequest();
}
rcvbuf = “”;
}
// 이 메서드는 서버로 HTTP 연결을 진행합니다.
void httpRequest() {
Serial.println();
// 새로운 요청을 하기전에 연결을 일단 닫는다.
// 이 작업으로 와이파이모듈의 소켓이 해제된다.
client.stop();
// 성공적으로 날씨정보 서버에 접속이 되는지 점검해 본다.
if (client.connect(server, 80)) {
Serial.println(“Connecting…”);
// 날씨정보를 얻고 싶은 도시명, 국가 정보를 웹서버로 보낸다.
client.print(“GET /data/2.5/weather?q=Anyang,kr&appid=”);
client.print(VARID);
client.println(” HTTP/1.1″);
client.println(“Host: api.openweathermap.org”);
client.println(“Connection: close”);
client.println();
// 연결이 만들어진 마지막 시각을 기록해 둔다.
lastConnectionTime = millis();
getIsConnected = true;
}
else {
// 연결이 성공적이지 못할 경우 에러처리를 한다.
Serial.println(“Connection failed”);
getIsConnected = false;
}
}
void printWifiStatus() {
// 연결된 SSID를 표출한다.
Serial.print(“SSID: “);
Serial.println(WiFi.SSID());
// IP주소를 표출한다.
IPAddress ip = WiFi.localIP();
Serial.print(“IP Address: “);
Serial.println(ip);
// 수신강도 인쇄
long rssi = WiFi.RSSI();
Serial.print(“Signal strength (RSSI):”);
Serial.print(rssi);
Serial.println(” dBm”);
}
// 스트링을 정수형으로 변환 시킨다.
int getInt(String input) {
char carray[20];
//Serial.println(input);
input.toCharArray(carray, sizeof(carray));
//Serial.println(carray);
temp = atoi(carray);
return temp;
}
3.3.4. 결과 화면
위 코드를 실행시킨 결과는 아래와 같이 주기적으로 필자의 거주지인 경기도 안양시의 현재 온도값이 시리얼 모니터상에 출력됩니다.
지역을 변경하고 싶다면?! 코드에서는 기본적으로 Anyang으로 되어 있지만 지역을 변경할 경우 다른 지역의 온도값을 출력해볼 수 있습니다.
Opening port
Port open
Attempting to connect to WPA SSID: myLivngRoom(2.4GHz)
[WizFi250] Connected to myLivngRoom(2.4GHz)
You’re connected to the network
SSID: myLivngRoom(2.4GHz)
IP Address: 172.30.1.22
Signal strength (RSSI):-42 dBm
Connecting…
HTTP/1.1 200 OK
Server: openresty
Date: Mon, 02 Jan 2017 13:34:27 GMT
Content-Type: application/json; charset=utf-8
Content-Length: 552
Connection: close
X-Cache-Key: /data/2.5/weather?q=anyang,kr
Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true
Access-Control-Allow-Methods: GET, POST
{“coord”:{“lon”:126.93,”lat”:37.39},”weather”:[{"id":701,"main":"Mist","description":"mist","icon":"50n"},{"id":721,"main":"Haze","description":"haze","icon":"50n"},{"id":741,"main":"Fog","description":"fog","icon":"50n"}],”base”:”stations”,”main”:{“temp”:227755..1166,”pressure”:1022,”humidity”:83,”temp_min”:273.15,”temp_max”:277.15},
“visibility”:1200,”wind”:{“speed”:1,”deg”:180},”clouds”:{“all”:75},”dt”:1483363200,”sys”:{“type”:1,”id”:8519,”message”:0.0089,”country”:”KR”,”sunrise”:1483310810,
“sunset”:1483345579},”id”:1846898,”name”:”Anyang”,”cod”:200}
[DISCONNECT 0]ÿ
===================
Temperature : 2.16
===================
날씨에 관한 다른 정보를 추출하고 싶다면?! 현재는 코드상에서 온도 값만을 가져와서 출력해주고 있습니다. 다른 정보를 출력하기 위해서 API에서 전달해주는 데이터를 먼저 분석해 볼 필요가 있습니다. 데이터를 요청하면 XML 형식으로 전달해주며, 우리는 코드상에서 파싱을 통해 원하는 정보만 추출하게 됩니다.
온도의 경우 “temp”라는 글 뒤에 절대온도로 306.15와 같이 보여집니다.
위에 보여드린 코드가 API에서 요청받은 XML 데이터에서 온도 값을 추출하는 코드입니다. 데이터를 차근차근 읽어오면서 “temp”: 라는 값이 오면 그 뒤에 값을 읽어서 온도로 저장하는 형식입니다. 만일 다른 데이터를 가지고 오고 싶다면 temp 대신에 pressure나 humidity를 넣어 파싱을 한다면 다른 데이터를 읽어올 수 있겠죠? 물론 온도의 경우 절대온도로 받아왔기 때문에 -273을 해주었지만, 다른 데이터는 그 데이터 타입에 맞게 값을 조절해 주어야 합니다.
3.4. 인터넷시간 동기화(NTP) 서비스
3.4.1. 개요
임베디드 장치를 활용해서 정확한 시간을 얻을 수만 있다면 그것 하나만으로도 모든 것이 용서가 되고도 남음이 있다고 봅니다. 오렌지보드WiFi도 당연히 인터넷접속이 가능한 기기이므로 가능하리라 생각되기에, 마지막 테스트로 인터넷시간 동기화에 대해서 다뤄보고자 합니다.
우선 아래와 같은 코드를 업로드합니다. 이 코드는 필자가 상당부분을 직접 작성한 것으로 NTP를 활용해 실제 어떻게 이용할 수 있는가를 가늠해 볼 수 있는 중요한 예제에 해당된다고 봅니다.
3.4.2. 시리얼모니터 실행 결과
프로그램이 복잡해지고 분량이 많아지면 아두이노 IDE에서는 아무래도 작업하기가 힘들어집니다.
■ 시리얼 모니터 창
필자는 간단한 프로그래밍이나 예제코드 등은 아두이노 IDE를 활용하나 본격적인 프로그래밍을 할 때는 Microsoft Visual Studio상에서 작업을 하곤 합니다.
위 시리얼 모니터 창은 비주얼 스튜디오상에서 뿌려지고 있는 모습입니다. 여기서 주목해야하는 부분이 아래의 문자열이며 NMEA형식으로 epoch타임값이 출력되고 있음을 알 수 있습니다.
■ Microsoft Visual Studio상 개발환경
■ Sample 전문
$Wi-Fi,1483423819,myLivngRoom(2.4GHz),pppppppp,203.248.240.103*33
3.4.3. NTP 서버(zero.bora.net) 접속용 소스코드
********************************************************************** 코코아팹 오렌지보드WiFi를 활용한 NTP(Network Time Protocol) 프로그램
***********************************************************************
* ProjectName: myUDPNTPclient2_OrangeWiFi_ATmega328P
* Dependencies: <SPI.h>,”WizFi250.h”,”WizFi250Udp.h”, <TimeLib.h>
* Processor: ATmega328P(28pin QFP)
* Complier: arduino.cc-1.6.9
* Microsoft Visual Studio vMicro
* Company:
* Author: 금강초롱(blog.naver.com/crucian2k3)
* History
* Date Version Description
* 2017-01-03 v1.0 최초작성
* 이크로칩사의 NTP 클라이언트 동작방식과 ESP8266기반의
* NTP클라이언트 동작방식을 참고하여 오렌지보드WiFi에 적합토록
* 전면 재작성 됨
***********************************************************************
***********************************************************************
* 동작내용:
* SSID, PASSWORD 등이 제대로 설정된 상황이라면 오렌지보드WiFi가 인터넷에 접속하여 일정한 간격으로 표준시간을 얻어와 자신이 사용하거나 다른 기계장치로 NMEA 전문규격에 부합토록 보내줄 수 있음
* 활용방법:
*전문을 수신하는 측에서는 ‘$Wi-Fi’ 문자열이 오면 버퍼에 저장을 시작하여 crlf가 나타나면 저장을 종료한다.
* *가 나타난후 2개의 문자는 2의 보수형으로 되어있는 체크섬이며, 계산하여 문제가 없다면 해당 전문에 실려 있는 epoc값을 바탕으로 9시간을 더해 한국시간에 맞게 활용하면 된다.
* Notes: 사용포트
* – D8 : LED0 Anode 연결
***********************************************************************
#include <SPI.h> // WizFi250 모듈과 ATmega328간 통신방식이 SPI
#include “WizFi250.h”
#include “WizFi250Udp.h”
#include <TimeLib.h> // EPOCH타임을 처리하기 위한 라이브러리
#define KOREA_TIME_ZONE 9 // 기준시에 비해 우리나라가 9시간 빠름
#define KOREA_TIME_ZONE_SEC (3600ul * KOREA_TIME_ZONE)
#define STATUSLED 8 // 동작상태 표출용 LED 출력포트(1초 간격 점멸시 정상 동작 중)
#define NTP_REFRESH_SECS 10
#define TICKS_PER_SECOND (1000L)
#define TICK_SECOND ((TICK)TICKS_PER_SECOND)
#define TICK_1000mS ((TICK)TICK_SECOND) // 1000ms 간격으로 실행 시
#define TICK_100mS ((TICK)TICK_SECOND / 10UL) // 100ms 간격으로 실행 시
#define TICK_10mS ((TICK)TICK_SECOND / 100UL) // 10ms 간격으로 실행 시
#define TICK_1mS ((TICK)TICK_SECOND / 1000UL) // 1ms 간격으로 실행 시
typedef unsigned long TICK; // TICK타입을 unsigned long으로 설정
char ssid[] = “myLivngRoom(2.4GHz)”; // 접속코자하는 AP의 SSID
char pass[] = “??????????”; // 패스워드
int status = WL_IDLE_STATUS; // AP의 상태값을 나타내는 변수
char wi_fiHeader[] = “$Wi-Fi”; // NTP 시각 외부 전송용 헤더
char timeServer[] = “zero.bora.net”; // NTP(Network Time Protocol) 서버주소
unsigned int localPort = 2390; // UDP 패킷 수신을 위한 로컬포트 지정
IPAddress timeServerIP; // 원격 NTP 서버의 IP주소
const int NTP_PACKET_SIZE = 48; // 메시지의 첫 48바이트에 NTP 정보가 담겨짐
uint8_t packetBuffer[NTP_PACKET_SIZE]; // 입출력이 일어나는 데이터에 대한 버퍼
String stringOne;
char LineBuffer[100];
int ledState = LOW;
int hours = 12; // 아날로그 모양의 시계를 움직이기 위한 초기 시간값
int minutes = 0;
int seconds = 0;
// UDP 인스턴스를 통해 udp데이터 수수가 일어난다.
WiFiUDP Udp;
unsigned long sendNTPpacket(char *ntpSrv);
unsigned long TickGet(); // 마이크로칩 라이브러리 스타일 명칭으로 호출
void setup()
{
Serial.begin(115200); // 디버깅을 위한 시리얼포트 초기화
pinMode(STATUSLED, OUTPUT); // 아두이노:D8
WiFi.init();
// 와이파이모듈과 오렌지보드간 SPI 통신이 가능한지 검사
if (WiFi.status() == WL_NO_SHIELD) {
Serial.println(“WiFi shield not present”);
// don’t continue
while (true);
}
}
void loop()
{
static enum
{
SM_HOME = 0,
SM_ATTEMPT_CONNECTION,
SM_WAIT_WL,
SM_WL_CONNECTED,
SM_SEND_NTP,
SM_WAIT_NTP_RECV,
SM_RCVCHECK,
SM_READPACKET,
SM_WAIT10SEC,
SM_NEXT,
SM_WAIT
} TASKState = SM_HOME;
static TICK myTimer0 = 0;
static int cycleCounter = 0;
int cb;
// 1900년부터 시작하는 NTP 타임체계를 1970년부터 시작하는 epoc 타임체계로 변환하기 위한 상수값
const unsigned long seventyYears = 2208988800UL;
unsigned long highWord;
unsigned long lowWord;
unsigned long secsSince1900;
unsigned long epoch;
// NTP 전문 생성용 변수
unsigned char calcchksum = 0;
unsigned char sl = 0;
unsigned char ct = 0;
unsigned char pl = 0;
int i;
switch (TASKState)
{
case SM_HOME:
// Wi-Fi망에 연결을 위한 사전 준비작업을 한다.
Serial.print(“Attempting to connect to WPA SSID: “);
Serial.println(ssid);
cycleCounter = 0;
myTimer0 = TickGet();
TASKState = SM_ATTEMPT_CONNECTION;
break;
case SM_ATTEMPT_CONNECTION:
// WPA/WPA2 망에 연결을 개시 한다.
status = WiFi.begin(ssid, pass);
if (status == WL_CONNECTED) {
// 연결에 성공되었음이 획인되면 다음 작업을 속행한다.
TASKState = SM_WL_CONNECTED;
}
else {
// 일단 연결에 실패하면….
// STATUSLED가 0.1초마다 한번씩 빠르게 점멸이 된다면 wi-fi를
// 찾지 못해 헤매는 중. 단, 오렌지보드wifi에서는 WizFi250모듈 내부
// 적으로 재전송 체계가 돌고 있는 것 같음 이로 인해 상태정보 응답
// 에 지연이 발생함
// ESP8266 등에서는 문제가 없는 코드이며, 오렌지보드에서도 큰 문
// 제는 없이 동작된다.
if (TickGet() – myTimer0 >= TICK_10mS * 10) {
myTimer0 = TickGet();
if (ledState == LOW)
ledState = HIGH; // LED on data
else
ledState = LOW; // LED off data
digitalWrite(STATUSLED, ledState);// LED action
// 만일 AP에 5초동안 연결을 반복 시도하였음에도 실패하면 에러처리 로직으로
cycleCounter++;
if (cycleCounter > 50) TASKState = SM_WAIT_WL;
}
}
break;
case SM_WAIT_WL:
// 5초 경과 후에도 여전히 연결이 되지 않은 경우 에러처리
if (WiFi.status() != WL_CONNECTED) {
// 5초가 경과되어도 여전히 접속되지 않으면 .을 한 개 출력한 후 처음부터 다시
Serial.print(“.”);
TASKState = SM_ATTEMPT_CONNECTION;
}
else {
// 만일 연결에 성공하면 retry counter를 찍은 후 다음 작업을 속행한다.
Serial.println(cycleCounter);
TASKState = SM_WL_CONNECTED;
}
break;
case SM_WL_CONNECTED:
// 성공적으로 wi-fi망에 연결되면 ip주소와 포트 번호를 표출한다.
Serial.println(“”);
Serial.println(“WiFi connected”);
Serial.print(“IP address: “);
Serial.println(WiFi.localIP());
Serial.println(“Starting UDP”);
Udp.begin(localPort);
Serial.print(“Local port: “);
Serial.println(localPort);
cycleCounter = 0;
TASKState = SM_SEND_NTP; // TIME 서버로 SNTP 패킷을 날린 후 응답을 기다린다.
break;
case SM_SEND_NTP:
// TIME 서버로 NTP 패킷을 전송한다.
//WiFi.hostByName(timeServer, timeServerIP);// ESP8266일 때 사용
Serial.println(“”); // 새로운 전문의 시작이 개시됨을 알리는 개행 작업
sendNTPpacket(timeServer); // time server로 NTP 패킷을 전송한다.
Serial.print(“Send NTP: “);
Serial.println(timeServer);
TASKState = SM_WAIT_NTP_RECV;
break;
case SM_WAIT_NTP_RECV:
// 타임서버로부터 응답이 왔는지를 검사하여 0.2초 후에도 수신이 되지
//않으면 한 번 더 요청을 해보고 그래도 응답이 1초 동안 없으면 wi-fi망에
//처음부터 다시 접속
cb = Udp.parsePacket();
if (!cb) {
// 정상적인 응답이 아닌 상태에 대한 처리
Serial.print(“*”);
cycleCounter++;
delay(100);
if (cycleCounter == 2) {
// TIME 서버로 NTP 패킷이 보내졌으나 0.2초를 기다려도
// 답신이 없으면 한 번 더 보내본다.
Serial.println(cycleCounter);
TASKState = SM_SEND_NTP;
}
else if (cycleCounter >= 10) {
// 답신이 없어 한 번 더 보내고도 1초가 또 경과되면 wi-fi망에
//이상이 있을 가능성이 높으므로 wi-fi 재접속을 시도해본다.
Serial.println(cycleCounter);
cycleCounter = 0;
TASKState = SM_WAIT_NTP_RECV;
}
}
else {
// 제대로 된 값이 온 것 같으면 데이터 파싱 단계를 진행한다.
Serial.println(cycleCounter);
cycleCounter = 0;
TASKState = SM_READPACKET;
}
break;
case SM_READPACKET:
// TIME 서버로 부터 NTP 패킷이 들어왔으므로 파싱 작업을 한다.
// Serial.print(“packet received, length=”);
// Serial.println(cb);
Udp.read(packetBuffer, NTP_PACKET_SIZE); // 일단 버퍼로 패킷을 옮긴다.
// 타임 스탬프는 수신된 패킷의 바이트 40에서 시작해 4바이트 또는
// 2 워드 길이이다. 먼저 두 워드구간을 추출해낸다.
highWord = word(packetBuffer[40], packetBuffer[41]);
lowWord = word(packetBuffer[42], packetBuffer[43]);
// 4 바이트 (두 워드)를 롱타입 정수로 머지합니다.
// 이것은 NTP 시간이 됩니다. (1900.1.1일 이후부터 현재까지의 초 값).
secsSince1900 = highWord << 16 | lowWord;
// Serial.print(“Seconds since Jan 1 1900 = ” );
// Serial.println(secsSince1900);
// 이제 NTP 시간을 우리가 일상적으로 사용하는 매일 시간형식(Unix Time)으로 변환합니다.
// Unix Time은 1970.1.1부터 시작합니다. 70년간을 초로 환산하면 2208988800:
// 즉, Unix Time을 얻기 위해 70년을 감해줍니다.
// 또한 이 시간을 epoch라고도 합니다.
epoch = secsSince1900 – seventyYears;
//Serial.print(“Unix time = “);
//Serial.println(epoch);
// stringOne에는 타임서버로부터 받은 unix epoch 타임 값이 들어가있다.
stringOne = String(epoch, DEC); // using a long and a base
************************************************************************
# NTP 타임 전문 생성 루틴:
1. 전문 생성목적
타임서버로부터 받아온 시간을 외부장치(전자시계, 서버 등등)에게 시리얼라인
으로 전송해주기 위해 GPS 수신기 등에서 사용하는 NMEA 전문 형태로 생상
하는 부분이다.
2. 전문의 형식 예(GPS신호와 매우 유사함)
$Wi-Fi,1470050489,myKT_WLAN,password,203.248.240.103*3A
[step1] [step2] [step3] [step4] [step5] [step6]
************************************************************************
// STEP1: 시리얼 버퍼에 “$Wi-Fi,” 를 시작 문자열로 발신하여 파싱이 일어나도록 한다.
sl = sizeof(wi_fiHeader) – 1; // null문자 위치 제거
pl = 0;
for (i = 0; i< sl; i++) {
LineBuffer[pl] = wi_fiHeader[i]; // copy to buffer : “$Wi-Fi,”
pl++;
}
LineBuffer[pl] = ‘,’; // 구분자를 추가한다.
pl++;
// STEP2: 시리얼버퍼에 “unix epoch 타임”을 어펜드한다.
sl = stringOne.length();
ct = 0;
while (sl) {
LineBuffer[pl] = stringOne[ct];
pl++; ct++;
sl–;
}
LineBuffer[pl] = ‘,’; // 구분자를 추가한다.
pl++;
// STEP3: 시리얼 버퍼에 SSID를 어펜드한다.
sl = sizeof(ssid) – 1; // null문자 위치 제거
for (i = 0; i< sl; i++) {
LineBuffer[pl] = ssid[i]; // copy to buffer : “ssid,”
pl++;
}
LineBuffer[pl] = ‘,’; // 구분자를 추가한다.
pl++;
// STEP4: 시리얼 버퍼에 wi-fi PASSWORD를 어펜드한다.
sl = sizeof(pass) – 1; // null문자 위치 제거
for (i = 0; i< sl; i++) {
LineBuffer[pl] = pass[i]; // copy to buffer : “pass,”
pl++;
}
LineBuffer[pl] = ‘,’; // 구분자를 추가한다.
pl++;
// STEP5: NTP 서버 IP주소를 파싱하여 어펜드한다.
timeServerIP = Udp.remoteIP();
for (i = 0; i<4; i++) {
//stringOne = String(timeServerIP[i], DEC);
stringOne = timeServerIP[i];
sl = stringOne.length();
ct = 0;
while (sl) {
LineBuffer[pl] = stringOne[ct];
pl++; ct++; sl–;
}
LineBuffer[pl] = ‘.’; pl++;
}
pl–; // IP주소의 맨 끝에 붙어있는 .은 제거 시켜 준다.
// STEP6: 체크섬을 계산한다.
calcchksum = 0;
for (i = 1; i < pl; i++) {
calcchksum = calcchksum ^ LineBuffer[i]; // USART 수신데이터로 직접 체크섬 계산
}
LineBuffer[pl] = ‘*’; pl++;
stringOne = String(calcchksum, HEX);
stringOne.toUpperCase();
LineBuffer[pl] = stringOne[0]; pl++;
LineBuffer[pl] = stringOne[1]; pl++;
// STEP7: 외부로 실제 전송을 단행한다.
for (i = 0; i< pl; i++) {
Serial.print(LineBuffer[i]);
}
Serial.println(“”);
//////////////////////////////////////////////////////////////////////////////////////
// DEBUG Code
// 아래는 이렇게 만들어진 시간값이 정말로 제대로 만들어 졌는지를 검증해보는 코드
// UTC시각은 그리니치천문대(Greenwich Meridian) 기준 시각이다. (GMT)
Serial.print(“The Local(Seoul, +9Hr) time is “);
setTime(epoch);
adjustTime(KOREA_TIME_ZONE_SEC); // 대한민국 시간대로 보정
Serial.print(year()); Serial.print(“/”);
Serial.print(monthStr(month())); Serial.print(“/”);
Serial.print(day());
Serial.print(“[");
Serial.print(hour()); Serial.print(":");
Serial.print(minute()); Serial.print(":");
Serial.print(second());
Serial.println("]“);
hours = hour();
minutes = minute();
seconds = second();
//////////////////////////////////////////////////////////////////////////////////////
cycleCounter = 0;
myTimer0 = TickGet();
TASKState = SM_NEXT;
break;
case SM_NEXT:
// 10초 간격으로 한번씩 동기화를 시도한다.
// STATUSLED가 1초마다 한번씩 점멸이 된다면 여기까지의 루프가 잘 돌고 있음을 의미
if (TickGet() – myTimer0 > TICK_100mS * 5UL) {
myTimer0 = TickGet();
if (ledState == LOW)
ledState = HIGH; // Note that this switches the LED *off*
else
ledState = LOW; // Note that this switches the LED *on*
digitalWrite(STATUSLED, ledState);
cycleCounter++;
// 타임서버로 부터 시간을 받아오는 주기를 설정한다.
// *2를 한것은 cycleCounter가 0.5초 단위로 증가하기 때문이다.
if (cycleCounter > NTP_REFRESH_SECS * 2) {
cycleCounter = 0;
// 연결이 성공적으로 잘되어 있는지 재검사하여 이상없으면 다음 작업을 속행
if (WiFi.status() == WL_CONNECTED) {
TASKState = SM_SEND_NTP;
}else {
// 만일 연결에 문제가 생기면 재접속 프로세스 진행
TASKState = SM_HOME;
}
}
}
break;
}
}
// send an NTP request to the time server at the given address
unsigned long sendNTPpacket(char *ntpSrv)
{
// set all bytes in the buffer to 0
memset(packetBuffer, 0, NTP_PACKET_SIZE);
// Initialize values needed to form NTP request
// (see URL above for details on the packets)
packetBuffer[0] = 0b11100011; // LI, Version, Mode
packetBuffer[1] = 0; // Stratum, or type of clock
packetBuffer[2] = 6; // Polling Interval
packetBuffer[3] = 0xEC; // Peer Clock Precision
// 8 bytes of zero for Root Delay & Root Dispersion
packetBuffer[12] = 49;
packetBuffer[13] = 0x4E;
packetBuffer[14] = 49;
packetBuffer[15] = 52;
// all NTP fields have been given values, now
// you can send a packet requesting a timestamp:
Udp.beginPacket(ntpSrv, 123); //NTP requests are to port 123
Udp.write(packetBuffer, NTP_PACKET_SIZE);
Udp.endPacket();
return 1;
}
unsigned long TickGet()
{
return millis();
}
3.4.4. 소스설명
이 프로그램은 NTP 프로토콜로 타임서버에 접속하기 위해 UDP를 사용한 점이 이전의 튜토리얼과는 다소 차이가 있는 점에 해당됩니다. 저는 제대로 동작되는 프로그램을 만들기 위해서는 에러처리가 중요한 부분에 해당된다고 봅니다.
이 프로그램에서는 AP에 접속하고자 하였으나 이미 에러가 난 경우 재접속하는 방안과 AP에 접속하여 한참 통신을 하는 과정에서 갑자기 AP가 사라지는 경우에 대한 대비가 되어 있습니다. 또한 다른 기기에게 타임 값을 넘겨줄 때 상대방에서 에러에 대한 부분을 검증해 볼 수 있도록 체크섬이 부가되어 있습니다.
비교적 상세히 한글 주석을 달아 놓았으므로 추가적인 설명은 생략하겠습니다.
거의 1주일 가량을 연속 가동해본 결과 짜여진 각본대로 잘 동작함을 확인했습니다.
이 기능을 활용해 실제로 디지털시계를 구현한다면 오렌지보드WiFi 만으로도 충분히 가치있는 프로젝트가 되지 않을까 조심스럽게 예상해 봅니다.
4. 총평
이번에는 한국형 아두이노 보드인 오렌지보드에 WiFi 쉴드를 온보드화 시킨 오렌지보드WiFi에 대해 살펴봤습니다. 필자가 가장 관심을 두었던 부분은 WizFi250 와이파이모듈과 ATmega328이 안정적으로 통신을 수행하며 동작이 잘 될 수 있을까 하는 점이었습니다. 위에서 언급된 바와 같이 여러 형태를 실험하는 내내 단 한 번도 양단간의 통신문제는 발생되지 않았습니다.
일단 안정적으로 동작이 된다는 것에 대해서는 이론의 여지가 없을 듯합니다. 이정도의 안정성을 보여주기 위해서는 무척 다양한 종류의 테스트를 수행했고 무난히 통과된 결과가 아닐까 예상해 봅니다.
다음으로 관심을 두었던 분야가 과연 이 보드로 무엇을 할 수 있을지에 대한 가이드와 관련된 부분이었습니다. 코코아팹 웹 싸이트에는 다음과 같은 예제가 기본적으로 10종 제시되어 있으며, 자세한 설명과 동작하는 장면에 대한 동영상까지 제시하고 있음을 보았을 때 기우였다는 것을 금방 알 수 있었습니다.
사실 LED가 동작되는 원리 하나만 갖고도 매우 어려운 학습에 해당 될 수 있으며, 아두이노 학습의 기초 중에 기초인 Blink만 배우는데도 어려워서 쩔쩔맬 수도 있습니다. 여기에다 PWM, ADC, UART, SPI, I2C 등으로 넘어가면 지레 질려버릴 수도 있을 듯합니다.
오렌지보드WiFi 에 장착된 WizFi250 모듈은 정확히는 무선랜 모듈이며 이더넷 통신을 지원하는 첨단기술이 집약된 제품이자 통신장치에 해당된다고 봅니다. 이 모듈을 제대로 활용하기 위해서는 TCP/IP 등 네트워크에 관련된 지식을 필요로 하며 웹이나 모바일을 기반으로 영역을 확대코자 한다면 CGI(Common Gateway Interface)에 대한 깊이있는 지식도 필요하다고 봅니다. 덧붙여 C, C++ 등 프로그래밍 언어에 대한 스킬도 상당한 수준으로 요구될 수 있습니다.
이 보드는 WiFi 통신기능과 아두이노 우노 보드의 접근 용이성이 결합되어 대학생 이상의 교육현장 등에서 인터넷, TCP/IP, IOT, 원격제어 등 이론과 실습과정에 매우 유용한 교재로 쓰일 수 있을 것으로 보이며 일렉트로닉스 분야에 취미를 가진 전 세계인들에게는 보다 저렴한 가격으로, 보다 완성도 있는 IOT 세계를 열어주는데 부족함이 없을 것으로 봅니다. 감사합니다.
자료참고
1. 위즈넷 홈페이지 제품소개 페이지
http://www.wiznet.co.kr/product/wifi-module/
2. 오렌지보드 튜토리얼
http://www.kocoafab.cc/product/orangewifi
3. 옐로우캣 소개
http://www.kocoafab.cc/product/yellowcat
4. 코코아팹 오렌지보드 핀아웃
http://kocoafab.cc/data/docs/orangeboard-wifi/orangeboard_wifi_pinout.pdf