홍창민정보·인공지능 교사

가스·공기 · 난이도 보통

MH-Z19B 이산화탄소 센서의 UART 명령, 체크섬, 자동 보정(ABC) 다루기

MH-Z19B는 적외선 흡수(NDIR) 방식으로 CO₂를 ppm 단위로 재는 모듈이에요. 9600bps UART로 9바이트 명령을 주고받는 법, 데이터시트 예시로 확인하는 체크섬 계산, 자동 영점 보정(ABC)을 켜고 끄는 기준, 교실 환기와 식물 탐구를 정리했어요.

글 홍창민 · 갱신 2026-10-31 · 읽는 데 약 8분 · 가상 실행 확인 (실물 미확인)

이 센서로 무엇을 잴 수 있나

MH-Z19B는 윈센(Winsen)이 만든 비분산 적외선(NDIR) 방식 CO₂ 모듈이에요. 데이터시트(1.4판)의 측정 범위는 0~2000ppm, 0~5000ppm, 0~10000ppm 가운데 하나로 설정하고, 정확도는 ±(50ppm + 읽은 값의 5%)예요. 예열은 3분, 응답 시간은 T90 기준 120초 미만, 사용 환경은 −10~50℃, 0~90%RH(이슬 맺힘 없이)예요.

MQ 센서와 달리 CO₂라는 한 가지 기체를 ppm으로 재요. 그래서 교실에 사람이 많아질수록 CO₂가 얼마나 쌓이는지, 창문을 열면 얼마나 빨리 줄어드는지, 밀폐 용기 속 식물이 빛을 받을 때와 못 받을 때 CO₂가 어떻게 달라지는지처럼 실제 농도로 비교하는 탐구를 할 수 있어요.

원리

CO₂는 특정 파장의 적외선을 흡수해요. 모듈 안의 금도금 측정 통에서 빛을 쏘아 반대편 검출기에 닿는 양을 재고, CO₂가 많을수록 줄어드는 빛의 양으로 농도를 계산해요. 온도 보정이 내장되어 있고, 결과는 UART, PWM, 아날로그 전압(기본 0.4~2V)으로 내보내요. 이 글은 UART를 써요.

UART 설정은 9600bps, 8비트, 정지 비트 1, 패리티 없음이에요. 농도를 읽는 명령은 FF 01 86 00 00 00 00 00 79 9바이트이고, 응답도 9바이트 FF 86 HIGH LOW … 체크섬으로 와요. 농도는 HIGH × 256 + LOW예요.

마지막 바이트인 체크섬은 1~7번 바이트를 더해 0xFF에서 빼고 1을 더한 값(아래 8비트)이에요. 데이터시트의 응답 예시 FF 86 02 20 00 00 00 00 58로 확인해 보면, 0x86 + 0x02 + 0x20 = 0xA8이고 0xFF − 0xA8 + 1 = 0x58이라 맞아요. 이때 농도는 2 × 256 + 32 = 544ppm이에요. 체크섬이 맞지 않는 응답은 중간에 바이트가 빠지거나 밀린 것이니 버려야 해요.

자동 영점 보정(ABC)은 전원을 켠 뒤 24시간마다 그동안의 가장 낮은 값을 400ppm으로 맞추는 기능이에요. 처음에는 켜져 있어요. 데이터시트는 사무실과 가정에는 맞지만 온실, 농장, 냉장고에는 맞지 않으니 그런 곳에서는 끄라고 적어요. 끄는 명령은 FF 01 79 00 00 00 00 00 86, 켜는 명령은 FF 01 79 A0 00 00 00 00 E6이에요.

준비물

  • 라즈베리파이 피코와 USB 케이블
  • MH-Z19B(핀형이면 브레드보드에, 단자형이면 전용 케이블로)
  • 점퍼선
  • (탐구 2) 투명 밀폐 용기, 작은 화분, 스탠드 조명

배선

MH-Z19B 이산화탄소 센서의 UART 명령, 체크섬, 자동 보정(ABC) 다루기: 브레드보드에 피코와 센서를 꽂은 배선 그림
시나브로 PICO 브레드보드 작업대와 같은 계산으로 그린 배선이에요. 위 + 줄은 3.3V, 아래 + 줄은 5V예요.
브레드보드 따라 꽂기 순서 (9단계)
  1. 피코를 브레드보드 가운데 홈에 걸쳐 꽂아요. USB 단자가 왼쪽을 보게 하고, 1번 핀이 f2, 40번 핀이 e2에 들어가요. 다리가 홈 양쪽에 모두 들어갔는지 옆에서 봐요.
  2. 빨간 선: 피코 40번 핀(VBUS) 옆 칸 a2 → 아래쪽 + 줄(5V). 이제 이 줄 전체가 5V예요. 위쪽 + 줄(3.3V)과 아래쪽 + 줄(5V)은 절대 서로 잇지 않아요.
  3. 검은 선: 피코 3번 핀(GND) 옆 칸 j4 → 아래쪽 − 줄(GND). 이제 이 줄 전체가 GND예요.
  4. 이산화탄소 CO2 Sensor 꽂기: 다리 Vin · GND · RX · TX를 아래쪽 j24 · j25 · j26 · j27에 차례로 꽂아요. 다리가 브레드보드에 바로 안 들어가면 암-수 점퍼선으로 같은 칸에 이어요.
  5. 빨간 선(5V 전원): 이산화탄소 Vin 줄 i24 → 아래쪽 + 줄(5V)
  6. 검은 선(GND): 이산화탄소 GND 줄 i25 → 아래쪽 − 줄(GND)
  7. 보라 선(신호): 이산화탄소 RX 줄 i26 → 피코 1번 핀(GP0) 옆 칸 j2
  8. 보라 선(신호): 이산화탄소 TX 줄 i27 → 피코 2번 핀(GP1) 옆 칸 j3
  9. 마지막 확인: ① 위쪽 + 줄(3.3V)과 아래쪽 + 줄(5V)이 서로 이어지지 않았나요? ② 모든 부품의 GND가 − 줄에 이어졌나요? ③ 5V 신호 다리는 저항을 거쳐 피코에 이어졌나요? ④ 긴 브레드보드는 + − 줄이 가운데에서 끊긴 것도 있어요(빨간·파란 선이 끊겨 있으면 짧은 선으로 이어요). ⑤ 그림과 한 칸씩 비교한 뒤 USB를 꽂아요.
모듈 핀 피코 핀 비고
Vin VBUS (40번) 4.5~5.5V, 순간 최대 150mA
GND GND (38번) 공통 접지
Tx GP1 (2번, UART0 RX) 엇갈려 연결
Rx GP0 (1번, UART0 TX) 엇갈려 연결
HD 연결 안 함 0V에 7초 넘게 닿으면 영점 보정
Vo, PWM 연결 안 함

이 센서는 분압 저항이 필요 없어요. 전원은 5V지만 데이터시트에 UART 신호 수준이 3.3V(5V 호환)로 적혀 있어서, 센서 Tx가 내보내는 신호를 피코가 그대로 받아도 돼요. 피코 TX의 3.3V 신호도 센서가 알아들어요. 전원은 다르게 챙겨요. 평균 전류는 20mA 아래지만 순간 150mA까지 쓰고, 데이터시트는 전원이 150mA 이상을 줄 수 있어야 한다고 적어요. HD 핀은 0V에 7초 넘게 닿으면 그 순간의 공기를 400ppm으로 보정해 버리니, 선이 GND에 닿지 않게 비워 둬요.

코드

from machine import UART, Pin
import time

uart = UART(0, baudrate=9600, tx=Pin(0), rx=Pin(1), timeout=500, timeout_char=20)
ABC_ON = False                 # 자동 영점 보정. 날마다 바깥 공기가 충분히 드는 곳이면 True

def checksum(p):
    # 데이터시트: 0xFF - (1~7번 바이트 합) + 1 의 아래 8비트
    return (0xFF - (sum(p[1:8]) & 0xFF) + 1) & 0xFF

def command(cmd, b3=0):
    p = bytearray([0xFF, 0x01, cmd, b3, 0, 0, 0, 0, 0])
    p[8] = checksum(p)
    return p

# 자가 점검: 데이터시트 예시 응답 FF 86 02 20 00 00 00 00 58 은 544ppm
ex = [0xFF, 0x86, 0x02, 0x20, 0, 0, 0, 0, 0x58]
ok = "맞음" if checksum(ex) == ex[8] else "틀림"
print(f"자가 점검: {ex[2] * 256 + ex[3]} ppm, 체크섬 {ok}")

time.sleep(1)
uart.write(command(0x79, 0xA0 if ABC_ON else 0x00))
print("ABC 켬" if ABC_ON else "ABC 끔")

t0 = time.ticks_ms()
while True:
    while uart.any():
        uart.read()            # 남아 있던 바이트 비우기
    uart.write(command(0x86))
    r = uart.read(9)
    if r and len(r) == 9 and r[0] == 0xFF and r[1] == 0x86:
        if checksum(r) == r[8]:
            ppm = r[2] * 256 + r[3]
            if time.ticks_diff(time.ticks_ms(), t0) < 180000:
                print(f"예열 중(3분): {ppm} ppm")
            else:
                print(f"DATA: co2={ppm}")
        else:
            print(f"체크섬이 달라 버렸어요(받음 {r[8]}, 계산 {checksum(r)})")
    else:
        print("응답 없음: TX↔RX 엇갈림과 5V 전원을 확인하세요")
    time.sleep(5)
  • checksum(p)는 데이터시트 식 그대로예요. command()는 명령 9바이트를 만들고 마지막에 체크섬을 넣어요.
  • 시작할 때 데이터시트 예시 응답으로 체크섬 함수를 자가 점검해요. "544 ppm, 체크섬 맞음"이 나와야 계산이 맞는 거예요.
  • ABC_ON이 False면 ABC를 끄는 명령을 보내요. 이 명령에는 응답이 없어요.
  • 읽기 전에 uart.any()로 남은 바이트를 비워 응답이 밀리지 않게 해요. 응답이 9바이트이고 FF 86으로 시작하며 체크섬까지 맞을 때만 DATA로 내보내요.
  • 켠 뒤 3분은 예열이라 값만 보여 주고 DATA로는 내보내지 않아요. 윈센 설명에 따르면 MH-Z19B는 5초마다 값을 새로 내서 5초 간격으로 읽어요.

값 해석과 보정

정확도 ±(50ppm + 5%)는 1000ppm일 때 ±100ppm이라는 뜻이에요. 두 센서를 나란히 두었는데 100ppm 안에서 다르다면 고장이라고 단정할 수 없어요.

ABC를 켤지는 장소로 정해요. 날마다 창문을 열어 바깥 공기 수준까지 내려가는 교실이라면 켜 두어도 돼요. 하지만 24시간 동안 한 번도 바깥 공기 수준에 닿지 않는 곳에서는, 그동안의 가장 낮은 값(예를 들어 600ppm)을 400ppm으로 맞춰 버려서 이후 값이 실제보다 낮게 나와요. 식물 상자처럼 밀폐한 실험이라면 꼭 꺼요.

직접 영점을 맞출 때는 센서를 바깥 공기(데이터시트 기준 400ppm)에 20분 넘게 둔 뒤 FF 01 87 00 00 00 00 00 78을 보내거나 HD를 0V에 7초 넘게 연결해요. 데이터시트는 이 보정을 6개월이 넘지 않게 주기적으로 하라고 권해요.

응답 시간이 T90 120초 미만이라, 센서를 다른 공기로 옮기면 2분쯤 지나야 새 값의 90%에 이르러요. 장소를 옮긴 직후의 값은 기록에서 빼요.

자주 하는 실수

  1. Tx는 Tx에, Rx는 Rx에 연결하기. 보내는 쪽과 받는 쪽은 엇갈려야 해요. "응답 없음"이 계속 나오면 이것부터 확인해요.
  2. 체크섬 없이 바이트를 그대로 믿기. 한 바이트만 밀려도 엉뚱한 ppm이 나와요.
  3. 식물 상자 실험에서 ABC를 켜 두기. 며칠 지나면 상자 속 낮은 값이 400ppm이 되어 결과가 어긋나요.
  4. 교실 안에서 영점 명령(0x87)을 보내기. 데이터시트는 400ppm 공기가 아닌 곳에서 보내지 말라고 경고해요. 그 뒤의 값이 모두 틀어져요.
  5. 금도금 통을 누르거나 햇빛 아래 두기. 데이터시트 주의 사항이에요. 통을 누르지 말고, 열원과 직사광선을 피해요.

이 센서로 잴 수 있는 탐구

1. 창문을 어떻게 열면 CO₂가 빨리 줄어들까 - 질문: 창문 하나를 열 때와 마주 보는 두 창을 열 때 CO₂가 줄어드는 빠르기가 다를까? - 바꾸는 것: 창문 여는 방법(한쪽, 맞바람) - 재는 것: 연 뒤 처음 10분 동안 1분마다 줄어든 양(ppm/분) - 방법: 수업이 끝난 직후 같은 자리에서 시작하고, 날을 바꿔 조건마다 3번 재요. 그날 바깥 바람도 함께 적어요.

2. 빛을 받은 식물 상자의 CO₂는 줄어들까 - 질문: 밀폐 용기 속 식물에 빛을 비출 때와 가릴 때 CO₂ 변화가 반대로 나타날까? - 바꾸는 것: 빛(스탠드 켬, 상자를 천으로 덮음) - 재는 것: 30분 동안의 CO₂ 변화량(ppm) - 방법: ABC를 끄고 화분과 센서를 용기에 넣어요. 데이터시트 주의대로 센서의 확산 창 앞은 막지 않게 공간을 둬요. 조건 사이에는 뚜껑을 열어 환기하고, 조건마다 3번 해요.

3. 데이터시트의 응답 시간이 맞을까 - 질문: 센서를 교실에서 복도로 옮기면 새 값의 90%에 이르기까지 정말 120초보다 짧을까? - 바꾸는 것: 옮기는 방향(교실에서 복도로, 복도에서 교실로) - 재는 것: 시작값과 끝값 차이의 90%에 이르기까지 걸린 시간(초) - 방법: 5초 간격 기록에서 처음과 마지막 1분 평균을 끝값으로 정하고 계산해요. 방향마다 3번 해요.

안전

  • CO₂를 일부러 만들지 않아요. 드라이아이스나 많은 양의 베이킹소다·식초 반응은 측정 범위를 넘기고, 좁은 곳에서는 사람에게도 해로워요.
  • 숨에는 CO₂가 아주 많아 범위를 넘을 수 있으니 센서에 숨을 불어 넣지 않아요. 밀폐 용기에는 사람이나 동물이 들어가지 않아요.
  • 이 장치는 인증받은 측정기가 아니에요. 법정 실내공기질 측정이나 환기 설비 판단에 쓰지 마세요.
  • Vin(5V)과 GP 핀이 닿지 않게 하고, 배선은 USB를 뽑고 바꿔요.

더 알아보기

글쓴이

홍창민 · 울산 학성고등학교 정보·인공지능 교사. 수업에 쓰는 도구(시나브로 PICO 웹 IDE, 학교 AI 서버 등)를 직접 만들어 운영해요. 소개 보기 · 고칠 곳 알려 주기