news 2026/9/9 4:09:43

51单片机读取SHT30温湿度传感器:I2C模拟时序与完整代码解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
51单片机读取SHT30温湿度传感器:I2C模拟时序与完整代码解析

简介:面向51单片机开发者的SHT30温湿度传感器读取例程,完整演示从I2C协议初始化、传感器寄存器配置到数据解析与串口打印的全过程,适合电子竞赛、智能家居环境监测等场景的入门与进阶学习。资源压缩包共36个文件,以C源码、头文件、Keil工程文件(.uvproj)及生成的hex、obj、lst等为主,编辑器配置、链接映射与调试信息一应俱全,可直接在Keil中打开编译烧录。压缩包仅83KB,结构紧凑。已有3071人学习下载。代码中已内置I2C驱动、SHT30通信协议实现与UART发送函数,并包含单次测量、周期测量等命令示例,用户只需接线即可快速验证,同时可参考工程目录与编译产物理解单片机程序构建流程。 最近在整理手头一个51的温度采集项目,把SHT30重新捡了起来用。很多朋友做温湿度采集,第一反应就是DHT11,便宜、教程多、到处都有,但如果你真想把数据做成一个相对靠谱的产品,而不是交个课程设计就完事,DHT11的精度和单总线时序稳定性,迟早会逼你换方案。SHT30是Sensirion出的数字温湿度传感器,I2C接口,温度精度±0.3℃,湿度精度±2%RH,内部带CRC校验,输出16位原始码。这类传感器通常在STM32上很容易驱动,反而在51单片机上因为要自己模拟I2C时序,很多新手一上来就卡住。这篇文章我尽量把“51单片机 + SHT30”这个组合讲透,包括读取协议、完整代码、换算公式、以及我实测中踩过的坑,适合刚学完51基础、正打算做温湿度相关课题的读者收藏。

1. 为什么用51去读SHT30:从选型逻辑说起

1.1 DHT11教会我的事

我之前做一个小项目,最初用的就是DHT11,网上例程一抓一把,接线也简单,DATA脚接一个IO就行。但用着用着就发现问题了。DHT11是单总线协议,它对时序的敏感程度非常高,主机拉低总线、释放总线、等待应答,每一步的时间窗口都是微秒级的。51单片机用软件延时来模拟这个时序时,延时函数稍微被中断打扰一下,数据就飘了。

更麻烦的是精度。DHT11温度精度±2℃,湿度精度±5%RH,显示在屏幕上看看还行,一旦要做数据记录或者和别的设备对比,误差就很明显。我曾经同时接了DHT11和SHT30做对比测试,同一个环境、同一分钟,DHT11波动能达到1度以上,SHT30几乎是一条平线。

这不是说DHT11一无是处,而是要看场景。做温控风扇、大棚温湿度显示这种精度要求不高的场合,DHT11完全够用,成本也确实低。但当你需要稳定数据,或者想省去单总线那套严格时序时,SHT30是51平台上非常合适的一个升级选择。

1.2 SHT30的关键参数

从我实际用的角度看,SHT30值得关注的参数主要有这几个:

参数数值说明
供电电压2.15V ~ 5.5V模块一般建议3.3V,实测5V也能跑
温度精度±0.3℃典型精度,24~30℃范围内表现更好
湿度精度±2%RH比DHT11的±5%RH高一档
输出分辨率16位温度、湿度各2字节原始码
接口I2C标准I2C协议,速率最高1MHz
默认地址0x44ADDR引脚接高电平时地址变为0x45
数据校验CRC8每个物理量后面带1字节校验

这里要提醒一点,SHT30有不同的封装和型号后缀,比如SHT30-DIS是贴片式,SHT30-ARP是带线束的防水型。市面上常见的模块大多用的SHT30-DIS,对这个模块来说,供电建议3.3V,虽然数据手册标称最高5.5V,但3.3V供电时I2C上拉电平更标准,和51单片机做信号交互时不容易因为电平不匹配产生奇怪问题。

1.3 I2C地址、命令与数据格式

SHT30的I2C地址是7位0x44,左移一位后对应的8位写地址是0x88,读地址是0x89。如果你的模块把ADDR引脚接到了高电平或者悬空方式不同,地址就变成0x45,对应写地址0x8A、读地址0x8B。地址搞错了,读出来永远是0xFF。

读取SHT30数据的流程,简单说就两步。第一步发送测量命令,常用的是0x2C 0x10,意思是高重复性、关闭时钟拉伸;第二步等待测量完成后,发送0xE0 0x00命令,然后从传感器读6个字节回来。这6个字节的排列是:温度高字节、温度低字节、温度CRC、湿度高字节、湿度低字节、湿度CRC。温度两个字节拼成一个16位原始码,湿度同理。

这个数据格式在写代码前一定要理解,因为后面所有换算和校验都建立在这6个字节上。有些教程只贴代码不解释字节格式,出了问题你连排查的入口都找不到。

2. I2C底层在51上的软件模拟:原理与代码照抄版

2.1 为什么在51上必须自己模拟I2C

不少51单片机型号,比如STC89C52,本身不带硬件I2C外设。即便一些增强型51带了I2C模块,很多开发板默认配置下用起来也别扭,不如软件模拟来得直接。

软件模拟I2C的核心就是控制SCL和SDA两个引脚的时序,完成起始、停止、发送字节、接收字节这几件事。SHT30的I2C时序是标准的,只要你的时序逻辑正确,延时慢一点完全没关系,因为I2C是同步通信,SCL多高多久由主机决定,不像单总线那样对时间窗口要求苛刻。

2.2 起始、停止、字节读写函数

我用STC89C52,晶振12MHz,P1.0接SCL,P1.1接SDA。完整的I2C底层代码如下:

#include <reg52.h> #include <intrins.h> sbit SCL = P1^0; sbit SDA = P1^1; // 简单延时,1个_nop_约1us(12MHz下) static void I2C_Delay(void) { _nop_(); _nop_(); _nop_(); _nop_(); } // I2C起始:SCL高电平时,SDA从高拉低 void I2C_Start(void) { SDA = 1; SCL = 1; I2C_Delay(); SDA = 0; I2C_Delay(); SCL = 0; } // I2C停止:SCL高电平时,SDA从低拉高 void I2C_Stop(void) { SDA = 0; SCL = 1; I2C_Delay(); SDA = 1; I2C_Delay(); } // 主机发送1字节,返回从机ACK状态,0为应答 unsigned char I2C_WriteByte(unsigned char dat) { unsigned char i; unsigned char 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,准备接收从机ACK SDA = 1; I2C_Delay(); SCL = 1; I2C_Delay(); ack = SDA; SCL = 0; I2C_Delay(); return ack; } // 主机读取1字节 unsigned char I2C_ReadByte(void) { unsigned char i, dat = 0; SDA = 1; for (i = 0; i < 8; i++) { I2C_Delay(); SCL = 1; I2C_Delay(); dat <<= 1; if (SDA) dat |= 0x01; SCL = 0; I2C_Delay(); } return dat; } // 主机发送应答,0表示继续读,1表示停止读 void I2C_SendAck(unsigned char ack) { SDA = ack; I2C_Delay(); SCL = 1; I2C_Delay(); SCL = 0; I2C_Delay(); SDA = 1; }

写这个底层的时候有一个容易被忽略的细节:读SDA之前,必须先给SDA写1。51单片机的IO口是准双向口,内部上拉虽然存在,但如果引脚之前输出过0,外部高电平信号会被内部拉低,读出来永远是0。很多新手在这个地方卡了一整天,就是因为读之前没把引脚置高。

2.3 时序延时的尺度把握

有人可能会问,I2C_Delay里放几个_nop_合适?SHT30支持标准模式100kHz和快速模式400kHz。软件模拟时,SCL周期由你的延时决定。以12MHz晶振为例,1个机器周期是1us,我在起始、停止、字节收发里各放了4个_nop_,再加上语句本身的指令周期,SCL高电平时间大约在几微秒以上,算下来SCL频率大概几十kHz,低于100kHz标准模式,工作绝对稳定。

这里有个核心思路:I2C协议没有规定SCL的最低频率,只规定了最高频率。所以你在51上软件模拟,延时宁可多一点,也不要太少。时序太慢顶多就是读取速度慢一点,时序太快反而可能触发SHT30的时序保持时间不够的问题。按这个尺度调,基本不会出岔子。

3. SHT30读取流程拆解:发命令、取数据、算温湿度

3.1 测量命令的选择

SHT30的数据手册里给出了好几组测量命令,最常用的是下面这两个:

命令含义适用场景
0x2C 0x06高重复性测量,时钟拉伸使能适合硬件I2C主机,可以等待传感器就绪
0x2C 0x10高重复性测量,时钟拉伸禁用软件模拟I2C推荐,固定等待即可

时钟拉伸是I2C协议里一个比较特殊的功能。传感器在测量期间会把SCL拉低,表示“我还没准备好,你等等”,测量完成后再释放SCL。这个功能在硬件I2C控制器下很好用,但在51软件模拟下要额外检测SCL是否被拉低,逻辑复杂得多。

我实际用的就是0x2C 0x10,发送完测量命令后,直接固定延时20ms再去读结果。SHT30高重复性测量时间最长在15ms左右,20ms的余量完全足够,既不复杂也不容易出错。

3.2 发送命令与读取测量结果

直接看代码,这段是把SHT30的操作封装好:

// 发送1条2字节命令 unsigned char SHT30_SendCmd(unsigned char cmd_msb, unsigned char cmd_lsb) { I2C_Start(); if (I2C_WriteByte(0x88)) { I2C_Stop(); return 1; } if (I2C_WriteByte(cmd_msb)) { I2C_Stop(); return 1; } if (I2C_WriteByte(cmd_lsb)) { I2C_Stop(); return 1; } I2C_Stop(); return 0; } // CRC8校验,多项式0x31,初始值0xFF unsigned char SHT30_CRC8(unsigned char msb, unsigned char lsb) { unsigned char crc = 0xFF; unsigned char i; crc ^= msb; for (i = 0; i < 8; i++) crc = (crc & 0x80) ? (crc << 1) ^ 0x31 : (crc << 1); crc ^= lsb; for (i = 0; i < 8; i++) crc = (crc & 0x80) ? (crc << 1) ^ 0x31 : (crc << 1); return crc; } // 读取温湿度原始数据 unsigned char SHT30_ReadData(unsigned char *t_msb, unsigned char *t_lsb, unsigned char *h_msb, unsigned char *h_lsb) { unsigned char crc_t, crc_h; // 发送读取测量数据命令 0xE0 0x00 I2C_Start(); if (I2C_WriteByte(0x88)) return 1; if (I2C_WriteByte(0xE0)) return 1; if (I2C_WriteByte(0x00)) return 1; I2C_Stop(); // 读取6字节:温度MSB、温度LSB、温度CRC、湿度MSB、湿度LSB、湿度CRC I2C_Start(); if (I2C_WriteByte(0x89)) return 1; *t_msb = I2C_ReadByte(); I2C_SendAck(0); *t_lsb = I2C_ReadByte(); I2C_SendAck(0); crc_t = I2C_ReadByte(); I2C_SendAck(0); *h_msb = I2C_ReadByte(); I2C_SendAck(0); *h_lsb = I2C_ReadByte(); I2C_SendAck(1); I2C_Stop(); // 分别校验温度和湿度的CRC if (crc_t != SHT30_CRC8(*t_msb, *t_lsb)) return 2; if (crc_h != SHT30_CRC8(*h_msb, *h_lsb)) return 3; return 0; }

这里有几个关键点必须注意。第一,发送完0xE0 0x00之后,要重新发一次I2C起始信号,再发送读地址0x89,这在I2C协议里叫“重复起始”,是合法操作,51代码里直接再调一次I2C_Start就行。第二,读取最后1个字节时,主机要发送NACK(即发送1),告诉从机“不用再发了”,然后才发停止信号。第三,CRC校验一定不能省,SHT30带校验是为了保证数据完整性,如果你读取过程中信号受干扰,CRC能帮你抓出问题,省掉它等于把传感器的优势废了一大半。

3.3 温湿度换算与CRC校验

读完的4个字节是原始码,需要换算成实际的物理量。SHT30的换算公式很规整,没有任何经验系数,就是线性映射:

温度公式:

unsigned int temp_raw = ((unsigned int)(*t_msb) << 8) | *t_lsb; float temperature = -45.0f + 175.0f * temp_raw / 65535.0f;

湿度公式:

unsigned int hum_raw = ((unsigned int)(*h_msb) << 8) | *h_lsb; float humidity = 100.0f * hum_raw / 65535.0f;

为什么要除以65535?因为16位原始码的范围是0到65535,而温度的测量范围是-45℃到130℃,跨度175℃,湿度范围是0%到100%RH。SHT30把测量范围映射到整个16位ADC量程,所以线性地乘一下就能还原出物理量。

CRC8校验的细节也需要讲明白。SHT30用的CRC8多项式是x^8 + x^5 + x^4 + 1,十六进制表示就是0x31,初始值固定为0xFF。温度CRC的计算对象是温度的高字节和低字节,湿度CRC的计算对象是湿度的高字节和低字节。上面代码里SHT30_CRC8函数传入两个字节,返回校验值,和传感器发来的CRC字节对比,相等就说明这一帧数据传输过程无误。

4. 实测踩坑记录:读不到数据、CRC报错、数据跳变的完整排查链路

4.1 读回全是0xFF:先排查电源、接线、地址

我头一回调试SHT30,烧完程序后串口打印出来的温度和湿度永远是65535,换算出来就是一堆奇怪的负数。这个现象的本质是主机读不到任何有效数据,大概率不是代码问题,而是物理链路压根没通。

我的排查顺序是这样的。第一步,检查供电。SHT30模块上电后,板上指示灯或者用万用表量VCC和GND之间的电压,正常情况下应该在模块工作电压范围内。我遇到过模块的VCC引脚接触不良,导致整个模块没上电的情况。第二步,检查接线顺序。SCL接P1.0,SDA接P1.1,如果两根线接反了,I2C通信会直接失败,因为从机收不到正确的时钟。第三步,确认模块地址。如果你的模块在背板上把ADDR引脚接高,那地址就是0x45,代码里所有0x88、0x89都要改,否则一点反应都不会有。

4.2 无应答和CRC失败:从ACK细节找根因

如果I2C_WriteByte函数每次都返回1,说明从机没有应答主机。这时候几乎可以断定传感器没在工作状态,或者地址写错了。我遇到过一次比较隐蔽的情况:模块的SDA上拉电阻虚焊,导致SCL高电平还算正常,但SDA在某些时刻拉不高,传感器就收不到正确的起始信号。

CRC失败则更微妙一点。正常读取时,6字节中温度CRC或湿度CRC对不上,我一开始以为是传感器坏了,后来用逻辑分析仪抓波形才发现,是主机在读最后一个字节后发送ACK而不是NACK,传感器以为主机还要继续读,多发了数据,导致后续时序错乱。修改方法就是上面代码里的,读取最后一个字节前发送1,然后立刻停止。另外,如果51的IO口驱动能力弱、信号边沿太缓,也会导致采样点位置不对,这种情况下可以适当增加I2C_Delay里的延时,让信号稳定了再采样。

4.3 数据跳变的隐蔽原因:上拉电阻和总线电容

还有一种让人抓狂的现象,就是数据偶尔对,偶尔不对,比如温度在25.0℃和25.4℃之间反复跳动。起初我以为是电源纹波,后来排查发现是模块到单片机之间的杜邦线太长,总线电容偏大,加上上拉电阻是10kΩ,导致SDA和SCL信号边沿被拉得很缓。在信号还没有达到有效电平阈值时,主机就去采样了,自然采到不稳定的值。

解决办法有两类。一类是硬件上把上拉电阻换成4.7kΩ,缩短杜邦线,尽量控制在10cm以内;另一类是软件上放慢I2C时钟,把I2C_Delay里的_nop_从4个增加到8个,给信号边沿更长的稳定时间。我最后是两者同时做了,问题彻底消失。

这套排查逻辑,说白了就一句话:I2C通信问题,先看电气,再看时序,最后才怀疑代码逻辑。大多数情况下,代码没问题,是硬件连接和信号质量的问题。

5. 把这段代码做成产品之前,还差哪几步

5.1 显示与串口输出:在51上处理浮点的小技巧

SHT30换算出来的温度湿度是浮点数,直接拿来做显示判决没问题,但51单片机上浮点运算比较占资源,尤其是用printf打印浮点数,代码体积会暴涨。

我实际处理的方式是:先把浮点数乘以10,强制转成整数,这样整数部分是实际温度的整数位,对10取余就是小数点后一位。比如温度是25.36℃,乘以10就是253,253/10=25,253%10=3,拼出来就是25.3℃。这样既保留了显示精度,又避开了浮点格式化带来的代码膨胀。

int temp_int = (int)(temperature * 10); // temp_int / 10 是整数部分,temp_int % 10 是小数部分

如果是接LCD1602,直接把整数部分和小数部分拆开送到显示缓冲区就行。如果是串口输出,可以用sprintf拼字符串,或者自己写一个格式化函数逐位转换。

5.2 周期性测量与低功耗思路

如果产品要用电池供电,SHT30有一个比较实用的特性:支持单次测量模式。每次发送测量命令,传感器完成测量后自动回到睡眠状态,功耗非常低。对应的代码逻辑就是,每隔几秒唤醒一次,发命令、延时、读数据、处理,然后让单片机也进入低功耗模式。

具体到51单片机,主循环里可以用定时器定时,比如定时1秒,每次到点后执行一次SHT30读取,其余时间让单片机休眠。如果直接把读取放在while(1)里不做延时,SHT30会以极快的速度反复测量,功耗和总线占用都上去了,而且测量值刷新得太快,本身也没有实际意义。

5.3 关于接线、模块选型和PCB布局的个人建议

给51单片机配SHT30,我现在的做法是直接用成品模块,模块上自带稳压和上拉电阻,省事很多。如果自己做PCB,要特别注意两点。一是SHT30传感器尽量远离发热元件,比如稳压芯片和单片机本身,否则温度读数会偏高;二是I2C信号线走线要短,最好包地处理,防止其他信号干扰。

模块选型上,建议选择板载4.7kΩ上拉电阻的版本。有些模块为了追求低功耗,把上拉电阻做大到10kΩ甚至不焊,在51这种IO口上就容易出现信号边沿过缓的问题。另外建议买SHT30而不是SHT20,SHT30在长期稳定性上更好,价格差距也不大,没必要省这个钱。

把一个传感器从“读出数”到“读得准”再到“稳定工作”,中间确实有不少门道。我在这个项目上最大的体会就是:SHT30和DHT11的差距,不只在精度参数上,更在于它的I2C协议和CRC校验让调试过程更有迹可循。出了任何问题,都能按协议一层层排查,而不是像单总线那样只能靠玄学调延时碰运气。如果你正在做温湿度相关的51项目,并且对数据可靠性有点要求,直接上SHT30就完事了。

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/9 4:08:41

GESP四级建造题:用并查集与Kruskal破解最小生成森林

GESP 四级出题风格里&#xff0c;我印象最深的一类题是“名称听起来像模拟&#xff0c;实际考的是图论板子”的题。比如 B4451 [GESP202512 四级] 建造&#xff0c;光看“建造”两个字&#xff0c;很容易让人以为要写一个盖房子、铺地板的模拟&#xff0c;可真到考场上拆开算&a…

作者头像 李华
网站建设 2026/9/9 4:07:46

@MybatisPlusTest自动插入报错排查:数据源、事务与SQL初始化

写Mapper层单测的时候&#xff0c;我一开始真的是被MybatisPlusTest这个注解坑得够呛。明明业务代码在Spring Boot启动后跑得好好的&#xff0c;数据也能正常插入&#xff0c;换成MybatisPlusTest一跑单元测试&#xff0c;自动插入就报错&#xff0c;而且错误乱七八糟&#xff…

作者头像 李华
网站建设 2026/9/9 4:06:30

桌面Agent实战:Crayfish与WorkBuddy容器版如何替代传统RPA

最近我把手头的自动化业务从传统RPA工具迁移到了以容器化桌面Agent为核心的方案上&#xff0c;工具链正好是标题里这两个项目&#xff1a;Crayfish 和 WorkBuddy 容器版。折腾了大半个月&#xff0c;踩了不少坑&#xff0c;也重新理清了一个问题——当大家都在说“大模型取代RP…

作者头像 李华
网站建设 2026/9/9 4:05:08

从ponytail到skill机制:AI Agent技能包安装与自定义实战

最近圈子里传得比较多的一个名字叫 ponytail&#xff0c;跟它一起出现的命令是 npx skill add dietrichgebert/ponytail 。乍一看你可能会以为是哪个发型相关的恶搞工具&#xff0c;实际上它是当前 AI Agent 生态里很典型的一个技能包&#xff0c;解决的是很多人在日常使用 A…

作者头像 李华
网站建设 2026/9/9 4:04:46

AI生成测试用例重复率高?从提示词约束到语义相似度的去重实践

如果你也用AI批量生成测试用例&#xff0c;多半会遇到一个很尴尬的问题&#xff1a;AI确实能在几分钟内给你吐出一大批用例&#xff0c;但里面总觉得“差不太多”。核心功能A的用例生成了三份&#xff0c;只是换了几种说法&#xff1b;同一个校验逻辑既能叫“用户名为空提示”&…

作者头像 李华
网站建设 2026/9/9 4:03:55

SHA256的Verilog实现:数字IC设计进阶练手项目

简介&#xff1a;一套基于Verilog的SHA256完整实现源码包&#xff0c;面向数字电路学习者、密码学爱好者及FPGA开发入门者&#xff0c;用于在硬件层面理解SHA256算法核心机制&#xff0c;掌握用硬件描述语言搭建数据填充、消息调度、压缩函数等模块的思路。资源合计18个文件、约…

作者头像 李华