아두이노 나노 날씨 스테이션: 센서 키트 & OLED 디스플레이
부품 및 소모품
부품 수량
아두이노 나노 R3 1
Seeed Grove 기압계 센서 (BMP280) 1
DHT11 온습도 센서 (4핀) 1
0.96" OLED 128x64 디스플레이 모듈 (ElectroPeak) 1
GUVA-S12SD UV 센서 1
DS3231 RTC 모듈 1
필수 도구 및 장비
납땜 인두
무연 납땜 와이어
3D 프린터 (엔더 3 등)
앱 및 온라인 서비스
Arduino IDE
Autodesk Fusion 360 (케이스 설계)
프로젝트 소개
인터넷 없이 로컬에서 기상 데이터를 측정하고 확인할 수 있는 날씨 스테이션을 만들고 싶었습니다. 주변 기압, 온도, 습도, 자외선 지수(창가 설치 시)를 측정하고, 시간/날짜/요일도 표시하는 시계 화면도 포함했습니다.
1단계: 케이스 제작 (3D 프린팅)
Autodesk Fusion 360 으로 케이스 설계, 녹색 PLA로 출력. 서포트 필요, 20% 인필, 70mm/s 속도(엔더 3). STL 파일은 프로젝트에 첨부.
2단계: BMP280 (기압/온도, SPI 모드)
CS → D10
SDA(MOSI) → D11
SDO(MISO) → D12
SCK → D13
라이브러리: Adafruit_BMP280.h. bmp.readPressure()로 압력(Pa) 읽고 100으로 나누어 hPa 환산. bmp.readTemperature()로 온도. bmp.readAltitude(해수면기압)로 고도 계산(해수면 기압은 지역 평균값으로 수정).
3단계: DS3231 RTC (I2C)
라이브러리: DS3231.h. 최초 라이브러리 예제 DS3231_set.ino로 시간/날짜 설정. 메인 코드에서 clock.getHour(), getMinute(), getDate(), getMonth(), getYear(), getDoW()(요일) 호출. 요일은 switch-case로 한글/영문 변환.
4단계: DHT11 (습도)
라이브러리: DHT.h. dht.readHumidity()로 습도(%) 읽기.
5단계: GUVA-S12SD UV 센서
GaN 기반 쇼트키 포토다이오드. 240~370nm 파장 감지(UVB 및 대부분 UVA 커버). 보정된 아날로그 전압 출력 → 아두이노 ADC로 읽기.
float sensorValue = analogRead(A0);
float sensorVoltage = sensorValue / 1024.0 * 5.0;
int UV_index = sensorVoltage / 0.1; // 0.1V당 UV Index 1
6단계: OLED 디스플레이 (I2C, 128x64)
라이브러리: Adafruit_GFX.h, Adafruit_SSD1306.h. 비트맵 이미지(프레임 0,1,2)로 아이콘 표시. 4페이지(시간, 기압/고도, 온도/습도, UV 지수)를 5초 간격으로 순환 표시.
영상 클립
동영상으로 동작 확인 가능.
코드 (전체 스케치)
#include <Wire.h>
#include <SPI.h>
#include <Adafruit_BMP280.h>
#include <DS3231.h>
#include <DHT.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <Fonts/FreeSans9pt7b.h>
#include <Fonts/FreeMonoBoldOblique12pt7b.h>
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define OLED_RESET 4
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
#define BMP_SCK 13
#define BMP_MISO 12
#define BMP_MOSI 11
#define BMP_CS 10
DS3231 clock;
bool century = false, h12Flag, pmFlag;
Adafruit_BMP280 bmp(BMP_CS, BMP_MOSI, BMP_MISO, BMP_SCK);
#define DHTPIN 2
#define DHTTYPE DHT11
DHT dht(DHTPIN, DHTTYPE);
// 비트맵 데이터 (frame0, frame1, frame2) - 생략, 원본 참조
void setup() {
Serial.begin(57600);
if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
Serial.println(F("SSD1306 failed"));
}
Wire.begin();
dht.begin();
if (!bmp.begin()) {
Serial.println(F("Problem.bmp"));
while (1) delay(10);
}
display.clearDisplay();
display.setTextColor(WHITE);
display.setTextSize(1);
display.setCursor(0, 9);
display.setFont(&FreeSans9pt7b);
display.println("**** LOCAL ****");
display.setCursor(0, 38);
display.setFont(&FreeMonoBoldOblique12pt7b);
display.println("wather");
display.setCursor(27, 58);
display.println("Station");
display.display();
delay(2000);
bmp.setSampling(Adafruit_BMP280::MODE_NORMAL,
Adafruit_BMP280::SAMPLING_X2,
Adafruit_BMP280::SAMPLING_X16,
Adafruit_BMP280::FILTER_X16,
Adafruit_BMP280::STANDBY_MS_500);
}
void loop() {
// 1. 시간 화면
display.clearDisplay();
display.setTextColor(WHITE);
display.setTextSize(1);
display.setCursor(13, 15);
display.setFont(&FreeMonoBoldOblique12pt7b);
display.print(clock.getHour(h12Flag, pmFlag));
display.setCursor(38, 15); display.println(":");
display.setCursor(50, 15); display.println(clock.getMinute());
display.setCursor(70, 15); display.println(pmFlag ? " PM" : " AM");
display.setFont(&FreeSans9pt7b);
display.setCursor(20, 60); display.println(clock.getDate());
display.setCursor(40, 60); display.println("/");
display.setCursor(46, 60); display.println(clock.getMonth(century));
display.setCursor(65, 60); display.println("/");
display.setCursor(70, 60); display.println("20");
display.setCursor(90, 60); display.println(clock.getYear());
display.setCursor(30, 30);
switch (clock.getDoW()) {
case 1: display.println("Saturday"); break;
case 2: display.println("Sunday"); break;
case 3: display.println("Monday"); break;
case 4: display.println("Tuesday"); break;
case 5: display.println("Wednesday"); break;
case 6: display.println("Thursday"); break;
case 7: display.println("Friday"); break;
}
display.display(); delay(5000);
// 2. 기압/고도 화면
display.clearDisplay();
display.drawBitmap(0, 0, frame0, 40, 40, 1);
display.setFont(&FreeSans9pt7b);
display.setCursor(41, 28); display.println(bmp.readPressure() / 100);
display.setCursor(110, 28); display.setFont(); display.println("hPa");
display.setCursor(0, 55); display.setFont(&FreeSans9pt7b); display.println("Altitude:");
display.setCursor(65, 62); display.println(bmp.readAltitude(1005.47));
display.setCursor(113, 62); display.println("m");
display.display(); delay(5000);
// 3. 온도/습도 화면
display.clearDisplay();
display.setFont(&FreeMonoBoldOblique12pt7b);
display.drawBitmap(0, 5, frame1, 40, 51, 1);
display.setCursor(35, 30); display.print(bmp.readTemperature());
display.setFont(&FreeSans9pt7b);
display.setCursor(102, 28); display.println(" *");
display.setCursor(110, 31); display.println(" C");
display.setFont(); display.setCursor(66, 45); display.println("RH :");
byte RH = dht.readHumidity();
display.setCursor(95, 45); display.println(RH);
display.setCursor(110, 45); display.println("%");
display.drawBitmap(0, 56, frame2, 135, 15, 1);
display.display(); delay(5000);
// 4. UV 지수 화면
display.clearDisplay();
float sensorValue = analogRead(A0);
float sensorVoltage = sensorValue / 1024.0 * 5.0;
int UV_index = sensorVoltage / 0.1;
display.setFont(&FreeSans9pt7b);
display.setCursor(0, 15); display.print(" UV INDEX ");
display.setCursor(58, 45); display.println(UV_index);
display.display(); delay(5000);
}
커스텀 파트 및 인클로저
STL 파일 프로젝트에 포함.
회로도