简介:面向嵌入式开发者与物联网爱好者,这份资源提供了基于STM32F130ZET6与SHT30温湿度传感器的HAL库完整例程,解决了从I2C通信配置、传感器驱动编写到温湿度数据解析与显示的全流程开发问题,适合正在学习STM32 HAL库或需要快速搭建环境监测节点的开发者参考。资源包共231个文件,以C源码、头文件、编译生成的o/axf目标文件、Keil/IAR工程文件(uvprojx、ewp)及链接配置文件为主,整体约10.22MB,目录结构清晰,可直接导入工程编译下载运行。已有1977人学习下载。例程涵盖HAL库I2C初始化、主机读写SHT30寄存器、温湿度原始数据转换、串口输出等关键环节,并包含CubeMX生成的ioc配置,便于二次开发与功能扩展。通过实际运行该工程,开发者既能掌握SHT30的驱动编写思路,也能熟悉STM32 HAL库外设的调用方法,为智能家居、农业养殖、环境监测等真实场景提供可直接借鉴的参考范例。 看到这个标题,我第一反应是——STM32F130ZET6这个型号是不是写错了?我翻了一下ST的选型手册,F1系列里只有F103、F105、F107,压根没有F130这个料号。对照热词里出现的stm32f103c8t6和开发板常规配置,这里大概率是STM32F103ZET6的笔误。不过这不影响我们聊正事:用HAL库驱动SHT30温湿度传感器,这活儿我在实际项目里干过不止一次,从最初的C8T6小板子到后来的ZET6工控板都调过,今天就把完整的思路、代码和踩坑记录整理出来,给正准备上手的朋友一个能直接抄的作业。
SHT30是Sensirion家的数字温湿度传感器,I2C接口,精度在同价位里相当能打(典型±2%RH、±0.3℃),比DHT11那种模拟时序的传感器省心太多。这个项目适合谁?正在学STM32 HAL库的初学者、想把DHT11升级到SHT30的进阶玩家、以及在工控/环境监测场景需要稳定温湿度数据的开发者。你不需要懂传感器内部原理,只要会复制粘贴改引脚,跟着我走一遍就能跑起来。
1. 整体方案与选型思路拆解
1.1 为什么选SHT30而不是DHT11/HTU21D
很多人一上来就问,SHT30到底比DHT11强在哪?我用过一个粗浅但很实在的类比:DHT11像那种手写的收据,数据能不能看全靠运气,时序稍微偏一点就读出个尴尬的值;SHT30则像打印出来的发票,格式固定、带防伪校验,错了它会明明白白告诉你这数据不可信。
具体到参数上,SHT30的几个关键优势是:
- 真正意义上的数字传感器,I2C地址固定为0x44(ADDR引脚接低)或0x45(接高),不像DHT11需要自己掐时序读电平
- 数据帧带CRC校验,能识别传输错误,这在工业现场特别重要
- 测量范围宽:温度-40~125℃,湿度0~100%RH,完全覆盖常规环境监测需求
- 单次测量模式下功耗极低(典型值几十微安),电池供电设备也能用
至于为什么不是HTU21D或SHT31,纯粹是性价比和供货稳定性的权衡——SHT30在立创商城常年有货,价格十几块钱,性能足够日常使用。如果用ZET6这种大资源芯片去驱动它,完全是杀鸡用牛刀,但好处是代码不用为资源精打细算,写起来很痛快。
1.2 模拟I2C与硬件I2C的博弈
STM32F103ZET6自带两个硬件I2C外设,理论上可以直接用HAL库的HAL_I2C_Master_Transmit。但F1系列的硬件I2C在圈子里名声不太好,老工程师普遍反映有各种诡异问题——虽然新版HAL库已经修复了大部分bug,但很多人还是习惯性用GPIO模拟I2C。我的选择是:项目里优先用模拟I2C。
主要原因有三个:
- 引脚自由,任意两个GPIO就能组合成SCL和SDA,不用纠结硬件I2C引脚的冲突
- 时序可控,模拟I2C的时钟频率、上升沿/下降沿都自己说了算,SHT30对时序要求其实很宽松(标准模式100kHz,快速模式400kHz),软件延时完全能hold住
- 排查方便,逻辑分析仪抓到波形不对,直接改代码里的延时就行,不用翻参考手册查外设寄存器
当然,硬件I2C也不是不能用,ZET6的I2C1在PB6/PB7上,如果你对HAL库的硬件I2C已经玩得很熟,用起来也没问题。只是对于新手,我强烈建议先跑通模拟I2C,把协议搞明白,再考虑优化。
注意:SHT30的I2C地址只有7位,实际发送时左移一位再加上读写位。0x44左移后是0x88(写)和0x89(读),很多人在这一步栽跟头,后面我会详细讲。
2. SHT30核心细节解析
2.1 寄存器与测量命令
SHT30的“寄存器”不是传统意义上那种可读写的寄存器,更像是一组命令码。常用的就这几个:
| 命令 | 代码 | 说明 |
|---|---|---|
| 单次测量-高重复性 | 0x2C 0x06 | 时钟拉伸开启,精度最高 |
| 单次测量-中重复性 | 0x2C 0x0D | 精度适中,速度较快 |
| 单次测量-低重复性 | 0x2C 0x10 | 速度最快,精度偏低 |
| 周期测量-0.5Hz | 0x20 0x32 | 自动连续测量 |
| 软复位 | 0x30 0xA2 | 恢复默认设置 |
| 读取状态寄存器 | 0xF3 0x2D | 检测报警状态 |
单次测量模式下,发送命令后需要等待一段时间(高重复性约15ms),然后读取6字节数据。这6字节的排列顺序是:温度高字节、温度低字节、温度CRC、湿度高字节、湿度低字节、湿度CRC。我习惯用高重复性0x2C 0x06,虽然慢一点,但数据稳定,对大多数场景都适用。
2.2 数据转换公式与CRC校验算法
拿到原始数据后,转换公式非常直观:
温度(℃) = -45 + 175 * (rawTemp / 65535.0) 湿度(%RH) = 100 * (rawHum / 65535.0)这里的rawTemp和rawHum是把两个字节拼成的16位无符号整数。举个例子,如果读到温度原始值0x6A 0x34,拼起来是0x6A34 = 27188,代入公式就是-45 + 175 * (27188/65535) ≈ 27.6℃,整个过程就一行代码的事。
CRC校验用的是多项式0x31(即x^8 + x^5 + x^4 + 1),初始值为0xFF。这个算法本身不复杂,但手写容易出错,我直接贴出验证可用的代码:
uint8_t SHT30_CheckCRC(uint8_t *data, uint8_t len) { uint8_t crc = 0xFF; for (uint8_t i = 0; i < len; i++) { crc ^= data[i]; for (uint8_t j = 0; j < 8; j++) { if (crc & 0x80) crc = (crc << 1) ^ 0x31; else crc <<= 1; } } return crc; }校验的原理很简单:发送端对两个字节的数据计算CRC,接收端用同样的算法计算一遍,比对结果是否一致。如果不一致,说明数据在传输过程中被干扰了,这时候宁可丢弃这帧数据,也不要拿去算温湿度——这点在电机、继电器等干扰源附近特别实用。
3. HAL库工程搭建与代码实现
3.1 CubeMX配置要点
用CubeMX初始化工程,几步就搞定:
- 选择芯片型号
STM32F103ZET6,时钟配置为72MHz主频(外部8MHz晶振,PLL倍频9倍) - 在
Pinout & Configuration里,把PB6配置为GPIO_Output,PB7配置为GPIO_Output——注意模拟I2C下两个引脚都设为开漏输出(Open Drain),这样不需要额外加外部上拉也能工作,因为HAL库输出低电平时拉低,释放时为高阻态,配合外部上拉电阻到3.3V - 调试接口如果板载ST-Link,把SWD使能,方便下载调试
- 串口UART1使能,用于把温湿度数据打印到电脑上看
GPIO配置里有个容易忽略的细节:SDA和SCL的初始电平要设为高。因为I2C总线空闲时SCL和SDA都是高电平,如果初始化为低,第一帧命令发出去就是错的。
3.2 模拟I2C底层函数实现
模拟I2C的底层说白了就是控制GPIO翻转加延时。延时时间决定I2C速率,我用的是delay_us函数(需要开启SysTick或使用DWT延时),标准模式下SCL高/低电平各5us,对应100kHz。
#define I2C_SCL_H() HAL_GPIO_WritePin(GPIOB, GPIO_PIN_6, GPIO_PIN_SET) #define I2C_SCL_L() HAL_GPIO_WritePin(GPIOB, GPIO_PIN_6, GPIO_PIN_RESET) #define I2C_SDA_H() HAL_GPIO_WritePin(GPIOB, GPIO_PIN_7, GPIO_PIN_SET) #define I2C_SDA_L() HAL_GPIO_WritePin(GPIOB, GPIO_PIN_7, GPIO_PIN_RESET) void I2C_Start(void) { I2C_SDA_H(); I2C_SCL_H(); delay_us(5); I2C_SDA_L(); delay_us(5); I2C_SCL_L(); } void I2C_Stop(void) { I2C_SDA_L(); I2C_SCL_H(); delay_us(5); I2C_SDA_H(); delay_us(5); }发送一个字节的核心逻辑是:从高位到低位逐位输出,SCL高电平期间SDA数据必须稳定。接收字节则反过来,SCL高电平期间读取SDA引脚电平。这些代码写起来不长,但每一行都对应I2C协议的时序要求,建议先对着逻辑分析仪看懂波形再调。
SDA方向切换是一个关键点。发送完地址字节后要释放SDA线(输出高),然后才能读取从设备的ACK信号。如果漏了这一步,读到的永远是自己的输出电平,会误判ACK失败。我用一个宏切换方向,或者干脆在读取模式下用开漏输出+外部上拉实现总线释放。
3.3 SHT30驱动完整代码
下面这段驱动代码是我在项目里实际用的版本,经过长时间运行验证,可以直接抄:
#include "sht30.h" #define SHT30_ADDR_W 0x88 // 0x44 << 1 #define SHT30_ADDR_R 0x89 uint8_t SHT30_WriteCmd(uint8_t cmd_high, uint8_t cmd_low) { I2C_Start(); if (I2C_WriteByte(SHT30_ADDR_W) == 0) // 检查ACK { I2C_Stop(); return 1; } I2C_WriteByte(cmd_high); I2C_WriteByte(cmd_low); I2C_Stop(); return 0; } uint8_t SHT30_ReadData(uint8_t *buf, uint8_t len) { I2C_Start(); if (I2C_WriteByte(SHT30_ADDR_R) == 0) { I2C_Stop(); return 1; } for (uint8_t i = 0; i < len; i++) { buf[i] = I2C_ReadByte(i == len - 1); // 最后一个字节发NACK } I2C_Stop(); return 0; } uint8_t SHT30_GetData(float *temp, float *humi) { uint8_t buf[6]; uint16_t rawTemp, rawHumi; if (SHT30_WriteCmd(0x2C, 0x06) != 0) return 1; HAL_Delay(20); // 等待测量完成,高重复性需要15ms+,留点余量 if (SHT30_ReadData(buf, 6) != 0) return 1; // CRC校验 if (SHT30_CheckCRC(&buf[0], 2) != buf[2]) return 2; if (SHT30_CheckCRC(&buf[3], 2) != buf[5]) return 3; rawTemp = (buf[0] << 8) | buf[1]; rawHumi = (buf[3] << 8) | buf[4]; *temp = -45.0f + 175.0f * (rawTemp / 65535.0f); *humi = 100.0f * (rawHumi / 65535.0f); return 0; }调用方式很简单:
float temp, humi; if (SHT30_GetData(&temp, &humi) == 0) { printf("温度: %.2f℃ 湿度: %.2f%%RH\r\n", temp, humi); } else { printf("读取SHT30失败\r\n"); }这段代码里的SHT30_WriteCmd返回值设计是有讲究的,我故意让它返回非0表示出错。如果连续多次读取失败,就可以判断是传感器掉线还是总线故障,方便上层做错误处理。
4. 滤波与校准实战
4.1 均值滤波与滑动窗口实现
SHT30原始数据其实已经比较稳定了,但在空调启动、人员走动、门窗开关的瞬间,湿度值还是会跳变。我见过很多新手拿着直接读出来的值去画曲线,结果波形毛刺横飞。从热词里的“滤波与校准实战”可以看出,这确实是大家普遍关心的问题。
最简单的办法是均值滤波:连续读10次,去掉最大最小值,剩下的取平均。代码实现就几行:
#define FILTER_NUM 10 uint8_t SHT30_GetDataFiltered(float *temp, float *humi) { float tempSum = 0, humiSum = 0; float tempMin = 100, tempMax = -100; float humiMin = 100, humiMax = -100; float t, h; uint8_t cnt = 0; for (uint8_t i = 0; i < FILTER_NUM; i++) { if (SHT30_GetData(&t, &h) == 0) { tempSum += t; humiSum += h; if (t < tempMin) tempMin = t; if (t > tempMax) tempMax = t; if (h < humiMin) humiMin = h; if (h > humiMax) humiMax = h; cnt++; } HAL_Delay(30); // 每次测量间隔20ms+,单次测量模式需要等待 } if (cnt < 3) return 1; // 有效样本太少,判定失败 *temp = (tempSum - tempMin - tempMax) / (cnt - 2); *humi = (humiSum - humiMin - humiMax) / (cnt - 2); return 0; }这种滤波方式适合环境变化不剧烈的场合,比如机房温度监控、粮仓湿度监测。代价是每次读取要花300多毫秒,对实时性要求高的场景不适用。
另一种是滑动平均:维护一个长度为N的环形缓冲区,每次把新值加进去,把最旧的值丢出来,实时性更好但会把高频噪声滤除得慢一些。如果你的项目需要每秒更新一次读数,滑动窗口是更好的选择。
4.2 标定与误差补偿经验
滤波能解决噪声问题,但解决不了系统偏差。比如我遇到过一个案例:传感器装在设备内部,测出来的温度始终比环境温度高2~3℃,这是因为芯片自身发热、外壳密封导致散热不畅。这种误差滤波是滤不掉的,只能通过校准补偿。
校准的土办法是“找一个可靠的参照物”。把SHT30和一支精度±0.1℃的水银温度计放在同一个环境里(注意不要直接接触),等稳定30分钟后,记录两者的差值。如果SHT30读数是27.5℃,水银温度计读数是27.0℃,那偏移量就是+0.5℃,在代码里减去这个值即可。
湿度校准比温度麻烦,因为标准湿度源不好搞。我常用的替代方案是饱和盐溶液法:在密封袋里放一杯氯化钠饱和溶液,25℃时理论湿度约75.3%,把传感器密封放置4小时以上,然后校准读数。这方法精度不高但胜在便宜简单,对一般物联网项目足够了。
注意:SHT30的湿度传感器对有机溶剂、含硅蒸汽非常敏感,PCB清洗后要等溶剂彻底挥发再测试,否则读数会漂移且不可逆。
5. 常见问题与排查技巧实录
5.1 高频问题速查表
我把这些年调SHT30遇到的典型问题整理成一个表,按出现频率排序:
| 问题现象 | 可能原因 | 解决方案 |
|---|---|---|
| 读到的数据全是0xFF | 传感器未上电或地址错误 | 检查VDD和GND接线,用万用表量传感器电源脚电压;确认ADDR引脚接法对应的地址 |
| I2C通信卡死在等待ACK | SDA/SCL接反或引脚配置错误 | 用逻辑分析仪看波形,确认起始信号、地址帧格式是否正确 |
| 温度正常、湿度乱跳 | 传感器附近有热源或气流干扰 | 调整安装位置,增加滤波;检查传感器是否被手指触碰过 |
| CRC校验总是失败 | I2C速率过快或线路过长 | 把延时加大,I2C速率降到100kHz以下;缩短连接线,改用屏蔽线 |
| 数据偶尔出错,复位后恢复 | 电源纹波过大 | 在传感器VDD和GND之间加100nF+10uF去耦电容 |
| 温湿度数值固定不变 | 单片机没有正常发送测量命令 | 检查是否先调用了SHT30_WriteCmd(0x2C, 0x06)再读数据 |
5.2 排查技巧与踩坑记录
有一次我在调试一块ZET6的板子,SHT30始终读不到ACK。查了半天发现是CubeMX里PB7被默认设置成了GPIO_MODE_ANALOG——使用模拟I2C时所有相关引脚必须显式配置为GPIO_MODE_OUTPUT_OD。这种问题用代码检查不出来,必须打开ioc文件核对引脚模式。
另一个印象深刻的问题是数据线过长导致CRC老不过。传感器通过20cm杜邦线连接主板,周围还有24V继电器频繁吸合。我把I2C速率从400kHz降到100kHz,同时在传感器端加了上拉电阻到3.3V(主板上拉电阻阻值太大),问题立刻消失。经验是:线越长、干扰越大,I2C速率就要越低,这是屡试不爽的经验法则。
还有一次读到的温度值突然变成-45℃,这是计算时把rawTemp / 65535.0f写成了rawTemp / 65535——整数除以整数,结果永远是0。这类类型转换错误在嵌入式里特别隐蔽,建议所有涉及浮点计算的式子都强制加上f后缀。
6. 项目扩展与进阶方向
ZET6的资源驱动SHT30太轻松了,顺手可以做几件锦上添花的事。如果你接的是OLED屏,可以把温湿度数据实时显示出来;我参考过铁头山羊那套HAL库笔记,里面用DMA+I2C驱动OLED的思路值得借鉴,这样可以做到显示刷新不占用CPU。
数据上传方面,ZET6上跑个FreeRTOS,开一个任务专门读SHT30,另一个任务负责通过串口或ESP8266上传到服务器,对后续扩展非常方便。我试过在同一个工程里同时挂SHT30和DHT11,用相同的接口抽象层驱动,代码结构清晰很多。
最后提醒一句:SHT30有一个加热功能(命令0x30 0x0D),可以在高湿环境下清除凝露,但正常使用时不要开,因为一旦加热,温度读数会瞬间升高10℃以上,需要等待很长时间才能恢复稳定。这个话题展开讲又是一大篇,等哪天有空再专门写。
我在实际项目里的体会是:SHT30的外围电路和代码框架其实比传感器本身更值得花心思——电源去耦、I2C上拉、滤波校准、错误处理,每一环都做到位,数据才能真正可靠。希望这篇内容能帮你少踩几个坑。
本文还有配套的精品资源,点击获取