ESP32/ESP32-S3

ESP-IDF ESP32 (ESP32-S3) 이산화 탄소 농도 센서 MH-Z19B 읽기

devkoko32 2025. 4. 20. 18:47

 

ESP32와 MH-Z19B 이산화 탄소 농도 센서를 사용해서 대기중 이산화탄소 농도를 확인하는 방법

 

 

데이터시트

https://www.winsen-sensor.com/d/files/infrared-gas-sensor/mh-z19b-co2-ver1_0.pdf

 

 

 

 

 

MH-Z19B 를 사용해서 이산화 탄소 농도를 읽는 방법은 크게 PWM 방식과 UART 를 사용한 두가지 방식이 있다.

 

 

PWM 방식은 MH-Z19B 가 출력하는 PWM을 1004ms 동안 read 한 뒤 HIGH 지속 시간과 LOW 지속 시간을 구분해 확인할 수 있다.

 

 

 

 

 

UART 를 사용한 방식은 지정된 커맨드를 보낸 뒤 MH-Z19B 가 Response하는 데이터를 확인 후 연산해서 확인할 수 있다.

 

 

 

UART를 사용하는 방법이 좀 더 편히 확인 할 수 있다.

URAT를 사용할 수 없거나 GPIO를 한개만 사용해야 하는 경우 PWM 을 사용하는게 좋을것 같다.

 

 

아래는 UART 를 사용한 회로도와 소스코드 예제

 

회로도

 

 

 

회로를 구성할 때 반드시 주의해야할 점이 있다.

5V 핀에서 5V 전압이 나오는지 반드시 확인 후 연결해야한다.

5V 단자로 출력은 없고 외부에서 입력 전원만 받는 핀 일 수 있다

 

2025.04.22 - [분류 전체보기] - ESP32-S3-DevkitC-1 5V 핀 출력 활성화

 

 

MH-Z19B 는 동작전압이 5V이기 때문에 5V를 공급 해줘야 한다. UART는 3.3V라서 다행.

외부에서 5V 를 공급해준다면 별다른 문제가 없지만 별다른 장치가 없이 진행 하라면 내 블로그에 ESP32 S3 5V 설정 관련 글을 참고.

 


2025.04.22 - [분류 전체보기] - ESP32-S3-DevkitC-1 5V 핀 출력 활성화

 

 

소스코드 중 일부

int mh_z19_read_co2()
{
    uint8_t cmd[9] = {0xFF, 0x01, 0x86, 0x00, 0x00, 0x00, 0x00, 0x00, 0x79};
    uart_write_bytes(MH_Z19_UART_NUM, (const char *)cmd, sizeof(cmd));

    uint8_t response[9];
    int len = uart_read_bytes(MH_Z19_UART_NUM, response, sizeof(response), 100 / portTICK_PERIOD_MS); // 약 100ms 대기

    if (len == 9 && response[0] == 0xFF && response[1] == 0x86)
    {
        /////RAW DATA 출력
        //
        printf("RAW DATA: ");
        for (int i = 0; i < len; i++)
        {
            printf("%02X ", response[i]);
        }
        printf("\n");
        //
        //////////

        int co2 = response[2] * 256 + response[3];
        return co2;
    }
    else
    {
        ESP_LOGW(TAG, "잘못된 응답 수신 또는 타임아웃");
        return -1;
    }
}

위 함수에서 RAW DATA 를 출력 후 측정 값을 return 하게 된다.

 

 

 

 

 

 

RAW DATA 받은 정보를 데이터시트에서 알려준대로 연산해보면

0x02 * 256 + 0x49

2 * 256 + 73  = 586 ppm 을 확인할 수 있고

 

소스코드에서

 

 int co2 = response[2] * 256 + response[3];

 

이렇게 연산해서 Return 한다.

 

 

 

 

 

전체 소스코드

#include "driver/uart.h"
#include "esp_log.h"
#include "freertos/FreeRTOS.h"
#include "freertos/portmacro.h"
#include "esp_timer.h"

#define MH_Z19_UART_NUM UART_NUM_1
#define MH_Z19_TXD_PIN 12
#define MH_Z19_RXD_PIN 13
#define MH_Z19_UART_BUF_SIZE (1024)

static const char *TAG = "MH-Z19";

void mh_z19_init()
{
    const uart_config_t uart_config = {
        .baud_rate = 9600,
        .data_bits = UART_DATA_8_BITS,
        .parity = UART_PARITY_DISABLE,
        .stop_bits = UART_STOP_BITS_1,
        .flow_ctrl = UART_HW_FLOWCTRL_DISABLE,
    };
    uart_driver_install(MH_Z19_UART_NUM, MH_Z19_UART_BUF_SIZE * 2, 0, 0, NULL, 0);
    uart_param_config(MH_Z19_UART_NUM, &uart_config);
    uart_set_pin(MH_Z19_UART_NUM, MH_Z19_TXD_PIN, MH_Z19_RXD_PIN, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE);
}

int mh_z19_read_co2()
{
    uint8_t cmd[9] = {0xFF, 0x01, 0x86, 0x00, 0x00, 0x00, 0x00, 0x00, 0x79};
    uart_write_bytes(MH_Z19_UART_NUM, (const char *)cmd, sizeof(cmd));

    uint8_t response[9];
    int len = uart_read_bytes(MH_Z19_UART_NUM, response, sizeof(response), 100 / portTICK_PERIOD_MS); // 약 100ms 대기

    if (len == 9 && response[0] == 0xFF && response[1] == 0x86)
    {
        /////RAW DATA 출력
        //
        printf("RAW DATA: ");
        for (int i = 0; i < len; i++)
        {
            printf("%02X ", response[i]);
        }
        printf("\n");
        //
        //////////

        int co2 = response[2] * 256 + response[3];
        return co2;
    }
    else
    {
        ESP_LOGW(TAG, "잘못된 응답 수신 또는 타임아웃");
        return -1;
    }
}

void app_main(void)
{
    mh_z19_init();

    while (1)
    {
        int co2 = mh_z19_read_co2();
        if (co2 > 0)
        {
            ESP_LOGI(TAG, "CO2 농도: %d ppm", co2);
        }

        // 약 1초 대기 (esp_timer 사용)
        esp_rom_delay_us(1000 * 1000); // 1000ms = 1초
    }
}