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;
}
完整单次采集流程:
- 发送0x2400命令触发测量
- 等待15ms(高重复性)
- 读取6字节数据
- 解析温度、湿度值
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驱动全景
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次数据的平均值,效果很好。