STM32 HAL库驱动SHT30温湿度传感器实战:从CubeMX配置到滤波校准
2026/9/9 22:21:27 网站建设 项目流程

简介:面向嵌入式开发者与物联网爱好者,这份资源提供了基于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

主要原因有三个:

  1. 引脚自由,任意两个GPIO就能组合成SCL和SDA,不用纠结硬件I2C引脚的冲突
  2. 时序可控,模拟I2C的时钟频率、上升沿/下降沿都自己说了算,SHT30对时序要求其实很宽松(标准模式100kHz,快速模式400kHz),软件延时完全能hold住
  3. 排查方便,逻辑分析仪抓到波形不对,直接改代码里的延时就行,不用翻参考手册查外设寄存器

当然,硬件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.5Hz0x20 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初始化工程,几步就搞定:

  1. 选择芯片型号STM32F103ZET6,时钟配置为72MHz主频(外部8MHz晶振,PLL倍频9倍)
  2. Pinout & Configuration里,把PB6配置为GPIO_Output,PB7配置为GPIO_Output——注意模拟I2C下两个引脚都设为开漏输出(Open Drain),这样不需要额外加外部上拉也能工作,因为HAL库输出低电平时拉低,释放时为高阻态,配合外部上拉电阻到3.3V
  3. 调试接口如果板载ST-Link,把SWD使能,方便下载调试
  4. 串口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通信卡死在等待ACKSDA/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上拉、滤波校准、错误处理,每一环都做到位,数据才能真正可靠。希望这篇内容能帮你少踩几个坑。

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询