STM32驱动SHT30温湿度传感器:从I2C时序到工程实践详解
发布时间:2026/9/3 23:38:47 锦皓数字建站

简介面向STM32单片机开发者的SHT30温湿度传感器驱动工程基于I2C通信实现温湿度采集覆盖硬件配置、驱动开发、数据读取、CRC校验及温湿度转换公式等关键环节适用于物联网、智能家居、环境监测等场景。工程采用STM32CubeMX完成底层初始化并适配1.54寸TFT屏显示支持单次采样与周期采样两种模式切换。资源共156个文件约488KB以C源文件、头文件、Keil工程配置uvprojx及编译输出文件o、hex、axf为主其中源文件与头文件构成完整驱动代码hex/axf可直接烧录验证CubeMX配置文件ioc便于重新生成初始化代码。目前已有115人学习适合需要快速移植SHT30驱动或学习I2C传感器开发的中初级嵌入式工程师。借助该工程可跳过底层寄存器配置直接掌握采样触发、数据校验、温湿度换算与屏幕显示的实现思路缩短项目开发周期。1. 整体设计思路与环境准备1.1 需求定位为什么是SHT30做嵌入式这几年我接触的温湿度传感器不算少。DHT11便宜但抗干扰差读时序的时候主控稍微忙一点就容易卡死数据还得靠软件校准DHT22精度还行可响应速度慢体积也偏大。后来项目需要加一个环境监测功能我把SHT30列为首选理由很直接I2C接口布线简单精度0.2°C和2%RH日常环境监测完全够用功耗低电池供电的便携设备也扛得住。最让我满意的是它内部集成了校准电路出厂前已经标定好我们不需要像DHT系列那样自己做补偿。SHT30的数字输出格式也很友好。它返回16位温度原始值和16位湿度原始值每段后面跟一个CRC校验字节转换公式是官方写死的线性关系算起来没有太多弯弯绕。相比SHT31和SHT35SHT30的性价比更高大部分智能化产品、农业大棚监测、机房环境看板这类场景它的性能已经绰绰有余。这篇文章就以我在STM32F103平台上的实际项目为背景完整复盘从硬件接线、I2C时序到驱动代码、调试排障的整个流程。如果你正准备做自己的第一个传感器驱动工程或者想把SHT30驱动移植到别的平台这篇内容可以直接当参考。1.2 工程目录与代码分层很多初学者拿到SHT30的例程直接把所有代码塞进一个main.c能用但没法维护。我习惯把驱动工程分成三层底层是I2C读写接口中间是SHT30专属命令和数据解析上层是应用逻辑。这样以后换传感器、换主控只动对应层就行。我的工程目录大致是这样Project/ ├── User/ │ ├── main.c │ └── sht30_app.c ├── Driver/ │ ├── sht30.c │ ├── sht30.h │ ├── i2c_soft.c │ └── i2c_soft.h ├── Hardware/ │ └── delay.c └── MDK-ARM/这种拆法的好处是sht30.c里面只关心SHT30的命令字和数据结构不关心底层I2C是用硬件外设还是GPIO模拟i2c_soft.c处理位带操作和时序完全不认识SHT30。我这次用的STM32标准外设库配合Keil5开发环境硬件调试用ST-Link Utility下载固件。如果你习惯用HAL库接口层稍作封装核心驱动代码可以直接平移。2. SHT30关键参数与底层通信解析2.1 I2C地址与硬件接线SHT30支持两个I2C从机地址由ADDR引脚的电平决定。ADDR接地时地址是0x44接高电平一般是VDD时地址是0x45。这里的地址是7位地址I2C通信时左移一位变成8位读写位补在最低位。很多人第一次调试读不到数据就是忘记左移直接在代码里写了0x44当成8位地址用。我建议接线的时候把ADDR引脚直接接地这样7位地址固定为0x44换算成8位写地址是0x88读地址是0x89。如果项目里需要挂两片SHT30就让一片的ADDR接地、另一片的ADDR接VDD两个地址正好分开。硬件上除了VCC、GND之外SCL和SDA都需要接上拉电阻。SHT30模块大多数已经板载了4.7k或者10k上拉如果是自己画的PCB记得在SCL和SDA各加一个4.7k电阻到VCC。没有上拉电阻的话I2C总线拉不高电平通信直接失败。2.2 测量命令与数据返回格式SHT30的命令都是16位发送的时候先发高字节再发低字节。常用的单次测量命令如下命令说明重复性0x2C 0x06高重复性测量精度与功耗较高0x2C 0x0D中重复性测量均衡0x2C 0x10低重复性测量低功耗我平时建议直接用0x2C 0x06高重复性模式下温度和湿度的噪声更低。在高速测量场景下用低重复性模式可以省电但数据跳动会明显一些。发起一次单次测量后需要等待一段时间再读取数据。高重复性模式典型转换时间是12.5ms实际代码里我一般延时至20ms左右确保数据稳定。读取时一次连续读6个字节Byte0: 温度高字节 Byte1: 温度低字节 Byte2: 温度CRC Byte3: 湿度高字节 Byte4: 湿度低字节 Byte5: 湿度CRC温度原始值和湿度原始值都是无符号16位数范围0到65535。计算公式如下温度 -45 175 * (rawTemp / 65535.0) 湿度 100 * (rawHumidity / 65535.0)所以温度分辨率大约是0.00267°C湿度分辨率大约是0.00153%RH对绝大多数监测需求来说绰绰有余。2.3 CRC校验与数据可靠性有些开发者会忽略CRC校验认为I2C本身有应答机制就够了。实际上在电机启动、继电器吸合的现场电源和信号线上的干扰很容易把数据字节改坏这时CRC是唯一能检测到错误的防线。SHT30的CRC多项式是x^8 x^5 x^4 1即多项式值0x31初始值为0xFF。实现代码如下uint8_t sht30_crc8(uint8_t *data, uint16_t len) { uint8_t crc 0xFF; uint8_t i; while (len--) { crc ^ *data; for (i 0; i 8; i) { if (crc 0x80) crc (crc 1) ^ 0x31; else crc 1; } } return crc; }每次读完6个字节分别对温度两字节和湿度两字节做CRC校验校验值不匹配就丢弃本次数据重新发起测量。我实测下来加上CRC校验后系统在强干扰环境下几乎不会出现异常温湿度值。3. 驱动代码实现从初始化到数据解析3.1 底层I2C接口的两种实现方式STM32的I2C外设口碑两极分化主要是意法半导体的硬件I2C在早期固件库的配置上有不少坑很多人被时序问题折磨过后干脆用GPIO模拟。我在这个项目里也用了模拟I2C简单可靠不受引脚复用限制。核心代码如下#define SDA_IN() { GPIOB-CRH 0xFFFF0FFF; GPIOB-CRH | 0x00008000; } #define SDA_OUT() { GPIOB-CRH 0xFFFF0FFF; GPIOB-CRH | 0x00003000; } #define I2C_SCL_H() GPIO_SetBits(GPIOB, GPIO_Pin_10) #define I2C_SCL_L() GPIO_ResetBits(GPIOB, GPIO_Pin_10) #define I2C_SDA_H() GPIO_SetBits(GPIOB, GPIO_Pin_11) #define I2C_SDA_L() GPIO_ResetBits(GPIOB, GPIO_Pin_11) #define I2C_SDA_READ() GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_11)启动信号是SCL高电平时SDA拉低停止信号是SCL高电平时SDA拉高。发送一个字节时从高位开始逐位把数据放到SDA线上SCL拉高再拉低完成一个时钟周期。读字节时把SDA配置为输入每个时钟周期从SDA上读一位。如果你用的是HAL库也可以用硬件I2CHAL_I2C_Master_Transmit(hi2c1, (uint16_t)(addr 1), cmd, 2, 100); HAL_I2C_Master_Receive(hi2c1, (uint16_t)(addr 1), data, 6, 100);硬件的优点是占用CPU少、时序由外设保证缺点是HAL库的阻塞式调用在延时上比较浪费中断方式又增加代码复杂度。我的建议是如果这个项目对功耗和实时性要求不高模拟I2C完全够用如果后续要接多个I2C设备硬件I2C加DMA会是更好的选择。3.2 SHT30核心驱动代码初始化函数很简单上电后等待10ms让传感器稳定然后发一条软复位命令0x30 0xA2再等10ms。之后就可以正常通信了。void sht30_init(void) { delay_ms(10); sht30_write_cmd(0x30, 0xA2); // soft reset delay_ms(10); } uint8_t sht30_read_temperature_humidity(float *temp, float *humi) { uint8_t buf[6]; uint16_t raw_temp, raw_humi; uint8_t temp_crc, humi_crc; sht30_write_cmd(0x2C, 0x06); // single shot, high repeatability delay_ms(20); i2c_start(); i2c_send_byte(0x88); // 0x44 1 | 0 if (i2c_wait_ack() ! 0) { i2c_stop(); return 1; } i2c_send_byte(0x00); // read start at register 0x00 i2c_wait_ack(); i2c_start(); // restart i2c_send_byte(0x89); // 0x44 1 | 1 i2c_wait_ack(); for (int i 0; i 6; i) { if (i 5) buf[i] i2c_read_byte(1); // send ACK else buf[i] i2c_read_byte(0); // send NACK } i2c_stop(); temp_crc sht30_crc8(buf[0], 2); humi_crc sht30_crc8(buf[3], 2); if (temp_crc ! buf[2] || humi_crc ! buf[5]) return 2; // CRC error raw_temp (buf[0] 8) | buf[1]; raw_humi (buf[3] 8) | buf[4]; *temp -45.0f 175.0f * raw_temp / 65535.0f; *humi 100.0f * raw_humi / 65535.0f; return 0; }有一点要注意单次测量模式下每次读数据之前都要重新发一次测量命令否则读出来的是旧数据。连续测量模式下传感器会自动更新不需要每次发命令但功耗会高一些。3.3 数据滤波与工程应用传感器原始数据再怎么稳在真实环境里也会有波动。比如人从旁边走过带起一阵风或者空调出风口刚好对着传感器温度值就会出现瞬时跳变。我做环境监测时习惯加一阶低通滤波static float last_temp 0.0f; float now_temp temp; if (last_temp 0.0f) last_temp now_temp; else last_temp last_temp * 0.7f now_temp * 0.3f; temp last_temp;0.3的权重系数是我反复试出来的滤波后数据既不太迟钝也不会被瞬时尖峰带偏。如果你想更严谨可以加限幅滤波如果相邻两次差值超过阈值直接丢弃本次数据。对于温湿度这种变化缓慢的对象一分钟采集一次就已经足够滤波效果非常理想。4. 常见问题与排查技巧实录4.1 通信失败地址、上拉、引脚冲突我刚开始调试SHT30时最常遇到的问题就是SCL和SDA波形拉不高。用示波器看波形发现数据线只有一点几伏明显不对劲。查了半天发现是模块的板载上拉电阻焊的是10k而我在面包板上又加了一根长杜邦线线缆的寄生电容把信号边沿拖慢了。解决方法是把I2C速率从400kHz降到100kHz或者把上拉电阻换成4.7k。再不行就用短一点的杜邦线毕竟面包板的寄生电容已经不小。另一个隐蔽的坑是引脚复用冲突。我用的是STM32F103的PB10和PB11这两个引脚刚好不是JTAG占用的口但如果选了PB3、PB4或者PA15这几个JTAG引脚需要先禁用JTAG功能否则怎么调都调不通。STM32的JTAG默认占用PA13、PA14、PA15、PB3、PB4这几个引脚在代码里直接配置成普通GPIO是没反应的得先调用GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE);4.2 温湿度数据跳变与环境干扰数据跳变这个问题我遇到过两次完全不同的原因。第一次是传感器放在PCB板角的走线附近板子上有一路开关电源纹波直接耦合到SDA线上导致CRC校验偶尔失败数据瞬间跳到负值。解决方法是把传感器挪远一点电源走线绕开I2C信号线然后在传感器的VCC和GND之间加一个0.1uF去耦电容。第二次是采集周期太短。我把单次测量命令放在一个1ms定时器中断里发结果传感器转换时间还没到中断又发了一次命令数据就越读越乱。后来我单独用了一个10ms软件定时器每次发命令后至少等20ms再读数据问题就消失了。还有一个容易被忽略的点SHT30是高精度传感器但它的响应速度不像热电偶那么快。你要是拿嘴对着哈气数据大概要好几秒才能稳定。所以判断传感器好坏时至少观察30秒再下结论别一看到数据波动就觉得坏了。4.3 ST-Link烧录与调试杂项开发过程中还有几个和驱动本身无关但很影响效率的问题。Keil5如果没有正确安装对应芯片的Pack包编译时会提示找不到目标芯片。解决办法是在Keil的Pack Installer里选择STM32F1系列或者去官网下载DFP包手动安装。如果ST-Link Utility连接不上芯片先检查ST-Link引脚接线SWDIO、SWCLK、GND三根先连好目标板最好单独供电避免ST-Link供电不够把整个板子拖崩。下载失败时按一下复位键再试很多时候是因为芯片还在运行调试接口没来得及响应。4.4 供电与参考电压细节SHT30的供电范围是2.4V到5.5V但测量精度在3.3V供电时最理想。如果你的主控是5V供电最好给传感器单独配一个3.3V的LDO别直接从5V引脚供电。另外SHT30的电源引脚对噪声比较敏感它内部的ADC是Σ-Δ型的电源纹波过大会直接影响测量结果。我实测过同一颗SHT30在3.3V干净电源下和5V经过长导线供电下测出来的温度能差到0.5°C左右。所以如果你的数据精度一直达不到标称值先查电源再查布局最后才怀疑传感器本身。5. 工程扩展与移植经验5.1 多路SHT30的挂载与独立控制一个I2C总线上最多可以挂两片SHT30靠ADDR引脚区分地址。ADDR接地为0x44ADDR接VDD为0x45。我在一个机柜环境监测项目里就是这么干的上下两个位置各放一片同时采集数据分别上报。初始化的时候分别对两个地址发软复位命令测量时也是分别发命令、分别读数据。需要注意的是同一时刻不要对两个从机同时发起测量命令否则总线竞争会导致数据错乱。正确做法是先发第一片的测量命令等转换完成、读完数据再操作第二片。虽然多花一点时间但可靠性高得多。5.2 向其他平台移植的思路做这个驱动之前我还在K210开发板上跑过SHT30后来又要迁移到STM32平台。核心驱动代码几乎没怎么动只改了底层I2C读写函数。这说明抽象层的设计非常关键。移植时注意以下几点把I2C的启动、停止、读写字节封装成独立的函数平台相关的代码只在这几个函数里出现。延时函数也尽量用统一的接口比如delay_ms底层实现可以依赖定时器也可以用系统滴答。CRC校验和温湿度计算是纯数学逻辑完全跨平台不用改。如果目标平台支持硬件I2C直接把底层几个函数替换成对应库的API即可。如果ST公司的HAL库版本不同I2C的句柄类型可能不一样但只要封装层做得好上层根本察觉不到。5.3 低功耗场景的设计建议如果你的产品是电池供电SHT30的低功耗特性就非常重要。建议用单次测量模式每次测量完成后让传感器进入空闲状态主控也进入睡眠等需要数据的时候再唤醒。我在一个温湿度记录仪项目里用SHT30单次测量模式加STM32的停机模式采集间隔设成60秒一次两颗AA电池居然撑了半年多。具体做法是主控醒来后发测量命令然后等20ms读数据存进Flash再进入停机模式。SHT30在单次测量模式下的平均功耗极低整体功耗大头反而在主控的唤醒和Flash写入上。6. 调试工具与实测数据分享6.1 调试工具组合推荐没有趁手的工具驱动调试就是抓瞎。我日常调试SHT30的组合如下工具用途ST-Link V2程序下载与在线调试逻辑分析仪抓取I2C时序波形ST-Link Utility下载固件、查看Flash串口助手打印温湿度数据万用表检查上拉电阻与供电逻辑分析仪强烈推荐入手一个几十块钱的就能用接上SCL和SDA以后通信有没有ACK、数据对不对一眼就能看出来。我第一次调通I2C就是靠逻辑分析仪发现发送从机地址后没有得到ACK才意识到地址左移的问题。6.2 实测数据与稳定性对照我在室温环境下用同一颗SHT30做了连续8小时采集测试环境温度在25°C附近缓慢波动。加入CRC校验和滤波算法后温度数据的最大跳变量从0.3°C降到了0.08°C湿度数据也稳定在±0.5%RH以内。数据稳定性提升的关键不是传感器本身而是三点电源干净、I2C速率别太高、采集间隔别太短。有一个朋友跟我反馈他把采集间隔从1秒改成5秒后数据乱跳的问题几乎消失了。原因很简单SHT30每次测量都是独立的频繁启动会让内部稳定时间不够尤其是高重复性模式下转换时间要12.5ms左右读太快反而容易出错。6.3 与其它传感器方案的选型建议如果你的项目预算很紧DHT11也能用但要做好数据误差大、响应慢的心理准备。DHT22精度稍好价格也不贵但单总线协议在代码层面不如I2C舒服。如果项目对体积要求高可以考虑SHTC3封装更小通信协议类似。如果对精度有极致要求SHT35精度更高但价格也贵不少。从驱动开发的角度看SHT30的寄存器结构最规整命令字不多数据手册写得清楚非常适合作为I2C传感器驱动的入门练习。不少网友在留言里问我第一次做传感器驱动选什么好我一般就推荐SHT30踩坑少成就感来得快。7. 手上这套方案的最终心得回头看这个STM32的SHT30温湿度计驱动工程其实核心工作就是把一个数字传感器的通信协议吃透然后做好分层的代码封装。I2C时序、寄存器读写、CRC校验这些说白了都是套路一旦理解了原理以后换成别的I2C传感器基本上半天就能搞定。我现在自己在用的这套代码已经在两个量产的壳子里稳定跑了大半年没出过数据错乱的问题。如果让我重新做一遍我可能会直接用HAL库的硬件I2C加DMA把CPU占用再降一降。但对于学习来说从模拟I2C开始动手才能真正理解时序的每一个细节这对后面排查更复杂的通信问题帮助很大。最后再分享一个小技巧如果你手头有之前调好的ST-Link和串口模块调试阶段尽量把传感器的原始温度字和湿度字直接打印出来不要只打印最终换算后的物理量。看到原始值你才能判断数据是通信层的问题还是计算层的偏差。用熟了以后你会发现调试效率比原来高出一大截。本文还有配套的精品资源点击获取
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。