目录

一、DHT22概述

1.关键特性

2.引脚定义

4.单总线协议描述

4.1数据格式

4.2数据计算

4.3负值的处理

5.通信时序

5.1 起始信号(主机发起)

5.2 从机响应

5.3 数据位传输

5.4 结束信号

二、硬件搭建

1.总览

​编辑

2.DHT22 → ESP32 

 3.ESP32 → USB to TLL

三、软件环境

四、驱动代码

1.DHT22

dht22.c

dht22.h

CMakeLists.txt

2.串口发送

uart_sender.c

uart_sender.h

CMakeLists.txt

3.主函数

main.c

CMakeLists.txt

五、运行示例


一、DHT22概述

DHT22 是一款数字温湿度复合传感器,使用AM2302温湿度传感器,并连接了一个 8 位单片机,输出已校准的数字信号,采用单总线协议。


1.关键特性

  • 湿度测量范围:0 ~ 100% RH,精度 ±2% RH

  • 温度测量范围:-40 ~ 80℃,精度 ±0.5℃

  • 分辨率:16 位(湿度)/ 16 位(温度)

  • 采样周期:≥ 2 秒

  • 供电电压:3.3 ~ 6V DC

  • 接口:单总线(DATA)


2.引脚定义

引脚号 名称 功能说明
1 VDD 供电电源,3.3 ~ 6V DC
2 DATA 串行数据总线(单总线)
3 NC 空脚,请悬空(不连接)
4 GND 电源地

3.原理图

注:模块已内置4.7k上拉电阻和100nF电容


4.单总线协议描述

DHT22 使用单总线协议,一次完整通信约 5ms,传输 40 bit 数据(高位先出)

4.1数据格式

字节 内容 说明
0 湿度高 8 位(整数部分) 16 bit 湿度数据
1 湿度低 8 位(小数部分)
2 温度高 8 位(整数部分) 16 bit 温度数据
3 温度低 8 位(小数部分)
4 校验和(CRC) 用于验证数据完整性

4.2数据计算

  • 湿度值(%RH) = (湿度高8位 << 8 | 湿度低8位) / 10.0

  • 温度值(℃) = (温度高8位 << 8 | 温度低8位) / 10.0

  • 校验规则(Byte0 + Byte1 + Byte2 + Byte3) 的低 8 位应等于 Byte4

4.3负值的处理

当温度 低于 0℃ 时,温度数据(16 bit)的最高位(第 15 位)置 1
eg:-10.1℃ 的原始数据为 1000 0000 1010 1010(即 0x80AA)


5.通信时序

5.1 起始信号(主机发起)

  • 主机(MCU)将 DATA 线拉低 ≥ 500 μs(建议 1~2 ms)。

  • 释放总线,等待 20 ~ 40 μs

5.2 从机响应

  • DHT22 拉低 DATA 线约 80 μs(响应信号)。

  • 随后拉高 DATA 线约 80 μs(准备发送数据)。

5.3 数据位传输

  • 每个 bit 以 约 50 μs 的低电平 开始(位同步信号)。

  • 随后是高电平,其持续时间决定 bit 值:

    • 高电平持续 26~28 μs → 逻辑 '0'

    • 高电平持续 70 μs → 逻辑 '1'

5.4 结束信号

  • 40 bit 数据传输完成后,从机拉低 DATA 线约 50 μs,然后释放总线,由外部上拉电阻拉高。


二、硬件搭建

1.总览

2.DHT22 → ESP32 

DHT22 引脚

ESP32 引脚

说明

VCC

3.3V

供电(3.3V ~ 5V)

GND

GND

共地

DATA

GPIO 4

数据线(内部上拉已启用)

 3.ESP32 → USB to TLL

ESP32 引脚

功能

USB to TLL

说明

GPIO 17

TX

RX

ESP32 发送 → 对端设备接收

GPIO 16

RX

TX

ESP32 接收 ← 对端设备发送

GND

GND

共地(确保参考电平一致)


三、软件环境

  • VS Code +  esp-idf插件
  • 串口助手

四、驱动代码

1.DHT22

dht22.c

#include "dht22.h"
#include "driver/gpio.h"
#include "esp_rom_sys.h"      // 提供 esp_rom_delay_us
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"    // 提供 vTaskDelay
#include "esp_log.h"         // 提供 ESP_LOGI 宏定义

// 宏定义微秒延迟
#define DELAY_US(us)  esp_rom_delay_us(us)

// 引脚操作宏
#define DHT22_PIN  DHT22_GPIO_PIN
#define DHT22_HIGH()  gpio_set_level(DHT22_PIN, 1)      // 拉高总线
#define DHT22_LOW()   gpio_set_level(DHT22_PIN, 0)      // 拉低总线
#define DHT22_READ()  gpio_get_level(DHT22_PIN)         // 读取总线电平

// 设置引脚方向
#define DHT22_OUTPUT()  gpio_set_direction(DHT22_PIN, GPIO_MODE_OUTPUT)
#define DHT22_INPUT()   gpio_set_direction(DHT22_PIN, GPIO_MODE_INPUT)

// 上电初始化
void dht22_init(void){
    gpio_config_t io_conf = {
        .pin_bit_mask = (1ULL << DHT22_PIN),
        .mode = GPIO_MODE_INPUT,
        .pull_up_en = GPIO_PULLUP_ENABLE,
        .pull_down_en = GPIO_PULLDOWN_DISABLE,
        .intr_type = GPIO_INTR_DISABLE
    };
    gpio_config(&io_conf);
    
    // 延时 1 秒,保证传感器稳定
    vTaskDelay(pdMS_TO_TICKS(1000));
    ESP_LOGI("DHT22", "DHT22 初始化完成, 引脚: %d", DHT22_PIN);
}

// 读取原始 40bit 数据(5 字节),成功返回 true
static bool dht22_read_raw(uint8_t data[5]){
    uint8_t i, j;
    uint8_t counter = 0;

    // 1. 主机发起起始信号:拉低总线至少 500us(此处用 2ms)后释放总线,等待 20~40us 后检测从机响应
    DHT22_OUTPUT();
    DHT22_LOW();
    DELAY_US(2000);
    DHT22_HIGH();
    DELAY_US(40);   // 释放后等待 20~40us

    // 2. 切换为输入,检测从机响应
    DHT22_INPUT();

    // 等待从机拉低总线(从机响应低电平约 80us)
    uint32_t timeout = 0;
    while (DHT22_READ() == 1){
        DELAY_US(1);
        if (++timeout > 100) return false;   // 超时
    }
    timeout = 0;
    while (DHT22_READ() == 0){
        DELAY_US(1);
        if (++timeout > 100) return false;  // 超时
    }
    // 响应后的高电平(约 80us),随后进入数据传输阶段
    timeout = 0;
    while (DHT22_READ() == 1){
        DELAY_US(1);
        if (++timeout > 100) return false;  // 超时
    }

    // 3. 读取 40 位数据(5 字节)
    for (j = 0; j < 5; j++){
        data[j] = 0;
        for (i = 0; i < 8; i++){
            // 等待每个 bit 开始的低电平(约 50us),即数据位起始位
            timeout = 0;
            while (DHT22_READ() == 0){
                DELAY_US(1);
                if (++timeout > 100) return false;  // 超时
            }
            // 测量高电平持续时间
            timeout = 0;
            while (DHT22_READ() == 1){
                DELAY_US(1);
                if (++timeout > 100) return false;
            }
            // 高电平持续时间 > 50us 视为 '1',否则为 '0'(高电平持续时间70us表示1,26-28us表示0)
            if (timeout > 40) {
                data[j] |= (1 << (7 - i));   // 高位先出
            }
        }
    }

    // 4. 结束信号:从机拉低 50us 后释放总线(无需特殊处理)
    return true;
}

/*
数据格式:
    40bit数据=16bit湿度数据+16bit温度数据+8bit校验和, 高位先出
校验:
    (湿度高8位+湿度低8位+温度高8位+温度低8位)的末8位=校验和
*/


// CRC 校验:前 4 字节之和的低 8 位等于校验字节
static bool dht22_check_crc(const uint8_t data[5]){
    uint8_t sum = data[0] + data[1] + data[2] + data[3];  // 截取后8位
    return (sum == data[4]);
}

// 解析数据,处理负温度
static void dht22_parse_data(const uint8_t data[5], float *humidity, float *temperature){
    uint16_t rawHum = (data[0] << 8) | data[1];     // 高位左移 8 位,低位直接拼接,形成16位湿度数据
    uint16_t rawTemp = (data[2] << 8) | data[3];    // 高位左移 8 位,低位直接拼接,形成16位温度数据
    
    *humidity = rawHum / 10.0f;

    if (rawTemp & 0x8000){   // 温度最高位为 1 表示负值
        rawTemp = rawTemp & 0x7FFF;
        *temperature = - (rawTemp / 10.0f);
    } 
    else{
        *temperature = rawTemp / 10.0f;
    }
}

// 公开的读取函数
bool dht22_read(float *humidity, float *temperature){
    uint8_t data[5] = {0};
    
    if (!dht22_read_raw(data)) {
        ESP_LOGE("DHT22", "读取原始数据失败");
        return false;
    }
    ESP_LOGI("DHT22", "Raw: %02X %02X %02X %02X %02X", data[0], data[1], data[2], data[3], data[4]);
    if (!dht22_check_crc(data)) {
        ESP_LOGE("DHT22", "校验和错误,计算和=0x%02X, 收到=0x%02X", (data[0]+data[1]+data[2]+data[3]) & 0xFF, data[4]);        
        return false;
    }

    dht22_parse_data(data, humidity, temperature);
    return true;
}

dht22.h

#ifndef DHT22_H
#define DHT22_H

#include <stdint.h>
#include <stdbool.h>

#define DHT22_GPIO_PIN  GPIO_NUM_4

void dht22_init(void);

bool dht22_read(float *humidity, float *temperature);

#endif

CMakeLists.txt

idf_component_register(
    SRCS "dht22.c"
    INCLUDE_DIRS "."
    REQUIRES driver
)

2.串口发送

uart_sender.c

#include "uart_sender.h"
#include "driver/uart.h"
#include "esp_log.h"
#include <stdio.h>
#include <string.h>

static const char *TAG = "UART_SENDER";

void uart_sender_init(void)
{
    // 配置 UART 参数
    const uart_config_t uart_config = {
        .baud_rate  = UART_SEND_BAUDRATE,
        .data_bits  = UART_DATA_8_BITS,
        .parity     = UART_PARITY_DISABLE,
        .stop_bits  = UART_STOP_BITS_1,
        .flow_ctrl  = UART_HW_FLOWCTRL_DISABLE,
        .source_clk = UART_SCLK_DEFAULT,
    };

    // 安装 UART 驱动
    ESP_ERROR_CHECK(uart_driver_install(UART_SEND_PORT, 2048, 0, 0, NULL, 0));

    // 配置 UART 参数
    ESP_ERROR_CHECK(uart_param_config(UART_SEND_PORT, &uart_config));

    // 设置 TX/RX 引脚
    ESP_ERROR_CHECK(uart_set_pin(UART_SEND_PORT,
                                  UART_SEND_TX_PIN,
                                  UART_SEND_RX_PIN,
                                  UART_PIN_NO_CHANGE,   // RTS
                                  UART_PIN_NO_CHANGE)); // CTS

    ESP_LOGI(TAG, "UART 发送器初始化完成, TX:%d, RX:%d, 波特率:%d",
             UART_SEND_TX_PIN, UART_SEND_RX_PIN, UART_SEND_BAUDRATE);
}

bool uart_send_th(float temperature, float humidity)
{
    // 格式化数据: T:xx.x,H:xx.x\n
    char buffer[64];    //发送缓冲区
    int len = snprintf(buffer, sizeof(buffer), "T:%.1f,H:%.1f\r\n", temperature, humidity);

    if (len < 0 || len >= sizeof(buffer)) {
        ESP_LOGE(TAG, "数据格式化失败");
        return false;
    }

    // 通过 UART 发送
    int sent = uart_write_bytes(UART_SEND_PORT, buffer, len);
    if (sent != len) {
        ESP_LOGE(TAG, "UART 发送失败, 预期:%d, 实际:%d", len, sent);
        return false;
    }

    ESP_LOGI(TAG, "已发送: %s", buffer);
    return true;
}

uart_sender.h

#ifndef UART_SENDER_H
#define UART_SENDER_H

#include <stdbool.h>
#include "driver/gpio.h"
#include "driver/uart.h"

// UART 配置参数
#define UART_SEND_PORT      UART_NUM_1      // 使用 UART1,UART0 一般用于调试输出
#define UART_SEND_TX_PIN    GPIO_NUM_17     // TX 引脚
#define UART_SEND_RX_PIN    GPIO_NUM_16     // RX 引脚
#define UART_SEND_BAUDRATE  115200          // 波特率

/**
 * @brief  初始化 UART 发送通道
 */
void uart_sender_init(void);

/**
 * @brief  通过串口发送温湿度数据
 * @param  temperature  温度值 (℃)
 * @param  humidity     湿度值 (%RH)
 * @return true 发送成功, false 发送失败
 */
bool uart_send_th(float temperature, float humidity);

#endif

CMakeLists.txt

idf_component_register(
    SRCS "uart_sender.c"
    INCLUDE_DIRS "."
    REQUIRES driver
)

3.主函数

main.c

#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "dht22.h"
#include "uart_sender.h"
#include "wifi_sender.h"

void app_main(void)
{
    dht22_init();           // 初始化 DHT22 传感器
    uart_sender_init();     // 初始化 UART 有线通道(始终可用)
    wifi_sender_init();     // 初始化 WiFi 无线通道(尽力而为)

    float h, t;
    while (1) {
        // 两次读取之间间隔必须 ≥ 2 秒
        vTaskDelay(pdMS_TO_TICKS(3000));

        if (dht22_read(&h, &t)) {
            // 控制台打印
            printf("湿度: %.1f %%RH, 温度: %.1f ℃\n", h, t);

            // 有线通道: 通过串口发送给 ARM 开发板(始终发送)
            uart_send_th(t, h);

            // 无线通道: 通过 WiFi 发送给 ESP8266 → ARM(尽力而为)
            wifi_sender_send_th(t, h);
        } else {
            printf("读取失败,请检查连接\n");
        }
    }
}

CMakeLists.txt

idf_component_register(
    SRCS "main.c"             
    INCLUDE_DIRS "."
    REQUIRES dht-22 uart-sender wifi-sender
   
)

五、运行示例

串口助手:

esp-idf监视设备:

更多推荐