☰
ESP32硬件IIC读取SHT30全攻略:从地址配置到CRC校验与排坑
2026/10/16 19:09:32 网站建设 项目流程

简介:面向嵌入式开发者与物联网爱好者的ESP32硬件I2C读取SHT30温湿度传感器示例工程,基于ESP-IDF环境,从硬件接线、GPIO引脚配置、I2C时钟设置,到传感器初始化和数据读取均有清晰实现。SHT30作为高精度数字温湿度传感器,支持单次和连续测量,配合ESP32硬件I2C接口可稳定获取环境数据。资源解压后共15个文件,以C源码(.c/.h)、构建脚本(CMakeLists、Makefile)、配置文件(sdkconfig、json)、容器化配置(Dockerfile、devcontainer)及说明文档(README.md)为主,整体仅21KB,结构精简,便于直接导入ESP-IDF工程或移植到其他平台。目前已有1509人学习下载。借助该工程,不仅能够理解硬件I2C通信协议和SHT30的寄存器命令,还能通过实际代码掌握初始化、温湿度读取及失败重试等排错思路;工程内包含sht3x驱动组件与main入口逻辑,可作为基础模板,快速扩展至其他I2C传感器应用场景。

1. 同样是IIC,为什么SHT30要选硬件外设

很多人拿到ESP32和SHT30模块后,第一反应是找软件模拟IIC的库,觉得这样能随意指定引脚,方便走线。但实际调试时你会发现,软件模拟IIC在ESP32这种双核跑着WiFi、蓝牙、RTOS的环境里,一旦中断频繁,时序就容易走样,表现为传感器偶尔返回空数据、温湿度跳变甚至直接卡死。改用ESP32自带的硬件IIC外设后,时钟由外设自己产生,CPU 只负责发起传输和读取结果,SHT30 的读取稳定性立刻上一个台阶。这篇文章就围绕“ESP32 硬件 IIC 读取 SHT30”展开,讲清楚 IIC 地址、寄存器命令、温湿度换算、CRC 校验以及调试中会遇到的坑,适合正在做环境采集或者想从软件模拟切到硬件 IIC 的开发者。

2. 硬件IIC与SHT30协议细节:地址、寄存器与命令时序

2.1 硬件IIC为什么更适合SHT30

SHT30 是数字温湿度传感器,使用 IIC 接口与主控通信。它的测量命令、读取数据都是标准 IIC 写读流程,对时钟稳定性有一定要求。ESP32 内部有两个硬件 I2C 外设,分别挂在不同的 GPIO 组上,支持标准模式 100kHz 和快速模式 400kHz,也可以配置到更高频率。使用硬件 IIC 时,SCL 的翻转、ACK 的采样、START 和 STOP 信号的生成全部由外设完成,CPU 只需要操作寄存器或者调用现成的 Wire 库接口。

软件模拟 IIC 的问题在于时序完全依赖代码和中断。ESP32 默认在跑 FreeRTOS,WiFi 任务、蓝牙任务和用户任务会抢占 CPU,如果模拟 IO 的延时函数被高优先级任务打断,SCL 上的高电平时间就会拉长。SHT30 对超时有一定容忍度,但拉长到一定程度,它会认为总线上出现了未定义的命令而进入异常状态。硬件 IIC 外设则不受任务切换影响,因为时钟连续产生,CPU 只在外设准备好的时候读取数据,这是它稳定性的根本来源。

2.2 SHT30的IIC地址与一根总线挂两个传感器

SHT30 的 7 位 IIC 地址由 ADDR 引脚决定。ADDR 接低电平时地址是 0x44,接高电平时地址是 0x45。市面上大多是接低电平,所以默认地址 0x44 最常见。如果你买的模块上有 ADDR 引脚且可以自己焊,就能把地址改成 0x45,这样同一条 IIC 总线上可以挂两个 SHT30。

注意 SHT30 的 IIC 地址不是像有些芯片那样几位可编程,它只有两种选择。当一条总线上需要更多温湿度传感器时,除了用地址扩展,也可以用 IIC 多路复用器,但常见做法是每两个传感器共享一组 IIC 引脚,ESP32 至少有两组硬件 IIC,再配合 ADDR 引脚,挂四个 SHT30 很轻松。读取前必须确认模块的实际地址,用 IIC 扫描程序扫出地址再改动代码最稳妥。

2.3 常用命令与16位寄存器格式

SHT30 是 16 位命令,每次写命令需要连续发送两个字节。最常用的测量命令是 0x2400,代表高重复性单次测量,适合大多数应用场景。还有一个是 0x2C10,代表高重复性且使能时钟拉伸,但 ESP32 的硬件 IIC 对 SCL 被从设备拉低有时支持不太好,所以一般不推荐用带时钟拉伸的测量命令,除非你对主控 IIC 的超时有把握。

SHT30 内部寄存器是 16 位数据加 8 位 CRC,比如读取测量结果时,返回温度 MSB、温度 LSB、温度 CRC、湿度 MSB、湿度 LSB、湿度 CRC,一共 6 个字节。状态寄存器则是通过命令 0xF32D 读取,会返回 2 个数据字节加 1 个 CRC 字节。此外还有清除状态寄存器命令 0x3041、软复位命令 0x30A2,软复位在传感器挂死后很好用。

2.4 硬件IIC的引脚分配与上拉电阻选择

ESP32 硬件 IIC 引脚是可以通过 IOMUX 和 GPIO 矩阵任意映射的,但并非所有引脚都能稳定输出高电平。默认很多开发板将 GPIO21 作为 SDA、GPIO22 作为 SCL。我一般建议使用这两个默认引脚,因为它们是 ESP32 数据手册里标注的 I2C 外设默认引脚,与其他外设复用冲突少。如果你需要换到其他 GPIO,也可以,但要注意避开 ADC 采样引脚和 IR 遥控引脚,免得干扰。

SHT30 模块一般自带 10kΩ 左右的上拉电阻,如果你的模块没有,必须在 SDA 和 SCL 上各接一个 4.7kΩ 上拉到 3.3V。上拉电阻太小会加重总线负载,太大则会让边沿变缓,在 400kHz 下容易产生通信错误。另外,ESP32 是 3.3V 逻辑,SHT30 也是 3.3V 供电,绝对不要直接接到 5V 上,否则模块可能直接烧掉。

3. 用ESP32硬件IIC驱动SHT30:引脚配置与最小可运行代码

3.1 工程配置与依赖

在 Arduino IDE 或者 PlatformIO 中开发 ESP32,硬件 IIC 的最直接接口是 Arduino 框架下的 Wire 库。Wire 库底层封装了 ESP32 的硬件 I2C 外设,调用Wire.begin(SDA, SCL, frequency)就能指定引脚和频率,这就是硬件 IIC。千万不要把 Wire 库和软件模拟混淆,软件模拟在 Arduino 生态里是一些第三方库自己用 GPIO 翻转实现的,不经过 Wire。只要你的代码里用的是Wire.h,跑在 ESP32 上就是硬件外设。

新建工程时不需要额外安装什么库,Wire 库和 ESP32 核心包自带的。如果你用 PlatformIO,只要板卡框架选择arduino,包含Arduino.h即可。如果你更习惯 ESP-IDF,那就用driver/i2c.h或新的driver/i2c_master.h接口,这里以 Wire 库展开,因为大部分新手都是从 Arduino 框架入手的,代码也更短。

3.2 初始化与单次测量:最简代码

下面这段是完整的 ESP32 硬件 IIC 读取 SHT30 的单次测量代码,直接用一个函数封装读取流程。先发送测量命令,等待一段时间,再读取 6 个字节,最终返回温度和湿度。

#include <Wire.h> #define SHT30_ADDR 0x44 #define SDA_PIN 21 #define SCL_PIN 22 float temperature = 0.0f; float humidity = 0.0f; bool readSHT30(float &temp, float &hum) { // 发送测量命令:0x2400 为高重复性单次测量 Wire.beginTransmission(SHT30_ADDR); Wire.write(0x24); Wire.write(0x00); uint8_t err = Wire.endTransmission(); if (err != 0) { return false; // 传输失败,检查接线和地址 } // 等待测量完成:高重复性典型 8.5ms,最大 15ms delay(15); // 请求 6 字节:温度 MSB, LSB, CRC, 湿度 MSB, LSB, CRC uint8_t buf[6] = {0}; Wire.requestFrom(SHT30_ADDR, 6); if (Wire.available() < 6) { return false; // 数据不足,可能传感器未就绪 } for (int i = 0; i < 6; i++) { buf[i] = Wire.read(); } // 温度原始值 = buf[0] << 8 | buf[1] uint16_t rawTemp = (uint16_t)buf[0] << 8 | buf[1]; // 湿度原始值 = buf[3] << 8 | buf[4] uint16_t rawHum = (uint16_t)buf[3] << 8 | buf[4]; // 转换为摄氏度和相对湿度 temp = -45.0f + 175.0f * (float)rawTemp / 65535.0f; hum = -6.0f + 125.0f * (float)rawHum / 65535.0f; return true; } void setup() { Serial.begin(115200); Wire.begin(SDA_PIN, SCL_PIN, 400000); } void loop() { if (readSHT30(temperature, humidity)) { Serial.printf("temp=%.2f hum=%.2f\n", temperature, humidity); } else { Serial.println("read fail"); } delay(2000); }

这段代码里用到了Wire.begin(21, 22, 400000),第三个参数是频率,单位 Hz。SHT30 官方支持最高 1MHz,但 400kHz 是 IIC 快速模式的标准频率,也足够稳定。如果发现偶尔读不出来,可以降到 100000。delay(15)是为了等传感器测量结束,SHT30 高重复性测量最大耗时 15ms,如果这期间去读,容易读到全 0xFF 或者上一次的数据。

3.3 连续读取时应该把测量命令放在哪

上面代码每次读之前都会重新发送测量命令,这是单次测量模式的标准做法,也是最不容易出错的。有些开发者会在setup()里只发一次测量命令,然后循环里只读取数值,结果往往读到的是固定的全 0xFF 或错误值。因为单次测量模式下传感器每完成一次测量就休眠,不再更新数据,必须重新触发。

如果你做周期测量,比如每秒测一次,就需要改用 SHT30 的周期测量命令。周期测量模式下传感器会自动按频率工作,主机可以随时读取最近一次数据,不需要每次先写命令。周期测量会在后面的章节细说,先用单次测量搞通流程是最稳的。

3.4 如何验证是否真的跑在硬件IIC上

很多人担心自己用的库会不会自动退回软件模拟。最直接的验证方法是看 ESP32 的 I2C 外设寄存器状态,或者用逻辑分析仪抓 SCL 波形,观察波形频率是否恒定 400kHz,有没有明显的毛刺或拉长的电平。逻辑分析仪是排查时序问题的最好工具,比用万用表量电压直观得多。

另一种验证方式是利用 Wire 库的Wire.getClock()返回实际时钟频率,如果返回你设置的数值,说明硬件外设工作正常。另外,在错误处理里打印Wire.endTransmission()的返回值,0 表示无错误,1 表示数据太长,2 表示 NACK 地址错误,3 表示 NACK 数据错误。这些返回值能快速定位是地址问题、接线问题还是传感器没上电。

4. 温湿度换算与CRC校验:从原始字节到准确数值

4.1 原始数据计算公式与定点优化

SHT30 返回的温湿度是 16 位无符号整数。温度按下式换算:T = -45 + 175 * raw / 65535,湿度为H = -6 + 125 * raw / 65535。这个公式是 SHT30 数据手册给的线性化公式,不需要查表,直接算就行。但注意浮点数在 ESP32 上计算速度也不慢,Fomu 的代价是代码更简单。如果希望避免浮点,可以把公式拆成整数运算,例如温度扩大 100 倍:temp100 = (17500 * rawTemp) / 65535 - 4500,这样直接得到百分之一℃,显示时再除 100。

我一般会同时保留浮点版本和整数版本,浮点版本用于调试输出,整数版本用于需要逐字节存储到 EEPROM 或者无线发帧的场景。注意 raw 的范围是 0 到 65535,SHT30 的温度测量范围是 -40℃ 到 125℃,湿度 0 到 100% RH。超出范围的值大概率是通信错误,不要直接相信换算结果,应该先做范围检查。

4.2 CRC8校验函数:手写还是用库

SHT30 的 CRC8 校验多项式是x^8 + x^5 + x^3 + x^2 + 1,即常见多项式0x31,初始值为0xFF。很多 IIC 传感器都用这个多项式,SHT3x 系列也是如此。数据手册要求对温度的两个数据字节做 CRC,对湿度的两个数据字节做 CRC,CRC 的初值是 0xFF,而不是 0x00。下面是标准实现,可以直接复制到项目里。

uint8_t sht30_crc8(const uint8_t *data, size_t len) { uint8_t crc = 0xFF; for (size_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; }

这个函数第一个参数是数据指针,第二个参数是长度。调用时传入&buf[0]长度为 2,然后对比buf[2];湿度部分传入&buf[3]长度为 2,对比buf[5]。如果 CRC 不匹配,说明数据在传输过程中被干扰。我建议在正式应用里一定做 CRC 校验,因为 SHT30 的数据线如果和继电器、电机驱动靠得近,电磁干扰很可能只影响某两个字节,不校验的话会得到离谱的温湿度值。

4.3 状态位也不容忽视

SHT30 内部还有一个状态寄存器,读取命令是 0xF32D,返回 3 字节:状态 MSB、状态 LSB、状态 CRC。状态寄存器的 bit 0 表示加热器是否开启,bit 1 表示湿度追踪警报,bit 2 表示温度追踪警报,bit 4 表示系统复位状况。如果传感器经历过一次断电复位,bit 4 会置 1,读取后并不会自动清零,需要发送清状态寄存器命令 0x3041。

调试时如果发现温湿度值是合理的但某些位异常,可能需要向传感器发送软复位命令 0x30A2。软复位会让传感器重新初始化,之后最好延时 10ms 再进行第一次测量。我遇到过硬件 IIC 在总线冲突后传感器进入了一种半挂死状态,单次测量永远返回上一次的数据,发送软复位后恢复正常。所以代码里预留一个复位函数会省很多事。

4.4 数据对齐与字节顺序

从 IIC 总线上读到的第一个字节是温度高字节,第二个是温度低字节,第三个是温度 CRC,而湿度字节往往是接着的。有些模块或者使用不同库的示例代码,会把温度高字节和低字节的顺序搞反,或者把湿度 CRC 误当做温度高位,这会导致测出负温度或者湿度超过 100%。我建议把原始字节打印出来,对照手册上的定义逐字节核对。

还有一种常见情况是读取 6 字节时,由于requestFrom返回实际数据长度,如果从设备响应缓慢,可能只返回 2 字节或 4 字节,代码里用Wire.available()判断不足 6 字节就返回 false,这样能避免把不完整的帧拿去做换算。不要用固定延时加硬读,因为 SHT30 的测量时间会随重复性等级微变。

5. 硬件IIC读取SHT30的常见坑与排查日志

5.1 坑一:第一次读取成功,之后全是 65535 或 0xFF

现象:代码能跑通,串口偶尔输出正常温湿度,但大部分时间是temp=nan或者hum=655.35。

原因分析:在单次测量模式下,如果每次读取前没有重新发送测量命令,传感器在完成一次测量后进入待机,数据寄存器不再更新。而requestFrom依然能读取旧数据,但旧数据可能已经失效,甚至某些芯片垃圾数据被读出来,CRC 也过不了。

解决:确保每次进入读取函数都先发送测量命令 0x2400,并且endTransmission返回 0 后再延时 15ms,最后再requestFrom。另外检查Wire.requestFrom是否真的返回 6 字节,如果返回 0 或负数,说明传感器根本没响应,需要检查接线。

5.2 坑二:IIC 扫描能找到地址,但读取时 NACK

现象:用扫描程序扫描总线,能发现 0x44 响应,但beginTransmission写命令后返回 2(地址 NACK)或 3(数据 NACK)。

原因:地址对了,但 SHT30 可能处于供电不稳的状态,或者测量过程中被反复复位。还有一种可能是 ADDR 引脚悬空,模块内部没有上下拉,导致地址在 0x44 和 0x45 之间跳跃。扫描时恰好扫描到的是另一个地址,而代码里写死了 0x44。

解决:先检查 ADDR 引脚的连接,ADDR 应明确接到 GND 或 VCC。然后用扫描程序打印所有响应地址,看是否同时出现 0x44 和 0x45,如果是就说明 ADDR 悬空。把 ADDR 接到 GND 后,地址固定为 0x44。另外给传感器单独供电,确认 VDD 和 GND 之间有没有 100nF 去耦电容,ESP32 开发板的 3.3V 输出在 WiFi 开启时跌落较大,会直接导致传感器 NACK。

5.3 坑三:温度读数偏高 2 到 3℃

现象:传感器放在空气中,和另一个参考温度计相比,明显偏高,且偏离比较稳定。

原因:PCB 上发热器件离传感器太近,比如板载 LDO、ESP32 芯片本身。ESP32 在 WiFi 工作时的功耗有上百毫安,芯片发热足以让周围温度升高。SHT30 封装与 PCB 热耦合,导致测量的是板温而非气温。

解决:用带引线的小模块,或者把传感器通过排线延长出去。在代码层面可以通过周期性采样后滑动平均来平滑噪声,但无法消除热量源。如果要用于精确环境测量,建议让传感器远离主控板 5cm 以上。另一个容易忽略的是 SHT30 的自热,如果频繁以高重复性连续测量,传感器内部也会轻微升温,单次测量模式每次间隔 2 秒以上,自热可以忽略。

5.4 坑四:软件模拟 IIC 能用,硬件 IIC 反而读不到

现象:项目原本用 GPIO 模拟时序的库能读到数据,换成 Wire 库后反而不行。

原因:软件模拟库通常以较慢的时钟运行,比如 50kHz,并且不管什么电平翻转都不会依赖外部上拉。硬件 IIC 如果设置为 400kHz,而你的模块上的上拉电阻是 10kΩ,线路电容稍大,SCL/SDA 的上升沿就会变缓,导致时序不符合规范。另外 ESP32 硬件 IIC 在某些引脚上默认开启漏极开路,而软件模拟可以配置推挽输出,所以相同模块在不同驱动下表现不同。

解决:先尝试把Wire.setClock(100000)降到 100kHz,看是否恢复正常。然后检查 SDA/SCL 的上拉电阻是否安装,阻值是否合适,我可以接受的范围是 2.2kΩ 到 4.7kΩ。如果模块已经接了 10kΩ,那么短线传输应该没问题,但杜邦线超过 10cm 就必须换更小的上拉或者降低频率。

5.5 坑五:CRC 校验总失败,但数据看着还能用

现象:打印原始字节发现温湿度波浪变化,而 CRC 经常不一致。

原因:在requestFrom之后,可能因为时钟抖动读了多余字节,或者读取时 SDA 线上有毛刺。最常见的是在Wire.available()判断时,用while (Wire.available())循环去读,结果多读了温度 CRC 之后的某些字节。还有的代码在requestFrom和read之间夹了太久的延时,导致总线空闲超时。

解决:严格只读 6 字节,用Wire.available() >= 6判断后再read。不要在主循环里同时去操作别的 IIC 设备,以免总线仲裁出错。如果 CRC 依然失败,在 IIC 通信后加一个Wire.endTransmission()清空状态,或者使用Wire.begin()内部的超时机制。另外确认发送测量命令后延时足够,一般 15ms 不会差。

5.6 坑六:插上传感器后 ESP32 自己启动失败或不断重启

现象:传感器和 ESP32 用了同一根电源线,上电后开发板反复重启,或者 sensor 一接上就 3.3V 跌落到 1.8V。

原因:SHT30 模块上可能带有电平转换芯片或稳压电路,在瞬态上电时产生过大的冲击电流。更夸张的是有些模块把 SDA 和 SCL 拉到 5V,而 ESP32 的 GPIO 不支持 5V,直接把 IO 烧了。

解决:先检查模块原理图,确保 SDA/SCL 是 3.3V 逻辑,不要碰 5V 模块。然后独立给传感器供电,或者确保 ESP32 的 3.3V 输出电流足够。最稳妥的做法是使用 IIC 隔离器,或者至少用 1kΩ 串联电阻保护 GPIO。我在开发板上遇到过类似问题,都是没有留意模块自带电平转换,更换成纯 3.3V 模块后一切正常。

6. 进阶用法:周期模式、多从机与总线稳定性

如果单次测量已经稳定跑通,可以再往前走一步。SHT30 支持周期测量模式,命令格式是 0x20XX,其中 XX 与重复性相关,比如 0x2020 表示高重复性每秒测量一次,0x2030 表示高重复性每分钟测量 10 次。使用周期模式后,传感器会按照内部定时器自动测量,主控不需要每次发命令,直接读取 6 字节即可。但要注意,周期模式下读取的数据是最近一次测量结果,可能不是当前请求时刻的精确值,对于大多数环境监测场景足够用。

多从机场景下,一条 IIC 总线上挂两个 SHT30 的方法是利用 ADDR 引脚。地址 0x44 和 0x45 的传感器可以同时挂在 SDA/SCL 上,各自上拉电阻各一份。读取时分别向对应地址发送测量命令和读取请求即可。ESP32 的硬件 IIC 外设支持多设备总线仲裁,但要注意总线上拉电阻的总阻值,两个传感器并联后上拉电阻等效值减半,建议每个模块的上拉电阻选 10kΩ,并联后 5kΩ,依然在合理范围。

另一个进阶技巧是利用 ESP32 的第二组硬件 IIC 外设。开发板默认的 I2C0 用了 GPIO21/22,如果你还有 I2C1,可以初始化Wire1,在 Arduino 框架下Wire1.begin(SDA1, SCL1, 400000)即可。这样传感器可以挂在不同总线上,每个总线独立配置,互不干扰。我在一个模拟项目里用两组 IIC 分别读取四个 SHT30 和一块气压计,总线负载均匀,稳定性比串接好得多。

最后提一个容易忽略的细节:硬件 IIC 的传输过程中如果被高优先级中断打断,外设会自动挂起并等待 CPU 处理,但中断过长会导致超时。ESP32 的 Wire 库默认没有设置超时,可能会一直卡在requestFrom。建议在主循环里加一个看门狗或者用esp_timer做超时重置,也可以偶尔调用Wire.end()然后重新Wire.begin()来重新初始化外设。我习惯在读取失败超过 5 次后,给传感器发送软复位命令,再重新初始化,这个习惯在长时间运行的项目里帮我避免了不少玄学挂死。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询