STM32F4驱动SHT30温湿度传感器:I2C通信与CRC校验实战
简介:面向STM32F4嵌入式开发者的SHT30温湿度传感器驱动资源,基于模拟IIC总线协议完成STM32F407与传感器之间的数据交互,覆盖从引脚初始化、时序控制到温湿度值换算的完整链路,适合需要在环境监控、智能家居或工业现场快速接入温湿度采集功能的开发人员。资源压缩包共包含3个文件,分别为C语言驱动源文件、对应的头文件以及一份PDF格式的中文说明文档,整体体积约858KB,结构精简,便于直接移植到自己的工程。目前已有1595人学习浏览。源码围绕普通GPIO模拟IIC通信展开,详细实现了起始信号、从机寻址、命令写入、数据读取和停止信号等关键步骤,并支持单次与周期两种测量模式;PDF文档则进一步讲解了SHT30的寄存器配置、地址引脚选择以及STM32F407外设初始化注意事项,可帮助读者理解传感器底层工作机制,同时代码稍作修改即可迁移到其他STM32型号或兼容单片机平台。
1. 项目概述与需求拆解
拿到这个压缩包名字“SHT30温湿度传感器+stm32f4.zip”的时候,我第一反应是:这兄弟的项目应该正处在“传感器数据采集”这个常见的嵌入式开发节点上。STM32F4系列在目前的MCU市场里仍然是性价比极高的选择——主频够高、外设丰富、生态成熟,而SHT30作为Sensirion推出的新一代数字温湿度传感器,用I2C接口通信,正好是STM32F4最基础也最常用的外设之一。这两者结合起来,几乎覆盖了环境监测、智能家居、农业大棚、冷链运输、机房监控等一大票真实应用场景,属于典型的“小项目但五脏俱全”的类型。
拆开看这个项目名字,它的本质需求其实是:在STM32F4平台上完成SHT30的驱动开发,实现温湿度数据的读取和输出,最终交付一个能稳定运行的工程,通常是基于Keil5环境,烧录进开发板后能从串口或者OLED上看到实时数据。这个需求看起来简单,但在实际开发中藏着不少坑——I2C时序问题、CRC校验问题、传感器响应速度问题、以及和FreeRTOS等系统配合时的资源竞争问题,每一个都能让初入行的朋友卡上一两天。
再说说这个压缩包最可能的内部结构。我猜测它应该包含一个完整的HAL库或标准库工程目录,里面有SHT30的驱动源文件(sht30.c和sht30.h)、main.c主函数、可能还有串口重定向或者OLED显示的相关代码。如果你拿到的压缩包里没有这些,那也不要紧,我会在下面的内容里把整个流程重新梳理一遍,让你能独立写出这套驱动。
这篇文章适合谁看?一是刚接触STM32传感器开发的在校学生,二是做物联网产品原型验证的硬件工程师,三是想了解I2C通信实战细节的自由开发者。我尽量不写“教科书式”的废话,直接把我踩过的坑、验证过的方法拿出来讲。
2. 核心硬件与通信协议分析
2.1 SHT30传感器的关键特性
SHT30是Sensirion公司SHT3x系列里最经典的型号之一。相比上一代SHT20、SHT21,SHT30在精度、功耗和长期稳定性上都有明显提升。具体来看几个核心参数:
- 温度测量范围:-40℃到125℃,精度典型值±0.2℃
- 湿度测量范围:0%RH到100%RH,精度典型值±2%RH
- 供电电压:2.15V到5.5V,对3.3V系统非常友好
- 通信接口:I2C,支持标准模式(100kHz)和快速模式(400kHz)
- 默认I2C地址:0x44(ADDR引脚接低电平),如果ADDR接高电平则为0x45
这个传感器的功耗控制也很出色,在单次测量模式下,平均电流可以低到几十微安级别,非常适合电池供电的便携设备。对于STM32F4这种性能溢出较多的MCU来说,驱动SHT30简直是杀鸡用牛刀,但这恰恰是工程上常见的选择——运算富余可以换开发效率,传感器性能才是精度的天花板。
SHT30支持多种测量模式,包括单次测量和周期测量。单次测量模式下,主机发送测量命令后,传感器执行一次测量并在完成后将结果存入数据寄存器,主机然后读取;周期测量模式下,传感器按照设定的频率自动测量并更新寄存器,主机可以随时读取最新数据,适合需要持续监控的场景。在实际项目中,如果设备需要低功耗睡眠,就选单次测量模式,测完就睡;如果是持续上电的监控设备,周期测量模式更省事,不用频繁发送测量命令。
2.2 I2C通信时序与命令格式
I2C是一种两线制的串行通信协议,SCL提供时钟,SDA传输数据。SHT30的I2C时序和绝大多数I2C从设备一样:起始信号、从机地址加读写位、寄存器地址或命令字、数据、停止信号。
SHT30的命令格式非常有特点,所有命令都是16位——两个字节。比如最常用的单次测量命令:
- 0x2C 0x06:高重复性测量,时钟拉伸使能
- 0x2C 0x0D:中重复性测量,时钟拉伸使能
- 0x2C 0x10:低重复性测量,时钟拉伸使能
这里“时钟拉伸”是个值得展开讲的概念。SHT30在接收到测量命令后会开始内部测量,测量需要时间(高重复性约15.5ms,低重复性约2.5ms)。如果使能了时钟拉伸,传感器会把SCL拉低,告诉主机“我还没测完,你先等着”;如果禁用了时钟拉伸,主机就发完命令后等待固定延时再读取数据。实际上在STM32的硬件I2C实现里,时钟拉伸的实现比较复杂,所以大多数国内开发者的代码都采用“延时后读取”的方式——发完测量命令,延时20ms或30ms,然后再发读命令取数据。实测下来,这种方式稳定可靠,完全满足项目需求。
读取数据时,主机发送从机地址加读位后,传感器会连续返回6个字节:温度高字节、温度低字节、温度CRC校验值、湿度高字节、湿度低字节、湿度CRC校验值。这里必须注意,SHT30带CRC校验,很多初学的朋友把这个校验当摆设直接跳过,这样做在大多数情况下数据也能正常读出来,但一旦遇到数据跳变、偶发错误,你根本分不清是传感器坏了还是通信干扰,所以强烈建议认真做CRC校验。
3. 从零搭建STM32F4驱动工程
3.1 Keil5工程配置要点
工欲善其事,必先利其器。我默认你用的是Keil5(MDK-ARM),这是STM32F4开发最主流的IDE之一。新建工程时有几个关键配置点容易出错:
芯片型号选择:如果用的是正点原子或野火的板子,通常是STM32F407ZGT6或STM32F429IGT6;如果用的是Nucleo或Discovery板,可能是STM32F411RET6或STM32F446RE。型号不同,Flash大小和外设资源略有差异,但I2C这个外设基本一致。
时钟配置:STM32F4上电默认使用HSI(16MHz内部高速时钟),这使得I2C的波特率计算会变得非常尴尬。务必在SystemInit或CubeMX生成的代码里把时钟树切换到HSE(外部晶振),并配置PLL锁相环把系统时钟提升到168MHz(F407)或100MHz(F411等)。I2C外设的时钟源通常是APB1总线时钟,APB1在168MHz系统时钟下一般为42MHz。I2C时钟频率的计算公式为:
SCL频率 = APB1时钟频率 / (I2C_CCR值 × 2)
如果APB1为42MHz,想得到400kHz的SCL,那么CCR值计算为:
I2C_CCR = 42MHz / (400kHz × 2) = 52.5 ≈ 53
这样算出来的实际SCL频率约为396kHz,在I2C快速模式的允许误差范围内。
如果不想手动算,用STM32CubeMX可以自动生成I2C初始化代码,非常方便。但只使用CubeMX生成初始化部分,业务驱动代码自己写,这种“半自动”的方式我最推荐——既节约时间,又能保证代码核心可控。
3.2 I2C底层驱动实现
STM32F4的I2C外设使用主要有两种方式:一种是使用芯片自带的硬件I2C,另一种是用GPIO模拟I2C。我在实际项目中两种都用过,给一个负责任的结论:STM32F4的硬件I2C没有传说中那么难用,但有兼容性坑;GPIO模拟I2C是万能的救命稻草。
硬件I2C驱动的核心是以下几个步骤:
- 初始化I2C外设:配置GPIO复用功能为I2C(F4系列中,I2C1的SCL和SDA通常是PB6和PB7),设置时钟频率,使能I2C。
- 发送起始信号和从机地址。
- 等待事件标志位置位(EV5、EV6、EV8等)。
- 发送数据或读取数据。
- 发送停止信号。
具体到代码,HAL库已经把底层的寄存器操作封装好了,写起来清爽很多。核心代码大致如下:
// I2C初始化
void MX_I2C1_Init(void)
{
hi2c1.Instance = I2C1;
hi2c1.Init.ClockSpeed = 400000;
hi2c1.Init.DutyCycle = I2C_DUTYCYCLE_2;
hi2c1.Init.OwnAddress1 = 0;
hi2c1.Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT;
hi2c1.Init.DualAddressMode = I2C_DUALADDRESS_DISABLE;
hi2c1.Init.OwnAddress2 = 0;
hi2c1.Init.GeneralCallMode = I2C_GENERALCALL_DISABLE;
hi2c1.Init.NoStretchMode = I2C_NOSTRETCH_DISABLE;
HAL_I2C_Init(&hi2c1);
}
// 写命令
uint8_t SHT30_WriteCommand(uint16_t cmd)
{
uint8_t buf[2];
buf[0] = (cmd >> 8) & 0xFF;
buf[1] = cmd & 0xFF;
return HAL_I2C_Master_Transmit(&hi2c1, SHT30_ADDR << 1, buf, 2, 100);
}
这里有个细节要注意:HAL_I2C_Master_Transmit的DevAddress参数需要传入的是从机地址左移一位(即7位地址加读写位),地址0x44要传0x88。很多新手在这里直接传0x44,导致通信失败,找半天bug,结果是地址没移位,这个错误非常经典。
3.3 SHT30驱动完整代码
有了底层I2C,SHT30的驱动就可以封装起来了。我提供一个经过验证的驱动核心代码:
// sht30.c
#include "sht30.h"
static uint8_t SHT30_CRC8(uint8_t *data, uint8_t len)
{
uint8_t crc = 0xFF;
uint8_t bit;
for (uint8_t i = 0; i < len; i++) {
crc ^= data[i];
for (bit = 0; bit < 8; bit++) {
if (crc & 0x80)
crc = (crc << 1) ^ 0x31;
else
crc <<= 1;
}
}
return crc;
}
uint8_t SHT30_Init(void)
{
// 发送软复位命令 0x30 0xA2
uint16_t cmd = 0x30A2;
return SHT30_WriteCommand(cmd);
}
uint8_t SHT30_ReadTempHum(float *temp, float *hum)
{
uint8_t buf[6];
uint16_t tmp_raw, hum_raw;
uint8_t crc_val;
// 发送单次测量命令:0x2C 0x06 高重复性
uint16_t cmd = 0x2C06;
if (SHT30_WriteCommand(cmd) != HAL_OK)
return 1;
// 等待测量完成,高重复性约15.5ms,这里延时取30ms
HAL_Delay(30);
// 读取6字节数据
if (HAL_I2C_Master_Receive(&hi2c1, SHT30_ADDR << 1, buf, 6, 100) != HAL_OK)
return 2;
// 校验温度数据
crc_val = SHT30_CRC8(buf, 2);
if (crc_val != buf[2])
return 3;
// 校验湿度数据
crc_val = SHT30_CRC8(&buf[3], 2);
if (crc_val != buf[5])
return 4;
// 转换原始值为实际温湿度
tmp_raw = (buf[0] << 8) | buf[1];
hum_raw = (buf[3] << 8) | buf[4];
*temp = -45.0f + 175.0f * (float)tmp_raw / 65535.0f;
*hum = 100.0f * (float)hum_raw / 65535.0f;
return 0;
}
这个CRC实现是标准的多项式0x31(对应多项式x^8 + x^5 + x^4 + 1)算法,初值0xFF,和SHT30数据手册中的描述一致。在《SHT30温湿度传感器+stm32f4.zip》这个项目中,CRC是区分“能用”和“稳定用”的关键细节。我在一个农业温室项目中就是因为没做CRC,读到的湿度偶尔偏差3%RH,传感器本身没问题,是线缆较长导致数据位翻转,后来加上CRC校验,异常数据直接丢弃重读,问题才根治。
温度计算公式的来历值得多说一句:SHT30内部是用16位ADC采样,输出原始值0到65535,线性映射到-45℃到+130℃(数据手册给出的映射范围实际是-40到125,但公式里用-45和175这两个常数,这是Sensirion官方推荐的线性化系数)。湿度则是0%到100%线性映射。所以用上面的公式做浮点计算就能得到精度达0.01级别的温湿度值,对绝大多数应用完全够用。
4. 数据展示与工程化扩展
4.1 串口输出与OLED显示
传感器数据读出来了,必须有输出通道才能看到。最常见的两种方式:串口打印和OLED屏幕显示。串口打印适合调试阶段,OLED显示适合最终产品原型。
串口打印我用的是标准库的printf重定向,把fputc函数重定向到USART1:
#include <stdio.h>
int fputc(int ch, FILE *f)
{
HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 100);
return ch;
}
然后在主循环里这样输出:
printf("Temperature: %.2f C, Humidity: %.2f %%RH\n", temp, hum);
OLED显示这部分,如果你用I2C接口的OLED(比如SSD1306驱动,0.96寸128x64),可以直接复用同一个I2C外设——注意,SHT30和OLED的地址不同(SHT30是0x44,OLED一般是0x3C),只要在通信时指定不同从机地址就行,两个器件挂一条I2C总线上互不干扰。这是我推荐的做法,比用SPI OLED省两个GPIO。
4.2 多传感器轮询与低功耗设计
如果你的项目不只有一个SHT30,而是挂着好几个I2C传感器(比如再加上BMP280气压传感器、BH1750光照传感器),怎么优雅地轮询?我的习惯做法是做一个简单的传感器管理层,每一个传感器封装成一个结构体,里面有初始化函数指针、读取函数指针、数据存储区,主循环统一调度。
SHT30的ADDR引脚这时候就有大用处了。一个ADDR接低(地址0x44),一个ADDR接高(地址0x45),一条I2C总线上就能挂两个SHT30。比如做一个上下两层温湿度监测的设备,用两个SHT30分别测两个位置,代码上做一个地址参数的抽象函数,传不同的设备地址进去就行:
uint8_t SHT30_ReadReg(uint8_t addr, float *temp, float *hum)
{
// addr可以是0x44或0x45
}
低功耗方面,如果用电池供电,建议把SHT30配置成单次测量模式,测完一个周期就进入睡眠状态。STM32F4也进入STOP模式,用RTC闹钟周期性唤醒,完成测量后继续睡,这种方式可以把整机平均功耗压到毫瓦级别以下。不过要注意,SHT30上电后默认是周期测量模式,如果引脚上拉了电源,它会持续测量,白白耗电,记得在初始化时发送一个0x30A2软复位命令,再切换到单次测量模式。
4.3 与SPI设备共存:一次真实的多外设工程经验
有朋友看了热词里提到“stm32f4配置spi1驱动mt6701”,可能会问:又是I2C又是SPI,这俩外设同时用会冲突吗?答案是完全不会。STM32F4的I2C1用的是PB6/PB7(或者其他映射引脚),SPI1用的是PA4/PA5/PA6/PA7,引脚资源不重叠,外设资源各自独立。
我在一个机器人关节模组项目里就同时挂了SHT30(测关节温度)和MT6701磁编码器(测电机角度),SHT30走I2C1,MT6701走SPI1(配置为主模式、8位数据、CPOL=0/CPHA=0)。初始化顺序有讲究:先初始化SPI和I2C的GPIO时钟,再初始化外设本身,最后再初始化传感器驱动。实际调试下来的经验是,两个外设驱动完全独立,互不干扰,唯一的注意点是中断优先级分配——如果SPI用DMA传输,中断优先级要低于定时器中断,避免电机控制信号被传感器数据读取打断。
至于eMMC,如果项目里有大容量存储需求,STM32F4可以挂eMMC芯片,一般走SDIO接口或者SDMMC接口。这个场景下SHT30的数据可以直接记录到eMMC里做长时间环境监测,比如气象站的数据存储。不过eMMC的初始化流程比较复杂,需要发送CMD0、CMD1、CMD2等一系列命令,不是一篇博文能讲完的,这里就先提个思路,后续有机会单独写一篇。
5. 常见问题与实战排查手册
5.1 I2C通信失败的五大原因
做I2C通信踩坑是常态,我把SHT30在STM32F4上最常见的失败原因总结成一个速查表:
| 现象 | 常见原因 | 解决思路 |
|---|---|---|
| 读回数据全是0xFF | I2C地址错误,或者器件未上电 | 检查从机地址是否左移一位,万用表测SHT30供电 |
| 读回数据全是0x00 | I2C总线被拉低,时钟线或数据线短路 | 检查SCL/SDA是否接反,换杜邦线 |
| 数据能读出但数值跳变严重 | 线缆过长或电磁干扰 | 缩短线缆,加I2C电平转换或铁氧体磁环 |
| 使用硬件I2C偶尔卡死 | STM32F4硬件I2C偶发死锁 | 增加超时机制和总线恢复逻辑,或者换GPIO模拟 |
| 温度正常、湿度偏差大 | CRC校验失败,数据位翻转 | 启用CRC校验,异常重读 |
第五种情况最能说明问题。SHT30的湿度传感器部分对ESD比较敏感,如果湿度数据经常突变,除了检查CRC,还要检查PCB布局上传感器的位置——是否靠近散热器件、是否被外壳内部的热空气直接吹到。我遇到过拿密封塑料外壳装传感器,内部湿气排不出去,导致湿度读数异常偏高的情况,后来在外壳上开了通风孔才解决。所以传感器数据异常,不只查软件,还要结合物理环境一起分析。
5.2 硬件I2C死锁的终极杀手锏
STM32F4的硬件I2C有一个著名的“坑”,就是总线忙(BUSY)标志位可能会被莫名置位,导致后续所有I2C操作卡死。这通常发生在通信中途掉线、或者有设备在I2C总线传输过程中被复位的情况下。解决这个问题,我提供两个方案:
方案一:在每次I2C通信前检查总线状态,如果发现BUSY标志被置位,就翻转一下SCL引脚(通过GPIO复用切换),发送9个时钟脉冲,让从机释放SDA。这个方法本质上是软件复位I2C总线。
方案二:最简单粗暴但也最可靠——如果硬件I2C反复出问题,直接换成GPIO软件模拟I2C。在CubeMX里把I2C1的两个引脚配置成普通开漏输出GPIO,然后写一个I2C_Start、I2C_Stop、I2C_WriteByte、I2C_ReadByte的软件模拟函数。虽然代码多一点,但稳定性极高,而且完全不依赖芯片的硬件I2C外设,对于主频高达168MHz的STM32F4来说,用GPIO模拟400kHz的I2C时序毫无压力,CPU占有率几乎可以忽略。
我在SHT30的实际项目里最后就用了GPIO模拟I2C。那是个批量出货的真空干燥箱控制器,硬件I2C在实验室测试都正常,到了客户现场,因为现场有大功率变频器和加热设备,电磁环境恶劣,硬件I2C偶尔死锁。我把驱动切换到软件模拟I2C以后,一年下来没再出现过通信故障。所以我的建议是:同一个产品如果要用SHT30,预留一个GPIO模拟I2C的驱动程序,作为硬件I2C的稳健后备方案。
5.3 Keil5烧录与调试技巧
关于“keil5烧录程序stm32f4”这个高频问题,我的经验是:烧录失败90%是硬件连接问题,而不是软件问题。如果你用的是ST-Link,检查接线时要注意SWDIO、SWCLK、GND三根线是否牢靠,如果目标板是独立供电,还要把ST-Link和目标板的GND共地。在Keil5的Settings页面里,查看SW Device是否能识别到芯片——识别不到就检查接线、供电、以及芯片的BOOT0引脚状态。
有时候烧录器能识别芯片,但下载时提示“RDDI-DAP Error”,这个错误在STM32F4上很常见,尤其是芯片内部Flash有读保护的情况。解决办法是按住开发板的复位键,点击下载按钮启动烧录的同时松开复位键,也就是“先按复位再点下载”的时序技巧。如果还不行,就用ST-Link Utility或者STM32CubeProgrammer做一次全芯片擦除,清除读保护位。
代码调试阶段,Keil5的调试器里有一个很重要的窗口——外设寄存器观察窗口,可以实时查看I2C1外设的各个寄存器状态。比如通信失败的时候,打开这个窗口看I2C_SR1和I2C_SR2寄存器的各位,能快速定位是哪一步出了问题——是起始信号没过(SB位没置1),还是地址发送完没等到ADDR位,还是数据传输阶段出错了。这个技巧比单纯打断点看变量高效得多,因为I2C的时序问题用断点调试反而会干扰时序。
6. 扩展思考与经验心得
SHT30的基本玩法在上面已经全覆盖了,接下来聊聊我做完几个项目后的一些体会,也算是给这套方案画个句号。
关于驱动架构,如果你经常做不同的STM32项目,建议花点时间把传感器驱动做成平台无关的代码。具体做法是:在驱动层把I2C读写函数抽象成接口,底层是硬件I2C还是软件模拟I2C,由具体的平台适配层去实现。这样换芯片平台的时候,驱动核心代码一行都不用改,只改底层的读写接口。这篇文章里的SHT30驱动代码我已经按这个思路写了,你可以直接把sht30.c和sht30.h提出来备用。
关于测量稳定性,经验来看SHT30的单次测量模式是最稳的。周期测量虽然省事,但在某些固件版本上会偶发测量值异常。如果你做的是长期运行的设备,建议在主循环里每100ms到1s发一次单次测量命令,读取数据后紧接着做一次合理性判断——温度和上次数值的差值超过10℃,或者湿度变化超过15%RH,基本可以判定为异常数据,直接丢弃。农业生产环境里,同样的算法可以过滤掉大部分由传感器自身或者瞬间干扰造成的毛刺。
最后讲一个性价比很高的扩展方向:如果你不止需要温湿度,还想测大气压(天气趋势预测)、光照强度(花房智能补光)、土壤湿度(盆栽自动浇水),SHT30只是生态里的第一块拼图。把这些传感器全部挂到同一根I2C总线上,加上STM32F4的蓝牙或者WiFi模块(比如ESP8266或HC-05),就是一个简易的物联网数据采集节点。整个系统的软件架构和SHT30驱动是同一套思路——发送命令、等待响应、校验数据、解析数据。把这一套流程吃透,传感器驱动的核心方法论基本就到手了。
更多推荐


所有评论(0)