|
|
민선생의 전자공방 ・ 2026. 5. 24. 22:25
브레드보드 활용 IO 인터페이스 4편
아두이노 + DHT11 온습도 센서 + OLED 대시보드
아두이노 + OLED + DHT11 브레드 보드 배선도, [도면작성 툴: Wokwi]
민선생의 전자공방
blog.naver.com/hansikmin
접기/펴기
아두이노, DHT11 센서
1장. DHT11 온습도 센서란?
1.1 스마트 센서의 등장
과거에는 온도와 습도를 따로 측정하고, 아날로그 신호를 디지털로 변환(ADC)하기 위해 복잡한 회로가 필요했습니다. 온도는 서미스터(NTC), 습도는 별도 센서, 여기에 OP-앰프와 ADC 회로까지 갖춰야 했습니다. 마치 3편 프롤로그에서 초음파 레이더를 만들기 위해 NE555, JRC082, LM324 등 수십 개의 부품을 사용해야 했던 것과 같은 상황입니다.
DHT11은 온도·습도 감지 소자, 아날로그-디지털 변환기(ADC), 그리고 통신 제어 칩이 하나로 통합된 '스마트 센서'입니다. 아두이노와는 단 1개의 디지털 핀으로 통신하며, 복잡한 회로 없이 온도와 습도를 동시에 디지털 값으로 얻을 수 있습니다.
1.2 DHT11 핵심 사양
| 항목 | 사양 | 비고 |
| 동작 전압 | 3.3V ~ 5.5V | 아두이노 5V 직접 연결 가능 |
| 동작 전류 | 0.3mA (측정 중 최대 2.5mA) | 초저전력 |
| 온도 측정 범위 | 0°C ~ 50°C | 오차 ±2°C |
| 습도 측정 범위 | 20% ~ 90% RH | 오차 ±5% RH |
| 통신 방식 | 단일 버스 (Single-Bus) | 디지털 핀 1개 |
| 측정 주기 | 최소 1초 (권장 2초) | 너무 빠르면 오류 |
| 데이터 출력 | 40비트 디지털 | 온도+습도+체크섬 |
| 패키지 | 4핀 or 3핀 모듈 | 모듈은 풀업저항 내장 |
| ⚠️ 민선생 실전 팁: 폴링(Polling) 주기 주의! |
| DHT11은 내부 MCU가 데이터를 처리하는 데 시간이 걸립니다. 최소 1초, 권장 2초 이상 간격으로 읽어야 정확한 값을 반환합니다. |
| 3편에서 배운 millis() 비블로킹 방식으로 2초마다 읽도록 구성하면 완벽합니다! |
1.3 DHT11 vs DHT22 비교
DHT11의 업그레이드 버전인 DHT22(AM2302)와 자주 비교됩니다. 라이브러리 사용법은 동일하고 정밀도와 범위만 다릅니다.
| 항목 | DHT11 (이 편 사용) | DHT22 (업그레이드) |
| 가격 | 약 500~1,000원 | 약 2,000~3,000원 |
| 온도 범위 | 0°C ~ 50°C | −40°C ~ 80°C |
| 온도 정밀도 | ±2°C | ±0.5°C |
| 습도 범위 | 20% ~ 90% RH | 0% ~ 100% RH |
| 습도 정밀도 | ±5% RH | ±2~5% RH |
| 측정 주기 | 1초 | 2초 |
| 권장 용도 | 실내 기본 측정 | 정밀/야외 측정 |
| 라이브러리 | DHT.h (동일) | DHT.h (동일) |
2장. DHT11의 물리적 동작 원리
2.1 내부 구조
DHT11의 파란색 플라스틱 케이스 내부에는 세 가지 핵심 요소가 들어있습니다. 습도 감지 소자(저항식 폴리머), 온도 감지 소자(NTC 서미스터), 그리고 이 두 값을 디지털로 변환하고 통신을 처리하는 8비트 마이크로컨트롤러(MCU)입니다.
AI 활용
DHT11 센서 모듈 구조 (습도 측정부 + 온도 측정부 + 내장 MCU) 와 아두이노 인터페이스
2.2 습도 측정 원리 — 저항식 폴리머
🧽 마법의 스펀지 비유로 이해하기
내부에는 수분을 흡수하는 얇은 폴리머(고분자 기판)가 있습니다. 전기가 통하는 두 전극 사이에 이 '스펀지'가 끼워져 있다고 생각해 보세요.
| 상태 | 원리 | 센서 반응 |
| 건조할 때 (습도 낮음) | 폴리머가 바짝 말라 전기를 잘 통과 못 시킴 | 저항 큼 → 습도 낮게 계산 |
| 습할 때 (습도 높음) | 공기 중 습기가 폴리머에 흡수되어 전기 잘 흐름 | 저항 작음 → 습도 높게 계산 |
내장 MCU는 이 저항 변화를 읽어 %RH(상대 습도) 수치로 변환합니다. 측정 가능한 범위는 20~90%RH이며, 결로(이슬점) 환경에서는 측정 오류가 발생할 수 있습니다.
2.3 온도 측정 원리 — NTC 서미스터
🏃 청개구리 저항과 복도 비유로 이해하기
NTC(Negative Temperature Coefficient) 서미스터는 온도가 올라갈수록 저항이 오히려 줄어드는 '청개구리' 같은 반도체 소자입니다.
| 상태 | 원리 (복도 비유) | 서미스터 반응 |
| 추울 때 | 사람들이 뭉쳐 복도를 막음 → 통행 어려움 | 저항 큼 → 전류 적게 흐름 |
| 더울 때 | 사람들이 사방으로 흩어져 빈 공간 생김 → 통행 쉬움 | 저항 작음 → 전류 많이 흐름 |
MCU는 서미스터의 저항값 변화를 읽어 온도(°C)로 변환합니다. DHT11의 측정 범위는 0~50°C입니다.
3장. 통신 원리와 아두이노 연결
3.1 단일 버스 통신 — 선 하나로 대화하기
이전 편의 OLED는 SDA, SCL 두 선을 쓰는 I2C 방식이었습니다. HC-SR04는 Trig, Echo 두 핀을 사용했습니다. DHT11은 DATA 핀 단 하나로 통신합니다. 이 방식은 무전기 통신과 같은 원리입니다.
| 📡 DHT11 통신 순서 (40비트 데이터 전송) |
① 아두이노 → DHT11: '센서야, 데이터 보내라!' (18ms LOW 신호, Start Signal) ② DHT11 → 아두이노: '네, 준비됐습니다!' (80μs LOW + 80μs HIGH, Response) ③ DHT11 → 아두이노: 40비트 데이터 전송 - 8비트: 습도 정수 (예: 55 = 55%RH) - 8비트: 습도 소수 (DHT11은 항상 0) - 8비트: 온도 정수 (예: 24 = 24°C) - 8비트: 온도 소수 (DHT11은 항상 0) - 8비트: 체크섬 (앞 4바이트 합계, 오류 검증) |
| ④ 아두이노: 체크섬 확인 → 데이터 유효성 검증 |
3.2 진짜 1-Wire 프로토콜과 DHT11의 차이
인터넷에서 DHT11을 흔히 '1-Wire 센서'라고 부르지만, 엄밀히 말하면 절반만 맞는 표현입니다. 이 차이를 정확히 알아두면 나중에 DS18B20 같은 센서를 쓸 때 혼동이 없습니다.
| 비교 항목 | 오리지널 1-Wire (Dallas) | DHT11 단일 버스 (Aosong) |
| 표준 여부 | 달라스 세미컨덕터 공식 규격 | 제조사 독자 규격 |
| 고유 주소 | 64비트 주소 (전 세계 유일) | 없음 |
| 병렬 연결 | 선 1개에 여러 센서 가능 | 1핀에 1센서만 가능 |
| 대표 소자 | DS18B20 방수 온도 센서 | DHT11, DHT22 |
| 라이브러리 | OneWire.h + DallasTemp | DHT.h |
| 아두이노 핀 | 1개로 다수 센서 | 센서마다 1핀 필요 |
| 📌 왜 아두이노에서 DHT11을 많이 쓸까? |
| 비록 오리지널 1-Wire는 아니지만, 선 한 가닥만 쓴다는 편리함과 전 세계 개발자들이 만들어 둔 DHT.h 라이브러리 덕분에 |
| 복잡한 통신 타이밍 없이 코드 한 줄로 쉽게 사용할 수 있기 때문입니다. |
3.3 핀 구성 및 연결 방법
시중에서 실습용으로 많이 쓰는 3핀 모듈을 기준으로 합니다.
| 모듈 핀 | 아두이노 핀 | 비고 |
| GND | GND | 접지 |
| VCC | 5V | 센서전원 |
| DATA (또는 S, OUT) | D2 | 데이터 통신선 (디지털 핀) |
| ⚠️ 풀업 저항 이야기 |
| 4핀 단독 센서(파란색 플라스틱 타입)는 DATA핀과 5V 사이에 10kΩ 풀업 저항이 필요합니다. 하지만 3핀 모듈(PCB 기판 포함)은 기판에 이미 풀업 저항이 납땜되어 있어 |
| 선만 꽂으면 바로 사용 가능합니다. 이 편에서는 3핀 모듈을 사용합니다. |
4장. 라이브러리 설치 및 회로 배선
4.1 DHT 라이브러리 설치
Servo.h(2편), Adafruit SSD1306(1편)처럼 DHT11도 전용 라이브러리가 필요합니다. 아두이노 IDE 라이브러리 매니저에서 설치합니다.
| 항목 | 내용 |
| 라이브러리 이름 | DHT sensor library |
| 제작사 | Adafruit Industries |
| 설치 방법 | 스케치 → 라이브러리 포함하기 → 라이브러리 관리 → 'DHT' 검색 |
| 의존 라이브러리 | Adafruit Unified Sensor (함께 설치 권장) |
| IDE 단축키 | Ctrl + Shift + I (Windows) |
라이브러리설치, 'DHT sensor library' 검색 → Adafruit 제작 선택
| 💡 설치 순서 |
| ① 라이브러리 매니저 열기 (Ctrl+Shift+I) ② 'DHT sensor library' 검색 → Adafruit 제작 선택 → 설치 ③ 'Install all' 팝업 → Install all 클릭 (Adafruit Unified Sensor 자동 설치) |
| ④ 설치 완료 후 스케치 상단에 #include <DHT.h> 추가 |
4.2 핀 연결표
| 부품 | 부품 핀 | 아두이노 핀 | 비고 |
| OLED | VCC | 5V | 1편 배선 그대로 |
| OLED | GND | GND | 1편 배선 그대로 |
| OLED | SDA | A4 | 1편 배선 그대로 |
| OLED | SCL | A5 | 1편 배선 그대로 |
| DHT11 | GND | GND | 접지 |
| DHT11 | VCC | 5V | 5V 전원 |
| DHT11 | DATA (S) | D2 | 데이터 통신 |
4.3 브레드보드 회로배선도
아두이노 + OLED + DHT11 브레드 보드 배선도
아두이노 + OLED + DHT11 브레드 보드 회로 배선
5장. 소스코드
5.1 동작 설명
DHT11로 2초마다 온도·습도를 측정하고 OLED에 대시보드 형태로 표시합니다.
| 항목 | 내용 |
| 온도 표시 | 큰 글자(TextSize 2)로 현재 온도(°C) 표시 |
| 습도 표시 | 큰 글자(TextSize 2)로 현재 습도(%RH) 표시 |
| 온도 바 그래프 | 0~50°C 범위의 수평 바 그래프 |
| 습도 바 그래프 | 20~90%RH 범위의 수평 바 그래프 |
| 상태 메시지 | 온도/습도 정상·주의·위험 상태 표시 |
| 측정 주기 | 2000ms마다 갱신 (millis() 비블로킹) |
| 오류 처리 | 센서 응답 없음 시 OLED에 오류 표시 |
5.2 OLED 화면 레이아웃
| Y 위치 | 크기 | 표시 내용 |
| y=0 | TextSize(1) | DHT11 SENSOR 타이틀 |
| y=10 | ───── | 가로 구분선 |
| y=14 | TextSize(1) | Temp: 레이블 |
| y=22 | TextSize(2) | 온도 숫자 (예: 24 C) — 큰 글자 |
| y=40 | 바 그래프 | 온도 비례 바 (0~50°C) |
| y=48 | TextSize(1) | Humi: 레이블 |
| y=56 | TextSize(1) | 습도 숫자 + 바 그래프 (20~90%RH) |
5.3 전체 소스코드
//================================================= // 민선생의 전자공방 - 브레드보드 IO 인터페이스 4편 // DHT11 온습도 센서 측정 + OLED 대시보드 표시 // 보드: Arduino Uno R3 // OLED: SSD1306 128x64 I2C (A4=SDA, A5=SCL, 0x3C) // DHT11: DATA=D2 // ================================================ // ── 라이브러리 포함 ──────────────────────────── #include <Wire.h> // I2C (OLED) #include <Adafruit_GFX.h> // OLED 그래픽 #include <Adafruit_SSD1306.h> // SSD1306 드라이버 #include <DHT.h> // DHT11/22 드라이버 // ── OLED 설정 ─────────────────────────────────── #define SCREEN_W 128 #define SCREEN_H 64 #define OLED_ADDR 0x3C #define OLED_RESET -1 // ── DHT11 설정 ────────────────────────────────── #define DHT_PIN 2 // DATA 핀 → D2 #define DHT_TYPE DHT11 // 센서 타입 (DHT22 사용 시 변경) // ── 표시 범위 설정 ────────────────────────────── #define TEMP_MIN 0 // 온도 바 그래프 최솟값 #define TEMP_MAX 50 // 온도 바 그래프 최댓값 #define HUMI_MIN 20 // 습도 바 그래프 최솟값 #define HUMI_MAX 90 // 습도 바 그래프 최댓값 #define READ_INTERVAL 2000 // 측정 주기 (2초) // DHT11 최소 1초, 권장 2초 // ── 객체 생성 ─────────────────────────────────── Adafruit_SSD1306 display(SCREEN_W, SCREEN_H, &Wire, OLED_RESET); DHT dht(DHT_PIN, DHT_TYPE); // DHT 객체 생성 // ── OLED 오류 표시 함수 ───────────────────────── void showError(String msg) { display.clearDisplay(); display.setTextSize(1); display.setTextColor(SSD1306_WHITE); display.setCursor(0, 0); display.print("DHT11 ERROR"); display.drawLine(0, 10, 127, 10, SSD1306_WHITE); display.setCursor(0, 20); display.print(msg); display.setCursor(0, 40); display.print("Check wiring!"); display.display(); } // ── 상태 텍스트 반환 함수 ─────────────────────── // 온도 상태 판단 String getTempStatus(float t) { if (t < 10) return "COLD"; else if (t <= 28) return "NORMAL"; else if (t <= 35) return "WARM"; else return "HOT!"; } // 습도 상태 판단 String getHumiStatus(float h) { if (h < 30) return "DRY"; else if (h <= 60) return "NORMAL"; else if (h <= 80) return "HUMID"; else return "WET!"; } // ── OLED 대시보드 표시 함수 ───────────────────── void showDashboard(float temp, float humi) { display.clearDisplay(); display.setTextColor(SSD1306_WHITE); // ── 타이틀 ──────────────────────────────────── display.setTextSize(1); display.setCursor(0, 0); display.print("DHT11 SENSOR"); display.drawLine(0, 10, 127, 10, SSD1306_WHITE); // ── 온도 섹션 ───────────────────────────────── display.setCursor(0, 13); display.print("Temp:"); // 온도 큰 글자 display.setTextSize(2); display.setCursor(36, 11); display.print((int)temp); display.print(" C"); // 온도 상태 display.setTextSize(1); display.setCursor(98, 13); display.print(getTempStatus(temp)); // 온도 바 그래프 display.drawRect(0, 26, 128, 5, SSD1306_WHITE); int tBar = map((int)temp, TEMP_MIN, TEMP_MAX, 0, 126); tBar = constrain(tBar, 0, 126); display.fillRect(1, 27, tBar, 3, SSD1306_WHITE); // ── 구분선 ──────────────────────────────────── display.drawLine(0, 33, 127, 33, SSD1306_WHITE); // ── 습도 섹션 ───────────────────────────────── display.setCursor(0, 36); display.print("Humi:"); // 습도 큰 글자 display.setTextSize(2); display.setCursor(36, 34); display.print((int)humi); display.print(" %"); // 습도 상태 display.setTextSize(1); display.setCursor(98, 36); display.print(getHumiStatus(humi)); // 습도 바 그래프 display.drawRect(0, 49, 128, 5, SSD1306_WHITE); int hBar = map((int)humi, HUMI_MIN, HUMI_MAX, 0, 126); hBar = constrain(hBar, 0, 126); display.fillRect(1, 50, hBar, 3, SSD1306_WHITE); // ── 하단 스케일 ─────────────────────────────── display.setCursor(0, 57); display.print("T:0-50C H:20-90%"); display.display(); // 화면 출력 } void setup() { Serial.begin(9600); // DHT11 초기화 dht.begin(); // OLED 초기화 if (!display.begin(SSD1306_SWITCHCAPVCC, OLED_ADDR)) { while(1); } display.clearDisplay(); display.setTextSize(1); display.setTextColor(SSD1306_WHITE); display.setCursor(10, 28); display.print("DHT11 Initializing"); display.display(); delay(2000); // DHT11 첫 측정 안정화 대기 (필수!) } unsigned long prevTime = 0; // 마지막 측정 시각 void loop() { unsigned long now = millis(); // READ_INTERVAL(2000ms)마다 측정 // delay() 대신 millis() 비블로킹 방식 if (now - prevTime >= READ_INTERVAL) { prevTime = now; // ── DHT11 데이터 읽기 ───────────────────── float humidity = dht.readHumidity(); // 습도(%RH) float temperature = dht.readTemperature(); // 온도(°C) // 화씨 읽기: dht.readTemperature(true) // ── 오류 확인 ───────────────────────────── // isnan(): 'Not a Number' 확인 // 센서 응답 없거나 배선 오류 시 NaN 반환 if (isnan(humidity) || isnan(temperature)) { Serial.println("DHT read failed!"); showError("Sensor NaN Error"); return; // 아래 코드 건너뜀 } // ── 정상값 처리 ─────────────────────────── showDashboard(temperature, humidity); // 시리얼 모니터 출력 (디버그용) Serial.print("Temp: "); Serial.print(temperature,1); Serial.print(" C "); Serial.print("Humi: "); Serial.print(humidity,1); Serial.println(" %"); } }
6장. 핵심 코드 해설
6.1 DHT.h 라이브러리 주요 함수
| 함수 | 설명 |
| DHT dht(pin, type); | DHT 객체 선언. pin=데이터핀, type=DHT11 or DHT22 |
| dht.begin(); | DHT 센서 초기화 (setup()에서 한 번만) |
| dht.readTemperature(); | 온도 읽기 (°C 반환, 화씨는 true 파라미터) |
| dht.readHumidity(); | 습도 읽기 (%RH 반환) |
| isnan(value); | NaN(오류값) 확인. true=오류, false=정상 |
| dht.computeHeatIndex(t,h); | 체감온도 계산 (온도+습도 조합) |
6.2 isnan() 오류 처리의 중요성
DHT11 통신 실패 시 readTemperature(), readHumidity()는 NaN(Not a Number) 을 반환합니다. NaN을 그냥 OLED에 출력하거나 계산에 사용하면 이상한 값이 표시됩니다. isnan()으로 반드시 확인해야 합니다.
NaN은 숫자처럼 생겼지만 숫자가 아닌 IEEE 754 부동소수점 표준의 특수값입니다. "정의할 수 없는 연산 결과"를 표현하기 위해 설계되었으며, Adafruit DHT 라이브러리는 센서 오류 시 이 값을 반환하도록 영리하게 활용하고 있습니다.
NaN에는 중요한 특성이 있습니다. 어떤 값과 비교해도 항상 false를 반환합니다 — 자기 자신과도 마찬가지입니다. 이것은 버그가 아니라 IEEE 754 규약에 명시된 의도적인 설계입니다.
float val = NAN;
val > 100 // → false
val < 0 // → false
val == NAN // → false (자기 자신과도 같지 않음!)
따라서 일반적인 범위 체크(if (t > 0 && t < 50))로는 NaN을 걸러낼 수 없고, isnan()만이 NaN을 확실히 감지할 수 있습니다.
// ❌ 범위 체크로는 NaN을 잡을 수 없음
if (temperature > 0 && temperature < 50) { ... } // NaN도 통과해버림!
// ✅ 올바른 처리 방법
if (isnan(humidity) || isnan(temperature)) {
showError("Sensor NaN Error");
return;
}
// 여기서부터 정상값 처리
6.3 millis() 비블로킹 + DHT11 조합
DHT11은 반드시 2초 이상 간격으로 읽어야 합니다. delay(2000)을 쓰면 2초 동안 아무것도 못 합니다. millis() 방식이면 2초 대기 중에도 버튼 입력, LED 제어 등 다른 작업이 가능합니다.
| // ❌ delay 방식 (2초 동안 멈춤) |
| // float t = dht.readTemperature(); // delay(2000); // ✅ millis() 방식 (2초 대기 중 다른 작업 가능) if (now - prevTime >= READ_INTERVAL) { // 2초 경과 시만 실행 prevTime = now; float t = dht.readTemperature(); // 나머지 측정 코드 } |
| // loop()가 계속 돌며 다른 처리 가능 |
| 📌 이 구조는 시리즈 전체에서 일관되게 사용됩니다. |
| 5편 화염감지 + 4편 DHT11 + 3편 초음파를 동시에 처리할 때 |
| 각각 다른 주기로 millis() 비블로킹 방식을 쓰면 완벽하게 동작합니다. |
접기/펴기
아두이노, DHT11 센서
다음 과제 예고 — 5편
다음 5편에서는 keyes MQ-135 대기질/가스센서 모듈을 활용해 대기질/ 가스공기질을 측정하고 OLED에 표시하는 프로젝트를 진행합니다.
KEYES MQ-135 대기질/가스 센서 동작 원리
MQ 계열 센서의 아날로그 출력 원리
analogRead()로 가스 농도 읽기
OLED 대기질 실시간 표시 + 경보 기능
4편(DHT11)과 5편(MQ-135) 동시 연결 — 온습도+대기질 통합 대시보드
태그#아두이노#DHT11#온습도센서#OLED#SSD1306#브레드보드#아두이노프로젝트#아두이노센서#스마트센서#단일버스통신#DHT11라이브러리#Adafruit#I2C통신#아두이노OLED#온도측정#습도측정#NTC서미스터#아두이노대시보드#millis비블로킹#isnan#아두이노코딩#전자공작#브레드보드실습#아두이노입문#피지컬컴퓨팅#민선생의전자공방#전기전자기초#아두이노프로그래밍#센서인터페이스#IOT입문 태그수정
