简介:面向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和SHT35,SHT30的性价比更高,大部分智能化产品、农业大棚监测、机房环境看板这类场景,它的性能已经绰绰有余。
这篇文章就以我在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库,也可以用硬件I2C:
HAL_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接地为0x44,ADDR接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和串口模块,调试阶段尽量把传感器的原始温度字和湿度字直接打印出来,不要只打印最终换算后的物理量。看到原始值,你才能判断数据是通信层的问题还是计算层的偏差。用熟了以后,你会发现调试效率比原来高出一大截。
本文还有配套的精品资源,点击获取