집에 부품박스를 뒤지다가 예전에 사둔 미개봉 DHT21이 있어서 ESP32에 연결하기로 하였다. DHT21에 대해서 이야기 하자면, 센서리온의 SHT21을 카피한 제품으로 필자는 인지하고 있다. 사실 SHT21이 지금도 나오는지는 모르겠지만, 필자가 한참 사용할 때 단종된다는 걸 보고 이후 SHT35로 변경하여 사용하였다.
--- contents ---
01. [ESP32] VS Code 개발환경 구성 : https://makeutil.tistory.com/303
02. [ESP32] 첫 프로젝트 생성하기 : https://makeutil.tistory.com/304
03. [ESP32] 멀티 테스크 예제 (2 Task) : https://makeutil.tistory.com/305
04. [ESP32] Task간 데이터 공유 (Queue, Mutex) : https://makeutil.tistory.com/306
05. [ESP32] 개발보드 별 형상 및 I/O (ESP32/ESP32-S3) : https://makeutil.tistory.com/307
06. [ESP32] UART 통신 예제 #1 : https://makeutil.tistory.com/309
07. [ESP32] UART 통신 예제 #2 : https://makeutil.tistory.com/313
08. [ESP32] GPIO LED 켜기 : https://makeutil.tistory.com/311
09. [ESP32] Timer와 PWM을 이용한 LED 점멸 : https://makeutil.tistory.com/312
10. [ESP32] WiFi SoftAP와 WiFi Station (기본예제) : https://makeutil.tistory.com/314
11. [ESP32] 무선 시리얼 통신장치 만들기 1 (SoftAP) : https://makeutil.tistory.com/315
12. [ESP32] 무선 시리얼 통신장치 만들기 2 (SoftAP STA) : https://makeutil.tistory.com/316
13. [ESP32] I2C Temperature Sensor (DHT21) : 현재글
A1. [ESP32] 오류 - Fatal Error : No such file or directory : https://makeutil.tistory.com/308
------------------
센서리온의 SHT시리즈는 가격이 상당히 비싸다. 필자가 처음 접했을 땐 그리 비싸지 않았는데, 코로나 앞뒤로 중국의 사재기와 코로나로 인한 생산 문제로 인해서인지 가격이 상당히 많이 비싸졌다. 어쨋든 이러한 SHT시리즈의 경우 현재 35를 필자는 주로 사용하고 있지만, 가격이 상당히 비싸서, 업무 외적으로 사용하기에는 부담스럽다. SHT시리즈는 필자의 기억으로 30 < 31 < 35순으로 정밀도가 차이가 나기 때문에, 일반적 온도화가 아주 미세한 정도가 아니라면 31정도면 충분히 사용이 가능하다.
하.지.만, 그마저도 가격이 비싸다.
SHT30, 1개단위 3,590원 (부가세 별도)
SHT31, 1개단위 5,360원 (부가세 별도)
SHT35, 1개 단위 10,610원 (부가세 별도)
디지키나 마우저에서는 찾을 수가 없고, 디바이스 마트에서 중국산 모듈로 파는게 7000원 정도이다. 이것도 많이 오른거 같다. 오히려 SHT부품이 가격이 저렴해진것 같다. 필자가 한참 품귀때 알아봤을 때는 SHT35가 25000원 정도 였던걸로 기억한다. 어쨋든 저걸 다시 붙혀서 사용하려면 더 많은 돈이 들어간다.
필자는 예전에 구매했는데, 아마 개당 2천워에 구매했던거 같다. 오래되서 기억이 잘 안나긴 하지만 말이다. 알리에 알아보니 대략, 1500원 정도에 구매가 가능한것 같다.
1. 연결
모듈의 연결은 다음과 같이 하면 된다.
2. 소스코드
소스코드는 다음과 같다.
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/i2c.h"
#include "esp_log.h"
#define I2C_MASTER_NUM I2C_NUM_0 // I2C 마스터 0번
#define I2C_MASTER_SCL_IO20 20 // IO핀 20번 기존에 UART로 썼던핀
#define I2C_MASTER_SDA_IO19 19 // IO핀 19번 기존에 UART로 썼던핀
#define I2C_MASTER_FREQ_HZ 100000 // I2C 일반 모드(100KHz속도)
#define I2C_MASTER_TIMEOUT_MS 1000
#define DHT21_SENSOR_ADDR 0x40 // 센서의 ADDR 주소(SHT와 동일)
#define DHT21_MEASURE_TEMP 0xF3 // 온도를 읽기 위한 레지스터 값
#define DHT21_MEASURE_HUMI 0xF5 // 습도를 읽이 위한 레지스터 값
static const char *TAG = "DHT21";
// i2c 설정을 입력하여 지정한다.
static esp_err_t i2c_master_init(void)
{
i2c_config_t conf = {
.mode = I2C_MODE_MASTER, // 마스터로 동작
.sda_io_num = I2C_MASTER_SDA_IO19,
.scl_io_num = I2C_MASTER_SCL_IO20,
.sda_pullup_en = GPIO_PULLUP_ENABLE, // 내부 풀업저항 활성화
.scl_pullup_en = GPIO_PULLUP_ENABLE, // 내부 풀업저항 활성화
.master.clk_speed = I2C_MASTER_FREQ_HZ,
};
// 앞에서 입력한 설정을 적용한다.
ESP_ERROR_CHECK(i2c_param_config(I2C_MASTER_NUM, &conf));
// 드라이버 설치
return i2c_driver_install(I2C_MASTER_NUM, conf.mode, 0, 0, 0);
}
static esp_err_t dht21_read_sensor(uint8_t command, float *out_value)
{
uint8_t data[3]; // 데이터를 담을 변수
esp_err_t rete; // 오류 코드를 담기위한 변수
i2c_cmd_handle_t cmd; // 커맨드 핸들 변수
// 센서 명령 전송
cmd = i2c_cmd_link_create();
i2c_master_start(cmd);
// 7Bit Address 주소를와 커맨드를 전송한다.
i2c_master_write_byte(cmd, (DHT21_SENSOR_ADDR << 1) | I2C_MASTER_WRITE, true);
i2c_master_write_byte(cmd, command, true);
i2c_master_stop(cmd);
// i2c 명령을 실행한다.
rete = i2c_master_cmd_begin(I2C_MASTER_NUM, cmd, pdMS_TO_TICKS(I2C_MASTER_TIMEOUT_MS));
i2c_cmd_link_delete(cmd);
if (rete != ESP_OK)
return rete;
vTaskDelay(pdMS_TO_TICKS(85)); // 데이터 준비시간, 너무 짧으면 데이터 못 읽음.
// 데이터 수신
cmd = i2c_cmd_link_create();
i2c_master_start(cmd); // I2C 마스터를 시작
// 읽어올 DH2 어드레스 센서 주소를 보내고
i2c_master_write_byte(cmd, (DHT21_SENSOR_ADDR << 1) | I2C_MASTER_READ, true);
i2c_master_read(cmd, data, 3, I2C_MASTER_LAST_NACK);
i2c_master_stop(cmd);
// 데이터를 가져온다
rete = i2c_master_cmd_begin(I2C_MASTER_NUM, cmd, pdMS_TO_TICKS(I2C_MASTER_TIMEOUT_MS));
// 링크를 해제한다.
i2c_cmd_link_delete(cmd);
if (rete != ESP_OK)
return rete;
// 가져온 값을 계산한다. 데이터에서 사용하지 않는 마지막 2비트는 무시한다.
uint16_t raw_value = (data[0] << 8) | (data[1] & 0xFC);
if (command == DHT21_MEASURE_TEMP) { // 커맨드가 온도일 경우 아래의 공식을 사용.
*out_value = -46.85 + 175.72 * (float)raw_value / 65536.0;
} else if (command == DHT21_MEASURE_HUMI) { // 커맨드가 습도일 경우 아래의 공식을 사용.
*out_value = -6.0 + 125.0 * (float)raw_value / 65536.0;
}
return ESP_OK;
}
void app_main(void)
{
ESP_ERROR_CHECK(i2c_master_init());
while (1) {
float value = 0.0;
// 온도 읽기
if (dht21_read_sensor(DHT21_MEASURE_TEMP, &value) == ESP_OK) {
ESP_LOGI(TAG, "Temperature: %.2f °C", value);
} else {
ESP_LOGE(TAG, "Failed to read temperature");
}
vTaskDelay(pdMS_TO_TICKS(1000));
// 습도 읽기
if (dht21_read_sensor(DHT21_MEASURE_HUMI, &value) == ESP_OK) {
ESP_LOGI(TAG, "Humidity: %.2f %%", value);
} else {
ESP_LOGE(TAG, "Failed to read humidity");
}
vTaskDelay(pdMS_TO_TICKS(1000));
}
}
SHT21와 DHT21의 데이터 처리 공식은 조금 차이가 있다. 거의 동일하다. 온도 부분에 있어서 DHT의 경우 조금 다른 값을 사용하여 계산한다.
- SHT21 Temperature / Humidity
온도 (°C) = -40 + (175.72 * raw_value) / 65536
습도 (%) = -6 + (125.0 * raw_value) / 65536
- DHT21 Temperature / Humidity
온도 (°C) = -46.85 + (175.72 * raw_value) / 65536
습도 (%) = -6.0 + (125.0 * raw_value) / 65536
3. 실행결과
실행 결과는 다음과 같다. 온도와 습도가 반복적으로 출력된다. 한참 놔둬도 잘 동작한다. DHT21은 앞서 언급했듯 SHT21을 거의 카피하여, 장치의 어드레스 뿐만 아니라 커맨드도 동일하다. 심지어 데이터를 읽어오와서 처리하는 공식도 비슷하다.
결론적으로는, 정밀도 면에서 많이 떨어지는건 둘째치고, Humidity 부분은 사실 신뢰가 가지 않는다. 칩이 잘못된건지 필자가 잘못한건지 모르겠지만 손으로 막았을 때, 온도가 올라가는 것과 비례하여 습도도 같이 오른다. 뭐 드라이기로 테스트 하면 좀더 정확하겠지만... 뭐 이정도로 해두겠다.
최근댓글