|
|
다양한 오염 가스 동시 감지: 암모니아(NH₃), 질소산화물(NOₓ), 알코올, 벤젠, 연기, 이산화탄소(CO₂) 등 여러 오염 물질에 동시에 반응합니다.
복합 가스 센서: 종합적인 공기 오염도를 나타내는 센서입니다. 단독으로는 현재 감지된 가스가 암모니아인지 연기인지 정확히 구분할 수 없습니다.
이중 신호 출력: 농도를 정밀하게 측정하는 아날로그 출력(AO)과 임계값 이상 시 알림을 주는 디지털 출력(DO)을 모두 제공합니다.
간편한 연결: 아두이노와 4가닥의 선으로 바로 연결할 수 있는 4핀 헤더 구조입니다.
| 센서 | 특징 | 적합한 용도 |
| MQ-135 | 종합적 실내 공기질 모니터링 (복합 가스 감지) | 실내 환기 알림, 오염도 측정 |
| NDIR CO₂ 센서 | 이산화탄소만 정밀 측정 | 스마트 농업, 강의실 CO₂ 관리 |
| MQ-7 | 일산화탄소(CO) 전용 감지 | 주차장, 보일러실 CO 경보 |
| MQ-2 | 가연성 가스(LPG, 프로판) 감지 | 주방 가스 누출 경보 |
2장. I/O 구성 및 주요 사양
▶ 2.1 핀 구성 (Pinout)
Keyes MQ-135 모듈은 4개의 핀으로 구성됩니다.
| 핀 번호 | 핀 이름 | 기능 설명 | 아두이노 연결 |
| 1 | VCC | 전원 입력 (+5V) | 5V 핀 |
| 2 | GND | 접지 (0V) | GND 핀 |
| 3 | DO | 디지털 출력 — 가스 농도 ≥ 임계값 → LOW (가스 감지) | 디지털 핀 (예: D7) |
| 4 | AO | 아날로그 출력 — 가스 농도 높을수록 높은 전압 (0~4.2V) | 아날로그 핀 (예: A1) |
▶ 2.2 주요 사양
| 항목 | 사양 |
| 동작 전압 | DC 5V |
| 작동 전류 | 약 150mA (히터 내장으로 다른 센서 대비 전류 소모 큼) |
| 출력 신호 | 이중 출력 (아날로그: 0~4.2V / 디지털: TTL High/Low) |
| 감지 가스 | 암모니아, 질소산화물, 알코올, 벤젠, 연기, CO₂ 등 |
| 감지 농도 범위 | 10 ~ 1000 ppm |
| 예열 시간 | 최소 60초 이상 (정확한 측정을 위해 수 시간 권장) |
| 작동 온도/습도 | -10~50°C / 95% RH 이하 |
| ⚠️ 150mA 전류 소모 주의! MQ-135는 내부 히터 코일 때문에 약 150mA의 전류를 소모합니다. DHT11(0.3mA), OLED(20mA)와 비교하면 매우 큰 값입니다. 아두이노 5V 핀 최대 공급 전류(500mA)를 고려하여, USB 허브가 아닌 PC 직결 또는 어댑터 전원 사용을 권장합니다. 총 소모 전류: MQ-135(150mA) + OLED(20mA) + DHT11(3mA) + Arduino(50mA) ≈ 223mA |
3장. 동작 원리와 내부 구조
▶ 3.1 동작 원리 — 금속 산화물 반도체 (MOS)
MQ-135 센서는 SnO₂(이산화주석) 금속 산화물 반도체 기술을 기반으로 작동합니다. 4편의 NTC 서미스터, 습도 폴리머와 같이 '저항 변화'를 측정하는 방식입니다.
| 단계 | 상태 | 내부 원리 | AO 출력 |
| 1 | 깨끗한 공기 | SnO₂ 표면의 산소 이온이 전자를 포착 → 저항 높음 | 낮은 전압 (~0.4V) |
| 2 | 오염 가스 감지 | 가스 분자가 산소 이온과 반응 → 전자 방출 → 저항 감소 | 전압 상승 |
| 3 | 고농도 오염 | 더 많은 전자 방출 → 저항 더욱 감소 → 전압 최대 | 높은 전압 (~4.2V) |
▶ 3.2 히터 코일의 역할
MQ-135 센서 소자 내부에는 히터 코일이 있습니다. 이것은 센서의 핵심 구성 요소입니다.
작동 온도 유지: SnO₂ 반도체가 가스와 반응하려면 약 200~400°C의 고온이 필요합니다. 히터 코일이 이 온도를 지속적으로 유지합니다.
예열 시간 필요: 전원 투입 후 히터가 작동 온도에 도달하기까지 최소 60초가 필요합니다. 이 시간 전에는 측정값을 신뢰하기 어렵습니다.
전류 소모 원인: 히터 코일이 약 150mA 전류 소모의 주된 원인입니다. 다른 센서 대비 전류 소모가 큰 이유가 바로 이 히터 때문입니다.
▶ 3.3 분압 회로와 아날로그 출력
모듈 내부의 부하 저항(RL)과 센서 저항(Rs)이 분압 회로를 구성합니다.
| 📐 분압 출력 공식 Vout = Vcc × RL / (Rs + RL) • Rs(센서 저항): 가스 농도가 높을수록 작아짐 • RL(부하 저항): 모듈 내부 고정값 (약 1kΩ) • 가스 많음 → Rs 작아짐 → Vout 커짐 → analogRead() 값 커짐 • 이것이 AO 핀 전압이 오염도에 비례하는 이유입니다. |
▶ 3.4 디지털 출력(DO)과 포텐셔미터
모듈의 LM393 비교기가 AO 전압과 포텐셔미터 설정값을 비교하여 DO 핀을 제어합니다.
포텐셔미터 값 > AO 전압 → DO = HIGH (가스 농도 낮음, 정상)
포텐셔미터 값 < AO 전압 → DO = LOW (가스 농도 높음, 경보 발령)
조절 방법: 드라이버로 센서의 파랑색 포텐셔미터를 돌려 경보 임계값을 설정합니다. 시계방향으로 돌리면 임계값이 올라가 민감도가 낮아지고, 반시계방향으로 돌리면 민감도가 높아집니다.
4장. 활용상 주의사항 ⚠️
| 🚫 반드시 금지해야 할 사항 — 센서 영구 손상 유발 ① 유기 실리콘 증기 노출 금지: 실리콘 접착제·실리콘 고무·화장품 등 → 감지 물질 표면이 코팅되어 감도를 영구적으로 상실합니다. ② 고농도 부식성 가스 노출 금지: H₂S, Cl₂, HCl 등 → 센서 구조 부식 및 성능 저하 ③ 물에 젖음 금지: 센서 절대 침수 금지 |
| 💡 정확한 측정을 위한 필수 주의사항 ① 예열 시간 준수: 전원 인가 후 최소 60초, 정확한 보정(Calibration)은 수 시간 권장 ② 온습도 영향 보정: MQ-135 감도는 온도·습도에 영향받음 → 4편 DHT11과 함께 사용하면 더 정확한 측정 가능함 ③ 기준 공기(Clean Air) 보정: Ro(기준 저항) 보정으로 PPM 정확도 향상 ④ 상대적 수치로 활용: 교정 없이는 절대값보다 '나쁨/좋음' 상대 비교에 적합 |
5장. 브레드보드 회로 배선
▶ 5.1 부품 목록
| 부품 | 수량 | 비고 |
| 아두이노 Uno R3 | 1 | 또는 호환 보드 |
| OLED 128x64 I2C (SSD1306) | 1 | 1편부터 계속 사용 |
| DHT11 센서 모듈 (3핀) | 1 | 4편부터 계속 사용 |
| MQ-135 가스 센서 모듈 (Keyes) | 1 | 이번 편 신규 추가 |
| 브레드보드 | 1 | |
▶ 5.2 핀 연결표
| 부품 | 부품 핀 | 아두이노 핀 | 비고 |
| OLED | VCC | 5V | 1편 배선 그대로 |
| OLED | GND | GND | 1편 배선 그대로 |
| OLED | SDA | A4 | 1편 배선 그대로 |
| OLED | SCL | A5 | 1편 배선 그대로 |
| DHT11 | GND | GND | 4편 배선 그대로 |
| DHT11 | VCC | 5V | 4편 배선 그대로 |
| DHT11 | DATA (S) | D2 | 4편 배선 그대로 |
| MQ-135 | VCC | 5V | 신규 연결 |
| MQ-135 | GND | GND | 신규 연결 |
| MQ-135 | DO | D7 | 디지털 경보 출력 (신규) |
| MQ-135 | AO | A1 | 아날로그 농도 출력 (신규) |
| 📌 민선생 배선 팁 — 브레드보드 전원 레일 활용 부품이 3개로 늘어나면서 5V·GND 연결이 복잡해집니다. 브레드보드 양쪽 전원 레일(빨간선=5V, 검은선=GND)을 적극 활용하세요. 아두이노 5V → 레일 빨간선 / GND → 레일 파란선 연결 후 OLED, DHT11, MQ-135 VCC·GND를 모두 해당 레일에서 연결하면 깔끔합니다. |
브레드보드 배선도 (Wokwi 작성) — OLED + DHT11 + MQ-135 통합 배선
브레드보드 실제 배선
6장. 라이브러리 설치
MQ-135 센서는 별도의 외부 라이브러리 없이 아두이노 기본 함수 analogRead()만으로 동작합니다. 4편에서 설치한 DHT.h 라이브러리와 Adafruit SSD1306 라이브러리가 이미 설치되어 있으면 추가 설치 없이 바로 진행 가능합니다.
| 라이브러리 | 용도 | 설치 상태 |
| Wire.h | I2C 통신 (OLED) | 아두이노 기본 내장 |
| Adafruit_GFX.h | OLED 그래픽 | 1편에서 설치 완료 |
| Adafruit_SSD1306.h | SSD1306 드라이버 | 1편에서 설치 완료 |
| DHT.h | DHT11 통신 | 4편에서 설치 완료 |
| | MQ-135 (analogRead 사용) | 추가 설치 불필요 ✅ |
7장. 소스코드
▶ 7.1 동작 설명
| 항목 | 내용 |
| 온도/습도 표시 | DHT11로 2초마다 측정, OLED 상단에 표시 |
| 공기질 표시 | MQ-135 AO로 500ms마다 측정, OLED 하단에 바 그래프+상태 표시 |
| 공기질 단계 | GOOD / FAIR / POOR / DANGER 4단계 판정 |
| 경보 기능 | MQ-135 DO 핀 LOW 감지 시 OLED 전체 반전(경보 화면) 표시 |
| millis() 비블로킹 | DHT11(2초), MQ-135(500ms) 각각 독립 주기로 동작 |
| 예열 안내 | 부팅 시 OLED에 60초 카운트다운 예열 화면 표시 |
▶ 7.2 OLED 화면 레이아웃
| Y 위치 | 크기 | 표시 내용 |
| y=0 | TextSize(1) | AIR QUALITY MONITOR 타이틀 |
| y=10 | ───── | 가로 구분선 |
| y=13~30 | TextSize(1)+2 | 온도(Temp: XX°C) + 습도(Humi: XX%) |
| y=33 | ───── | 가로 구분선 |
| y=36~46 | TextSize(1) | AQI: XXXX 수치 + 상태(GOOD/FAIR/POOR/DANGER) |
| y=49~56 | 바 그래프 | 공기질 바 그래프 (0~1023 범위) |
| y=57 | TextSize(1) | 하단 스케일 안내 |
=============================================================
AQI(Air Quality Index, 대기질 지수)
AQI(대기질 지수)란 현재 공기의 오염 정도를 숫자로 나타낸 지표입니다. 미세먼지(PM2.5, PM10), 오존(O₃), 이산화질소(NO₂) 등의 대기오염 물질 농도를 종합하여 계산하며, 숫자가 높을수록 공기 상태가 나쁘고 건강에 미치는 영향도 커집니다. 일반적으로 초록색은 좋음, 노란색은 보통, 빨간색은 나쁨 상태를 의미합니다.
=============================================================
▶ 7.3 전체 소스코드
| //================================================ // 민선생의 전자공방 - 브레드보드 IO 인터페이스 5편 // MQ-135 공기질 + DHT11 온습도 + OLED 통합 대시보드 // 보드: Arduino Uno R3 // OLED: SSD1306 128x64 I2C (A4=SDA, A5=SCL, 0x3C) // DHT11: DATA=D2 // MQ-135: AO=A1, DO=D7 //================================================ // ── 라이브러리 포함 ───────────────────────────── #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 // ── MQ-135 설정 ────────────────────────────────── #define MQ135_AO_PIN A1 // 아날로그 출력 #define MQ135_DO_PIN 7 // 디지털 경보 출력 // ── 공기질 단계 임계값 (analogRead 기준, 0~1023) ─ // ※ 이 값은 예열 후 실제 환경에서 보정하세요 #define AQI_GOOD 200 // 0~200: 좋음 #define AQI_FAIR 400 // 201~400: 보통 #define AQI_POOR 600 // 401~600: 나쁨 // 601~: 위험 // ── 측정 주기 설정 ─────────────────────────────── #define DHT_INTERVAL 2000 // DHT11: 2초마다 #define MQ_INTERVAL 500 // MQ-135: 0.5초마다 #define WARMUP_SEC 60 // 예열 시간(초) // ── 객체 생성 ───────────────────────────────────── Adafruit_SSD1306 display(SCREEN_W, SCREEN_H, &Wire, OLED_RESET); DHT dht(DHT_PIN, DHT_TYPE); // ── 전역 변수 ───────────────────────────────────── unsigned long prevDHT = 0; // DHT11 마지막 측정 시각 unsigned long prevMQ = 0; // MQ-135 마지막 측정 시각 float lastTemp = 0; float lastHumi = 0; int lastAQI = 0; // ── 공기질 단계 문자열 반환 ────────────────────── String getAQIStatus(int val) { if (val <= AQI_GOOD) return "GOOD"; else if (val <= AQI_FAIR) return "FAIR"; else if (val <= AQI_POOR) return "POOR"; else return "DANGER"; } // ── 온도 상태 문자열 반환 ──────────────────────── String getTempStatus(float t) { if (t < 10) return "COLD"; else if (t <= 28) return "NORMAL"; else if (t <= 35) return "WARM"; else return "HOT!"; } // ── 예열 카운트다운 화면 ───────────────────────── void showWarmup(int remaining) { display.clearDisplay(); display.setTextColor(SSD1306_WHITE); display.setTextSize(1); display.setCursor(20, 0); display.print("MQ-135 WARM-UP"); display.drawLine(0, 10, 127, 10, SSD1306_WHITE); display.setCursor(10, 18); display.print("Heating sensor..."); display.setTextSize(3); display.setCursor(44, 32); display.print(remaining); display.setTextSize(1); display.setCursor(20, 56); display.print("sec remaining"); display.display(); } // ── 경보 화면 (반전 표시) ───────────────────────── void showAlarm(int aqiVal) { display.invertDisplay(true); // 화면 반전! display.clearDisplay(); display.setTextColor(SSD1306_WHITE); display.setTextSize(2); display.setCursor(15, 10); display.print("!! ALARM !!"); display.setTextSize(1); display.setCursor(10, 35); display.print("Air Quality DANGER"); display.setCursor(25, 48); display.print("AQI: "); display.print(aqiVal); display.display(); delay(1500); // 1.5초 경보 표시 display.invertDisplay(false); // 반전 해제 } // ── 통합 대시보드 표시 ─────────────────────────── void showDashboard(float temp, float humi, int aqi) { display.clearDisplay(); display.setTextColor(SSD1306_WHITE); // ── 타이틀 ────────────────────────────────── display.setTextSize(1); display.setCursor(5, 0); display.print("AIR QUALITY MONITOR"); display.drawLine(0, 10, 127, 10, SSD1306_WHITE); // ── 온도 표시 ──────────────────────────────── display.setCursor(0, 13); display.print("T:"); display.setTextSize(1); // 온도가 유효한 경우만 출력 if (!isnan(temp)) { display.setCursor(12, 13); display.print((int)temp); display.print((char)247); // ° 기호 display.print("C"); display.setCursor(52, 13); display.print(getTempStatus(temp)); } else { display.setCursor(12, 13); display.print("--"); } // ── 습도 표시 ──────────────────────────────── display.setCursor(80, 13); display.print("H:"); if (!isnan(humi)) { display.setCursor(92, 13); display.print((int)humi); display.print("%"); } else { display.setCursor(92, 13); display.print("--"); } display.drawLine(0, 23, 127, 23, SSD1306_WHITE); // ── 공기질 수치 표시 ───────────────────────── String status = getAQIStatus(aqi); display.setCursor(0, 27); display.print("AQI:"); display.setTextSize(2); display.setCursor(28, 24); display.print(aqi); display.setTextSize(1); display.setCursor(80, 27); display.print("["); display.print(status); display.print("]"); // ── 공기질 바 그래프 ────────────────────────── display.drawRect(0, 38, 128, 7, SSD1306_WHITE); int aqiBar = map(aqi, 0, 1023, 0, 126); aqiBar = constrain(aqiBar, 0, 126); display.fillRect(1, 39, aqiBar, 5, SSD1306_WHITE); // ── 단계 경계 표시선 (바 그래프 위) ───────── // GOOD/FAIR 경계: AQI_GOOD=200 → map(200,0,1023,0,126)≈25 display.drawFastVLine(25, 37, 9, SSD1306_WHITE); // FAIR/POOR 경계: AQI_FAIR=400 → ≈50 display.drawFastVLine(50, 37, 9, SSD1306_WHITE); // POOR/DANGER 경계: AQI_POOR=600 → ≈74 display.drawFastVLine(74, 37, 9, SSD1306_WHITE); // ── 하단 레이블 ───────────────────────────── display.setCursor(0, 48); display.print("G"); display.setCursor(22, 48); display.print("F"); display.setCursor(47, 48); display.print("P"); display.setCursor(72, 48); display.print("D"); // ── 하단 정보 ──────────────────────────────── display.setCursor(0, 57); display.print("G<200 F<400 P<600 D:600+"); display.display(); } // ───────────────────────────────────────────────── void setup() { Serial.begin(9600); pinMode(MQ135_DO_PIN, INPUT); // DO 핀 입력 설정 // DHT11 초기화 dht.begin(); // OLED 초기화 if (!display.begin(SSD1306_SWITCHCAPVCC, OLED_ADDR)) { while(1); // OLED 연결 실패 시 정지 } display.clearDisplay(); // MQ-135 예열 카운트다운 (60초) // ※ 이미 수분간 켜둔 상태라면 이 루프를 줄여도 됩니다 for (int i = WARMUP_SEC; i > 0; i--) { showWarmup(i); delay(1000); } // 예열 완료 메시지 display.clearDisplay(); display.setTextSize(1); display.setTextColor(SSD1306_WHITE); display.setCursor(20, 25); display.print("Warm-up Complete!"); display.setCursor(15, 40); display.print("Starting monitor..."); display.display(); delay(2000); } // ───────────────────────────────────────────────── void loop() { unsigned long now = millis(); // ── DHT11: 2초마다 측정 ───────────────────── if (now - prevDHT >= DHT_INTERVAL) { prevDHT = now; lastHumi = dht.readHumidity(); lastTemp = dht.readTemperature(); if (isnan(lastHumi) || isnan(lastTemp)) { Serial.println("DHT read failed!"); } else { Serial.print("Temp:"); Serial.print(lastTemp,1); Serial.print("C Humi:"); Serial.print(lastHumi,1); Serial.println("%"); } } // ── MQ-135: 0.5초마다 측정 ────────────────── if (now - prevMQ >= MQ_INTERVAL) { prevMQ = now; lastAQI = analogRead(MQ135_AO_PIN); // 0~1023 // DO 핀 경보 감지 (LOW = 경보) if (digitalRead(MQ135_DO_PIN) == LOW) { showAlarm(lastAQI); // 경보 화면 표시 } Serial.print("AQI:"); Serial.print(lastAQI); Serial.print(" ["); Serial.print(getAQIStatus(lastAQI)); Serial.println("]"); } // ── 대시보드 갱신 (MQ-135 주기마다) ───────── if (now - prevMQ < 10) { // MQ 측정 직후 화면 갱신 showDashboard(lastTemp, lastHumi, lastAQI); } } |
8장. 핵심 코드 해설
▶ 8.1 analogRead()와 공기질 수치 매핑
MQ-135의 AO 핀은 0~4.2V 아날로그 전압을 출력합니다. 아두이노 analogRead()는 이를 0~1023의 정수로 변환합니다. 가변저항으로 동작의 임계값을 조정할 수 있습니다.
| 아날로그 전압 | analogRead() 값 | 의미 |
| 0V (≈0.4V 최소) | ~80 | 매우 깨끗한 공기 |
| 1.2V | ~245 | 양호 |
| 2.5V | ~512 | 보통 |
| 4.2V (최대) | ~860 | 매우 오염 |
| 📌 임계값 보정 방법 코드의 AQI_GOOD, AQI_FAIR, AQI_POOR 값은 환경마다 다릅니다. 1단계: 신선한 실외 공기에서 Serial Monitor 값 확인 → 이 값이 'GOOD' 기준선 2단계: 알코올 솜을 가까이 대면서 값이 올라가는 것을 확인 3단계: 각 환경에 맞게 임계값을 수정하여 사용하세요. |
▶ 8.2 invertDisplay()를 활용한 경보 효과
display.invertDisplay(true)는 OLED 픽셀을 전부 반전시킵니다. 별도 LED나 부저 없이 시각적 경보 효과를 내려 합니다.
| void showAlarm(int aqiVal) { display.invertDisplay(true); // 화면 전체 반전 (경보 효과) // ... 경보 텍스트 출력 ... display.display(); delay(1500); display.invertDisplay(false); // 반전 해제 } |
▶ 8.3 두 센서의 독립적 millis() 비블로킹
DHT11(2초)과 MQ-135(0.5초)가 각자 다른 주기로 동작합니다. 각자 독립적인 타이머 변수를 사용하기 때문에 서로 간섭하지 않습니다.
| unsigned long prevDHT = 0; // DHT11 전용 타이머 unsigned long prevMQ = 0; // MQ-135 전용 타이머 void loop() { unsigned long now = millis(); if (now - prevDHT >= 2000) { // 2초마다 DHT 측정 prevDHT = now; // DHT 코드... } if (now - prevMQ >= 500) { // 0.5초마다 MQ 측정 prevMQ = now; // MQ-135 코드... } // loop()는 계속 돌며 두 조건을 모두 체크! } |
| 💡 millis() 비블로킹의 진정한 힘 이 구조는 시리즈 전체를 관통하는 핵심 패턴입니다. 3편(초음파 100ms) + 4편(DHT11 2000ms) + 5편(MQ-135 500ms) 모두 동시에 하나의 loop()에서 간섭 없이 각자 주기로 동작할 수 있습니다. delay()를 사용했다면 절대 불가능한 일입니다! |
▶ 8.4 주요 함수 정리
| 함수 | 설명 |
| analogRead(A1) | MQ-135 AO 핀 읽기 → 0~1023 반환 |
| digitalRead(D7) | MQ-135 DO 핀 읽기 → LOW=경보, HIGH=정상 |
| display.invertDisplay(true) | OLED 화면 픽셀 전체 반전 (경보 효과) |
| map(val, 0, 1023, 0, 126) | analogRead 값을 바 그래프 픽셀 길이로 변환 |
| constrain(val, 0, 126) | 변환값이 0~126 범위를 벗어나지 않도록 제한 |
| display.drawFastVLine(x,y,h,c) | 수직선 그리기 — 단계 경계 표시에 활용 |
9장. 동작 확인 및 테스트
▶ 9.1 업로드 및 초기 동작 확인
코드 업로드 후 다음 순서로 동작을 확인합니다.
| 단계 | 확인 내용 | 정상 동작 |
| 1 | 전원 인가 직후 | OLED에 'MQ-135 WARM-UP' + 카운트다운 표시 |
| 2 | 60초 후 | 'Warm-up Complete!' 메시지 표시 |
| 3 | 대시보드 진입 | 온도/습도/공기질 수치 표시 시작 |
| 4 | 알코올 솜 가까이 대기 | AQI 수치 상승, 상태 POOR/DANGER로 변경 |
| 5 | DO 포텐셔미터 조절 후 | AQI 임계값 초과 시 화면 반전 경보 발생 |
| 6 | 시리얼 모니터 확인 | Temp/Humi/AQI 값 2줄 출력 확인 |
OLED 대시보드 동작 영상 (예열→정상→경보 3단계 시연)
▶ 9.2 시리얼 모니터 출력
=====================================================================
🎁 [보너스 노트] 아두이노 멀티태스킹의 비밀: 블로킹(Blocking) vs 비블로킹(Non-blocking)
이번 5편 프로젝트의 핵심 코드는 아두이노가 두 가지 센서(DHT11, MQ-135)를 동시에 다루면서도 화면을 매끄럽게 갱신하도록 설계되었습니다. 이것이 가능한 이유는 바로 '비블로킹(Non-blocking)' 방식을 사용했기 때문입니다. 아두이노 제어의 성패를 가르는 이 두 개념을 핵심만 쏙쏙 정리해 드립니다!
1. 한 줄 요약
블로킹 (Blocking): 하나의 작업이 끝날 때까지 CPU가 아무것도 하지 못하고 그 자리에 멈춰 서서 기다리는 방식.
비블로킹 (Non-blocking): 작업이 끝나기를 기다리지 않고, CPU가 계속 돌면서 다른 일을 즉시 처리하는 방식.
2. 일상생활로 이해하는 비유
블로킹 방식의 카페 (delay): 손님이 주문을 하자, 직원이 커피가 다 나올 때까지 카운터에 가만히 서서 기다립니다. 커피가 나오는 2분 동안 뒤에 줄 선 다른 손님들은 주문조차 하지 못하고 계속 대기해야 합니다.
비블로킹 방식의 카페 (millis): 손님이 주문을 하면 직원은 진동벨을 쥐어주고, 즉시 뒤에 선 다음 손님의 주문을 받습니다. 직원은 카운터에서 계속 일을 하다가, 진동벨이 울리는 손님이 오면 그때 커피를 내어줍니다.
3. 코드로 보는 치명적인 차이
❌ delay()를 쓴 블로킹 방식
void loop() { readDHT11(); // 온습도 읽기 delay(2000); // ⚠️ 2초 동안 아두이노의 두뇌가 완전히 얼어붙음 readMQ135(); // 가스 감지 (delay에 갇혀 2초에 한 번만 실행됨) }
문제점: 온습도를 2초 주기로 읽으려고 delay(2000)을 넣는 순간, 그 2초 사이에 가스가 누출되더라도 아두이노는 바보가 되어 위험 상황을 전혀 감지하지 못합니다.
⭕ millis()를 쓴 비블로킹 방식 (이번 편에 적용된 방식!)
void loop() { unsigned long now = millis(); // 흘러가는 시계를 실시간으로 체크 // 2초마다 온습도 읽기 (시간이 안 되었으면 기다리지 않고 즉시 통과!) if (now - prevDHT >= 2000) { prevDHT = now; readDHT11(); } // 0.5초마다 가스 감지 (온습도 대기 시간과 상관없이 즉각 감지 가능!) if (now - prevMQ >= 500) { prevMQ = now; readMQ135(); } }
장점: loop() 함수가 1초에 수만 번씩 초고속으로 회전하면서 "시간 됐나?" 확인하고 바로 다음 코드로 넘어가기 때문에, 온습도를 기다리는 중에도 가스를 촘촘하게 감지하여 돌발 상황에 즉각 반응할 수 있습니다.
💡 결론: 진짜 제품을 만들려면?
눈앞에서 며칠 잘 돌아가는 시제품은 delay()로도 빠르게 만들 수 있습니다. 하지만 센서가 늘어나고, 사용자의 버튼 입력이나 위험 상황에 실시간으로 반응해야 하는 '상용 제품'의 영역으로 넘어가면 비블로킹(millis)은 선택이 아닌 필수가 됩니다.
공방 회원 여러분도 이번 기회에 millis()를 활용한 시간 제어 패턴을 꼭 숙지하셔서, 절대 멈추지 않는 튼튼하고 견고한 아두이노 프로그램을 설계해 보세요!
| 다음 편 예고 — 6편 ▶ 6편: 광센서(조도) + OLED 조도 모니터 + 자동 밝기 조절 • CDS 광센서(조도 저항) 동작 원리와 analogRead() 활용 • 조도 범위에 따른 밝기 단계 판정 (매우 밝음/밝음/보통/어두움/매우 어두움) • OLED 화면 자동 밝기 조절 (조도에 따라 OLED 밝기 변경) • 5편(MQ-135) + 6편(광센서) + 4편(DHT11) 3센서 통합 환경 모니터 |
민선생의 전자공방 | blog.naver.com/hansikmin
태그#아두이노#MQ135#MQ#135#가스센서#공기질센서#대기질센서#공기질모니터링#대기질측정#실내공기질#환경모니터링#DHT11#온습도센서#OLED#SSD1306#아두이노OLED#아두이노대시보드#아날로그센서#analogRead#브레드보드실습#아두이노프로젝트#민선생의전자공방#전자공방#피지컬코딩#아두이노입문#millis비블로킹#금속산화물반도체#LM393#브레드보드#전자회로 태그수정