简介:基于51单片机读取SHT30温湿度传感器并串口输出的完整程序源码,面向单片机入门及嵌入式开发者,帮助理解I2C通信协议与数字传感器数据解析流程。资源包共36个文件,压缩包大小83KB,包含C语言源码(.c)、头文件(.h)、编译生成的HEX烧录文件,以及Keil工程中的列表文件、目标文件等,可直接查阅工程结构或用于仿真调试。SHT30采用I2C接口,代码中实现了初始化、命令发送、数据读取与校验、温湿度换算及UART串口打印等关键模块,配套的工程文件清晰展示了主机与传感器交互的完整链路。目前已有3075人学习下载,适合需要快速上手SHT30驱动、或希望基于51单片机搭建环境监测系统的开发者参考使用。通过分析驱动代码和工程配置,可掌握I2C时序控制、传感器寄存器操作及串口输出调试的常见写法。
1. 组合背后的价值:51单片机读取SHT30温湿度传感器程序的完整线索
从十几块起步的51开发板,到量产的小型温湿度记录仪,51单片机配SHT30在低成本环境监测场景里算得上经典组合。SHT30走I2C,直接输出数字温湿度,精度对日常监控足够,驱动它只需要两根引脚。很多人以为这和DHT11差不多,但SHT30真用起来才发现,I2C地址、CRC8、电平匹配这些细节一个都躲不掉。
这篇文章围绕「51单片机读取SHT30温湿度传感器程序」这条主线,把硬件接线、命令时序、CRC校验、数据换算、完整代码和几个高频坑串起来。新手能照着写出第一版可运行的程序,熟手可以直接拿走周期测量、滤波、串口验证这些参数细节。读完你手里会有一套能每秒刷新一次、串口输出温湿度,并且知道哪里出问题该怎么查的最小系统。
2. 硬件连接与电平匹配:SHT30的I2C地址、引脚和51的三种接线方式
2.1 SHT30的引脚功能与I2C地址选择
SHT30是标准的8脚DFN封装芯片,引脚不从两侧伸出来,而是焊盘压在底面。手工焊的时候容易虚焊,这是后话。从功能上看,真正参与通信的引脚只有五个:VDD、GND、SDA、SCL、ADDR,其余两个脚内部保留,悬空就行。
ADDR引脚是我每次都要强调的重点。这个引脚的接法直接决定传感器的I2C从机地址:ADDR接地或者悬空时,7位地址是0x44;ADDR接到高电平时,7位地址是0x45。换算成8位带读写方向的地址就是:
| ADDR引脚 | 7位地址 | 写地址 | 读地址 |
|---|---|---|---|
| 接地/悬空 | 0x44 | 0x88 | 0x89 |
| 接高电平 | 0x45 | 0x8A | 0x8B |
第一次用的人很容易把0x44记成0x40,或者干脆照抄别人的代码没注意ADDR接线。我的建议是:板子上只挂一个传感器时,ADDR直接接地,代码里写地址0x88、读地址0x89,后续怎么换传感器都不用改地址。如果一块总线上要挂两颗SHT30,那就让一颗ADDR接地、一颗ADDR接高,用两套地址分别访问。
提示:SHT30的I2C接口标准工作电压是多少?I2C总线的上拉电平需要和VDD保持一致。如果你用5V上拉去拉1k的电阻,传感器的输入引脚电压会超出规格范围,长期运行容易出问题。
2.2 与51单片机接线的三种方案:从电平匹配到串联限流
SHT30的推荐供电范围是2.15V到3.6V,典型就是3.3V。多数51开发板默认用5V供电,于是出现了一个绕不开的问题:5V的逻辑高电平和3.3V的传感器引脚怎么共存。直接硬接不是不行,短时间测试运气好也能跑,但I2C引脚电压长期超过VDD+0.4V,轻则读数据偶发错误,重则芯片损坏。
我一般把接线方案分成三种,按项目条件选:
方案一是整板3.3V供电。很多STC系列51单片机本身支持2.7V到5.5V宽电压工作,开发板上只要有3.3V跳线或者稳压输出,直接把整个系统跑在3.3V下,SDA和SCL电平天然一致,这是最省心的接法,没有之一。
方案二是5V的51配3.3V的SHT30,在SDA和SCL上各串联一个1kΩ电阻,再在传感器侧对地接一个3.3V稳压管钳位。这个做法能让高压被电阻和稳压管分掉,实测稳定。缺点是多两个器件,焊接量增加。
方案三是直接用带电平转换的SHT30模块。市面上的模块很多已经把转换电路做在板子上了,引脚出来是兼容5V的,接51不用再操心电平。但要注意模块上默认有没有焊上拉电阻,以及模块自带的LDO质量。我遇到过模块自带LDO压降偏大,传感器实际供电只有2V的情况,数据飘得没法用。
无论哪种方案,VDD到GND之间都要放一个0.1µF的陶瓷退耦电容,尽量靠近传感器引脚。这个电容能吸收传感器测量瞬间的电流尖峰,对抑制读湿度跳变有明显帮助。
2.3 I2C上拉电阻选4.7k还是10k
I2C是开漏结构,SDA和SCL的高电平全靠上拉电阻撑起来。51单片机的P1口虽然是准双向口,内部有弱上拉,但在快速翻转方向时驱动能力不稳定,软件模拟I2C尤其依赖外部上拉的一致性,所以不要省这两个电阻。
具体阻值我按总线长度和时钟频率来定。线长在10cm以内、软件I2C的SCL频率在100kHz上下,用10kΩ足够。如果传感器和主板之间用杜邦线走20cm以上,或者总线上还挂了其他I2C设备,降到4.7kΩ,保证信号边沿足够陡。再往下用1kΩ不是不可以,但功耗会增加,而且在3.3V供电时低电平的灌电流可能超出规格。
还有一个细节:上拉电阻是各接一个到VDD,不是SDA和SCL共用一个。电阻的接法是把传感器侧的SDA、SCL通过电阻拉到传感器供电的3.3V,而不是拉到51单片机的5V。如果拉错到5V,等于又把电平匹配问题带回来了。
2.4 GPIO引脚选择:P1口比P0口省心
代码里最常见的接法是SDA接P1.0、SCL接P1.1。选P1口有两个原因:一是P3口有RXD、TXD、INT0这些复用功能,调试串口、中断程序时容易被占用;二是P2口经常要接LCD1602这类并行显示屏,8根数据线已占满。P1口相对空闲,留给I2C很合适。
真正要注意的是别用P0口不加上拉电阻直接接SHT30。P0口和P1口不一样,P0是真正的开漏结构,内部没有上拉晶体管,必须外接上拉电阻才能输出高电平。如果你刚好把SDA和SCL接到P0.0、P0.1,又一并忽略上拉电阻,总线永远是高电平拉不上去,传感器不会有任何响应。
如果引脚实在紧张,也可以把SCL放到P1.0、SDA放到P1.1,对应代码里修改两个sbit宏即可。SCL和SDA的顺序对软件I2C没有本质影响,只要代码定义和实际接线保持一致,但建议遵循SCL在前、SDA在后的习惯,方便其他人接手时快速理解。
2.5 供电纹波、PCB布局以及多设备地址冲突
SHT30对供电纹波不算特别敏感,但也不是完全无所谓。测量瞬间传感器会有微安级的电流变化,如果供电已经接近临界值,这些毛刺会让低位数据跳动。SHT30的精度指标在数据手册里写得很清楚,但要复现指标,供电质量得一并注意。量级上,纹波控制在50mV以内就足够。
PCB布局上,传感器本体上的开孔是通气用的,要让空气能接触到这个开孔,不要把传感器贴在发热电阻或电源芯片旁边。我调试过一台模拟项目X的温湿度记录仪,温度一直比水银温度计高3°C,查了一圈发现传感器装在LDO正下方,热量直接传上来了。挪开2cm后读数就正常了。
多设备地址冲突也是一个容易被忽视的问题。如果总线上原来挂着一颗EEPROM,比如AT24C02,它的地址是0xA0,和SHT30的0x88并不冲突,但你写代码时要区分谁是主、谁是从,以及每个从设备对应哪个读地址。同一颗I2C总线上多个设备共享SDA和SCL,每个设备的地址必须唯一,SHT30靠ADDR引脚提供一个地址位,所以在多设备系统里ADDR的接线要先规划好。
3. 命令与数据格式:单次测量、CRC8与实际温湿度换算
3.1 命令表:单次测量、周期测量与软复位
SHT30没有传统意义上的一大堆寄存器地址,交互方式是主设备发两个字节的命令,从设备根据命令执行操作或回传数据。理解这一点,读数据手册的Command Overview就不容易犯晕。实际开发中最常遇到的命令有这些:
| 功能 | 命令第一字节 | 命令第二字节 | 说明 |
|---|---|---|---|
| 单次测量高重复性 | 0x2C | 0x06 | 默认推荐,精度最稳 |
| 单次测量中重复性 | 0x2C | 0x0D | 速度优先、精度稍降 |
| 单次测量低重复性 | 0x2C | 0x14 | 最省功耗 |
| 周期测量1mps高重复性 | 0x21 | 0x26 | 每秒自动更新 |
| 周期测量0.5mps高重复性 | 0x20 | 0x32 | 每2秒更新一次 |
| 读取周期模式最新数据 | 0xE0 | 0x00 | 发送后等数据返回 |
| 读取状态寄存器 | 0xF3 | 0x2D | 返回值可做自检 |
| 软复位 | 0x30 | 0xA2 | 清空错误状态 |
需要区分单次测量和周期测量的使用场景。代码里主循环是“读一次、显示一次、等一秒”这种节奏,单次测量最合适。如果项目是低功耗巡检设备,希望传感器自己按固定节奏更新,主控随时去取最新值,那就用周期测量模式。周期模式的好处是主控不用每次都等测量完成,缺点是传感器一直处于工作状态,功耗会高一些。
3.2 单次测量时序:从发命令到读6字节
单次测量的完整时序可以拆成两步,第一步是发命令,第二步是读数据。发命令阶段,主机先发起始条件,发送写地址0x88,SHT30应答后,再发0x2C和0x06,每发一个字节都要等到ACK,最后停止条件收尾。
注意等待时间的控制点:发送完命令后,SHT30需要一段时间完成内部测量,高重复性典型12.5ms、最大15.5ms。代码里不要用最大的测量时间做延时,应该再留一点余量,我通常延时20ms,保证各种电源和晶振偏差下都稳定。
读数据阶段的过程是:重新发起始条件,发送读地址0x89,SHT30应答后,连续读6个字节,顺序是温度高字节、温度低字节、温度CRC、湿度高字节、湿度低字节、湿度CRC。前5个字节主机要回ACK,最后一个字节回NACK,通知从机“读完收工”。不要小看最后这个NACK,如果给它ACK,SHT30会认为主机还要继续读,总线状态就乱了。
3.3 原始数据到温湿度的换算公式与调试技巧
读回来的6字节里,有效数据是四个字节:温度16位、湿度16位,均为大端格式。温度原始值的范围是0到65535,对应的物理范围是-45°C到130°C,换算公式是:
温度(°C) = -45 + 175 × raw / 65535 湿度(%RH) = 100 × raw / 65535
我在代码里习惯把公式写成两个独立函数,在下一章给出。调试时有个捷径:先把原始值通过串口打印出来,对照两个特征点就能判断传感器是否正常。常温25°C左右时,温度raw大概在12000到15000之间;湿度50%RH时,湿度raw接近32768。如果打印出来的raw离这个范围差得太远,基本不是公式的问题,而是读取本身有错。
还有一个容易翻车的地方是零下温度。当环境温度低于0°C时,换算结果应该是负数,但如果你用unsigned char或者无符号整型去接收乘法结果,打印出来会是一个奇怪的大数。务必在换算时用float类型保存中间结果,或者至少把raw先转成有符号数再算。
3.4 CRC8校验的完整算法与常见误区
SHT30在温度和湿度数据后各附了一个CRC字节,用于检测传输过程中的数据错误。CRC8的参数是:多项式0x31,初始值0xFF,输入输出不做反转。不要用Modbus那种CRC查表法直接套,两者算法完全不同。
常见的实现方法是逐字节、逐位进异或移位,完整函数在下一章代码里给出。调用时要注意:要对温度的两个字节单独算,把计算结果和温度后的CRC字节比较;再对湿度的两个字节单独算,和湿度后的CRC字节比较。不少人写了个通吃6字节的函数,一次性算完再跟两个CRC比对,那肯定对不上。
CRC校验的定位是保险丝,不是万能药。如果加了两次CRC重试后偶尔能读对,说明总线存在偶发干扰,要从布线、时序、供电三个方向排查问题,而不是程序里多循环几次来掩盖。
4. 用51的软件I2C读取SHT30:完整可编译代码与逐段解释
4.1 为什么用软件I2C而不是51的硬件I2C
经典STC89C52没有硬件I2C外设,后续STC15系列虽然集成了一部分,但各品牌增强型51的I2C寄存器设计并不统一,移植时很容易被硬件差异绊住。软件模拟I2C的优势就很直接了:任意两根GPIO就能干活,代码照着数据手册的时序逐条写,出故障时能用逻辑分析仪或示波器对着波形找问题。
软件模拟I2C的代价是速度上不去。SHT30的I2C最高支持1MHz时钟,但51在12MHz晶振下,机器周期就是1µs,软件翻转引脚最快也就几十到一百多kHz。这速度对温湿度采集场景来说完全够用,每秒读几十次都没问题,完全不用纠结。
4.2 I2C底层驱动:起始、停止、发送和接收
底层四个函数是所有上层逻辑的地基。起始条件是SCL高电平期间SDA从高变低,停止条件是SCL高电平期间SDA从低变高。发送字节时,每次在SCL低电平期间把数据放到SDA上,再拉高SCL让从机采样,最后释放SDA读ACK。
// 软件延时,约10us@12MHz(实际以你的晶振为准) void I2C_Delay(void) { unsigned char i; for (i = 0; i < 20; i++); } // 起始条件:SCL高电平期间SDA从高到低 void I2C_Start(void) { SDA = 1; SCL = 1; I2C_Delay(); SDA = 0; I2C_Delay(); SCL = 0; I2C_Delay(); } // 停止条件:SCL高电平期间SDA从低到高 void I2C_Stop(void) { SDA = 0; SCL = 1; I2C_Delay(); SDA = 1; I2C_Delay(); } // 发送一个字节,返回从机的ACK信号;0表示有ACK,1表示无ACK bit I2C_SendByte(unsigned char dat) { unsigned char i; bit ack; for (i = 0; i < 8; i++) { SDA = (dat & 0x80) ? 1 : 0; dat <<= 1; I2C_Delay(); SCL = 1; I2C_Delay(); SCL = 0; I2C_Delay(); } SDA = 1; // 释放SDA,准备接收从机的ACK I2C_Delay(); SCL = 1; I2C_Delay(); ack = SDA; SCL = 0; I2C_Delay(); SDA = 1; return ack; } // 接收一个字节,ack参数为1时主机回ACK,为0时回NACK unsigned char I2C_RecvByte(bit ack) { unsigned char i, dat = 0; for (i = 0; i < 8; i++) { dat <<= 1; SCL = 1; I2C_Delay(); if (SDA) dat |= 0x01; SCL = 0; I2C_Delay(); } SDA = ack ? 0 : 1; I2C_Delay(); SCL = 1; I2C_Delay(); SCL = 0; I2C_Delay(); SDA = 1; return dat; }代码里两个关键参数值得说明。I2C_Delay的循环次数直接决定总线时钟频率,12MHz晶振下循环20次左右,实测SCL频率约80到100kHz。注意接收字节时,dat先左移再判断SDA,这样数据是从高位到低位填充的,和发送时的顺序完全对应。
发送字节后读ACK的顺序不能颠倒。必须先把SDA释放成1,再拉高SCL读ACK,如果SDA一直输出低电平,从机想拉低也没法拉,协议就卡死了。这也是初学时最容易写错的地方。
4.3 读取函数:单次测量命令与6字节接收
读取函数把命令时序和数据校验串在一起,函数把原始温度、湿度通过指针传回,返回值为1表示成功,0表示通信或校验失败。
// CRC8校验函数,多项式0x31,初始值0xFF unsigned char SHT30_CRC8(unsigned char *buf, unsigned char len) { unsigned char crc = 0xFF; unsigned char i, j; for (i = 0; i < len; i++) { crc ^= buf[i]; for (j = 0; j < 8; j++) { if (crc & 0x80) { crc = (crc << 1) ^ 0x31; } else { crc <<= 1; } } } return crc; } // 读取SHT30温湿度原始值,成功返回1,失败返回0 bit SHT30_ReadTH(unsigned int *temp_raw, unsigned int *hum_raw) { unsigned char buf[6]; unsigned char crc; I2C_Start(); if (I2C_SendByte(0x88)) { // 写地址0x44<<1 I2C_Stop(); return 0; } if (I2C_SendByte(0x2C)) { // 单次测量命令 I2C_Stop(); return 0; } if (I2C_SendByte(0x06)) { // 高重复性参数 I2C_Stop(); return 0; } I2C_Stop(); delay_ms(20); // 等待测量完成,高重复性最大15.5ms I2C_Start(); if (I2C_SendByte(0x89)) { // 读地址0x44<<1 | 1 I2C_Stop(); return 0; } buf[0] = I2C_RecvByte(1); // 温度高字节 buf[1] = I2C_RecvByte(1); // 温度低字节 buf[2] = I2C_RecvByte(1); // 温度CRC buf[3] = I2C_RecvByte(1); // 湿度高字节 buf[4] = I2C_RecvByte(1); // 湿度低字节 buf[5] = I2C_RecvByte(0); // 湿度CRC,最后一个回NACK I2C_Stop(); // 分别校验温度CRC和湿度CRC crc = SHT30_CRC8(&buf[0], 2); if (crc != buf[2]) return 0; crc = SHT30_CRC8(&buf[3], 2); if (crc != buf[5]) return 0; *temp_raw = (unsigned int)((buf[0] << 8) | buf[1]); *hum_raw = (unsigned int)((buf[3] << 8) | buf[4]); return 1; }我在三个发送步骤都做了ACK判断,任何一个字节没等到应答就立刻返回失败。另一种偷懒写法是把判断全省了,反正最后CRC能拦住错数据,但那样当总线异常时程序就会一直卡在等待上。提前返回错误码可以让上层逻辑知道通信断开,方便做重试或者报警。
延时参数20ms是对应高重复性0x2C 0x06的。如果你把命令改成中重复性0x0D,延时可以缩到10ms,低重复性0x14则可以再短。但我建议不要极限压缩,反正这个函数调用频率很低,多等几毫秒不影响整体性能。
4.4 温湿度换算与串口打印主函数
主函数负责初始化串口、循环采样并把原始值换算成温湿度。换算函数单独写,逻辑简单,但能让你在调试时单独调用验证。
// 原始值转温度,单位°C float SHT30_Temp(unsigned int raw) { return -45.0f + 175.0f * (float)raw / 65535.0f; } // 原始值转湿度,单位%RH float SHT30_Humi(unsigned int raw) { return 100.0f * (float)raw / 65535.0f; } void main(void) { unsigned int t_raw = 0, h_raw = 0; float temp, humi; UART_Init(); // 串口初始化 delay_ms(100); // 等待传感器上电稳定 while (1) { if (SHT30_ReadTH(&t_raw, &h_raw)) { temp = SHT30_Temp(t_raw); humi = SHT30_Humi(h_raw); printf("T=%.2f C H=%.2f %%RH\n", temp, humi); } else { printf("SHT30 no response\n"); } delay_ms(1000); // 每秒采样一次 } }主循环里最核心的节奏点是最后的delay_ms(1000)。如果你为了测试把延时去掉,程序会以极快的速度重复发命令、读数据,传感器还没完成上一次测量就接收下一次命令,数据必然混乱。我向来建议初始采样周期放到1秒,稳定后再逐步缩短。
printf在Keil环境里使用有两个前置条件。第一,工程设置里要钩选Use MicroLIB,标准库的printf引入浮点打印后会占用巨大的代码空间,不钩选MCU资源不足时会直接跑飞。第二,需要自己实现putchar或者使用Keil自带的串口重定向,否则printf输出没地方去。如果不想折腾printf,完全可以用sprintf拼字符串再通过串口中断发送,效果一样。
4.5 Keil编译、波特率与烧录注意
编译时最常遇到的警告是UNCALLED SEGMENT,说明有函数没有被调用。某个函数没调用不影响烧录,但你要清楚它是暂时未用还是真的不小心写漏了。如果报DATA SEGMENT TOO LARGE,多半是缓冲数组占用了过多内部RAM,把不需要常驻的大数组挪到code段去。
波特率的坑需要单独说。STC89C52用12MHz晶振跑115200波特率,定时器初值根本配不准,误差接近8%,串口助手收到全是乱码。要么换11.0592MHz晶振,要么把波特率降到9600,误差才能控制在1%以内。这是51平台上最经典的时间“玄学”之一,看起来是SHT30的问题,实际是串口自己乱了。
烧录失败时先冷启动再重试,也就是断电后重新上电再点烧录,不要连着电反复操作。STC系列在冷启动时才能进入ISP状态,这个习惯能帮你排除八成烧录报错。
5. SHT30读取避坑指南:5个高频问题清单
5.1 传感器无应答:SDA/SCL接反、地址错误与供电不足
现象是串口一直打印SHT30 no response,或者读回来的原始值固定为0xFFFF。
先查SDA和SCL这两根线是否接反。杜邦线插反是最高频的原因,因为两根线在视觉上不明显。接着确认ADDR引脚的电平,ADDR悬空时部分批次的内部下拉状态不稳定,地址会在0x44和0x45之间漂移,把它明确接到GND再量一下对地电压。
供电不足也比较隐蔽。万用表量LDO输出端是3.3V,但接上传感器后实际到VDD引脚的可能只有2V左右,传感器不工作。解决方法是直接量SHT30的VDD引脚电压,带电测量,别只看电源芯片的输出。还有P0口接传感器却忘记加上拉电阻的情况,这在2.4小节已经提过,P0开漏没上拉,输出高电平根本拉不起来。
5.2 数据跳变:等待时间、长线干扰与电源纹波
现象是温度在25°C和31°C之间来回跳,湿度也跟着大幅波动。
先怀疑采样周期。如果while循环里没有延时或只延时了几毫秒,SHT30上一次测量还没完成就被再次发命令,数据必然错乱。把读取间隔提升到100ms以上,多数跳变会自己消失。
再排查走线长度。SHT30用20cm杜邦线连接时,数据跳变的概率会明显增加,因为I2C长线的分布电容和电磁干扰会让信号边沿变差。缩短到10cm以内,或者把杜邦线换成双绞线,问题一般能解。还不行就在VDD与GND之间补焊0.1µF电容,吸收传感器测量瞬间的供电跌落。
5.3 CRC8校验频繁失败:时序过紧与中断抢占
现象是读十次错三次,去掉CRC校验后数据“看起来正常”,但这种正常不可信。
排查方向一:软件I2C的时序过紧。I2C_Delay里的循环次数太少,SDA在SCL上升沿还没稳定就被采样,个别数据位读错,CRC自然对不上。放宽延时到20次以上,总线速率降下来后再看结果。
排查方向二:中断和I2C时序抢时间。定时器中断或串口中断里如果执行时间较长,SCL翻转过程中会被中断打断,波形产生毛刺。在SHT30的发送、读取关键步骤前后临时关中断,读完再开,这也是正规做法。
排查方向三:最后一个字节的ACK/NACK。如果不了解I2C的设备习惯,连续6字节全回ACK,SHT30会认为主机要读更多数据,后续的第7个“字节”会读到乱七八糟的电平。确认最后一个字节回NACK即可。
5.4 温度读数偏高:发热源、自热与校准偏差
现象是传感器温度稳定比环境真实温度高2到4°C,重复几次都一样。
优先怀疑传感器旁边有大功率发热元件。LDO、继电器、电源模块的发热通过PCB铜箔传导到传感器,温度必然偏高。把传感器挪远5cm以上,或者在PCB上开散热隔离槽,读数会明显下降。
其次是传感器自身自热。SHT30的转换频率高到几十赫兹时,内部微弱自热会累积0.1°C左右的偏差,长年累月跑下来也不是完全可忽略。把测量周期放到5秒以上,自热影响基本可以消失。
如果排除了以上所有因素读数仍然偏高1°C上下,那就是传感器个体校准偏移。可以在换算结果后面加一个常数补偿,比如temp += 0.8,用精密温度计校正后踩点。
5.5 换传感器后读不到:虚焊、翻新片与地址确认
现象是一颗SHT30读得好好的,换了一颗新的同型号芯片,突然读不到数据了。
第一步检查焊接。DFN封装引脚在器件底面,手工焊接很容易虚焊或者桥连,ADDR、SDA、SCL这三个引脚必须逐根用万用表蜂鸣档量到接口端确认通断。
第二步确认器件来源。低价渠道的SHT30偶尔会混入来路不明的翻新片,反应在I2C上就是地址和命令行为不一致。发0xF3 0x2D读状态寄存器,如果回传的数据完全不满足规格,基本可以判断是芯片本身的问题。
第三步是回顾自己的代码。换了芯片意味着也可能换了传感器模块,如果新模块的ADDR引脚接了高电平,地址就从0x88变成了0x8A,代码里地址没改的话当然读不到。这是最吃亏的坑,因为现象和硬件故障一模一样,但其实只是地址位不同。
6. 进阶:周期测量、数据平滑与串口验证
6.1 周期测量模式:从查询到自动更新
如果你希望主循环少做一次“发命令、等延时”的步骤,周期测量模式是更优雅的选择。以1mps高重复性为例,开机时给SHT30发0x21 0x26,传感器内部就开始按固定节奏连续测量。主循环里每次需要数据时,只要发0xE0 0x00,然后直接读6字节就行,不用再等测量完成。
配置周期模式的代码和单次测量的命令阶段完全一样,只是命令码不同。需要特别留意的是切换回单次模式的方式:周期模式下直接发0x2C 0x06不会响应,必须先软复位(0x30 0xA2)或者断电重启,再重新发送单次命令。这个切换逻辑容易让人调试半天找不到原因。
6.2 移动平均滤波:消掉偶发尖刺
即使避坑措施都做到位,湿度数据偶尔还是会蹦出一个偏离真实值几个百分点的尖刺。对这种慢变量,最简单的滤波是长度为5的移动平均。每采到一个新值,就丢掉最旧的那个,保留最近5次求平均。
实现时用一个5元素数组,累加和需要用unsigned long来存,因为5个接近65535的raw加起来会超过16位整形的上限。实时性方面,5个点最多引入几秒的延迟,对温湿度监测这种分钟级变化量的应用完全够用。
6.3 72小时串口记录与验收习惯
代码跑通之后先别急着说完成,用串口记录数据至少跑72小时。观察两个指标:一是长时间运行后数据是否缓慢漂移,二是每天有没有偶发的读失败次数。如果CRC校验失败超过总读取次数的千分之一,基本可以判定要回头处理干扰源。
还有一个验证技巧是通电后先让传感器跑半小时再记录数据,这段时间能让传感器自身达到热平衡,消除冷启动的初始偏差。我一直习惯把原始raw值先打印一天,确认原始数据稳定后再加换算和显示,排查问题时就能把变量控制在最小范围。这个习惯帮我少踩了不知道多少坑,希望对你也能有帮助,希望帮到你。
本文还有配套的精品资源,点击获取