신짱구의 개발일지
[Raspberry Pi5] DHT22 온습도센서 연결 및 데이터 전송 본문
1. 라즈베리파이 연결 방법
2. GPIO 제어 라이브러리 설치
3. DHT22 센서 통신 프로세스
4. 코드 부분 설명
사용된 장비
- 라즈베리파이5
- DHT22 센서
- 저항 (10kΩ)
- 점퍼 와이어
하드웨어 연결
- VCC: 5V (예: GPIO 2번 핀)
- GND: GND (예: GPIO 6번 핀)
- Data: GPIO 핀 (예: GPIO 17번 핀)
라즈베리파이5 GPIO 핀맵
온습도 센서 핀맵
핀 구성은 왼쪽에서부터 아래와 같다.
- Pin 1: VDD (전원 공급, 3.3~6V DC 필요)
- Pin 2: DATA (디지털 신호 출력)
- Pin 3: NULL (연결되지 않음)
- Pin 4: GND (접지)
온습도 센서의 범위 오차표
라즈베리파이 연결
DHT22 데이타시트에 따르면, 전원 안정화를 위해 VDD와 GND 사이에 100nF의 Capacitor를 추가하는 것이 좋다고 한다. 그러나, 현재 Capacitor를 따로 가지고 있지 않기 때문에 비슷한 효과를 내기 위해 220Ω 짜리 저항을 추가해줬다.
GPIO 제어 라이브러리 (WringPi)
DHT22 센서에서 데이터를 C++ 코드로 받아오기 위해 WiringPi 라이브러리를 설치한다. wiringPi는 GPIO를 지원하는 C언어 전용 라이브러리이다. 그러나, 최신 라즈베리파이 OS에서 더 이상 공식적으로 지원하지 않기 때문에 apt-get으로 설치할 수 없다. 그렇기 때문에 아래와 같이 수동으로 직접 설치해줘야 한다.
- GitHub에서 클론
git clone <https://github.com/WiringPi/WiringPi.git>
- WringPi 빌드 및 설치
cd WiringPi
./build
- 설치 확인
DHT22 센서 통신 프로세스
1단계: MCU가 시작 신호를 보낸다(1ms 이상 Low 신호 유지).
2단계: 센서가 응답 신호(80μs Low, 80μs High)를 MCU로 전송한다.
3단계: 센서가 데이터를 비트 단위로 MCU에 전송하고, 각 비트는 50μs Low 신호로 시작하며, 이후 High 신호의 길이에 따라 "1" 또는 "0"으로 구분된다.
- 전원 및 핀 관련
- 전원을 연결한 직후, 센서는 저전력 상태에서 동작 상태로 전환되며 초기화에 약 1초가 소요되기 때문에 센서에 전원을 공급한 직후 1초간은 신호를 보내지 않아야 안정화가 이루어질 수 있다.
- 통신 및 신호
- Single-bus 데이터 방식을 사용하여 MCU와 통신한다.
- MCU는 시작 신호를 보내 센서를 활성화시킨다.
- 센서는 응답 신호로 상대 습도 및 온도 데이터를 40비트로 반환한다.
- 데이터 구성
- 8비트 습도 정수값 + 8비트 습도 소수값 + 8비트 온도 정수값 + 8비트 온도 소수값 + 8비트 체크섬
- 체크섬 비트는 앞의 데이터를 검증하는 데 사용된다.
- Single-bus 데이터 방식을 사용하여 MCU와 통신한다.
C++ 테스트 코드
위 통신 프로세스를 기준으로 작성된 코드가 아래와 같다.
#include <wiringPi.h>
#include <iostream>
#include <fstream>
#define MAX_TIMINGS 85 // 신호 전환 횟수
#define DHT_PIN 17 // GPIO 17번 핀 사용
int data[5] = {0, 0, 0, 0, 0}; // 5바이트 데이터(온습도 + 체크섬)
// DHT22 센서에서 데이터 읽기
void read_dht_data() {
uint8_t laststate = HIGH;
uint8_t counter = 0;
uint8_t j = 0, i;
data[0] = data[1] = data[2] = data[3] = data[4] = 0;
pinMode(DHT_PIN, OUTPUT);
digitalWrite(DHT_PIN, LOW);
delay(18);
digitalWrite(DHT_PIN, HIGH);
delayMicroseconds(40);
pinMode(DHT_PIN, INPUT);
for (i = 0; i < MAX_TIMINGS; i++) {
counter = 0;
while (digitalRead(DHT_PIN) == laststate) {
counter++;
delayMicroseconds(1);
if (counter == 255) {
break;
}
}
laststate = digitalRead(DHT_PIN);
if (counter == 255) break;
if ((i >= 4) && (i % 2 == 0)) {
data[j/8] <<= 1;
if (counter > 16)
data[j/8] |= 1;
j++;
}
}
// 체크섬 확인
if ((j >= 40) && (data[4] == ((data[0] + data[1] + data[2] + data[3]) & 0xFF))) {
float h = (float)((data[0] << 8) + data[1]) / 10.0; // DHT22는 16비트 습도 데이터
float c = (float)(((data[2] & 0x7F) << 8) + data[3]) / 10.0; // DHT22는 16비트 온도 데이터
if (data[2] & 0x80) c = -c; // 음수 온도 처리
float f = c * 1.8f + 32; // 섭씨를 화씨로 변환
std::cout << "Humidity = " << h << " % Temperature = " << c << " *C (" << f << " *F)" << std::endl;
} else {
std::cout << "Data not good, skip" << std::endl;
}
}
int main() {
if (wiringPiSetupGpio() == -1)
return 1;
while (1) {
read_dht_data();
delay(2000); // 2초마다 읽기
}
return 0;
}
코드 부분 설명
DHT22 센서와 통신을 시작하기 위한 초기화 과정
pinMode(DHT_PIN, OUTPUT); // 1
digitalWrite(DHT_PIN, LOW); // 2
delay(18); // 3
digitalWrite(DHT_PIN, HIGH); // 4
delayMicroseconds(40); // 5
pinMode(DHT_PIN, INPUT); // 6
- DHT22 센서는 단일 데이터 핀을 통해 MCU와 통신한다. 처음에는 MCU가 해당 핀을 제어해야 하기 때문에 핀이 외부로 신호를 내보낼 수 있는 상태로 만들기 위해 GPIO 핀을 출력 모드(OUTPUT)로 설정한다.
- MCU는 핀을 LOW(0V) 상태로 설정하여 센서에게 "데이터 요청" 신호를 보낸다.
- 데이터 핀을 LOW 상태로 약 18ms로 길게 유지하여 센서가 MCU의 요청 신호를 확실히 인지하도록 보장한다.
- 요청 신호를 마치고, 데이터 핀을 HIGH(3.3V or 5V)로 설정하여 센서가 응답 신호를 보낼 준비한다.
- 핀을 HIGH로 설정한 후, 센서로 부터 LOW 신호(응답 시작 신호)를 받기 위해 약 40μs 동안 대기한다.
- 이제 MCU는 센서가 보내는 데이터를 읽을 준비를 하고, GPIO 핀을 입력 모드(INPUT)로 변경한다.
if ((i >= 4) && (i % 2 == 0)) {
data[j/8] <<= 1;
if (counter > 16)
data[j/8] |= 1;
j++;
}
- i >= 4
이 코드는 센서가 초기 3개의 신호 전환(응답 신호 및 준비 신호)을 무시하고, 실제 데이터 비트 전송이 시작된 이후의 신호를 처리하기 위해 사용된다. 즉, 초기 3개의 신호는 데이터 전송 과정의 일부가 아니므로 건너뛴다.
- i % 2 == 0
센서의 데이터 비트는 HIGH와 LOW 신호 쌍으로 전송되며, 이 쌍 중에서 짝수 번째 신호 전환(HIGH의 신호 길이)이 실제 데이터를 나타낸다. 즉, 이 코드는 실제 데이터인 짝수 번째 신호만을 처리하기 위해 사용된다.
- data[j/8] <<= 1
현재 처리 중인 데이터를 저장하기 위해 data[j/8]에 1비트 왼쪽으로 쉬프트하여 새로운 비트를 추가할 공간을 만든다. j/8은 비트 인덱스를 통해 배열의 현재 바이트에서의 위치를 결정한다.
- if (counter > 16) data[j/8] |= 1
HIGH 신호의 길이(counter)가 16보다 크면, 해당 비트를 1로 해석하여 현재 바이트의 가장 오른쪽 비트를 1로 설정한다. 16보다 작으면 비트는 0으로 유지된다.
센서의 프로토콜에서 HIGH 신호의 길이를 카운트하여 데이터 비트 값을 인코딩한다. 그리고, 20μs는 짧은 신호(비트 0), 26~28μs는 긴 신호(비트 1)를 의미한다. 16을 기준으로 한 이유는 센서의 신호 길이는 마이크로초 단위로 측정되지만, MCU는 정확히 1μs 단위로 측정하기 어려울 수 있다. 즉, MCU의 실행 속도를 고려한 값이라고 볼 수 있다.
- j++
처리한 비트의 개수를 증가시켜 다음 비트를 처리할 준비를 한다. j는 현재 비트의 인덱스를 나타내며, 8개의 비트가 모이면 data 배열의 다음 바이트로 넘어간다.
References
https://ejleep1.tistory.com/442
https://blog.naver.com/PostView.nhn?blogId=emperonics&logNo=222092518468&photoView=5
Datasheet
'임베디드시스템' 카테고리의 다른 글
[Raspberry Pi5] 라즈베리파이 OS 부팅 스크립트 설정하기 (0) | 2024.11.14 |
---|---|
[Raspberry Pi5] USB 마이크와 스피커 테스트 명령어 (1) | 2024.11.13 |
[Raspberry Pi5] 라즈베리파이 OS 설치 및 공유기 포트포워딩 (0) | 2024.11.11 |