아두이노 날씨 시계 – 실시간 날짜, 시간, 온도 및 습도 표시
부품 및 소모품
| 부품 |
수량 |
| 아두이노 우노 (Arduino UNO) |
1 |
| DHT11 온도 및 습도 센서 (4핀) |
1 |
| Maxim Integrated DS3231 RTC 모듈 |
1 |
| 저항 10k 옴 |
1 |
| Adafruit 128x64 OLED 그래픽 디스플레이 |
1 |
| 브레드보드 (일반) |
1 |
| 점퍼 와이어 (일반) |
15 |
프로젝트 소개
직접 시간, 날짜, 온도, 습도를 표시하는 인터페이스를 만들고 싶었던 적이 있나요? 생각보다 훨씬 간단합니다. 아래와 같은 몇 가지 핵심 부품만 있으면 됩니다.
아두이노 우노/나노
먼저 마이크로컨트롤러가 필요합니다. 이 프로젝트에서는 아두이노 우노를 사용했지만, 아두이노 나노도 핀 수가 충분하므로 사용할 수 있습니다.
DHT11
DHT11은 매우 저렴한 디지털 온도 및 습도 센서입니다. 정전식 습도 센서와 서미스터를 사용하여 주변 공기를 측정합니다. 측정 간격은 2초마다 한 번씩 읽는 것이 좋습니다.
중요: 시간을 한 번 설정한 후에는 해당 코드 줄을 주석 처리하고 다시 컴파일해야 합니다. 이렇게 하면 RTC가 시간을 계속 추적하며, 컴파일 시점의 시간으로 리셋되지 않습니다.
DS3231
DS3231은 일반적으로 사용되는 DS1307 모듈과 같은 RTC 모듈입니다. 가격이 저렴하면서도 DS1307보다 훨씬 정확합니다. 온도 보상 크리스탈 발진기(TCXO)와 크리스탈이 통합되어 있으며, 메인 전원이 차단되어도 배터리 입력을 통해 정확한 시간 유지가 가능합니다.
128x64 OLED 그래픽 디스플레이
OLED 화면에 데이터를 표시합니다. 이 화면 크기는 콤팩트하면서도 매우 밝습니다. Adafruit OLED 화면은 뒷면의 설정을 통해 SPI 또는 I2C 인터페이스로 제어할 수 있습니다. 더 저렴한 화면(이베이 등에도 많음)을 사용할 수도 있지만, Adafruit 라이브러리의 헤더 파일과 호환되는지 확인해야 합니다.
코드
프로젝트를 컴파일하는 코드입니다. 필요한 헤더 파일을 반드시 추가해야 합니다.
// hackster.io 사용자 여러분 안녕하세요!
// 날짜, 시간, 온도 및 습도 표시
///////////////////////// OLED 용 ////////////////////////
#include <SPI.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
// 소프트웨어 SPI 사용 시 (기본값):
#define OLED_MOSI 9 // MOSI aka SDA
#define OLED_CLK 10 // CLK aka SCL
#define OLED_DC 11
#define OLED_CS 12
#define OLED_RESET 13
Adafruit_SSD1306 display(OLED_MOSI, OLED_CLK, OLED_DC, OLED_RESET, OLED_CS);
#if (SSD1306_LCDHEIGHT != 64)
#error("Height incorrect, please fix Adafruit_SSD1306.h!");
#endif
/////////////////////////////////////////////////////////////
//////////////////////////// DHT 용 //////////////////////////
#include <dht.h>
dht DHT;
#define DHT11_PIN 3
int DHTtimer = 0;
/////////////////////////////////////////////////////////////
//////////////////////////// RTC (DS3231) 용 //////////////////////////
// SDA를 A4, SCL을 A5에 연결
#define DS3231_I2C_ADDRESS 0x68
// 일반 10진수를 BCD(이진화 10진수)로 변환
byte decToBcd(byte val) {
return( (val/10*16) + (val%10) );
}
// BCD를 일반 10진수로 변환
byte bcdToDec(byte val) {
return( (val/16*10) + (val%16) );
}
/////////////////////////////////////////////////////////////
void setup() {
Wire.begin();
Serial.begin(9600);
// 여기에 초기 시간 설정, 설정 후 이 섹션을 주석 처리하고 다시 컴파일
// DS3231 초, 분, 시, 요일, 날짜, 월, 년
// setDS3231time(00,24,12,6,3,2,17);
display.begin(SSD1306_SWITCHCAPVCC);
display.clearDisplay();
}
//////////////////////////// DS3231 코딩 //////////////////////////
void setDS3231time(byte second, byte minute, byte hour, byte dayOfWeek, byte dayOfMonth, byte month, byte year) {
Wire.beginTransmission(DS3231_I2C_ADDRESS);
Wire.write(0); // 다음 입력이 초 레지스터부터 시작하도록 설정
Wire.write(decToBcd(second));
Wire.write(decToBcd(minute));
Wire.write(decToBcd(hour));
Wire.write(decToBcd(dayOfWeek)); // 요일 (1=일요일, 7=토요일)
Wire.write(decToBcd(dayOfMonth)); // 날짜 (1~31)
Wire.write(decToBcd(month));
Wire.write(decToBcd(year)); // 년도 (0~99)
Wire.endTransmission();
}
void readDS3231time(byte *second, byte *minute, byte *hour, byte *dayOfWeek, byte *dayOfMonth, byte *month, byte *year) {
Wire.beginTransmission(DS3231_I2C_ADDRESS);
Wire.write(0); // DS3231 레지스터 포인터를 00h로 설정
Wire.endTransmission();
Wire.requestFrom(DS3231_I2C_ADDRESS, 7); // 00h 레지스터부터 7바이트 요청
*second = bcdToDec(Wire.read() & 0x7f);
*minute = bcdToDec(Wire.read());
*hour = bcdToDec(Wire.read() & 0x3f);
*dayOfWeek = bcdToDec(Wire.read());
*dayOfMonth = bcdToDec(Wire.read());
*month = bcdToDec(Wire.read());
*year = bcdToDec(Wire.read());
}
void displayTime() {
byte second, minute, hour, dayOfWeek, dayOfMonth, month, year;
readDS3231time(&second, &minute, &hour, &dayOfWeek, &dayOfMonth, &month, &year);
// 시리얼 모니터에 출력
Serial.print(hour, DEC);
Serial.print(":");
if (minute<10) Serial.print("0");
Serial.print(minute, DEC);
Serial.print(":");
if (second<10) Serial.print("0");
Serial.print(second, DEC);
Serial.print(" ");
Serial.print(dayOfMonth, DEC);
Serial.print("/");
Serial.print(month, DEC);
Serial.print("/");
Serial.print(year, DEC);
Serial.print(" Day of week: ");
//////////////////////////////////////////////////////////////////
// OLED에 날짜/시간 표시
display.setCursor(50,0);
display.print(dayOfMonth, DEC);
display.print(",");
display.setCursor(91,0);
display.print("20");
display.print(year, DEC);
if (hour<10) {
display.setCursor(40,10);
display.print("0");
display.print(hour, DEC);
display.print(":");
} else {
display.setCursor(40,10);
display.print(hour, DEC);
display.print(":");
}
if (minute<10) {
display.setCursor(58,10);
display.print("0");
display.print(minute, DEC);
display.print(":");
} else {
display.setCursor(58,10);
display.print(minute, DEC);
display.print(":");
}
if (second<10) {
display.setCursor(75,10);
display.print("0");
display.print(second, DEC);
} else {
display.setCursor(75,10);
display.print(second, DEC);
}
//////////////////////////////////////////////////////////////////
////////////////////////////// 요일 표시 /////////////////////////
switch(dayOfWeek){
case 1: Serial.println("Sunday"); display.setTextSize(1); display.setTextColor(WHITE); display.setCursor(20,0); display.print("Sun"); break;
case 2: Serial.println("Monday"); display.setTextSize(1); display.setTextColor(WHITE); display.setCursor(20,0); display.print("Mon"); break;
case 3: Serial.println("Tuesday"); display.setTextSize(1); display.setTextColor(WHITE); display.setCursor(20,0); display.print("Tue"); break;
case 4: Serial.println("Wednesday"); display.setTextSize(1); display.setTextColor(WHITE); display.setCursor(20,0); display.print("Wed"); break;
case 5: Serial.println("Thursday"); display.setTextSize(1); display.setTextColor(WHITE); display.setCursor(20,0); display.print("Thur"); break;
case 6: Serial.println("Friday"); display.setTextSize(1); display.setTextColor(WHITE); display.setCursor(20,0); display.print("Fri"); break;
case 7: Serial.println("Saturday"); display.setTextSize(1); display.setTextColor(WHITE); display.setCursor(20,0); display.print("Sat"); break;
}
////////////////////////////// 월 표시 /////////////////////////
switch(month) {
case 1: display.setTextSize(1); display.setTextColor(WHITE); display.setCursor(70,0); display.print("Jan"); break;
case 2: display.setTextSize(1); display.setTextColor(WHITE); display.setCursor(70,0); display.print("Feb"); break;
case 3: display.setTextSize(1); display.setTextColor(WHITE); display.setCursor(70,0); display.print("Mar"); break;
case 4: display.setTextSize(1); display.setTextColor(WHITE); display.setCursor(70,0); display.print("Apr"); break;
case 5: display.setTextSize(1); display.setTextColor(WHITE); display.setCursor(70,0); display.print("May"); break;
case 6: display.setTextSize(1); display.setTextColor(WHITE); display.setCursor(70,0); display.print("Jun"); break;
case 7: display.setTextSize(1); display.setTextColor(WHITE); display.setCursor(70,0); display.print("Jul"); break;
case 8: display.setTextSize(1); display.setTextColor(WHITE); display.setCursor(70,0); display.print("Aug"); break;
case 9: display.setTextSize(1); display.setTextColor(WHITE); display.setCursor(70,0); display.print("Sep"); break;
case 10: display.setTextSize(1); display.setTextColor(WHITE); display.setCursor(70,0); display.print("Oct"); break;
case 11: display.setTextSize(1); display.setTextColor(WHITE); display.setCursor(70,0); display.print("Nov"); break;
case 12: display.setTextSize(1); display.setTextColor(WHITE); display.setCursor(70,0); display.print("Dec"); break;
}
///////////// 시침 그리기 ///////////////
float x1, y1, a, b;
const float pi = 3.14;
a=((hour-15)*30);
b = (a*pi)/180;
x1=40+(9*cos(b));
y1=41+(9*sin(b));
display.drawLine(40,41, x1, y1, WHITE);
///////////// 분침 그리기 ///////////////
a=((minute-15)*6);
b = (a*pi)/180;
x1=40+(17*cos(b));
y1=41+(17*sin(b));
display.drawLine(40,41, x1, y1, WHITE);
///////////// 초침 그리기 ///////////////
a=((second-15)*6);
b = (a*pi)/180;
x1=40+(19*cos(b));
y1=41+(19*sin(b));
display.drawLine(40,41, x1, y1, WHITE);
//////////////// 움직이지 않는 아날로그 시계 부분 ////////////////
display.drawCircle(40, 41, 22, WHITE);
display.drawCircle(40, 41, 1, WHITE);
display.drawLine(40, 20, 40, 25, WHITE); // 12시
display.drawLine(40, 63, 40, 58, WHITE); // 6시
display.drawLine(62, 41, 57, 41, WHITE); // 3시
display.drawLine(19, 41, 24, 41, WHITE); // 9시
display.drawLine(50, 24, 47, 28, WHITE); // 1시
display.drawLine(57, 31, 53, 34, WHITE); // 2시
display.drawLine(60, 51, 54, 48, WHITE); // 4시
display.drawLine(51, 58, 48, 54, WHITE); // 5시
display.drawLine(29, 58, 32, 54, WHITE); // 7시
display.drawLine(21, 51, 25, 48, WHITE); // 8시
display.drawLine(22, 31, 27, 33, WHITE); // 10시
display.drawLine(30, 23, 32, 28, WHITE); // 11시
}
void ReadTempAndHum () {
int chk = DHT.read11(DHT11_PIN);
display.setTextSize(1);
display.setTextColor(WHITE);
display.setCursor(70,45);
display.print("Temp: ");
display.print(DHT.temperature, 0);
display.print("C");
display.setCursor(70,56);
display.print("Hum: ");
display.print(DHT.humidity, 0);
display.print("%");
Serial.print(" Humidity: " );
Serial.print(DHT.humidity, 1);
Serial.print("%");
Serial.print(" | Temperature: ");
Serial.print(DHT.temperature, 1);
Serial.println("C");
}
void loop() {
displayTime();
DHTtimer = DHTtimer + 1;
// DHT를 2초마다 한 번만 읽도록 카운터 사용
if (DHTtimer > 1) {
display.fillRect(70, 45, 128, 64, BLACK);
ReadTempAndHum ();
DHTtimer = 0;
}
delay(1000);
display.display();
// 시간과 날짜가 표시되는 화면 부분 지우기
display.fillRect(0, 0, 64, 64, BLACK);
display.fillRect(0, 0, 128, 20, BLACK);
}
회로도
참고: Fritzing에서 적절한 OLED 화면을 찾을 수 없었습니다. OLED의 핀아웃 연결에 대한 자세한 내용은 코드의 주석을 확인하세요.
- 브레드보드 다이어그램:
breadboard_diagram_QvhPKCpIo5.fzz
- 회로도:
schematic_XKU0jQoWbx.fzz