브레드보드에 점퍼선으로 임시회로를 구성하는 것은 실습에는 간편하지만 조금만 잘못 움직여도 선이 뽑히거나 접촉불량이 발생하는 등 내구성이 없다. 그래서 실제 사용하는 기기 회로로 제작하려면 튼튼하게 납땜작업을 해야 한다.

 

동일한 회로를 대량으로 제작할 때에는 배선까지 미리 연결된 형태의 전용PCB를 설계해서 제작해야 하지만, 한두개 시험적으로 제작할 때에는 만능기판을 활용해서 수작업으로 전선 납땜하는 방식이 사용된다.     

 

 

 

만능기판의 종류 :

 

- 크기는 2x8cm 작은 사이즈에서 15x20cm 까지 다양한 크기들이 있으므로 회로 규모에 따라 선택할 수 있다.

 

- 기판 소재에 따라 페놀 수지형과 에폭시 형이 있고, 한 쪽면에서만 납땜 가능한 단면형과 양쪽 아무곳에서나 납땜가능한 양면형이 있는데 페놀수지 단면형이 저렴하고 에폭시 양면형이 조금 더 비싸다.

 

 

 

 

 

 

 

다음과 같이 부드러운 소재로 되어 있어 가위로 임의대로 잘라서 사용할 수 있는 만능기판도 있다.

 

 

 

그리고 다음과 같이 브레드보드와 똑같이 생긴 만능기판도 있는데, 브레드보드에서 구성했던 임시회로 모양 그대로 납땜을 할 수 있고 전원용 라인이 길게 연결되어 있어 브레드보드의 장점을 살릴 수도 있다.

 

 

 

 

 사용방법

 

1) 부품의 배치 : 양면기판 위에 연결하고자 하는 부품들을 배치해서 꽂아 본다. 부픔의 배치 모양에 따라 배선 연결작업이 복잡해 질 수도 있고 단순해질 수도 있으므로 부품 배치 모양을 잘 구성해야 한다. 

 

2) 부품이 배치된 면의 반대쪽 뒷면에 전선이나 납을 이용해서 납땜해서 회로를 구성한다. 배선이 겹치지 않을 경우 기판 에 바로 납땜할 수 있지만 배선이 겹치게 되면 별도 전선을 이용해서 연결해야 한다.

   

 

3) 한번 남땜하면 다시 해체하기 힘들기 때문에 아두이노 보드와 같은 경우 아래와 같이 핀소켓을 납땜하고 아두이노 보드를 착탈식으로 하는 것이 좋다. 아두이노 보드가 고장이 나서 교체할 경우도 있고 나중에 비싼 아두이노 보드만을 빼서 다른 용도로 활용할 수도 있다.      

 

 

 

 

주의사항 : 가혹 만능기판 중에는 홀과 홀사이의 간격이 2.54mm가 아닌 경우도 있다. 아두이노 실습용으로는 홀간 간격이 2.54mm인 것을 구매해야 사용하기 편하므로 구매시 이를 확인해 보아야 한다.

 

구매좌표

 

9x15 8x12 7x9 6x8 5x7 4x6 3x7 2x8 cm Double Side Prototype Diy Universal Printed Circuit PCB Board Protoboard For DIY - AliExpre

Smarter Shopping, Better Living! Aliexpress.com

www.aliexpress.com

 

 

 

아누이노와 여기에 연결된 부품간에는 신호용 전류 정도가 흐르기 때문에 일반적인 듀폰케이블이나 가는 연결 케이블을 사용해도 무방하다. 하지만 모터를 가동시킨다 든지 대형 전구를 가동시킨다 든지 할 때에는 큰 전류가 흐르기 때문에 케이블도 이 용량에 맞는 케이블을 사용해서 연결해 주어야 한다. 그렇지 않으면 기기가 작동을 하지 않거나 열이 발생해서 케이블이 불타 버리는 현상이 발생할 수 있다. 

 

 

케이블의 규격 및 허용 전류     

 

일반적으로 미국에서 사용하는 AWG(American Wire Guauge) 전선규격으로 케이블의 굵기 및 허용 전류치를 사용하는데 AWG값이 커질수록 전선의 굵기가 가늘어 지고 허용전류가 적어진다. 여기에서 허용전류치는 최대치이므로 실제 사용은 두배정도의 용량을 가진 케이블을 사용하는 것이 안전하다.

 

 

-위는 일반 PVC비닐 피복전선 기준이고 실리콘 전선일 경우 2~3배 용량이 더 크다.

 

 

점퍼선 : 아두이노 점퍼선으로 사용하는 듀폰 케이블은 보통 28AWG~24AWG로서 허용 전류는 250mA ~588mA이다.   

 

 

 

 

소형전원용 : 소형기기 전원 공급용 케이블은 다음과 같이 18AWG ~ 26AWG 규격이 많이 사용된다.  

 

 

 

 대용량 케이블 : 파워뱅크라든지 중대형기기 전원 공급용은 굵은 실리콘케이블을 사용하는 것이 좋다.  

 

 

 

 

 

모터연결용 케이블  : 서보모터를 연결하기 위해서는 3개의 선이 필요하고 스테핑모터를 연결하기 위해서는 4선이 필요하므로 모터 연결용으로 3~4선을 함께 구성하고 커넥터까지 연결해 놓은 모터연결용 케이블을 사용하면 편리하다. 

 

 

 

 

 

 

ESP8266은 중국 ESPressif Systems라는 회사에서 개발한 다음과 같은 저가형 WIFI 칩인데, 완전한 WIFI통신을 지원함과 동시에 32bit Micro Processor를 함께 내장하고 있어 저렴한 비용으로 WIFI통신 뿐만 아니라 프로세서의 기능을 함께 사용할 수 있어 많이 사용된다.     

 

 

 

ESP8266의 주요 사양

 

- 80Mhz대 빠른 속도의 32비트 Micro Precessors내장 ( 아두이노 우노는 16Mhz )

 

- 80KB의 사용자데이터 RAM

 

- 2.4GHz대의 802.11b/g/a WIFI 통신 지원

 

- 16개의 범용입출력 핀, 1개의 아날로그 입력

 

- SPI,I2C,I2S,UART 등 다양한 인터페이스 지원

 

- 자체 ESP8266 SDK 이외에도 다양한 개발 환경을 지원하는데 특히 아두이노IDE를 그대로 이용할 수 있도록 지원  

 

 

그런데 이 ESP8266에는 펌웨어 저장을 위한 플래시메모리가 포함되어 있지 않아 단독으로는 사용하기 곤란하기 때문에 메모리 등 기타 부품을 부착하여 여러가지 모듈형태로 제작되어 유통된다.  AI-Thinker라는 회사에서 이 ESP8266칩을 이용한 ESP-01이라는 통신 및 프로세서 기능을 결합한 모듈이 출시되었고  이후 시리즈로 계속해서 비슷한 모듈들이 출시되고 있어 사양에 따라 여러 용도로 활용되고 있다.  종류별 상세 사양은 다음 Wikipedia 링크에서 참조할 수 있다.

            https://en.wikipedia.org/wiki/ESP8266

 

 

NodeMCU

 

위 ESP01~ESP14 모듈들은 대부분 PCB 납땜용 또는 타 모듈에 연결장치로 부착하는 형태이어서 실습이나 개발용으로 활용하기에는 아두이노처럼 편리하지 못하다.  

 

ESP모듈시리즈 중 가장 많이 사용되는 ESP-12 모듈을 기본으로 하여 아두이노 개발환경에 통합할 수 있는 펌웨어와 USB연결장치 등을 부착하여 마치 아두이노의 한 종류인 것처럼 사용할 수 NodeMCU라는 모듈이 메이커 제작용 프로세서로 많이 사용된다.


즉, 인터넷 환경에서 하나의 노드 역할을 할 수 있도록 제작된 아두이노의 한 종류로 이해하고 사용할 수 있다.

 

 

 

WeMos D1, D1 Mini

 

NodeMCU 보드들 중 WeMos라는 모듈이 있는데 다음과 같이 아두이노 우노에 ESP-12를 합친 형태의 제품으로써, 아두이노 우노를 사용하듯이  듀폰케이블만으로 편리하게 임시회로를 구성하면서 실습할 수 있다.그리고  WeMos D1 Mini는 WeMos D1을  작게 소형화한 것으로서 일반적인 NodeMCU 모듈들 보다 크기가 작다.  

 

 

 

 

아두이노 피지컬컴퓨팅 교구로 활용할 수 있는 실 생할용품 메이킹 프로젝트(워터맨IOT) 열네번째.

 

 

종합과제  :   현재까지 학습한 내용을 종합하여 다음과 같은 기능을 가진 WATERman IOT 시스템을 제작.

 관리대상 화분에 꽃아 두고 언제 어디에서는 스마트폰으로 화분을 관리.

 1)화분의 조도LUX, 온도및습도, 토양수분상태가 변화되는 내역을 그래프형태로 모니터링  
 2)수분이 부족하면 스마트폰으로 "목 말라요" 알람메세지 전송받기
 3)원격지에서 스마트폰 버튼 조작으로 펌프 가동시켜 화분에 물주기

 

 

 

구현할 기능 : 이를 위해 IOT Device 프로그램에서는 다음과 같은 기능을 수행

1) 1분에 한번씩 조도LUX, 온도및습도, 토양수분상태를 측정하여
    - ThingSpeak 서버에 Upload하기
    - 온도가 4도 이하로 떨어지면 스마트폰에 ‘추워요’메시지 보내기
    - 토양수분이 10이하로 떨어지면 스마트폰에 ‘목말라요’ 메시지 보내기
    - 필요시 스마트폰에서는 Blynk기능으로 원격에서 토양수분수치 확인하며 물주기  
    - 사용자가 원격물주기를 하지 않아 토양수분이 10이하인 상태가 4회이상 계속되면 워터펌프를 자동으로 가동시켜 10초간 물주기
    - 토양수분이 10이하인 상태가 5회이상 반복되면, 즉 펌프를 자동기동시켜도 토양수분이 올라가지 않는 것은 물통에 물이 떨어진 경우이므로 ‘물이 없어요’메시지 보내기
2) Device가 정상 가동중임을 알리기 위해 내부 LED 1초에 한번씩 깜박이기 
3) 접속할 공유기의 SSID,password와 ThingSpak,IFTT,Blynk키 등을 프로그램 변경없이 다른 값으로 교체할 수 있는 설정하기 기능    

 

샘플프로그램

 

#include <Wire.h>
#include <BH1750.h>
#include <ESP8266WiFi.h>
#include <ESP8266WebServer.h>
#include <BlynkSimpleEsp8266.h>
#include <EEPROM.h>
BH1750 lightMeter;
#include "DHT.h"
#define DHTTYPE DHT11  
int BtnSetting = D3;   
int pinPower = D5; 
int pinRelay = D7; 
#define DHTPIN D6
DHT dht(DHTPIN, DHTTYPE);
uint16_t lux;
int temp;
int humid;
int moist;
int moistr;
char  ssid[30] = "xxxx";
char  password [30] = "xxxxxxxxxxxxxx";
const char* ThingSpeak_server = "api.thingspeak.com"; 
const char* IFTTT_server = "maker.ifttt.com"; 
char ThingSpeak_key[50] = "COZ4HZEITR4F0X9R";
char IFTTT_key[50] = "xxxxxxxxxxxxxxxxxxxxxxx";
char Blynk_auth[50] = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";   
const int recordPeriod = 600;                
ESP8266WebServer server(80); 
WiFiClient client; 
int   WifiMode = 0 ; // 0: AP mode,  1 : STA Mode
const char *ap_ssid = "WatermanS_setting";
const char *ap_pass = "";
String postStr;
long time_upload = 0;
long  time_start = 0;
long time_blink = 0;
int  flag_blink =  0;
int   time_on = 0;
int  go_stop = 0;
int i, ii,ss;
int cnt_low_temp = 0;
int cnt_low_moist = 0;
long tmDown;

void procReadSensors(){
    digitalWrite(pinPower, HIGH);
    delay(500);
    for ( int i= 0; i < 30; i++ ) {
      delay(200);
      temp  = dht.readTemperature();
      humid = dht.readHumidity();
      if ( humid >  0 &&  temp < 100 && humid < 101 ) break;
    }
    lux = lightMeter.readLightLevel();
    moistr = analogRead(A0);
    moist = map(moistr,1023,0,0,100) ; 
    digitalWrite(pinPower, LOW);
}

void procIFTTT(String trigger){
    WiFi.mode(WIFI_STA);
    WiFi.disconnect();
    delay(300);
    WiFi.begin(ssid, password); 
    for ( int i= 0; i < 10; i++ ) {
      if ( WiFi.status() == WL_CONNECTED ) {
        break;
      }
      delay(1000);
    } 
    if ( WiFi.status() != WL_CONNECTED ) {
       return;
    }
    if (client.connect(IFTTT_server, 80)) { 
        String getStr = "/trigger/";
        getStr += trigger;
        getStr += "/with/key/";
        getStr += IFTTT_key; 
        
        Serial.print("IFTTT="); 
        Serial.println(getStr);
        
        client.print("GET "); 
        client.print(getStr);
        client.print(" HTTP/1.1\r\n");
        client.print("Host: maker.ifttt.com\r\n"); 
        client.print("Connection: close\r\n"); 
        client.print("Content-Type: 'text/html;'"); 
        client.print("\r\n\r\n"); 
        delay(1000); 
        client.stop(); 
     }
    delay(100);
}
void procThingSpeak(){
    WiFi.mode(WIFI_STA);
    WiFi.disconnect();
    delay(100);
    WiFi.begin(ssid, password);  
    for ( int i= 0; i < 10; i++ ) {
      if ( WiFi.status() == WL_CONNECTED ) {
        break;
      }
      delay(1000);
    } 
    if ( WiFi.status() != WL_CONNECTED ) {
       return;
    }
    if (client.connect(ThingSpeak_server, 80)) { 
         String postStr = ThingSpeak_key; 
         postStr += "&field1="; 
         postStr += String(temp); 
         postStr += "&field2="; 
         postStr += String(humid); 
         postStr += "&field3="; 
         postStr += String(lux); 
         postStr += "&field4="; 
         postStr += String(moist); 
         postStr += "\r\n\r\n"; 
         
        Serial.print("ThingSpeak="); 
        Serial.println(postStr);
        
        client.print("POST /update HTTP/1.1\n"); 
        client.print("Host: api.thingspeak.com\n"); 
        client.print("Connection: close\n"); 
        client.print("X-THINGSPEAKAPIKEY: ");   
        client.print(ThingSpeak_key); 
        client.print("\n"); 
        client.print("Content-Type: application/x-www-form-urlencoded\n"); 
        client.print("Content-Length: "); 
        client.print(postStr.length()); 
        client.print("\n\n"); 
        client.print(postStr); 
        delay(1000); 
        client.stop(); 
     }
    delay(100);
}
void procBlynk() {
     if ( go_stop ==  1 ) {
          time_on = ( millis() - time_start ) / 1000; 
          Blynk.virtualWrite(V2, time_on);
          delay(200);
          moistr = analogRead(A0);
          moist = map(moistr,1023,0,0,100) ; 
          Blynk.virtualWrite(V3, moist);
          
    }
    if ( time_on > 20 ) {
        go_stop = 0; 
        digitalWrite(pinRelay, LOW);
        digitalWrite(LED_BUILTIN, LOW);
    }
}

BLYNK_WRITE(V1) {  
   go_stop = param.asInt();
   if ( go_stop ==  1 ) {
      digitalWrite(pinPower, HIGH);
      digitalWrite(pinRelay, HIGH);
      digitalWrite(LED_BUILTIN, HIGH);
      time_start =  millis();
   }
   else{
      digitalWrite(pinPower, LOW);
      digitalWrite(pinRelay, LOW);
      digitalWrite(LED_BUILTIN, LOW);
   }
}
void config_read() {
    EEPROM.begin(300);  
    WifiMode = EEPROM.read(0); 
    if ( WifiMode == 1)  {   
       if ( EEPROM.read(1) == 1  ) { // if config is written
         ss = 2;
         for (i = 0;  i < 30; i++ ) {
             ssid[i] = EEPROM.read(ss + i );
         }
         ss = 32;
         for (i = 0;  i < 30; i++ ) {
             password[i] = EEPROM.read(ss + i );
         }          
         ss = 62;
         for (i = 0;  i < 50; i++ ) {
             ThingSpeak_key[i] = EEPROM.read(ss + i );
         }          
         ss = 112;
         for (i = 0;  i < 50; i++ ) {
             IFTTT_key[i] = EEPROM.read(ss + i );
         }          
         ss = 162;
         for (i = 0;  i < 50; i++ ) {
             Blynk_auth[i] = EEPROM.read(ss + i );
         }  
       }  
    }
    else if ( WifiMode != 0 ){
       WifiMode = 0;
    }
}
void config_write() {
    EEPROM.begin(300);  
    EEPROM.write(0, 1);
    EEPROM.write(1, 1);
    ss = 2;
    for (i = 0;  i < 30; i++ ) {
       EEPROM.write(ss + i, ssid[i] );
    }
    ss = 32;
    for (i = 0;  i < 30; i++ ) {
       EEPROM.write(ss + i, password[i] );
    }          
    ss = 62;
    for (i = 0;  i < 50; i++ ) {
       EEPROM.write(ss + i, ThingSpeak_key[i] );
    }         
    ss = 112;
    for (i = 0;  i < 50; i++ ) {
       EEPROM.write(ss + i, IFTTT_key[i] );
    }         
    ss = 162;
    for (i = 0;  i < 50; i++ ) {
       EEPROM.write(ss + i, Blynk_auth[i] );
    }             
    EEPROM.commit();
    delay(500);
    WifiMode = EEPROM.read(0); 
    EEPROM.end(); 
 }
void procAProot() {
   Serial.println("procAProot start");
   String strHtml = "<!DOCTYPE html><html><head><meta name='viewport' content='width=device-width,initial-scale=1.0' /></head><body>";
   strHtml = strHtml +  "<form action='/setting' method='POST'><table width=100%><tr><td align=center><h3> Setting Waterman IOT</h3><br></td></tr>";
   strHtml = strHtml+ "<tr><td>WiFI ID: <input type=text name='ID' value='" + ssid + "'/><br></td></tr>"; 
   strHtml = strHtml+ "<tr><td>Password: <input type=text name='PW' value='" + password + "'/><br></td></tr>"; 
   strHtml = strHtml+ "<tr><td>ThingSpeak key: <input type=text name='TSK' value='" + ThingSpeak_key + "'/><br></td></tr>"; 
   strHtml = strHtml+ "<tr><td>IFTTT key: <input type=text name='IFK' value='" + IFTTT_key + "'/><br></td></tr>"; 
   strHtml = strHtml+ "<tr><td>Blynk key: <input type=text name='BLK' size=25 value='" + Blynk_auth + "'/><br></td></tr>"; 
   strHtml = strHtml+ "<tr><td></p></fieldset>";
   strHtml = strHtml+ "<tr><td align=center> <input type=submit value='confirm' /></td></tr></table></form></body>";
   server.send(200,"Text/html",strHtml);
}
void procAPsetting() {
    server.send(200,"Text/html","<body><br><h1>Registering...<h1></body>");
    String ID = server.arg("ID");
    String PW = server.arg("PW");
    String TSK = server.arg("TSK");
    String IFK = server.arg("IFK");
    String BLK = server.arg("BLK");
    strcpy(ssid, ID.c_str());
    strcpy(password, PW.c_str());
    strcpy(ThingSpeak_key, TSK.c_str());
    strcpy(IFTTT_key, IFK.c_str());
    strcpy(Blynk_auth, BLK.c_str());
    config_write();
    ESP.restart();
}
void ICACHE_RAM_ATTR procSetting();
void procSetting() {
    EEPROM.begin(300);  
    EEPROM.write(0, 0);
    EEPROM.commit();
    EEPROM.end();
    ESP.restart();
}
void setup() {
   Serial.begin(115200);
   Serial.println("==waterman IOT  START==");
   Wire.begin();
   pinMode(pinPower, OUTPUT);
   digitalWrite(pinPower, HIGH);
   pinMode(pinRelay, OUTPUT);
   digitalWrite(pinRelay, LOW);
   pinMode(LED_BUILTIN, OUTPUT);  
   lightMeter.begin();
   dht.begin();
   config_read();
   if ( WifiMode  == 1 )  {   
     Serial.println("READY mode Server start"); 
     WiFi.mode(WIFI_STA);
     WiFi.disconnect();
     pinMode(BtnSetting,INPUT_PULLUP);
     attachInterrupt(BtnSetting,  procSetting, FALLING); 
     Blynk.begin(Blynk_auth, ssid, password);
   }
   else{
       WiFi.mode(WIFI_AP);
      Serial.println("strart AP MODE ");
      WiFi.softAP(ap_ssid,ap_pass );
      IPAddress myIP = WiFi.softAPIP();
      Serial.print("AP mode ip=");
      Serial.println(myIP);
      server.on("/",procAProot);
      server.on("/setting",procAPsetting);
      server.begin();
      Serial.println("AP mode Server start"); 
   }
}
void loop() {
   if ( WifiMode  != 1 )  {
       server.handleClient();
       delay(500);
   }
   else {
      if ( millis() - time_blink  > 1000 ) {
          if ( flag_blink == 1 ) {
             digitalWrite(LED_BUILTIN, HIGH);
             flag_blink = 0;
          }
          else {
             digitalWrite(LED_BUILTIN, LOW);
             flag_blink = 1; 
          }
          time_blink = millis();
      }
      if ( millis() - time_upload  > 1000 * 60 * 1) {
         procReadSensors();
         if ( temp < 5) {
              if ( cnt_low_temp++ < 4 ) 
                 procIFTTT("low_temp");
         }
         else {
             cnt_low_temp = 0;
         }
        if ( moist < 10) {
            cnt_low_moist++;
            if ( cnt_low_moist < 4 ) {
                 procIFTTT("low_moist");
            }
            if ( cnt_low_moist == 4 ) {
               digitalWrite(LED_BUILTIN, HIGH);
               digitalWrite(pinRelay, HIGH);
               delay(10000);
               digitalWrite(LED_BUILTIN, LOW);
               digitalWrite(pinRelay, LOW);
            }
            if ( cnt_low_moist == 5 ) {
                procIFTTT("low_water");
            }
        }
        else {
           cnt_low_moist = 0;
        }
         procThingSpeak();
         time_upload  = millis();
      }
  Blynk.run();
  procBlynk();
  }
}

 

+ Recent posts