4. SHT30温湿度传感器驱动:数据手册解读、初始化、单次与连续采集模式实现

SHT30,这颗来自Sensirion的温湿度传感器,是我在项目里用得最多的环境传感器之一。它精度高、体积小,而且I2C接口用起来非常顺手。今天我们就把它彻底吃透。

4.1 先看数据手册:关键信息提取

拿到一颗新芯片,我习惯先翻数据手册的这几页,而不是从头读到尾。你想想看,几百页的文档,哪有时间全看?

关键信息 SHT30参数 说明
I2C地址 0x44 (ADDR引脚接GND)
0x45 (ADDR引脚接VDD)
默认0x44,我一般用这个
供电电压 2.15V ~ 5.5V ESP32的3.3V直接供电没问题
测量范围 温度: -40~125°C
湿度: 0~100%RH
日常环境完全够用
精度 温度: ±0.2°C
湿度: ±2%RH
这个精度在消费级里算很好了
通信速率 最高1MHz ESP32默认400kHz,兼容
我的习惯:拿到新传感器,先把I2C地址和初始化命令记下来。这两个搞定了,驱动就完成了一半。

4.2 初始化流程:让SHT30开始工作

SHT30上电后不会自动测量,你得先发一条命令让它进入工作状态。说白了,就是告诉它"嘿,准备好了吗?"

4.2.1 软复位命令

我个人习惯在初始化第一步先做软复位。为什么?因为如果传感器之前处于异常状态,复位一下能保证后续操作可靠。我曾经遇到过传感器卡死的情况,加个复位就解决了。

// SHT30命令定义
#define SHT30_CMD_SOFT_RESET    0x30A2
#define SHT30_CMD_MEAS_HIGH     0x2400  // 单次测量,高重复性
#define SHT30_CMD_MEAS_MEDIUM   0x240B  // 单次测量,中重复性
#define SHT30_CMD_MEAS_LOW      0x2416  // 单次测量,低重复性
#define SHT30_CMD_PERIODIC_2S   0x2130  // 连续测量,2秒间隔

// 软复位函数
esp_err_t sht30_soft_reset(i2c_port_t i2c_num) {
    uint8_t cmd[2] = {0x30, 0xA2};
    return i2c_master_write_to_device(i2c_num, SHT30_ADDR, cmd, 2, 100);
}
注意:软复位后需要等待至少1.5ms,传感器才能准备好接收下一条命令。别急着发下一帧数据。

4.2.2 检查传感器是否在线

复位之后,我通常会做一个简单的"心跳检测"——发一个空命令或者读一下状态寄存器。如果I2C通信正常,说明传感器在线。这一步在量产测试中特别有用。

esp_err_t sht30_check_connection(i2c_port_t i2c_num) {
    uint8_t data[2] = {0};
    // 尝试读取状态寄存器
    uint8_t cmd[2] = {0xF3, 0x2D};
    esp_err_t ret = i2c_master_write_to_device(i2c_num, SHT30_ADDR, cmd, 2, 100);
    if (ret != ESP_OK) return ret;
    vTaskDelay(pdMS_TO_TICKS(10));
    return i2c_master_read_from_device(i2c_num, SHT30_ADDR, data, 2, 100);
}

4.3 单次采集模式:按需读取

单次模式,说白了就是你叫它一声,它测一次。适合低功耗场景,比如电池供电的设备。我做过一个温湿度记录仪,就是用的单次模式,每5分钟唤醒一次,测完就睡。

4.3.1 触发单次测量

发送0x2400命令,等待测量完成。测量时间取决于重复性设置,高重复性大约需要15ms。

esp_err_t sht30_trigger_single_shot(i2c_port_t i2c_num) {
    uint8_t cmd[2] = {0x24, 0x00};
    return i2c_master_write_to_device(i2c_num, SHT30_ADDR, cmd, 2, 100);
}

4.3.2 读取并解析数据

SHT30返回6个字节:温度高字节、温度低字节、CRC校验、湿度高字节、湿度低字节、CRC校验。嗯,这里要注意,CRC校验可以不做,但做了更可靠。

typedef struct {
    float temperature;
    float humidity;
} sht30_data_t;

esp_err_t sht30_read_data(i2c_port_t i2c_num, sht30_data_t *data) {
    uint8_t buf[6] = {0};
    esp_err_t ret = i2c_master_read_from_device(i2c_num, SHT30_ADDR, buf, 6, 100);
    if (ret != ESP_OK) return ret;

    // 温度转换: 原始值 / 65535 * 175 - 45
    uint16_t raw_temp = (buf[0] << 8) | buf[1];
    data->temperature = -45.0f + 175.0f * raw_temp / 65535.0f;

    // 湿度转换: 原始值 / 65535 * 100
    uint16_t raw_humi = (buf[3] << 8) | buf[4];
    data->humidity = 100.0f * raw_humi / 65535.0f;

    return ESP_OK;
}
完整单次采集流程:
  1. 发送0x2400命令触发测量
  2. 等待15ms(高重复性)
  3. 读取6字节数据
  4. 解析温度、湿度值

4.4 连续采集模式:自动上报

连续模式适合需要实时监控的场景,比如气象站。SHT30支持多种采样频率:0.5Hz、1Hz、2Hz、4Hz、10Hz。我一般用2Hz,够用又不浪费带宽。

4.4.1 启动连续采集

发送0x2130命令,传感器就会以2Hz的频率自动测量并更新内部寄存器。你只需要定时去读就行了。

esp_err_t sht30_start_periodic(i2c_port_t i2c_num) {
    uint8_t cmd[2] = {0x21, 0x30};
    return i2c_master_write_to_device(i2c_num, SHT30_ADDR, cmd, 2, 100);
}

4.4.2 连续模式下的数据读取

启动连续采集后,你随时可以读取最新数据。注意,读操作不会影响传感器的自动测量周期。

void sht30_periodic_task(void *pvParameters) {
    i2c_port_t i2c_num = (i2c_port_t)pvParameters;
    sht30_data_t data;

    // 启动连续采集
    sht30_start_periodic(i2c_num);

    while (1) {
        if (sht30_read_data(i2c_num, &data) == ESP_OK) {
            printf("温度: %.2f°C, 湿度: %.2f%%\n", 
                   data.temperature, data.humidity);
        }
        vTaskDelay(pdMS_TO_TICKS(500));  // 每500ms读一次
    }
}
避坑指南:我曾经在连续模式下忘记停止采集就直接进入休眠,结果传感器一直在工作,白白耗电。记得在休眠前调用停止命令0x3093。

4.5 两种模式对比与选择

对比项 单次模式 连续模式
功耗 低(测完即停) 高(持续测量)
实时性 低(需要手动触发) 高(数据自动更新)
适用场景 电池设备、间歇采集 气象站、实时监控
代码复杂度 简单 中等(需管理周期)

4.6 知识结构图:SHT30驱动全景

SHT30温湿度传感器驱动知识结构 SHT30驱动 数据手册解读 初始化流程 采集模式 I2C地址 测量范围 软复位 连接检测 单次模式 连续模式 核心思路:先读懂手册 → 初始化保证通信正常 → 根据场景选择单次或连续模式

4.7 完整驱动示例

最后,我把单次和连续模式整合成一个完整的驱动文件。你直接拿去用,改一下I2C引脚就能跑。

// sht30_driver.h
#ifndef SHT30_DRIVER_H
#define SHT30_DRIVER_H

#include "driver/i2c.h"

#define SHT30_ADDR          0x44
#define SHT30_CMD_RESET     0x30A2
#define SHT30_CMD_SINGLE    0x2400
#define SHT30_CMD_PERIODIC  0x2130
#define SHT30_CMD_STOP      0x3093

typedef struct {
    float temperature;
    float humidity;
} sht30_data_t;

esp_err_t sht30_init(i2c_port_t i2c_num);
esp_err_t sht30_read_single(i2c_port_t i2c_num, sht30_data_t *data);
esp_err_t sht30_start_continuous(i2c_port_t i2c_num);
esp_err_t sht30_read_continuous(i2c_port_t i2c_num, sht30_data_t *data);
esp_err_t sht30_stop_continuous(i2c_port_t i2c_num);

#endif
// sht30_driver.c
#include "sht30_driver.h"

static esp_err_t sht30_write_cmd(i2c_port_t i2c_num, uint16_t cmd) {
    uint8_t buf[2] = {cmd >> 8, cmd & 0xFF};
    return i2c_master_write_to_device(i2c_num, SHT30_ADDR, buf, 2, 100);
}

esp_err_t sht30_init(i2c_port_t i2c_num) {
    // 软复位
    ESP_ERROR_CHECK(sht30_write_cmd(i2c_num, SHT30_CMD_RESET));
    vTaskDelay(pdMS_TO_TICKS(10));
    return ESP_OK;
}

esp_err_t sht30_read_single(i2c_port_t i2c_num, sht30_data_t *data) {
    // 触发单次测量
    ESP_ERROR_CHECK(sht30_write_cmd(i2c_num, SHT30_CMD_SINGLE));
    vTaskDelay(pdMS_TO_TICKS(20));  // 等待测量完成

    // 读取数据
    uint8_t buf[6];
    ESP_ERROR_CHECK(i2c_master_read_from_device(i2c_num, SHT30_ADDR, buf, 6, 100));

    // 解析
    uint16_t raw_temp = (buf[0] << 8) | buf[1];
    uint16_t raw_humi = (buf[3] << 8) | buf[4];
    data->temperature = -45.0f + 175.0f * raw_temp / 65535.0f;
    data->humidity = 100.0f * raw_humi / 65535.0f;

    return ESP_OK;
}

esp_err_t sht30_start_continuous(i2c_port_t i2c_num) {
    return sht30_write_cmd(i2c_num, SHT30_CMD_PERIODIC);
}

esp_err_t sht30_read_continuous(i2c_port_t i2c_num, sht30_data_t *data) {
    uint8_t buf[6];
    ESP_ERROR_CHECK(i2c_master_read_from_device(i2c_num, SHT30_ADDR, buf, 6, 100));

    uint16_t raw_temp = (buf[0] << 8) | buf[1];
    uint16_t raw_humi = (buf[3] << 8) | buf[4];
    data->temperature = -45.0f + 175.0f * raw_temp / 65535.0f;
    data->humidity = 100.0f * raw_humi / 65535.0f;

    return ESP_OK;
}

esp_err_t sht30_stop_continuous(i2c_port_t i2c_num) {
    return sht30_write_cmd(i2c_num, SHT30_CMD_STOP);
}
小技巧:如果你发现读取的数据偶尔跳变,可以在解析前加一个简单的滑动平均滤波。我一般取最近5次数据的平均值,效果很好。