K씨의 별별제작소

아두이노로 온습도계만들기- 시계추가 (DHT22 온습도센서, 0.96 OLED, DS3231 RTC) 본문

아두이노 프로젝트

아두이노로 온습도계만들기- 시계추가 (DHT22 온습도센서, 0.96 OLED, DS3231 RTC)

K씨 2018. 11. 28. 22:08

앞에서 DHT22온습도센서로 온도 습도값을 측정하여

0.96인치 OLED에 출력하는 장치를 만들었다.

만들다보니 온도 습도 뿐 아니라 시계 기능도 함께 있으면 좋겠다는 생각이 들어

OLED 상단에 시간과 날짜를 표시해 주기로 했다.

 

정확한 시간 표시를 위해 DS3231 RTC를 사용하기로 했다.

예전에 7세그먼트 시계를 만들때 DS3231 RTC를 사용하여 만들었기에

이 블로그에서 한번 설명한 적이 있으나

다시한번 더 상기하면서 DS3231 RTC 연결하는 것부터 자세히 설명하기로 한다.

 

1. DS3231 RTC 연결하기

 

1> DS3231 RTC 모듈

 


 

DS3231 모듈은 저렴하면서도 꽤 정확한(1년에 2분 정도의 오차가 발생한다고 한다.)

Real Time Clock (RTC)로

현재 날짜, 시간 등을 실시간으로 알 수 있는 모듈이다.

이모듈은 CR2032 (듀라셀은 DL2032로 표시되기도 함) 배터리를 사용하는데,

배터리 덕분에 시계 전원을 껐다 켜도 시간이 초기화되지 않고 정확하게 표시된다.

알리익스프레스에서 구입했고, 가격은 $0.99 (배터리 별도 구입)

 

 

2> 앞에서 만든 온습도계에 연결하기

 

앞에서 만든 온습도계는 아래 글 참고

아두이노로 온습도계만들기 (DHT22 온습도센서, 0.96 OLED) - 1

아두이노로 온습도계만들기 (DHT22 온습도센서, 0.96 OLED) - 2

 

 

 

▲ DHT22와 0.96인치 OLED를 이용해서 만든 온습도계에 DS3231 RTC를 연결했다.

내가 사용한 0.96인치 OLED가 I2C통신을 하는 모듈이었는데

DS3231 RTC 역시 I2C통신을 하고 있어서

GND, VCC 핀 뿐만 아니라 SCL, SDA핀도

아두이노 우노에 직접 연결하지 않고 브래드보드를 이용했다.

 

I2C 통신에 대한 이해는 아래 블로그를 참고했다.

https://m.blog.naver.com/yuyyulee/220323559541

 

 

 

 

 

▲ I2C 통신을 하는 OLED와 DS3231 RTC 모듈 모두

SCL은 A5핀과 연결하고, SDA핀은 A4핀과 연결한다.

(브레드보드와 점퍼선을 이용한다.

이들은 연결하는 핀번호가 정해져있다.)

 

 

2. RTC 시간 설정하기

 

DS3231 RTC에 현재 시간을 입력하기 위해

아두이노IDE 프로그램을 열고, 아래 스케치 코드를 입력한다.

출처는 http://deneb21.tistory.com/327

 

#include <Wire.h>

 

#define DS3231_I2C_ADDRESS 104

 

// 데이터핀 연결

// SCL - pin A5

// SDA - pin A4

 

byte seconds, minutes, hours, day, date, month, year;

char weekDay[4];

 

byte tMSB, tLSB;

float temp3231;

 

void setup()

{

  Wire.begin();

  Serial.begin(9600);

}

 

void loop()

{

 

  watchConsole();

  get3231Date();

 

  Serial.print(weekDay);

  Serial.print(", 20");

  Serial.print(year, DEC);

  Serial.print("/");

  Serial.print(month, DEC);

  Serial.print("/");

  Serial.print(date, DEC);

  Serial.print(" - ");

  Serial.print(hours, DEC); 

  Serial.print(":"); 

  Serial.print(minutes, DEC); 

  Serial.print(":"); 

  Serial.print(seconds, DEC);

  Serial.print(" - Temp: "); 

  Serial.println(get3231Temp());

 

  delay(1000);

}

 

// 10진수를 2진화 10진수인 BCD 로 변환 (Binary Coded Decimal)

byte decToBcd(byte val)

{

  return ( (val/10*16) + (val%10) );

}

 

void watchConsole()

{

  if (Serial.available()) {      // Look for char in serial queue and process if found

    if (Serial.read() == 84) {   //If command = "T" Set Date

      set3231Date();

      get3231Date();

      Serial.println(" ");

    }

  }

}

 

//시간설정

// T(설정명령) + 년(00~99) + 월(01~12) + 일(01~31) + 시(00~23) + 분(00~59) + 초(00~59) + 요일(1~7, 일1 월2 화3 수4 목5 금6 토7)

// 예: T1605091300002 (2016년 5월 9일 13시 00분 00초 월요일)

void set3231Date()

{

  year    = (byte) ((Serial.read() - 48) *10 +  (Serial.read() - 48));

  month   = (byte) ((Serial.read() - 48) *10 +  (Serial.read() - 48));

  date    = (byte) ((Serial.read() - 48) *10 +  (Serial.read() - 48));

  hours   = (byte) ((Serial.read() - 48) *10 +  (Serial.read() - 48));

  minutes = (byte) ((Serial.read() - 48) *10 +  (Serial.read() - 48));

  seconds = (byte) ((Serial.read() - 48) * 10 + (Serial.read() - 48));

  day     = (byte) (Serial.read() - 48);

 

  Wire.beginTransmission(DS3231_I2C_ADDRESS);

  Wire.write(0x00);

  Wire.write(decToBcd(seconds));

  Wire.write(decToBcd(minutes));

  Wire.write(decToBcd(hours));

  Wire.write(decToBcd(day));

  Wire.write(decToBcd(date));

  Wire.write(decToBcd(month));

  Wire.write(decToBcd(year));

  Wire.endTransmission();

}

 

 

void get3231Date()

{

  // send request to receive data starting at register 0

  Wire.beginTransmission(DS3231_I2C_ADDRESS); // 104 is DS3231 device address

  Wire.write(0x00); // start at register 0

  Wire.endTransmission();

  Wire.requestFrom(DS3231_I2C_ADDRESS, 7); // request seven bytes

 

  if(Wire.available()) {

    seconds = Wire.read(); // get seconds

    minutes = Wire.read(); // get minutes

    hours   = Wire.read();   // get hours

    day     = Wire.read();

    date    = Wire.read();

    month   = Wire.read(); //temp month

    year    = Wire.read();

       

    seconds = (((seconds & B11110000)>>4)*10 + (seconds & B00001111)); // convert BCD to decimal

    minutes = (((minutes & B11110000)>>4)*10 + (minutes & B00001111)); // convert BCD to decimal

    hours   = (((hours & B00110000)>>4)*10 + (hours & B00001111)); // convert BCD to decimal (assume 24 hour mode)

    day     = (day & B00000111); // 1-7

    date    = (((date & B00110000)>>4)*10 + (date & B00001111)); // 1-31

    month   = (((month & B00010000)>>4)*10 + (month & B00001111)); //msb7 is century overflow

    year    = (((year & B11110000)>>4)*10 + (year & B00001111));

  }

  else {

    //oh noes, no data!

  }

 

  switch (day) {

    case 1:

      strcpy(weekDay, "Sun");

      break;

    case 2:

      strcpy(weekDay, "Mon");

      break;

    case 3:

      strcpy(weekDay, "Tue");

      break;

    case 4:

      strcpy(weekDay, "Wed");

      break;

    case 5:

      strcpy(weekDay, "Thu");

      break;

    case 6:

      strcpy(weekDay, "Fri");

      break;

    case 7:

      strcpy(weekDay, "Sat");

      break;

  }

}

 

float get3231Temp()

{

  //temp registers (11h-12h) get updated automatically every 64s

  Wire.beginTransmission(DS3231_I2C_ADDRESS);

  Wire.write(0x11);

  Wire.endTransmission();

  Wire.requestFrom(DS3231_I2C_ADDRESS, 2);

 

  if(Wire.available()) {

    tMSB = Wire.read(); //2's complement int portion

    tLSB = Wire.read(); //fraction portion

   

    temp3231 = (tMSB & B01111111); //do 2's math on Tmsb

    temp3231 += ( (tLSB >> 6) * 0.25 ); //only care about bits 7 & 8

  }

  else {

    //error! no data!

  }

  return temp3231;

}

 

▲ 아두이노에는 I2C통신을 쉽게 할 수 있는 Wire 라이브러리가 포함되어있어서

별도의 라이브러리는 설치하지 않아도 된다.

 

위 소소를 업로드한 후 시리얼모니터를 연다.

 

 

▲ 아직 시간 설정이 되지 않아서 시간이 제대로 나타나지 않는다.

 

이제 시리얼모니터에 현재 시간을 입력하고 전송버튼을 누른다.

 

▲ 현재시간 입력 방법은 소스코드에 설명이 나와있다.

T(설정명령) + 년(00~99) + 월(01~12) + 일(01~31) + 시(00~23) + 분(00~59) + 초(00~59)

+ 요일(1~7, 일1 월2 화3 수4 목5 금6 토7)

 

이렇게 입력하고 전송하면 된다.

참고로, 네이버시계를 켜놓고 초를 딱 맞춰 전송시키면 좀더 정확한 시계가 된다.

 

DS3231 RTC에 시간설정을 완료했다.

보드에 전원연결을 끊어도 시간이 정확히 흘러가도록하려면

RTC의 배터리는 항상 끼워두어야 한다.

 

 

3. 날짜와 시간을 0.96인치 OLED에 출력하기

 

참고로 한 사이트

https://github.com/rydepier/Arduino-OLED-Clock/blob/master/Arduino-OLED-Clock%20using%20U8GLIB%20library.ino

 

위 사이트의 소스를 입력하면 아래와 같이 뜬다.

 

 

▲ 빨간 동그라미 부분만 수정하여 사용할 예정이라

소스를 아래와 같이 변경하였다.

 


// Add libraries
  #include "U8glib.h"
  #include <Wire.h>
  #include "RTClib.h"
 
// setup u8g object
  U8GLIB_SSD1306_128X64 u8g(U8G_I2C_OPT_NONE);  // I2C


 

// Setup RTC
  RTC_DS3231 RTC;
  int  monthIndex[122] ={0,3,6,9,12,15,18,21,24,27,30,33};
  String thisMonth = "";
  String thisTime = "";
  String thisDay="";

 

void draw(void) {
  // graphic commands to redraw the complete screen should be placed here 
  u8g.setFont(u8g_font_7x13r);

 

  //***** RTC **********
  DateTime now = RTC.now();
  // display date at bottom of screen
  thisDay = String(now.day(), DEC) + "/";
  thisMonth= String(now.month(), DEC) + "/";
  thisDay=thisDay + thisMonth + String(now.year() , DEC);
  const char* newDay = (const char*) thisDay.c_str();
  u8g.drawStr(50,10, newDay);  
  // *********************

 

  // display time in digital format
  thisTime="";
  thisTime=String(now.hour()) + ":";
  if (now.minute() < 10){ thisTime=thisTime + "0";} // add leading zero if required
  thisTime=thisTime + String(now.minute()); // + ":";
 // if (now.second() < 10){ thisTime=thisTime + "0";} // add leading zero if required
//  thisTime=thisTime + String(now.second());
  const char* newTime = (const char*) thisTime.c_str();
  u8g.drawStr(5,10, newTime); 
  // *********************
 
//
}

void setup(void) {
  Wire.begin();
  RTC.begin();
}

void loop(void) {
  // picture loop
  u8g.firstPage(); 
  do {
    draw();
  } while( u8g.nextPage() );
 
  // rebuild the picture after some delay
  delay(50);
}

▲ U8glib.h와 RTClib.h 라이브러리는 앞의 프로젝트 진행하면서 설치했기에

따로 설치하지 않아도 된다.

만약 이 라이브러리가 없다면 이전글들을 참고하기 바람.

 

폰트는 u8g_font_7x13r를 사용했다.

폰트 변경하는 법도 이전 글에 있으니 참고.

 

위의 코드를 아두이노IDE 스케치 프로그램에 입력하고

보드에 업로드하면 아래와 같이 뜬다.

 

 

▲ 컴파일 시간이 오래 걸리긴 했지만 시간과 날짜가 정확하게 뜬다.

 

 

4. 시간 표시되는 온습도계 완성하기

 

앞에서 만든 온습도계 코드와 방금 완성한 시계 코드를 합친다.

#include <U8glib.h>
#include <Adafruit_Sensor.h>
#include <DHT.h>
#include <DHT_U.h>
#include <Wire.h>
#include "RTClib.h"

 

// 0.96인치 128x64 OLED
U8GLIB_SSD1306_128X64 u8g(U8G_I2C_OPT_NONE);  // I2C / TWI

 

// Setup RTC
  RTC_DS3231 RTC;
  int  monthIndex[122] ={0,3,6,9,12,15,18,21,24,27,30,33};
  String thisMonth = "";
  String thisTime = "";
  String thisDay="";

 

// DHT22 온도/습도 센서
#define DHTPIN            2         // Pin which is connected to the DHT sensor.
#define DHTTYPE           DHT22     // DHT 22 (AM2302)
DHT_Unified dht(DHTPIN, DHTTYPE);

uint32_t dht22DelayMS = 0;

 

//온도 이미지삽입
const uint8_t temp[] PROGMEM = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF,
0x07, 0xFF, 0xC0, 0x07, 0xFF, 0x87, 0xFF, 0xC0, 0x07, 0x01, 0xC7, 0x00, 0x00, 0x07, 0x01, 0xC7,
0x00, 0x00, 0x07, 0xFF, 0xC7, 0x00, 0x00, 0x07, 0xFF, 0x87, 0x00, 0x00, 0x01, 0xFF, 0x07, 0xFF,
0xC0, 0x00, 0x38, 0x07, 0xFF, 0xC0, 0x0F, 0xFF, 0xC0, 0x38, 0x00, 0x0F, 0xFF, 0xC0, 0x38, 0x00,
0x07, 0x00, 0x00, 0x38, 0x00, 0x07, 0x00, 0x00, 0x38, 0x00, 0x07, 0x00, 0x00, 0x38, 0x00, 0x07,
0xFF, 0xCF, 0xFF, 0xC0, 0x07, 0xFF, 0xCF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
};          

 

//습도 이미지삽입
const uint8_t hum[] PROGMEM = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFC, 0x07, 0xFF, 0xC0, 0x01, 0xFE,
0x07, 0xFF, 0xC0, 0x01, 0xCF, 0x07, 0x00, 0x00, 0x03, 0xC7, 0x87, 0x00, 0x00, 0x07, 0x83, 0xC7,
0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x0F, 0xFF, 0xC7, 0xFF, 0xC0, 0x0F, 0xFF, 0xC7, 0xFF,
0xC0, 0x00, 0x00, 0x00, 0x38, 0x00, 0x07, 0x03, 0xC0, 0x38, 0x00, 0x07, 0xFF, 0xC0, 0x38, 0x00,
0x07, 0xFF, 0xC0, 0x38, 0x00, 0x07, 0x03, 0xC0, 0x38, 0x00, 0x07, 0xFF, 0xCF, 0xFF, 0xC0, 0x07,
0xFF, 0xCF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
};          

 

void setup() {
  Wire.begin();
  RTC.begin();
  dht.begin();

  sensor_t sensor;
  dht.temperature().getSensor(&sensor);
  dht22DelayMS = sensor.min_delay / 1000;
}

void loop() {
  float temperature;
  float humidity;

  getDHT22Data(&temperature, &humidity);
 
  u8g.firstPage();
  do {

    draw(); //온도 습도 이미지 출력위해 삽입
    u8g.setFont(u8g_font_fub14);   //폰트지정
    u8g.setPrintPos(35, 35);
    u8g.print(": ");
    u8g.print(temperature);
    u8g.print("\xb0""C"); //온도기호삽입
 

    u8g.setFont(u8g_font_fub14);   //폰트지정
    u8g.setPrintPos(35, 58);
    u8g.print(": ");
    u8g.print(humidity);
    u8g.print("%");

    delay(dht22DelayMS);
  } while(u8g.nextPage());
}


void draw(void) {
 
  // graphic commands to redraw the complete screen should be placed here 
  u8g.setFont(u8g_font_7x13r);

  //***** RTC **********
  DateTime now = RTC.now();
  // display date at bottom of screen
  thisDay = String(now.day(), DEC) + "/";
  thisMonth= String(now.month(), DEC) + "/";
  thisDay=thisDay + thisMonth + String(now.year() , DEC);
  const char* newDay = (const char*) thisDay.c_str();
  u8g.drawStr(50,10, newDay);  
  // *********************
  // display time in digital format
  thisTime="";

  if (now.hour() < 10){ thisTime="0"+ thisTime;} // add leading zero if required
  thisTime=thisTime + String(now.hour()) + ":";
  if (now.minute() < 10){ thisTime=thisTime + "0";} // add leading zero if required
  thisTime=thisTime + String(now.minute()); // + ":";
 // if (now.second() < 10){ thisTime=thisTime + "0";} // add leading zero if required
 // thisTime=thisTime + String(now.second());
  const char* newTime = (const char*) thisTime.c_str();
  u8g.drawStr(5,10, newTime); 
  // *********************
 
  u8g.drawBitmapP(0, 14, 5, 24, temp); //온도글자위해삽입.0과 14는 좌표.5는 비트맵이미지 가로(40)/8한거. 24는 비트맵 세로픽셀
  u8g.drawBitmapP(0, 38, 5, 24, hum); //습도글자위해삽입.0과 38는 좌표.5는 비트맵이미지 가로(40)/8한거. 24는 비트맵 세로픽셀
}

 

void getDHT22Data(float *temperature, float *humidity) {
  sensors_event_t event;
  dht.temperature().getEvent(&event);
  if(!isnan(event.temperature)) {
    *temperature = event.temperature;
  } else {
    *temperature = -1;
  }

 

  // Get Humidity
  dht.humidity().getEvent(&event);
  if (!isnan(event.relative_humidity)) {
    *humidity = event.relative_humidity;
  } else {
    *humidity = -1;
  }
}

 

▲ 최종적으로 완성한 시계겸 온습도계 코드

이 코드를 보드에 업로드한다.

 

 

▲ 시계 겸 온습도계가 완성되었다.

화면이 작지만 선명하게 잘 출력된다.

 

 

▲ 오전이 되어서 보니 시간 표시할때 한자리로 나타나서

두자리로 표시하기 위해 코드를 약간 수정했다.

이부분은 시계를 보는 사람의 취향 차이이니

한자리로 보고 싶다면 위 소스에서

 

  if (now.hour() < 10){ thisTime="0"+ thisTime;} // add leading zero if required
  thisTime=thisTime + String(now.hour()) + ":";

 

이 부분을 아래처럼 수정해주면 된다.

 

  thisTime= String(now.hour()) + ":";

 

케이스를 만들고 다른 장치들을 좀더 추가할 예정인데

그 전까지는 일단 배터리로 외부전원을 넣어서 열린채로 사용예정~

Comments