Summary: 강의실 환경 모니터링 프로젝트에서 사용하는 두 센서의 통신 방식을 분석한다. DHT22는 마이크로초 펄스 폭으로 비트를 구분하는 단선 시리얼 40비트 프로토콜을, PMS7003은 UART 9600 8N1 위에서 고정 32바이트 프레임을 사용하며, 각각 리눅스에서 어떻게 읽고 검증하는지까지 정리했다.

Raspberry Pi 4 GPIO 핀맵

DHT22 (온습도 센서)

DHT22(AM2302)는 정전용량식 습도 소자와 서미스터(NTC)를 하나의 모듈에 담아 온도와 상대습도를 함께 측정하는 디지털 센서다. 측정 범위는 습도 0~100%RH(±2~5%RH), 온도 -40~80°C(±0.5°C)이며, 데이터는 단선(single-wire) 시리얼로 40비트를 출력한다.

 

배선은 VCC, GND, DATA 3선으로 단순하고 DATA에 풀업 저항이 필요하다. 이 프로젝트에서는 GPIO4에 연결했다.

 

사양에서 실제 코드 설계에 영향을 주는 항목은 샘플링 주기다. 센서 자체는 1Hz로 샘플링하지만 연속 요청 간격은 최소 2초가 필요하고, 그보다 짧게 재요청하면 이전 값이나 오류가 돌아온다. 즉 "빨리 여러 번 읽어서 평균 내는" 접근이 이 센서에서는 성립하지 않는다.

 

DHT22 단선 시리얼 프로토콜

단선 프로토콜은 데이터선 한 가닥으로 요청과 응답을 주고받는 반이중 방식이다. 이름 때문에 Dallas 1-Wire로 오해하기 쉬운데, DHT 계열은 자체 타이밍 규격을 쓰는 별개의 프로토콜이다.

 

통신 순서는 다음과 같다. MCU가 시작 신호로 데이터선을 최소 1ms LOW로 끌어내리고 20~40µs를 기다리면, 센서가 80µs LOW + 80µs HIGH로 응답한 뒤 40비트 데이터를 전송한다.

 

40비트의 구성은 습도 정수 8bit + 습도 소수 8bit + 온도 정수 8bit + 온도 소수 8bit + 체크섬 8bit이며, 체크섬은 앞 4바이트 합의 하위 8비트다.

 

비트 판별 방식이 이 프로토콜의 핵심이자 문제의 근원이다. 각 비트는 50µs LOW 뒤에 오는 HIGH의 유지 시간으로 구분되는데, 26~28µs면 0, 70µs면 1이다. 0과 1의 차이가 수십 마이크로초에 불과하므로, 일반 사용자 공간 프로세스가 GPIO를 폴링해서 이 폭을 재는 방식은 커널 스케줄링에 밀리는 순간 비트를 통째로 놓친다. 이 프로젝트에서 커널 dht11 IIO 드라이버를 쓰기로 한 것은 이 때문이다. 타이밍 측정을 커널에 위임하면 사용자 코드는 sysfs 파일(in_temp_input, in_humidityrelative_input)을 읽는 평범한 파일 I/O로 단순해진다.

 

PMS7003 (미세먼지 센서)

PMS7003(Plantower)은 레이저 산란 방식으로 입자 농도를 측정하는 디지털 미세먼지 센서다. 0.3~1.0 / 1.0~2.5 / 2.5~10µm 구간의 입자를 감지하며, 유효 범위 0~500µg/m³, 분해능 1µg/m³로 PM1.0/PM2.5/PM10 농도와 입자 개수를 UART로 출력한다.

 

전원 계통은 주의가 필요하다. 팬과 내부 로직은 5V로 구동하지만 통신 핀은 3.3V TTL이라 Raspberry Pi GPIO에 레벨 변환 없이 직결할 수 있다.

 

동작 모드는 두 가지다. 액티브 모드(기본)는 측정값을 자동으로 주기 전송하고, 패시브 모드는 요청이 있을 때만 응답한다. 이 프로젝트는 액티브 모드를 사용한다. 센서가 알아서 보내주므로 수신 측은 명령을 보낼 필요 없이 스트림을 읽기만 하면 되고, 결과적으로 RX 한 선만으로도 수신이 성립한다.

 

UART 통신

UART(Universal Asynchronous Receiver/Transmitter)는 클록선 없이 시작·정지 비트로 프레임을 구분하는 비동기 직렬 통신이다. 송신(TX)과 수신(RX) 두 선을 교차 연결하며, PMS7003의 설정은 9600bps, 데이터 8bit, 패리티 없음, 정지 1bit(8N1)이다.

 

연결은 센서 TX → Pi RX(GPIO15)다. 리눅스 구현은 /dev/ttyAMA0을 open()한 뒤 termios로 raw 모드와 9600bps를 설정하고 read()로 바이트를 받는 구조다. DHT22와 비교하면 대조가 뚜렷하다. UART는 하드웨어(UART 컨트롤러)가 비트 타이밍을 처리해 주므로, 사용자 공간에서 읽어도 DHT22 같은 타이밍 문제가 없다. 같은 "센서 읽기"라도 프로토콜 계층이 어디까지 하드웨어/커널에 내려가 있느냐에 따라 사용자 코드의 난이도가 갈린다.

 

PMS7003 32바이트 프레임 구조

액티브 모드의 출력 프레임은 고정 32바이트이고, 값은 빅 엔디언 16비트 워드 단위로 실린다.

바이트 내용
0–1 시작 문자 0x42 0x4D (ASCII "BM")
2–3 프레임 길이 (2×13+2 = 28)
4–9 PM1.0 / PM2.5 / PM10 농도 (CF=1, 표준 입자 기준)
10–15 PM1.0 / PM2.5 / PM10 농도 (대기 환경 기준)
16–27 0.1L당 입자 개수 (>0.3 / 0.5 / 1.0 / 2.5 / 5.0 / 10µm)
28 버전
29 오류 코드
30–31 체크섬

값 조립은 (상위바이트 << 8) | 하위바이트이고, 체크섬은 앞 30바이트(0~29)의 합이 30~31바이트 값과 일치해야 유효 프레임이다.

 

파싱할 때 헷갈리기 쉬운 지점이 두 곳 있다. 첫째, 같은 PM2.5가 CF=1 기준(바이트 6~7)과 대기 환경 기준(바이트 12~13)으로 두 번 실려 온다. 실내·일반 환경 측정에는 대기 환경 값을 쓰는 것이 맞고, 이 프로젝트는 바이트 12~13(PM2.5), 14~15(PM10)를 파싱한다. 둘째, 수신 시점이 프레임 경계와 일치한다는 보장이 없다. 액티브 모드 스트림을 중간부터 읽기 시작할 수 있으므로, 0x42 0x4D 헤더를 먼저 찾아 동기화한 뒤 나머지 30바이트를 받아야 한다.

 

동작 확인

설정이 끝난 상태에서 각 센서를 코드 없이 먼저 확인할 수 있다.

DHT22는 sysfs를 직접 읽는다. 값은 밀리 단위다(24300 = 24.3°C).

cat /sys/bus/iio/devices/iio:device0/in_temp_input
cat /sys/bus/iio/devices/iio:device0/in_humidityrelative_input

I/O error가 간헐적으로 나오는 것은 DHT22 응답 실패로, 이 센서의 정상 동작 범위다.

PMS7003은 원시 바이트에서 헤더가 주기적으로 보이는지 확인한다.

sudo stty -F /dev/ttyAMA0 9600 cs8 -cstopb -parenb raw
sudo hexdump -C -n 256 /dev/ttyAMA0

출력에서 42 4d가 32바이트 간격으로 반복되면 액티브 모드 송신이 정상이다.

 

데이터 경로 요약

두 센서의 값이 모두 유효할 때만 5분 간격으로 INSERT한다.

 

결론

두 센서는 "타이밍을 누가 책임지는가"에서 갈린다. DHT22의 단선 프로토콜은 µs 타이밍을 수신 측이 재야 하므로 커널 드라이버에 위임했고, PMS7003의 UART는 하드웨어가 타이밍을 처리하므로 사용자 공간 termios로 충분하다. 대신 PMS7003은 스트림 동기화와 체크섬 검증이라는 프레임 파싱 문제를 수신 코드가 책임져야 한다. 다음 편에서는 이 데이터를 받을 PostgreSQL 스키마를 설계한다.