
简介面向嵌入式开发者与物联网爱好者的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为什么更适合SHT30SHT30 是数字温湿度传感器使用 IIC 接口与主控通信。它的测量命令、读取数据都是标准 IIC 写读流程对时钟稳定性有一定要求。ESP32 内部有两个硬件 I2C 外设分别挂在不同的 GPIO 组上支持标准模式 100kHz 和快速模式 400kHz也可以配置到更高频率。使用硬件 IIC 时SCL 的翻转、ACK 的采样、START 和 STOP 信号的生成全部由外设完成CPU 只需要操作寄存器或者调用现成的 Wire 库接口。软件模拟 IIC 的问题在于时序完全依赖代码和中断。ESP32 默认在跑 FreeRTOSWiFi 任务、蓝牙任务和用户任务会抢占 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 到 65535SHT30 的温度测量范围是 -40℃ 到 125℃湿度 0 到 100% RH。超出范围的值大概率是通信错误不要直接相信换算结果应该先做范围检查。4.2 CRC8校验函数手写还是用库SHT30 的 CRC8 校验多项式是x^8 x^5 x^3 x^2 1即常见多项式0x31初始值为0xFF。很多 IIC 传感器都用这个多项式SHT3x 系列也是如此。数据手册要求对温度的两个数据字节做 CRC对湿度的两个数据字节做 CRCCRC 的初值是 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现象代码能跑通串口偶尔输出正常温湿度但大部分时间是tempnan或者hum655.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 次后给传感器发送软复位命令再重新初始化这个习惯在长时间运行的项目里帮我避免了不少玄学挂死。希望帮到你。本文还有配套的精品资源点击获取