ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

SHT31 温度湿度传感器驱动:从协议原理到完整驱动实现

SHT31 温度湿度传感器驱动:从协议原理到完整驱动实现 1. 引言为什么选择 SHT31在温湿度采集领域Sensirion 的 SHT 系列传感器凭借体积小、精度高、功耗低的特点长期占据工业与消费类产品的核心位置。SHT31 是该家族中的经典型号它相比早期的 SHT10、SHT20 在响应速度、长期稳定性和接口友好度上都有明显提升。对于嵌入式工程师来说SHT31 的 I2C 接口简洁、命令集规整非常适合作为传感器驱动开发的学习样板也能直接用于量产产品。SHT31 官方典型指标如下温度测量范围 -40 到 125 摄氏度分辨率 0.01 摄氏度典型精度正负 0.3 摄氏度湿度测量范围 0 到 100%RH分辨率 0.01%RH典型精度正负 2%RH。从这些指标可以看出它既能满足普通环境监测也能覆盖冷链运输、工业过程监控等较宽温区场景。本文会从传感器本身的硬件与协议讲起逐步深入到驱动架构设计最后给出一套可跨平台移植的完整 C 驱动实现。无论你使用的是 STM32、ESP32、GD32 还是其他带有 I2C 外设的 MCU都能把本文的驱动框架直接落到工程中使用。2. SHT31 传感器概述2.1 核心特性SHT31 属于 SHT3x 系列温湿度传感器内部集成了电容式湿度敏感元件和带隙温度敏感元件。与分立方案相比它在封装内就完成了模拟信号调理、模数转换、校准系数管理和数字接口封装用户通过 I2C 总线读到的已经是经过线性化和温度补偿的原始数字量剩下只需要做简单的公式换算。它的关键特性可以归纳为以下几点全数字化输出内部 16 位 ADC 完成采样无需外接运放和滤波电路。出厂校准每个传感器在出厂时都经过精密校准校准系数存储在内部无需用户再做多点标定。I2C 接口最高支持 1MHz 的快速模式加强版通信速率地址可通过地址引脚配置。多种测量模式支持单次测量、周期测量和带报警阈值的周期测量。低功耗单次测量模式下典型平均电流仅 1 微安级别非常适合电池供电设备。内置加热器可用于自检、防结露和在高湿环境下做短期除湿。2.2 封装与引脚常见的 SHT31-DIS 采用 DFN 封装尺寸只有 2.5mm 乘 2.5mm 乘 0.9mm在 PCB 上占用面积非常小。引脚功能如下引脚名称功能说明1VDD电源正极SHT31-DIS 供电范围 2.15V 到 3.6VSHT31-ARP 可到 5.5V2SDAI2C 数据线3SCLI2C 时钟线4ADDR地址选择引脚接地时地址 0x44接 VDD 时地址 0x455ALERT周期测量模式下的报警输出开漏输出需外接上拉电阻6R接地引脚连接到电源地需要特别注意的是SHT31 对电源上电速度有要求。官方建议 VDD 上电时间不超过 1 毫秒如果上电太慢可能触发内部不完全复位导致通信异常。因此在电源设计时应避免使用上升沿很缓的电源方案或者在启动后由 MCU 主动发送一次软复位命令。3. I2C 通信基础与硬件接线3.1 I2C 总线回顾I2C 是一种两线制同步串行总线由 SCL 时钟线和 SDA 数据线构成。SHT31 始终作为从机存在所有通信都由主机 MCU 发起。一次完整的读写通常遵循这样的流程主机发送起始条件然后发送 7 位从机地址加 1 位读写方向从机应答后再传输命令字节或数据字节最后主机发送停止条件。在 7 位地址模式下SHT31 的从机地址由 ADDR 引脚决定ADDR 引脚状态7 位地址写地址字节读地址字节接地0x440x880x89接 VDD0x450x8A0x8B这里写地址字节等于 7 位地址左移一位再加 0读地址字节等于 7 位地址左移一位再加 1。0x44 左移一位得到 0x88写操作用 0x88读操作用 0x89。驱动代码中一般直接保存 7 位地址在收发函数里统一做移位处理这样切换地址时只需要改一个宏。3.2 硬件电路设计典型电路非常简单。VDD 与 GND 之间放置 100nF 去耦电容尽量靠近传感器引脚。SDA 和 SCL 各自通过 4.7k 到 10k 电阻上拉到 VDD。ADDR 引脚直接接地或接 VDD 以选择地址不要悬空。如果使用周期测量模式的 ALERT 报警功能ALERT 引脚需要额外接一个上拉电阻并连接到 MCU 的中断输入引脚。布局上要注意传感器底部焊盘通常也需要接地同时应避免将传感器放置在发热器件附近否则热源会直接影响温度测量精度。传感器的感应窗口应尽量朝向被测环境避免被外壳完全密闭导致响应变慢。3.3 通信速率与时钟拉伸SHT31 支持标准模式 100kHz、快速模式 400kHz 和快速模式加强版 1MHz。需要注意的是时钟拉伸功能只在某些测量命令下有意义。所谓时钟拉伸是指从机在准备数据期间把 SCL 拉低让主机等待数据就绪后再释放 SCL。对于没有正确实现时钟拉伸检测的主机程序建议统一使用不带时钟拉伸的命令由主机通过延时或轮询的方式等待转换完成。4. SHT31 命令系统详解4.1 命令格式SHT31 的所有操作都是 16 位命令。主机通过 I2C 写入两个命令字节第一个字节是命令高 8 位第二个字节是命令低 8 位。部分命令后需要跟额外数据。命令的两个字节通常不是随机定义的第二个字节的低位段往往带有规律掌握这些规律有助于理解命令含义。命令可以分为以下几大类单次测量命令、周期测量命令、状态寄存器命令、加热器命令、软复位命令、停止周期测量命令以及读取芯片标识命令。4.2 单次测量命令单次测量命令又分为时钟拉伸使能和时钟拉伸禁用两组并且可以指定测量精度。精度等级的可重复性从高到低分别为低、中、高重复性越高测量时间越长噪声越小。下表汇总了常用的单次测量命令命令十六进制重复性时钟拉伸单次测量0x2400高禁用单次测量0x240B中禁用单次测量0x2416低禁用单次测量0x2C06高使能单次测量0x2C0D中使能单次测量0x2C10低使能观察规律可以发现第一字节 0x24 表示禁用时钟拉伸0x2C 表示使能时钟拉伸。第二字节的低 4 位决定重复性0x00、0x0B、0x16 分别对应高、中、低重复性。高重复性的典型测量时间约 15 毫秒中重复性约 6 毫秒低重复性约 4 毫秒实际工程中如果需要快速刷新可以选择中或低重复性。4.3 周期测量命令周期测量模式让传感器自动按照设定频率重复采样MCU 只需要在需要数据时发起读取即可。周期测量可以配合 ALERT 报警功能做到阈值自动监控。常用周期测量命令如下命令十六进制每秒测量次数周期测量0x20320.5 次每秒周期测量0x20241 次每秒周期测量0x202F2 次每秒周期测量0x20224 次每秒周期测量0x202110 次每秒周期测量命令自身的重复性固定为中等重复性不需要像单次测量那样选择精度。进入周期测量模式后读取数据的命令统一使用 0xE000。要退出周期测量模式需要发送停止周期测量命令 0x3093这相当于一个中断操作让传感器停止自动采样。4.4 状态寄存器与系统命令除测量命令外SHT31 还提供一组系统级命令命令十六进制说明读状态寄存器0xF32D返回 16 位状态字加 CRC清除状态寄存器0x3041清除所有状态标志软复位0x30A2复位传感器内部逻辑停止周期测量0x3093退出周期测量模式加热器开启0x306D打开内部加热器加热器关闭0x3066关闭内部加热器读取序列号0x3780返回芯片序列号用于溯源软复位是上电初始化时非常有用的命令。由于 SHT31 对电源上电速度敏感有些系统上电后直接通信可能失败此时先执行软复位并等待 1 毫秒以上可以有效提高初始化成功率。5. 测量模式详解5.1 单次测量模式单次测量模式是所有电池供电设备首选的工作方式。主机发送一条单次测量命令传感器完成一次转换主机再读取 6 字节数据随后传感器自动进入空闲低功耗状态。整个周期内平均电流极小非常适合定时唤醒采集的场景比如每 10 秒采集一次的无线温湿度节点。单次测量模式的数据读取流程如下先通过 I2C 写操作发送 16 位测量命令等待测量完成后执行一次读操作读取 6 个字节。这 6 个字节依次是温度高字节、温度低字节、温度 CRC、湿度高字节、湿度低字节、湿度 CRC。温度数据在前湿度数据在后每个 16 位数据后都紧跟一个 8 位 CRC 校验字节。如果使用时钟拉伸使能的命令传感器会在数据准备好之前把 SCL 拉低主机无需手动延时读操作会被传感器自动延长。如果使用时钟拉伸禁用的命令主机必须等待标称测量时间后再发起读操作。两种方式各有利弊时钟拉伸方式控制更简单但要求主机的 I2C 控制器能正确处理时钟拉伸手动延时方式兼容性最好几乎在任何平台上都能稳定工作。5.2 周期测量模式周期测量模式适合传感器连续工作、主机周期性取数的场景。传感器进入周期测量模式后会按照设定的频率自动测量并更新内部数据使用 0xE000 命令读取时它会返回最新一次转换结果。周期测量模式的优势在于读取延迟非常低因为数据始终是最新的不需要临时等待转换。周期测量模式有三种变体主要是为了配合报警功能。基础周期测量每秒测量 0.5 次、1 次、2 次、4 次或 10 次。带报警阈值的周期测量则额外允许设置温度上限、湿度上限、温度下限和湿度下限当测量值越界时ALERT 引脚会被拉低主机可以通过外部中断立即响应。周期测量模式下的读取命令 0xE000 会返回 6 字节数据排列方式与单次测量完全一致都是温度两字节加 CRC、湿度两字节加 CRC。因此无论是单次还是周期模式驱动层的数据解析函数可以完全复用。6. CRC 校验原理与实现6.1 为什么需要 CRCI2C 总线本身没有硬件校验机制传输过程中如果受到电磁干扰数据位可能发生翻转。对于温湿度这种对连续性较敏感的数据一个错误的高字节足以让温度值跳变几十度。SHT31 在每个 16 位测量数据后附加一个 8 位 CRC用来检测传输错误这是工业级传感器保证数据可靠性的标准做法。6.2 CRC-8 算法SHT31 使用的 CRC-8 多项式是 x8 加 x5 加 x4 加 1二进制表示为 0x31初始值为 0xFF。校验数据只包含该组的前两个字节不包含 CRC 字节本身。校验时把前两个字节与第三个字节一起送入 CRC 计算函数如果最终结果等于 0说明数据没有错误。CRC-8 的逐位计算方法如下为每个输入字节执行 8 次移位每次先判断最高位是否为 1如果为 1则左移后与多项式 0x31 异或否则直接左移。这种位运算法虽然直观但速度较慢。工程中更常用查表法预先把 0 到 255 每个字节对应的 CRC 值计算出来存成表查表一次就能完成一个字节的 CRC 更新效率提高数倍。下面给出一个不带查表的 CRC-8 参考实现uint8_t sht31_crc8(const uint8_t *data, uint8_t len) { uint8_t crc 0xFF; for (uint8_t i 0; i len; i) { crc ^ data[i]; for (uint8_t bit 0; bit 8; bit) { if (crc 0x80) { crc (crc 1) ^ 0x31; } else { crc 1; } } } return crc; }这段代码中外层循环处理每个字节内层循环按位处理。校验时把温度两个字节和温度 CRC 三个字节一起传入函数返回 0 表示校验通过。实际使用时常会给 CRC 计算函数做一个包装比如校验温度与校验湿度分开判断这样出现错误时能立即定位到是温度段还是湿度段发生异常。6.3 常见失败原因CRC 校验失败的常见原因包括I2C 总线速率过高导致信号质量差、上拉电阻过大使上升沿变缓、线路过长带来反射和串扰、传感器供电不稳等。遇到 CRC 持续失败时优先级最高的排查动作是降低 I2C 速率到 100kHz 再测试同时用示波器观察 SDA 和 SCL 的波形质量。7. 温湿度数据转换7.1 温度计算公式SHT31 返回的温度原始值是 16 位无符号整数范围是 0 到 65535。官方给出温度换算公式如下其中 St 表示温度原始值T -45 175 × (St / 65535)这个公式把 0 映射到 -45 摄氏度把 65535 映射到 130 摄氏度。虽然传感器的标称上限是 125 摄氏度但公式的上限留有余量。计算出的温度单位是摄氏度。在工程中为了保留小数精度通常会先放大后除以比如把结果放大 100 倍得到百分之一摄氏度单位的整数再进行显示和比较。7.2 湿度计算公式湿度原始值 Srh 同样是 16 位无符号数公式如下RH 100 × (Srh / 65535)湿度的最小值是 0对应 0%RH最大值 65535 对应 100%RH。本质上湿度原始值就是相对湿度的线性映射因此转换极为简单。7.3 定点数处理技巧在资源有限的 MCU 上浮点数运算可能带来额外的代码体积和运行时间。对于精确定时要求严格的场景可以把浮点运算转换为整数运算。例如温度把原始值先乘以 17500 再除以 65535就能得到放大 100 倍的温度值。若需要更高精度放大倍数可以继续提高例如放大 1000 倍再统一处理。这种定点计算方法是驱动层常用的优化手段。typedef struct { uint16_t raw_temperature; uint16_t raw_humidity; } sht31_raw_data_t; typedef struct { int32_t temperature_mc; /* 温度单位 0.01 摄氏度 */ int32_t humidity_mrh; /* 湿度单位 0.01 %RH */ } sht31_th_data_t;上面定义了原始数据结构和物理量数据结构原始温度与湿度分别保存 16 位原始值物理量则保存放大 100 倍的整数值。这样的结构让上层既能看到原始值用于调试又能直接取物理值用于业务逻辑。8. 驱动架构设计8.1 分层思想一个可维护、可移植的传感器驱动通常分为硬件抽象层、协议逻辑层和业务应用层。硬件抽象层负责 I2C 的读写时序、延时等平台相关操作协议逻辑层负责拼装命令、解析数据、校验 CRC这一层与硬件无关业务应用层则根据产品需求决定单次测量还是周期测量、是否设置报警阈值、如何做数据滤波。分层的好处非常明显把 SHT31 驱动从 STM32 移植到 ESP32只需要改硬件抽象层的几个函数其他代码一行不动。即使将来更换主控芯片驱动的核心逻辑也保持稳定这大大降低了维护成本。8.2 硬件抽象层接口驱动需要平台提供的接口通常只有两个写数据与读数据。为了支持寄存器式访问和命令式访问两种风格可以定义如下接口typedef struct { int (*i2c_write)(uint8_t addr, const uint8_t *buf, uint16_t len); int (*i2c_read)(uint8_t addr, uint8_t *buf, uint16_t len); void (*delay_ms)(uint32_t ms); } sht31_hal_t;这个结构体把 I2C 写、I2C 读和毫秒延时三个平台相关操作抽象为函数指针。初始化驱动时把具体平台的实现函数绑定进去。后续所有协议层代码都通过这个结构体访问硬件绝不直接调用平台 API。8.3 设备描述结构驱动内部需要一个设备句柄来保存从机地址和硬件抽象层指针。所有驱动 API 的第一个参数都是这个句柄以支持总线上同时挂载多个 SHT31 的场景。typedef struct { uint8_t addr; sht31_hal_t hal; uint8_t is_initialized; } sht31_dev_t;这种句柄式设计在很多成熟的驱动库中都能看到例如 Linux 内核中的 i2c_client。它让一个驱动实例化多个设备变得自然也便于后续做单元测试时替换硬件。9. 驱动代码实现命令与初始化9.1 命令定义驱动代码的第一部分是把所有命令做成宏或枚举。宏定义清晰直观也是后续维护的重点。下面给出完整命令集#define SHT31_CMD_MEAS_HIGH_NOCS 0x2400 #define SHT31_CMD_MEAS_MED_NOCS 0x240B #define SHT31_CMD_MEAS_LOW_NOCS 0x2416 #define SHT31_CMD_MEAS_HIGH_CS 0x2C06 #define SHT31_CMD_MEAS_MED_CS 0x2C0D #define SHT31_CMD_MEAS_LOW_CS 0x2C10 #define SHT31_CMD_PERIODIC_0P5 0x2032 #define SHT31_CMD_PERIODIC_1 0x2024 #define SHT31_CMD_PERIODIC_2 0x202F #define SHT31_CMD_PERIODIC_4 0x2022 #define SHT31_CMD_PERIODIC_10 0x2021 #define SHT31_CMD_READ_DATA 0xE000 #define SHT31_CMD_READ_STATUS 0xF32D #define SHT31_CMD_CLEAR_STATUS 0x3041 #define SHT31_CMD_SOFT_RESET 0x30A2 #define SHT31_CMD_STOP_PERIODIC 0x3093 #define SHT31_CMD_HEATER_ON 0x306D #define SHT31_CMD_HEATER_OFF 0x3066 #define SHT31_CMD_READ_SERIAL 0x3780 #define SHT31_DEFAULT_ADDR 0x44 #define SHT31_POLY_CRC8 0x31 #define SHT31_CRC8_INIT 0xFF #define SHT31_MEAS_WAIT_HIGH_MS 15 #define SHT31_MEAS_WAIT_MED_MS 6 #define SHT31_MEAS_WAIT_LOW_MS 4每一个宏都对应数据手册中的一条命令注释应当标注命令含义。延时宏存放不同重复性对应的典型测量时间使用时钟拉伸禁用命令时主机按照这些数值等待即可。9.2 发送 16 位命令SHT31 的所有命令都是两个字节因此可以把发送命令封装成一个统一函数。命令传输时通常先发高字节再发低字节也就是大端序。static int sht31_send_command(const sht31_dev_t *dev, uint16_t cmd) { uint8_t buf[2]; buf[0] (uint8_t)((cmd 8) 0xFF); buf[1] (uint8_t)(cmd 0xFF); return dev-hal.i2c_write(dev-addr, buf, 2); }这个函数内部把 16 位命令拆成两个字节然后调用硬件抽象层的写接口。返回值沿用底层返回约定一般 0 表示成功负数表示错误。9.3 初始化与软复位初始化流程通常包括绑定硬件层、发送软复位、等待复位完成。软复位后传感器进入空闲状态如果之前处于周期模式也会被退出。初始化函数同时校验底层 I2C 读写是否正常确保通信可用。int sht31_init(sht31_dev_t *dev, const sht31_hal_t *hal, uint8_t addr) { if ((dev NULL) || (hal NULL) || (hal-i2c_write NULL) || (hal-i2c_read NULL)) { return -1; } dev-addr addr; dev-hal *hal; dev-is_initialized 0; if (sht31_send_command(dev, SHT31_CMD_SOFT_RESET) ! 0) { return -2; } if (dev-hal.delay_ms ! NULL) { dev-hal.delay_ms(1); } dev-is_initialized 1; return 0; }函数首先对传入参数做完整性检查避免空指针导致崩溃。随后保存地址和硬件层发送软复位命令等待 1 毫秒让内部逻辑稳定最后标记初始化完成。所有错误都通过返回码区分方便上层根据不同错误执行对应处理。10. 驱动代码实现单次测量读取10.1 测量与读取流程单次测量的完整流程可以抽象为三步发送测量命令、等待转换完成、读取并解析 6 字节数据。针对时钟拉伸禁用模式等待时间由重复性决定针对时钟拉伸使能模式可以跳过等待但为了代码统一也可以保留极短延时。下面实现一个接口允许调用者指定测量命令从而自由选择重复性和时钟拉伸配置static int sht31_read_raw(const sht31_dev_t *dev, uint16_t meas_cmd, uint32_t wait_ms, uint8_t data[6]) { if (sht31_send_command(dev, meas_cmd) ! 0) { return -1; } if (dev-hal.delay_ms ! NULL) { dev-hal.delay_ms(wait_ms); } if (dev-hal.i2c_read(dev-addr, data, 6) ! 0) { return -2; } return 0; }这个函数先发送测量命令再延迟等待最后连续读取 6 字节。之所以把测量命令和等待时间作为参数传入是因为不同调用方对精度和响应速度的需求不同一个函数即可覆盖所有单次测量场景。10.2 高频采集接口在业务层最常见的需求是高精度单次测量。可以基于上面的底层函数封装一个专用接口int sht31_read_th_high_res(const sht31_dev_t *dev, sht31_th_data_t *th) { uint8_t data[6]; uint16_t temp_raw; uint16_t humi_raw; if (sht31_read_raw(dev, SHT31_CMD_MEAS_HIGH_NOCS, SHT31_MEAS_WAIT_HIGH_MS, data) ! 0) { return -1; } temp_raw ((uint16_t)data[0] 8) | data[1]; if (sht31_crc8(data, 3) ! 0) { return -2; } humi_raw ((uint16_t)data[3] 8) | data[4]; if (sht31_crc8(data[3], 3) ! 0) { return -3; } th-temperature_mc sht31_calc_temperature_mc(temp_raw); th-humidity_mrh sht31_calc_humidity_mrh(humi_raw); return 0; }这段代码的核心是数据校验。温度原始值由前两个字节拼成随后用前三个字节做 CRC 校验返回非 0 表示温度段传输错误湿度同理。只有两段都校验通过才进行物理量转换。CRC 校验放在转换之前保证错误数据不会进入后续业务逻辑。10.3 转换函数温度与湿度的定点转换函数如下int32_t sht31_calc_temperature_mc(uint16_t raw) { return (-4500L ((int32_t)raw * 17500L) / 65535L); } int32_t sht31_calc_humidity_mrh(uint16_t raw) { return ((int32_t)raw * 10000L) / 65535L; }温度函数中 -4500L 表示 -45.00 摄氏度raw 乘以 17500 再除以 65535得到放大 100 倍的温度增量两部分相加即为最终温度。湿度函数把 raw 映射到 0 到 10000也就是 0.00%RH 到 100.00%RH。使用 int32_t 存放中间结果避免 16 位乘法溢出。11. 驱动代码实现周期测量与状态管理11.1 启动周期测量周期测量模式下传感器会持续自动采样。启动时传入一个周期测量命令例如每秒测量一次对应 0x2024。启动后进入空闲等待传感器内部会以设定频率更新数据。int sht31_start_periodic(const sht31_dev_t *dev, uint16_t period_cmd) { return sht31_send_command(dev, period_cmd); }11.2 读取周期测量数据周期模式下的数据读取与单次模式不同它使用固定的读取命令 0xE000不需要等待转换完成因为传感器已经在后台持续刷新。读取后同样返回 6 字节并做 CRC 校验。int sht31_read_periodic_th(const sht31_dev_t *dev, sht31_th_data_t *th) { uint8_t data[6]; uint16_t temp_raw; uint16_t humi_raw; if (sht31_send_command(dev, SHT31_CMD_READ_DATA) ! 0) { return -1; } if (dev-hal.i2c_read(dev-addr, data, 6) ! 0) { return -2; } temp_raw ((uint16_t)data[0] 8) | data[1]; if (sht31_crc8(data, 3) ! 0) { return -3; } humi_raw ((uint16_t)data[3] 8) | data[4]; if (sht31_crc8(data[3], 3) ! 0) { return -4; } th-temperature_mc sht31_calc_temperature_mc(temp_raw); th-humidity_mrh sht31_calc_humidity_mrh(humi_raw); return 0; }周期读取与单次读取的解析部分几乎一致说明良好的底层封装可以显著减少重复代码。两者唯一差别在于测量命令不同以及是否需要等待转换。11.3 状态寄存器状态寄存器共 16 位其中只有部分位有效。通过读状态寄存器可以了解报警状态、加热器状态以及最后一次测量是否有校验错误。读状态命令发送后传感器返回两个状态字节和一个 CRC 字节。int sht31_read_status(const sht31_dev_t *dev, uint16_t *status) { uint8_t buf[3]; if (sht31_send_command(dev, SHT31_CMD_READ_STATUS) ! 0) { return -1; } if (dev-hal.i2c_read(dev-addr, buf, 3) ! 0) { return -2; } if (sht31_crc8(buf, 3) ! 0) { return -3; } *status ((uint16_t)buf[0] 8) | buf[1]; return 0; }获取状态后可以按位判断。状态字的第 15 位是报警挂起标志第 14 位无定义第 13 位是加热器状态第 11 位是湿度报警第 10 位是温度报警第 4 位是系统复位检测。检测到异常后通常读取状态并执行清除状态寄存器命令为下一次报警检测做好准备。12. 加热器功能与自检12.1 加热器的作用SHT31 内部集成一个小功率加热器它的主要用途有三个。第一是高湿环境下防止结露当传感器表面凝结水珠时湿度读数会达到 100% 并暂时失效短时间开启加热器可以驱散水汽。第二是功能自检开启加热器后温度读数应该明显上升如果温度毫无变化说明传感器可能损坏或接触不良。第三是某些气流测量应用中通过加热改变局部温度差来辅助判断。12.2 开启与关闭加热器控制非常简单发送开启命令 0x306D 或关闭命令 0x3066 即可。加热器可以与其他测量命令叠加使用也就是先开启加热器再发送单次测量命令同样能正常测量。int sht31_heater_enable(const sht31_dev_t *dev) { return sht31_send_command(dev, SHT31_CMD_HEATER_ON); } int sht31_heater_disable(const sht31_dev_t *dev) { return sht31_send_command(dev, SHT31_CMD_HEATER_OFF); }需要注意的是加热器会明显增加整机功耗同时加热过程会使温度读数高于环境温度。因此加热器只应在需要时短时间开启加热结束后还要等待传感器恢复到环境温度温度读数才有意义。加热器开启期间采集的温湿度数据不能直接当作环境值使用。12.3 加热自检流程一个完整的自检流程是先关闭加热器读取一次基准温度开启加热器等待几十毫秒再次读取温度与基准温度比较。如果温差超过设定阈值说明加热器工作正常。反之则提示硬件异常。这个自检可以在出厂测试阶段执行也可以在设备运行过程中定时做健康检查。13. 报警模式与阈值配置13.1 报警模式命令SHT31 的报警周期测量模式允许设置温度和湿度的上下限。当测量值越过阈值时内部报警标志置位ALERT 引脚被拉低。主机可以通过查询状态寄存器或触发外部中断来获知报警事件。报警周期的测量频率命令与基础周期测量不同需要查阅数据手册中带报警功能的对应命令。报警阈值的写入命令格式比较特殊。以设置温度上限为例主机先发送阈值写命令再写入数值高字节、数值低字节和 CRC 字节共三个字节。阈值数值采用与测量数据相同的 16 位原始值格式因此设置前需要把目标摄氏温度按公式反向换算成原始值。13.2 阈值反向换算温度原始值与摄氏温度的关系在前面已经给出反向换算公式为St (T 45) × 65535 / 175湿度原始值反向换算更为直接Srh RH × 65535 / 100这两个公式可以把人类可读的阈值转换为传感器需要的原始值。设置阈值程序可以把摄氏度输入换算为原始值再按大端序写入并附带 CRC。13.3 报警处理建议报警模式下MCU 可以长时间休眠把 ALERT 引脚接到外部中断输入。一旦温湿度越界传感器主动拉低 ALERTMCU 被唤醒后读取状态寄存器确认是哪一项报警执行对应业务动作最后清除报警标志。这种方式可以让系统在无异常时保持极低功耗同时又不会错过关键事件非常适合冷链运输记录仪、仓库环境监控等场景。14. 低功耗设计实践14.1 功耗估算低功耗应用的核心是理解每一毫安电流花在哪里。SHT31 在空闲状态下电流通常只有几微安主要功耗集中在测量转换期间。单次测量高重复性的转换时间约 15 毫秒转换期间电流约 800 微安那么一次测量的耗电约等于 800 微安乘以 15 毫秒换算后大约 3.3 微安时。如果每分钟测量一次平均电流几乎可以忽略此时系统总功耗往往被 MCU 的睡眠电流和无线通信功耗主导。14.2 建议的采集策略对于电池供电设备推荐采用单次测量加深度睡眠的组合。MCU 定时唤醒后先给传感器上电等待稳定后发起单次高重复性测量读取并校验数据随后关闭传感器电源或让传感器进入空闲MCU 回到睡眠状态。如果系统可以控制传感器的 VDD还可以把传感器完全断电彻底消除空闲电流。在周期测量模式下永远不要让 MCU 以轮询方式高频询问传感器。正确做法是把测量频率和 MCU 唤醒周期对齐或者干脆改用 ALERT 报警中断来替代轮询从而最大限度减少无效唤醒。14.3 电源瞬态注意测量转换过程中传感器内部数字电路和 ADC 都会工作电流会从微安级瞬时上升到毫安级。如果传感器供电走线过长或去耦电容太小瞬态电流会导致供电电压跌落轻则测量值抖动重则通信中断。建议在 VDD 引脚旁放置至少 100nF 电容必要时再并联 1uF 到 4.7uF 电容保证瞬态响应。15. 调试方法与常见问题排查15.1 通信失败的排查路径调试 I2C 传感器时最常遇到的第一个问题就是初始化后读不到数据、CRC 错误或设备无应答。排查时建议按照从硬件到软件的顺序逐层确认检查地址用示波器或逻辑分析仪确认地址字节是否与 ADDR 引脚配置一致0x44 和 0x45 混淆是最常见的低级错误。检查上拉确认 SDA 和 SCL 都有上拉电阻且在 100kHz 速率下波形上升沿足够陡峭。降低速率如果高频率下不稳定先降到 100kHz 测试排除信号质量问题。软复位在初始化流程最前面加软复位和延时排除上电时序导致的内部状态异常。检查电源测量传感器 VDD 在通信和转换期间的电压波动确认没有低于最低工作电压。15.2 数据异常的表现与原因数据异常可以分为几类。读数恒定为 0 或 65535往往说明传感器还没完成转换就被读取或者 I2C 总线出现了挂死。温度读数明显偏高可能是传感器靠近发热芯片或者加热器没有关闭。湿度读数长期为 100%很可能是传感器表面结露或受污染。温度湿度来回跳变但 CRC 一直通过多半是采样策略不合适需要做滤波处理。15.3 常用滤波方法温湿度数据容易受到气流、人体活动、设备发热等短时干扰。常用的滤波手段有中值滤波、滑动平均和加权指数滤波。中值滤波对偶发的脉冲毛刺特别有效取连续 5 次采样中间值即可。滑动平均能平滑高频抖动但响应稍慢。加权指数滤波实现简单内存占用小通过调节系数可以在响应速度和稳定性之间平衡是嵌入式应用中最常用的选择。16. 完整工程示例STM32 平台适配16.1 HAL 层适配代码以 STM32 的 HAL 库为例硬件抽象层的三个函数可以这样实现static int stm32_i2c_write(uint8_t addr, const uint8_t *buf, uint16_t len) { HAL_StatusTypeDef st HAL_I2C_Master_Transmit( hi2c_instance, (uint16_t)(addr 1), (uint8_t *)buf, len, 100); return (st HAL_OK) ? 0 : -1; } static int stm32_i2c_read(uint8_t addr, uint8_t *buf, uint16_t len) { HAL_StatusTypeDef st HAL_I2C_Master_Receive( hi2c_instance, (uint16_t)((addr 1) | 1), buf, len, 100); return (st HAL_OK) ? 0 : -1; } static void stm32_delay_ms(uint32_t ms) { HAL_Delay(ms); }这里 hi2c_instance 是 STM32 CubeMX 生成的 I2C 句柄全局变量实际工程中可以改成设备句柄里的指针。写地址为地址左移一位读地址为左移一位后或 1这与 SHT31 协议完全一致。超时时间设置为 100 毫秒如果总线异常函数会返回错误而不是无限阻塞。16.2 初始化硬件层把平台函数绑定到驱动结构体的过程如下static sht31_dev_t g_sht31; static sht31_hal_t g_hal { .i2c_write stm32_i2c_write, .i2c_read stm32_i2c_read, .delay_ms stm32_delay_ms, }; int sht31_platform_init(void) { return sht31_init(g_sht31, g_hal, SHT31_DEFAULT_ADDR); }整个平台的差异被收敛到这一个初始化位置。以后移植到其他平台只需要替换 hal 结构体中三个函数指针驱动核心完全不动。16.3 业务层调用业务层通过一个定时任务周期调用采集接口并把结果送给显示或上报模块。典型的采集处理如下void temp_humi_task(void) { sht31_th_data_t th; if (sht31_read_th_high_res(g_sht31, th) 0) { display_update(th.temperature_mc, th.humidity_mrh); report_upload(th.temperature_mc, th.humidity_mrh); } else { handle_sensor_error(); } }当读取接口返回 0 表示成功业务层才使用数据。返回负值则进入错误处理。这个简单的调用模式已经能覆盖大多数温湿度采集产品的核心逻辑。17. 进阶话题多传感器与滤波17.1 总线多个传感器一条 I2C 总线上理论上可以挂接两个 SHT31分别使用地址 0x44 和 0x45。再多的话就需要借助 I2C 多路复用器或者使用不同总线的独立通道。驱动句柄设计中每个设备实例保存自己的地址因此多传感器场景下只需要创建两个设备句柄并分别初始化即可读取逻辑完全复用。17.2 温湿度滤波策略如果直接把原始转换值展示给用户数值会有明显抖动。建议在驱动层之上增加一个滤波层对温度采用加权指数滤波对湿度采用滑动平均。温度响应快主要滤除瞬时噪声湿度响应慢需要处理气流的阶跃变化。两者参数应独立调节不要共用一套滤波系数。17.3 自校准与长期漂移SHT31 出厂校准数据已经保证了典型精度但长期暴露在污染、高湿或化学气体环境中精度仍可能发生漂移。对于高可靠应用建议周期性做基准校验。例如在恒温恒湿箱中多点比对建立修正表写入 MCU 存储。部分高端应用会同时挂接两个传感器做冗余互检当两者读数偏差超过阈值时触发维护告警。18. 总结SHT31 是一款协议清晰、资料完善、工程友好的温湿度传感器。本文从硬件引脚、命令系统、CRC 校验、数据换算讲到分层驱动设计并给出了可移植的完整 C 驱动框架。总结起来掌握一个传感器驱动的关键在于三件事透彻理解数据传输协议、严格做好错误处理与校验、把平台相关代码和协议逻辑彻底分离。在实际项目中建议优先使用单次测量模式配合低功耗调度数据校验严格把关错误路径要完备。只要按照本文的思路落地SHT31 驱动可以在很短的时间内完成移植和验证为产品提供稳定可靠的温湿度采集能力。
返回列表