ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

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

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. 项目梳理为什么坚持用硬件IIC1.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.3VGND接GNDSDA接GPIO21SCL接GPIO22。GPIO21和GPIO22是ESP32默认的I2C引脚对应开发板丝印上的D21和D22不同板子丝印可能略有差异以板载印刷为准。SHT30引脚连接ESP32引脚说明VCC3.3V供电绝对不要接5VGNDGND共地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结果传感器模块上的上拉电阻接到5VSDA和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(); // 默认引脚 SDA21, SCL22 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%RHCRC错误次数0次每次读取耗时约16ms这个波动属于正常范围传感器本身的噪声加上空气流动影响温湿度小幅波动是正常的。如果某次数据偏离特别大先检查是不是手碰到了传感器或者传感器紧挨着开发板上的WiFi模块——ESP32的射频模块发热明显如果SHT30靠得太近测出来的温度会偏高实测最高能偏2℃左右。所以传感器的安装位置要尽量远离发热源或者在结构上隔热这是一个被很多人忽略的细节。我在后来的项目中特意把SHT30做成独立的传感器探头用四根软线引出到机壳外部配合一段短的PCB延长线实测温度读数跟水银温度计对比误差在0.3℃以内效果很好。如果你的项目对温湿度精度有要求这个“远离板载热源”的小改动非常值得做。本文还有配套的精品资源点击获取
返回列表