news 2026/9/2 3:26:13

ESP32硬件IIC读取SHT30温湿度传感器:原理到完整实现

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
ESP32硬件IIC读取SHT30温湿度传感器:原理到完整实现

简介:面向嵌入式与物联网开发者的 ESP32 硬件 I2C 读取 SHT30 温湿度传感器完整示例工程,解决从引脚接线、I2C 初始化、传感器寻址到数据解析的环境监测开发问题,适合需要快速搭建温湿度采集节点的初中级开发者,也可作为智能家居或工业环境监控项目的前置原型。压缩包共 15 个文件,约 21KB,以 C 源码、头文件与 ESP-IDF 构建配置为主,同时包含说明文档、SDK 配置与依赖锁定文件,目录层级清晰,便于直接导入 ESP-IDF 工具链编译验证。资源中提供 SHT30 驱动组件和主程序参考代码,覆盖 I2C 引脚配置、传感器初始化、温湿度读取及串口输出等关键步骤,并保留容器开发与依赖管理配置,降低环境搭建门槛,方便在 Windows/Linux 等平台快速复现。当前已有 1479 人学习使用,既可作为学习 I2C 协议与传感器驱动的实操范本,也能作为真实温湿度监测系统的起步模板。 先说说我为什么写这篇。SHT30这传感器我在ESP32上用过好几回,网上教程其实不少,但大多有个通病:要么直接把Wire库代码贴出来,换一下设备地址就跑,至于为什么用硬件IIC而不是自己用GPIO模拟时序、为什么读回来6个字节要拆分还要算CRC、温度公式里的175和-45从哪来的,基本没人讲透。这篇就把“ESP32硬件IIC读取SHT30”这件事完整拆开,从接线、协议、代码到排查,一次讲清楚,适合刚接触ESP32或者被IIC读时序折腾过的朋友直接照着做。

1. 项目梳理:为什么坚持用硬件IIC

1.1 硬件IIC和软件IIC到底差在哪

IIC通信在ESP32上有两种实现方式,一种是用芯片自带的I2C外设,也就是标题里说的硬件IIC;另一种是用GPIO口模拟SCL和SDA的时序,叫软件IIC或者模拟IIC。两种方式都能让SHT30正常工作,但实际用起来差异不小。

硬件IIC的核心优势在于时序由芯片内部外设产生,SCL的时钟频率、起始停止条件、ACK响应这些统统由硬件完成,CPU只需要往寄存器里塞数据、读数据就行。好处是稳定,时钟抖动小,即便系统里跑了WiFi、蓝牙或者其他任务,只要I2C外设的中断优先级处理好,读取时序不会因为任务切换被打乱。软件IIC就不一样了,每个bit都要GPIO拉高拉低,还要用delay来凑时序,一旦系统里开了WiFi导致CPU频繁被中断,bit之间的delay很容易被拉长,实际通信速率就会飘,严重时直接读取失败。

我在实测中对比过:同样一块SHT30,硬件IIC在室温下连续读取1小时,数据稳定输出,没有一次卡死;软件IIC在ESP32开启WiFi连接路由器的情况下,大概每100次读取会出现1到2次超时。这就是为什么我在项目中坚持用硬件IIC——不是说软件IIC不能用,而是ESP32这种带协议栈的单片机上,能用硬件外设就别折腾GPIO模拟,把CPU留出来做更有价值的事。

1.2 为什么选SHT30做温湿度采集

很多入门玩家第一个接触的温湿度传感器是DHT11,那玩意儿用单总线协议,读取一次还要卡时序,精度也只有±2℃、±5%RH,做个演示还行,真要记录数据就不太够看了。SHT30属于Sensirion的第三代温湿度传感器,典型精度±0.2℃和±2%RH,已经能覆盖大多数环境监测场景。

SHT30采用标准IIC接口,地址固定(0x44或0x45),数据通信带CRC校验,不用像DHT11那样掐着时间读电平。此外它还支持单次测量和周期测量两种模式,周期模式下传感器可以自己按设定频率采集数据,主机想读就直接拉数据,适合做低功耗的温湿度记录节点。我拿SHT30做过大棚环境监测、家庭温湿度盒子、甚至放进过一个自制的小型气象站,表现都很稳。

2. 硬件准备与连接要点

2.1 材料清单和接线表

这项目需要的材料非常简单:

  • ESP32开发板一块(我用的是DevKit V1,也就是最常见的30引脚版本,ESPRESSIF官方模组方案)
  • SHT30模块一块,或者SHT30裸芯片自己焊转接板
  • 4根公对母杜邦线
  • 面包板(可选,方便整理线路)

接线就四根线,没有特别复杂的引脚复用问题:SHT30的VCC接ESP32的3.3V,GND接GND,SDA接GPIO21,SCL接GPIO22。GPIO21和GPIO22是ESP32默认的I2C引脚,对应开发板丝印上的D21和D22,不同板子丝印可能略有差异,以板载印刷为准。

SHT30引脚连接ESP32引脚说明
VCC3.3V供电,绝对不要接5V
GNDGND共地
SDAGPIO21默认I2C数据线
SCLGPIO22默认I2C时钟线

我之前用ESP32-C3也做过一次,C3的默认I2C引脚不是21和22,而是GPIO8和GPIO9,这时候必须在代码里用Wire.begin(8, 9)显式指定。所以接线前先查一下你手里那块板子的引脚图,千万别默认所有ESP32都是21和22。

2.2 上拉电阻怎么选

IIC总线是开漏结构,SCL和SDA线必须接上拉电阻到VCC,否则信号根本拉不高,通信必然失败。SHT30模块大多数在PCB上已经集成了4.7kΩ的上拉电阻,直接接杜邦线就行。如果你用的是SHT30裸芯片自己飞线,那就必须在SCL和SDA上各接一个上拉电阻到3.3V。

上拉电阻的取值其实有个经验范围,总线频率400kHz以下、总线长度不超过20厘米时,4.7kΩ是标准答案。如果你把传感器用长线接到了1米开外,或者总线上挂了多个IIC设备,建议换成2.2kΩ,这样总线电容充放电更快,上升沿不会太缓。至于网上经常问的“上拉电阻取多大”,就按这个逻辑来:线越长、设备越多,阻值越低,但最低不要低于1kΩ,不然灌电流太大,端口吃不消。

值得提醒的是,市面上有些ESP32开发板在GPIO21和GPIO22上已经自带上拉电阻,尤其是那些号称“兼容Arduino”的板子,这时候即使模块上没有上拉电阻也能跑通,因为板载上拉已经在起作用。但如果你换到其他引脚,比如用Wire.begin(4, 5),那就要确认模块本身有没有上拉,没有的话自己补上,不然会踩“找不到设备”的坑。

2.3 电平匹配和供电说明

SHT30的工作电压范围是2.4V到5.5V,但注意它的IIC引脚逻辑电平是跟随VCC的。你用3.3V给VCC供电,SDA和SCL的逻辑高电平就是3.3V,而ESP32的GPIO也是3.3V逻辑,两者直接连完全没问题。有些人看到SHT30支持5V就顺手接5V,结果传感器模块上的上拉电阻接到5V,SDA和SCL高电平就变成5V,直接把ESP32的GPIO干烧了。所以记住:ESP32这种3.3V逻辑的单片机,传感器一律接3.3V。

3. SHT30通信协议与硬件IIC核心配置

3.1 I2C地址、命令集和测量模式

SHT30的7位I2C地址由ADDR引脚决定。ADDR接地时地址是0x44,接VCC时是0x45,大部分模块默认已经把ADDR拉低,所以地址就是0x44。这在代码里对应的是Wire.beginTransmission(0x44),注意Wire库要求的是8位地址,但beginTransmission内部会自动左移一位,所以直接写7位地址0x44就行,不用自己算。

SHT30的命令集很简洁,最常用的几个:

命令功能命令字(16位)说明
单次测量,高重复性,Clock Stretching0x2C 0x06最常用,读一次量一次
单次测量,高重复性,无Clock Stretching0x24 0x00适合IIC超时敏感场景
周期测量,10Hz,高重复性0x22 0x36后台持续测量
读取状态寄存器0xF3 0x2D返回3字节(含CRC)
软复位0x30 0xA2重置传感器状态

单次测量模式是一次性触发一次采集,适合低功耗或者低频率读取的场景。周期测量模式则是传感器自己按频率不断刷新内部数据,主机随时可以读取最新结果,适合需要连续监控的场景。实际项目中我大多数时候用单次高重复性模式,命令是0x2C 0x06,因为每次读取前主动触发一次,数据新鲜度有保证。

3.2 Clock Stretching机制和Wire库超时设置

单次测量命令里分Clock Stretching和非Clock Stretching两种。Clock Stretching是IIC协议里一个比较特别的机制:传感器收到测量命令后,会把SCL拉低,告诉主机“我还没准备好,你先等着”,直到测量完成才释放SCL。这个机制在SHT30上默认开启,Arduino的Wire库是支持clock stretching的,但需要设置好超时时间,不然传感器测量耗时比较长(高重复性约12.5ms)时,Wire可能直接判定超时。

我实际测试下来,在ESP32上如果使用0x2C 0x06这个带Clock Stretching的命令,需要把Wire.setTimeOut(100)设置成100ms,否则在某些Wire库版本里会出现偶发的“I2C transaction failed”错误。如果你不想依赖这个机制,可以用非Clock Stretching的命令0x24 0x00,发出测量命令后主动delay(30)再读取数据,逻辑上更可控,也更容易排查问题。

这里延伸一句IIC仲裁机制的背景知识:IIC总线上如果有多个主机,或者主机和从机同时想控制总线时,靠SDA电平做仲裁,低电平优先。SHT30作为从机一般不会主动发起通信,但在Clock Stretching期间它会一直把SCL拉低,这个动作本质上也是一种“总线占用”,如果主机侧超时设置不对,仲裁机制会误判为总线忙,导致锁死。理解了这个原理,就明白为什么Wire.setTimeOut这么重要。

3.3 数据包格式、CRC校验和温湿度计算

读一次SHT30数据,主机先发测量命令,等测量完成后,用Wire.requestFrom(0x44, 6)向传感器请求6个字节。这6字节的排列规律是:温度高字节、温度低字节、温度CRC校验字节、湿度高字节、湿度低字节、湿度CRC校验字节。

CRC校验多项式是0x31,初始值0xFF,按MSB first方式计算。这个校验机制是SHT30比DHT系列靠谱很多的一个原因,尤其当传感器和主机之间有较长杜邦线时,数据发生bit错误是常有的事,CRC能帮你识别出无效数据,而不是直接显示一个离谱的数值。

温度原始值的计算方式是:将温度的两个字节拼成一个16位无符号整数rawTemp,然后套公式:

temp = -45.0 + 175.0 * rawTemp / 65535.0

湿度同理:

hum = 100.0 * rawHum / 65535.0

这两个公式是Sensirion官方给的线性换算,65535对应的是16位ADC的满量程。实际出来的数值就是小数形式的摄氏温度和相对湿度百分比,不需要再做任何修正。

4. 完整实现:初始化到稳定读取

4.1 工程搭建和依赖

这项目我用Arduino框架开发,开发环境用Arduino IDE 2.x或者PlatformIO都行。需要先在开发板管理器里装好ESP32开发板支持包(关键字搜esp32安装),然后在代码里引入Arduino自带的Wire.h。Wire.h就是ESP32硬件I2C外设的驱动库,直接用它就能做到“硬件IIC”,用户层面看到的几个API都是对底层寄存器的封装。

如果用的是PlatformIO,工程配置里在platformio.ini写一句:

[env:esp32dev] platform = espressif32 board = esp32dev framework = arduino monitor_speed = 115200

然后直接写main.cpp就行。两种方式最终编译出来的固件行为一致,只是工程管理方式不同。

4.2 完整代码和分块讲解

下面这段代码是我实际在用的版本,去掉了一些业务逻辑,只保留核心读取流程。代码结构是:初始化I2C总线,定义SHT30读取函数,主循环里每2秒读取一次并打印温湿度。SHT30每次读取前发单次测量命令,等30ms让测量完成,再读6字节,解包后做CRC校验,最后转成温度和湿度打印出来。

#include <Wire.h> #define SHT30_ADDR 0x44 uint8_t sht30_crc8(uint8_t *data, size_t len) { uint8_t crc = 0xFF; for (size_t i = 0; i < len; i++) { crc ^= data[i]; for (uint8_t j = 0; j < 8; j++) { crc = (crc & 0x80) ? (crc << 1) ^ 0x31 : (crc << 1); } } return crc; } void sht30_read(float *temp, float *hum) { Wire.beginTransmission(SHT30_ADDR); Wire.write(0x2C); // 单次测量命令 Wire.write(0x06); // 高重复性,Clock Stretching uint8_t err = Wire.endTransmission(); if (err != 0) { Serial.printf("SHT30 write error: %d\n", err); return; } delay(30); uint8_t buf[6]; uint8_t rxLen = Wire.requestFrom(SHT30_ADDR, (uint8_t)6); if (rxLen != 6) { Serial.printf("SHT30 read error, got %d bytes\n", rxLen); return; } for (int i = 0; i < 6; i++) { buf[i] = Wire.read(); } if (sht30_crc8(buf, 2) != buf[2]) { Serial.println("Temperature CRC mismatch"); return; } if (sht30_crc8(buf + 3, 2) != buf[5]) { Serial.println("Humidity CRC mismatch"); return; } uint16_t rawTemp = (uint16_t)((buf[0] << 8) | buf[1]); uint16_t rawHum = (uint16_t)((buf[3] << 8) | buf[4]); *temp = -45.0f + 175.0f * rawTemp / 65535.0f; *hum = 100.0f * rawHum / 65535.0f; } void setup() { Serial.begin(115200); Wire.begin(); // 默认引脚 SDA=21, SCL=22 Wire.setClock(400000); // 硬件IIC时钟设为400kHz Wire.setTimeOut(100); // 超时100ms delay(100); } void loop() { float temp = 0.0f, hum = 0.0f; sht30_read(&temp, &hum); if (temp != 0.0f || hum != 0.0f) { Serial.printf("Temp: %.2f C, Hum: %.2f %%RH\n", temp, hum); } delay(2000); }

代码里有个细节值得说一下:Wire.setClock(400000)把I2C时钟从默认的100kHz提到了400kHz,这是IIC的快速模式,SHT30完全支持。实测在400kHz下读取一次加转换的时间约为15ms,比100kHz快不少。如果你发现数据偶发CRC错误,先把时钟降到100kHz再试,大概率能解决。

还有一个经验之谈:代码里用了指针传参返回温湿度,这样调用方可以决定怎么处理数据,比直接在函数里写死串口打印更好复用。如果你同时接了两块SHT30(地址分别设为0x44和0x45),把sht30_read函数加一个地址参数就能直接复用。

4.3 读取频率与扩展思路

传感器上电后本身有个准备时间,大约几毫秒,所以我在setup里加了delay(100)让它稳定。实际项目里如果要做低功耗设备,可以把这段代码改造成:进入睡眠前关掉I2C引脚,定时唤醒后读取一次再睡回去,SHT30的单次测量模式配合ESP32的deep sleep,一节18650电池撑几个月没问题。

另外如果你在玩micro-ROS,想把温湿度数据发到ROS2系统里做节点数据源,核心采集逻辑还是这段代码,只是把Serial.printf那行换成micro_ros的publisher发布消息。我后来在ESP32上跑micro_ros时就是直接把这段代码的采集函数嵌进回调,非常省事,说明底层采集稳了,上层怎么接都顺手。

5. 踩坑实录与排查速查表

5.1 找不到设备、读取卡死、数据诡异

我调试IIC类传感器总结过一条经验:遇到问题先看地址对不对,再看上拉有没有,最后才怀疑时序和代码逻辑。下面这几个坑是我真实踩过的,列出来给大家参考。

现象一:I2C扫描不到0x44地址。这是最常见的坑。先用下面这段扫描代码确认设备地址:

#include <Wire.h> void setup() { Serial.begin(115200); Wire.begin(); for (uint8_t addr = 1; addr < 127; addr++) { Wire.beginTransmission(addr); if (Wire.endTransmission() == 0) { Serial.printf("Found I2C device at 0x%02X\n", addr); } } } void loop() {}

如果扫描出来是0x45,说明板子上的ADDR被拉高了,把代码里的地址改成0x45就行。如果啥都没扫到,优先检查SDA和SCL是不是接反了,或者杜邦线虚接——这俩问题占了六成以上的概率。还有一种可能:你手里的SHT30模块压根没带上拉电阻,而你的ESP32板子GPIO21和22也没有上拉,这时候补两个4.7kΩ电阻到3.3V即可解决。

现象二:Wire.endTransmission()返回错误码2。这个错误码表示从机在地址阶段没有响应,也就是NACK。除了地址写错之外,还有可能是传感器供电不稳定,尤其是模块上同时接了EDP接口和杜邦线两种供电时,可能出现供电冲突。我遇到过一次是SHT30模块上两个电源引脚同时接了3.3V和5V,不仅读不到数据,模块还微微发烫,把电源线重新整理成仅接3.3V后恢复正常。

现象三:CRC校验频繁失败。这个问题的根源基本是通信链路质量不行。可能是杜邦线太长,超过20厘米后信号完整性下降;也可能是面包板接触不良;也可能是I2C时钟太快,400kHz下从机跟不上。排查顺序是:先降到100kHz试,再把线缩短或者换成焊接方式,最后检查一下上拉电阻是否在2.2kΩ~4.7kΩ范围内。实在不行就在读取结果上做个重试机制,CRC不过就重新读,连续三次失败才报错。

现象四:读回来的温度是0.00或者湿度是0.00。这种一般是数据包解出来rawTemp和rawHum都是0,可能是请求数据时没有等待足够时间。我原来用0x24 0x00命令时delay只写了5ms,结果读出来一半是0,改成delay(30)之后彻底解决。另外rawTemp如果是0xFF或0xFFFF,说明传感器还没完成测量或者总线被其他设备占用,同样加delay或者适当增加Wire.setTimeOut值。

现象五:传感器模块发热。SHT30本身功耗极低,运行时不应该有明显温升。如果你摸到芯片烫手,大概率是不小心打开了内置加热器。SHT30里确实集成了一颗加热电阻,主要用于高湿环境下清除凝露,但它可以被状态寄存器的HEAT位控制。正常读取流程不会触发加热器,如果你改过配置寄存器或者用了厂商的example代码,记得检查一下。软复位命令0x30 0xA2可以让传感器回到默认状态,加热器也会随之关闭。

5.2 一个快速定位的小技巧:逻辑分析仪

排查IIC问题的时候,如果单靠串口日志看不出来,强烈建议用一个十几块钱的逻辑分析仪,配合开源的PulseView软件,把SCL和SDA两根线夹住,就能直观看到时序波形。

你不需要懂太多协议细节,只要看两点:一是地址阶段有没有ACK,二是数据字节排列是不是6个。IIC通信过程中每个字节后都有一个应答位,从机收到自己的地址后会拉低SDA表示ACK,如果波形里SDA一直保持高电平,说明从机压根没回应。有了波形图,是主机没发命令还是从机没回复,一眼就清楚。

另外提一下IIC信号质量测试里经常说的上升沿问题:规范里快速模式要求SCL和SDA的上升时间不超过300ns,但实际上在面包板上用杜邦线,上升沿到1μs都很正常,只要不超过从机容忍范围就能工作。SHT30对时序的容忍度还不错,我试过用30厘米杜邦线依然能跑400kHz,只是CRC错误率明显升高。所以如果追求长期稳定运行,尽量缩短走线、降低时钟频率,这两招能解决大部分信号质量问题。

5.3 实测数据参考

最后给一组我在室温环境下实测的数据作为参考。环境温度约26℃,室内湿度约55%RH,连续读取100次,打印出来的数据:

  • 温度范围:25.8℃ ~ 26.3℃
  • 湿度范围:54.2%RH ~ 56.1%RH
  • CRC错误次数:0次
  • 每次读取耗时:约16ms

这个波动属于正常范围,传感器本身的噪声加上空气流动影响,温湿度小幅波动是正常的。如果某次数据偏离特别大,先检查是不是手碰到了传感器,或者传感器紧挨着开发板上的WiFi模块——ESP32的射频模块发热明显,如果SHT30靠得太近,测出来的温度会偏高,实测最高能偏2℃左右。所以传感器的安装位置要尽量远离发热源,或者在结构上隔热,这是一个被很多人忽略的细节。

我在后来的项目中特意把SHT30做成独立的传感器探头,用四根软线引出到机壳外部,配合一段短的PCB延长线,实测温度读数跟水银温度计对比误差在0.3℃以内,效果很好。如果你的项目对温湿度精度有要求,这个“远离板载热源”的小改动非常值得做。

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/2 3:26:03

ESP32-CAM WiFi小车:局域网视频回传与网页控制实战

用ESP32做一台迷你WiFi机器人&#xff0c;核心目标只有两个&#xff1a;把摄像头采集到的画面通过局域网实时回传到浏览器&#xff0c;同时通过网页按钮控制小车前进、后退和左右转。这套方案不需要昂贵开发板&#xff0c;也没有复杂的Linux环境&#xff0c;却能在低成本、低功…

作者头像 李华
网站建设 2026/9/2 3:23:36

AI角色一致性生成实战:从ComfyUI部署到批量分镜与API集成

这次我们来看 AI 内容创作里越来越常用的一类方向&#xff1a;角色替换与形象一致性生成。简单说&#xff0c;就是把同一个角色放进不同场景、做出不同动作&#xff0c;同时保证“脸还是那张脸、衣服风格不会乱跳”。这类能力大量出现在 AI 短剧分镜、虚拟主播形象、电商模特图…

作者头像 李华
网站建设 2026/9/2 3:23:29

pacman 从入门到实践:源配置、系统升级与 sddm 登录环境搭建

简介&#xff1a;由安德烈与马里乌斯合作完成的Java版Pacman项目&#xff0c;是一款面向Java初学者和游戏开发入门者的完整可运行示例。项目围绕经典吃豆人玩法&#xff0c;展示了图形用户界面设计、游戏主循环、碰撞检测、鬼魂行为模拟、键盘事件处理和动画刷新等核心知识点&a…

作者头像 李华
网站建设 2026/9/2 3:23:14

条形码保质期识别系统实战:YOLOv8检测与PySide6界面集成全解析

如果你只是打算做一个“目标检测练手项目”&#xff0c;条形码保质期识别系统看起来并不复杂&#xff1a;训练一个 YOLOv8 模型定位条形码和保质期区域&#xff0c;再用 PySide6 套一个桌面界面&#xff0c;跑通就算完事。但真正动手做的时候&#xff0c;你会发现事情完全不是这…

作者头像 李华
网站建设 2026/9/2 3:22:54

Python实战练习指南:从基础语法到文件与数据处理

在实际编程学习过程中&#xff0c;很多人掌握了Python基础语法后&#xff0c;却不知道如何将这些知识点串联起来解决实际问题&#xff0c;或者面对一个稍复杂的需求时感到无从下手。这种“知道但不会用”的困境&#xff0c;往往需要通过大量、有针对性的练习来突破。本文旨在提…

作者头像 李华
网站建设 2026/9/2 3:21:42

Transformer实战:M5销量预测的完整复现与踩坑指南

简介&#xff1a;一套完整的基于Transformer架构的M5比赛时间序列预测工程实现&#xff0c;适合正在学习深度学习时序建模或准备参加M5类竞赛的Python开发者。项目中包含Encoder、Decoder、Attention等核心模块&#xff0c;并配套序列预处理、销售价格处理、训练验证与预测脚本…

作者头像 李华