아두이노 대신 ESP8266 을 사용

by 임성국 | 2016-12-31 22:27
wearable 디자인씽킹포트폴리오 라즈베려파이 미세먼지 아두이노




사진에는 개발 중 테스트를 위한 별도의 NodeMCU 가 하나 더 있습니다. 




PMS7003 이 Fritzing 에 없으므로 10 핀 짜리 커넥터와 만능기판을 사용해서 비슷하게 만들어 사용했다.


ESP8266 에서 할 일 


1. nodeMCU 를 사용하여 PMS7003 의 먼지데이터를 가져온다.

2. nodeMCU 를 사용하여 AP 에 연결한다.(스마트폰으로 nodeMCU 를 AP 에 연결한다.)

3. AP 와 연결된 nodeMCU 는 Thingspeak 와 sparkfun 서버에 값을 전송한다.

4. Lua 언어를 사용하지 않고 아두이노 IDE 를 사용하여 작업한다.

5. PUT/GET 을 사용하여 서버에 값을 전달한다.





세부 작업 내용


## Arduino IDE 에서 ESP8266 보드 사용을 위한 준비 


1. Arduino IDE 실행

2. 파일(FILE)

3. 환경설정(Preference)


Additional Board Manager URLs : 에   http://arduino.esp8266.com/stable/package_esp8266com_index.json  입력 


4. Tools 에서 Board : >> Board Manager 실행

5. esp8266 검색 후 설치(Install)

6. Tools >> Board >> NodeMCU 1.0 (ESP-12E Module) 선택

7. Sketch >> Include Library >> Manage Libraries 선택

8. WiFiManager 검색 후 설치





esp8266 검색


NodeMCU 1.0 (ESP-12E Module) 선택






WiFiManager 검색 후 설치 (ESP8266 보드 설치 후 자동으로 설치되는 예제와 라이브러리 외에 필요)




## PMS7003 와 NodeMCU 연결


NodeMCU 는 1개의 하드웨어 시리얼을 가지고 있으며, 이 시리얼이 다른 포트로 연결되어 사용할 수 있다. 이때 Serial.swap() 함수를 사용한다.


단, 사용해 보았으나 펌웨어의 안정성문제인지 지나치게 노이즈가 많아서 도저히 사용할 수 없을 정도로 판단되었다. Serial.swap() 함수는 사용하지 않았다.



소프트웨어 시리얼의 사용은 아두이노에서 사용했던 방법이다. NodeMCU 에서도 이 방식을 사용해보려고 하였으나 제대로 된 값을 받아내지 못했다. 다른 작업을 하면서 소트프웨어시리얼로 통신을 하는 것이 아직까지 안정적이지 못해 보인다.


하드웨어 시리얼 사용하기로 결정.


결국 RX, TX 를 사용하기로 결정했다. 단, USB 로 PC 와 연결해서 프로그램실행파일을 업로드해야 하므로 PMS7003 은 실행파일업로드가 마쳐진 다음에 NodeMCU 와 연결하면 된다. 개발과정중에 이러한 작업을 반복해야 하므로 두개의 슬라이드 스위치를 두었다. 두개의 스위치는 NodeMCU 의 RX/TX 와 PMS7003 의 TX/RX 를 연결하거나 끊는 용도로 사용한다. 즉, NodeMCU 에 프로그램을 업로드할 때는 스위치를 끄고, 업로드후에는 다시 스위치를 켜서 PMS7003 과 NodeMCU 가 서로 통신이 되도록 하였다.





AP-SW 는 D3 (GPIO0) 의 핀에 해당되며 여기에 들어오는 입력값에 따라 AP 선택이 가능해진다.



## NodeMCU Firmware


아두이노의 max(), min() 이 ESP8266 에서 제대로 작동하지 않는 문제를 발견하고 재정의하여 사용하였다. #include <ESP8266WiFi.h> 이후에 다음의 내용을 넣으면 된다.


#undef max
#define max(a,b) ((a)>(b)?(a):(b))
#undef min
#define min(a,b) ((a)>(b)?(b):(a))



전체 프로그램이 많아 져서 ino 파일과 h 파일로 나눠서 저장하였다. 각각의 다음과 같다.



아두이노 소스

/*
  PMS7003 - ESP8266 (nodeMCU)
  WiFi Selectable AP & Clients
     D3 (GPIO0) --- > PULLUP 
CLIENT 1 - Thingspeak
CLIENT 2 - data.sparkfun.com 
  made by winduino.co.kr (2016.10.28.)
  
*/
#include <ESP8266WiFi.h>
#include <ESP8266WebServer.h>
#include <DNSServer.h>
#include <WiFiManager.h>          //https://github.com/tzapu/WiFiManager
#include "ThingSpeak.h"
#include "PMS7003s.h"
    // Thingspeak
    unsigned long myChannelNumber = 111111;
    const char * myWriteAPIKey = "11111111111111";
    // sparkfun client using get
    const char* host = "data.sparkfun.com";
    const char* streamId   = "11111111111112";
    const char* privateKey = "11111111111113";
#define TRIGGER_PIN   0
#define  DEBUG        1
#define  MEAN_NUMBER  10
#define  MAX_PM       0
#define  MIN_PM       32767
#define  VER          20161025
#ifndef    MAX_FRAME_LEN
#define    MAX_FRAME_LEN   64
#endif
#undef max
#define max(a,b) ((a)>(b)?(a):(b))
#undef min
#define min(a,b) ((a)>(b)?(b):(a))
int status = WL_IDLE_STATUS;
//const int MAX_FRAME_LEN = 64;
int pm1_0=0, pm2_5=0, pm10_0=0;
unsigned int tmp_max_pm1_0, tmp_max_pm2_5, tmp_max_pm10_0; 
unsigned int tmp_min_pm1_0, tmp_min_pm2_5, tmp_min_pm10_0; 
byte i=0;
unsigned long previousMillis = 0;
const long interval = 1000;
bool ledState = LOW;
bool startNumber =  true;

WiFiClient  client;
void setup() {
  pinMode(LED_BUILTIN, OUTPUT);     
  pinMode(TRIGGER_PIN, INPUT);
  Serial.begin(9600);
}
void loop() {
  digitalWrite(LED_BUILTIN, LOW);     delay(500);                     
  digitalWrite(LED_BUILTIN, HIGH);    delay(500); 
  if ( digitalRead(TRIGGER_PIN) == LOW ) {
    wifiManagerStart();
    startNumber = ~startNumber;
  }
  else {
    if (startNumber == true ) { 
      ThingSpeak.begin(client);
      startNumber = false;
    }
  if(i==0) { 
    tmp_max_pm10_0 = tmp_max_pm2_5 = tmp_max_pm1_0  = MAX_PM;
    tmp_min_pm10_0 = tmp_min_pm2_5 = tmp_min_pm1_0  = MIN_PM;
  }
  
  if (pms7003_read()) {
    tmp_max_pm1_0  = max(PMS7003S.concPM1_0_CF1, tmp_max_pm1_0);
    tmp_max_pm2_5  = max(PMS7003S.concPM2_5_CF1, tmp_max_pm2_5);
    tmp_max_pm10_0 = max(PMS7003S.concPM10_0_CF1, tmp_max_pm10_0);
    tmp_min_pm1_0  = min(PMS7003S.concPM1_0_CF1, tmp_min_pm1_0);
    tmp_min_pm2_5  = min(PMS7003S.concPM2_5_CF1, tmp_min_pm2_5);
    tmp_min_pm10_0 = min(PMS7003S.concPM10_0_CF1, tmp_min_pm10_0);
    pm1_0 += PMS7003S.concPM1_0_CF1;
    pm2_5 += PMS7003S.concPM2_5_CF1;
    pm10_0 += PMS7003S.concPM10_0_CF1;
    i++;
  }
  if(i==MEAN_NUMBER) {
    pm1_0 = ((pm1_0-tmp_max_pm1_0-tmp_min_pm1_0)/(MEAN_NUMBER-2));
    pm2_5 = ((pm2_5-tmp_max_pm2_5-tmp_min_pm2_5)/(MEAN_NUMBER-2));
    pm10_0= ((pm10_0-tmp_max_pm10_0-tmp_min_pm10_0)/(MEAN_NUMBER-2));
    thingSpeakClient(pm1_0, pm2_5, pm10_0);
    sparkfunClient(pm1_0, pm2_5, pm10_0);
    
    delay(20000); // ThingSpeak will only accept updates every 15 seconds. 
    
    pm1_0=pm2_5=pm10_0=i=0;
    }     
  }
}
void wifiManagerStart() {
    WiFiManager wifiManager;
    if (!wifiManager.startConfigPortal("OnDemandAP")) {
      delay(3000);
      ESP.reset();
      delay(5000);
    }
}
void thingSpeakClient(int pm1_0, int pm2_5, int pm10_0) {
    ThingSpeak.setField(1,pm1_0);
    ThingSpeak.setField(2,pm2_5);
    ThingSpeak.setField(3,pm10_0);
    ThingSpeak.setField(4,VER);
    ThingSpeak.setLatitude(42.0000);
    ThingSpeak.setLongitude(-71.0000);
    ThingSpeak.setElevation(100);
    ThingSpeak.writeFields(myChannelNumber, myWriteAPIKey);  
}
void sparkfunClient(int pm1_0, int pm2_5, int pm10_0){
  const int httpPort = 80;
  if (!client.connect(host, httpPort)) {
    return;
  }
  
  String url = "/input/";
  url += streamId;
  url += "?private_key=";
  url += privateKey;
  url += "&dust10=";
  url += pm1_0;
  url += "&dust100=";
  url += pm10_0;
  url += "&dust25=";
  url += pm2_5;
  client.print(String("GET ") + url + " HTTP/1.1\r\n" +
               "Host: " + host + "\r\n" + 
               "Connection: close\r\n\r\n");
  unsigned long timeout = millis();
  while (client.available() == 0) {
    if (millis() - timeout > 5000) {
      client.stop();
      return;
    }
  }
}









































































































































본문 내용 중 myChannelNumber, myWriteAPIKey, streamId, privateKey 는 각각 Thingspeak 와 sparkfun 에서 부여된 번호를 넣으면 된다. (SPARKFUN 은 별도의 회원가입절차 없이 사용할 수 있다)



헤더파일 PMS7003s.h

#ifndef    MAX_FRAME_LEN
#define    MAX_FRAME_LEN   64
#endif
struct PMS7003_framestruct {
    byte  frameHeader[2];
    unsigned int  frameLen = MAX_FRAME_LEN;
    unsigned int  concPM1_0_CF1;
    unsigned int  concPM2_5_CF1;
    unsigned int  concPM10_0_CF1;
    unsigned int  checksum;
} PMS7003S;
bool pms7003_read() {
    bool packetReceived = false;
    unsigned int calcChecksum = 0;
    bool inFrame = false;
    int detectOff = 0;
    char frameBuf[MAX_FRAME_LEN];
    int frameLen = MAX_FRAME_LEN;
    while (!packetReceived) {
        if (Serial.available() > 32) {
            int drain = Serial.available();
            for (int i = drain; i > 0; i--) {
                Serial.read();
            }
        }
        if (Serial.available() > 0) {
            int incomingByte = Serial.read();
            if (!inFrame) {
                if (incomingByte == 0x42 && detectOff == 0) {
                    frameBuf[detectOff] = incomingByte;
                    PMS7003S.frameHeader[0] = incomingByte;
                    calcChecksum = incomingByte; // Checksum init!
                    detectOff++;
                }
                else if (incomingByte == 0x4D && detectOff == 1) {
                    frameBuf[detectOff] = incomingByte;
                    PMS7003S.frameHeader[1] = incomingByte;
                    calcChecksum += incomingByte;
                    inFrame = true;
                    detectOff++;
                }
                else {
                }
            }
            else {
                frameBuf[detectOff] = incomingByte;
                calcChecksum += incomingByte;
                detectOff++;
                unsigned int val = (frameBuf[detectOff-1]&0xff)+(frameBuf[detectOff-2]<<8);
                switch (detectOff) {
                    case 4:
                        PMS7003S.frameLen = val;
                        frameLen = val + detectOff;
                        break;
                    case 6:
                        PMS7003S.concPM1_0_CF1 = val;
                        break;
                    case 8:
                        PMS7003S.concPM2_5_CF1 = val;
                        break;
                    case 10:
                        PMS7003S.concPM10_0_CF1 = val;
                        break;
                    case 32:
                        PMS7003S.checksum = val;
                        calcChecksum -= ((val>>8)+(val&0xFF));
                        break;
                    default:
                        break;
                }
                if (detectOff >= frameLen) {
                    packetReceived = true;
                    detectOff = 0;
                    inFrame = false;
                }
            }
        }
    }
    return (calcChecksum == PMS7003S.checksum);
}















































































## 실행파일 업로드 방법


위 두개의 파일을 하나의 폴더에 넣는다. .ino 파일(아두이노파일)은 폴더 이름과 동일하게 만들어 둔다. 아두이노 IDE 를 사용해서 .ino 파일을 연다. 파일이 열리고 파일이름 택 옆에 또 하나의 택이 있고 그 이름이 PMS7003s.h 인지 확인한다. 




두개의 슬라이드 스위치를 모두 OFF 시킨 후 (연결이 끊어진 쪽) 컴파일-업로드를 진행한다.



## AP 설정방법

NodeMCU 에는 프로그램이 실행되는 상태이다. 이 상태에서 AP-SW 를 누른 상태에서 NodeMCU 를 리셋(RST버튼) 시킨다. 


AP-SW 누름 -> NodeMCU RST 누름 ->  NodeMCU RST 뗌 -> (약 3초후)AP-SW 뗌


1. 스마트폰으로 OnDemandAP 에 접속


스마트폰이나 기타 WiFi 에 연결이 가능한 기기로 주변 AP 를 찾아보면 OnDemandAP 라는 AP 를 찾을 수 있다. 크롬등의 웹브라우저로 접속한다.


2. 브라우저에서 192.168.4.1 을 쳐서 NodeMCU 에 접속한다. 192.168.4.1 은 고정된 IP 주소이므로 기억해두어야한다.


3. 화면에 나온 버튼을 클릭해서 자신이 사용하는 AP 에 접속한다. (AP는 공유기를 의미)


      

 

        



공유기에 접속하게 되면 이후 자동적으로 Thingspeak 와 Sparkfun 서버에 접속하여 20초마다 한번씩 3개의 미세먼지데이터를 보내준다. 각각의 사이트로 가서 데이터를 확인한다. AP 정보는 NodeMCU 의 EEPROM 에 저장되므로 AP 가 변경될 때 한번만 설정하면 된다.



Thingspeak 는 데이터를 그래프화해서 쉽게 확인할 수 있게 해 준다.




sparkfun 에서 제공하는 서비스는 별도의 로그인이 필요없다. 데이터를 저장하고 json 이나 csv 등의 형식으로 다운로드 할 수 있다.




댓글 0

MADE BY

임성국

c언어, iot, pcb제작, 라즈베리파이, 스크래치, 아두이노, 인벤터, 회로설계