ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

51单片机驱动SHT30:I2C时序与电平匹配实战

51单片机驱动SHT30:I2C时序与电平匹配实战 简介面向51单片机开发者与物联网入门者的SHT30温湿度传感器驱动工程解决在仅有普通GPIO的单片机上模拟I2C总线并稳定读取SHT30数据的问题。资源仅含2个文件分别为C源文件与配套头文件整个压缩包仅4KB代码精简、无冗余依赖可直接移植到Keil等常见开发环境。驱动全面覆盖I2C起始/停止信号、设备地址写入、读取命令发送、温湿度两字节数据接收与物理量换算并加入应答错误处理机制可避免总线死锁与数据错乱同时提供清晰的引脚配置说明便于根据实际板卡调整时钟线与数据线。已有1545人浏览学习适合智能家居、气象站、农业监测等环境采集项目可作为51单片机快速集成SHT30高精度温湿度检测的实用参考。1. 51单片机驱动SHT30先解决I2C时序和电平匹配再谈代码用51单片机驱动SHT30第一坑不是代码编译不过而是SHT30这颗数字温湿度传感器只在I2C总线上工作而且供电范围只有1.8V到3.6V很多学生板上的51还是5V供电。SHT30没有像DHT11那样的单总线方案也不可能像PT2262那样靠定时器模拟无线编码所以你要先做好两件事用普通IO口模拟出标准I2C时序以及把传感器挂在3.3V一侧保证SCL、SDA电平不会被5V灌坏。这个标题里反复出现的51sht30、sht3051、SHT30驱动本质上就是同一件事在51这类没有硬件I2C外设或硬件I2C用法别扭的单片机上用GPIO翻转写一个可靠且能复用的SHT30驱动。对于准备做课程设计、毕设温控风扇或者产品原型里想快速采样温湿度的工程师这篇把初始化、单次测量、CRC校验、周期读取和排错路径一次讲完。2. I2C总线初始化与SHT30的启动顺序用GPIO模拟起始、停止和写命令2.1 为什么不用51的硬件I2C而用普通IO模拟传统51单片机例如STC89C52、AT89S52内部没有硬件I2C控制器。有些增强型51比如STC15系列、STC8系列带硬件I2C但寄存器配置不统一换一颗芯片驱动代码就要跟着改。而SHT30满速I2C是400kHz51用IO模拟跑到100kHz完全够用代码移植性还更好。真正重要的不是模拟本身而是时序参数。SHT30要求SCL高电平和低电平的最小时间约1.3us100kHz模式到0.6us400kHz模式51主频12MHz时一个机器周期是1us直接用空延时函数就能满足。关键是启动和停止信号SDA必须在SCL高电平期间稳定顺序不能反。常见做法是用两个sbit定义SCL和SDA引脚写四个基础函数起始、停止、发送字节、接收字节。很多失败的案例都出在起始和停止信号上比如SDA先拉高SCL再拉高这在I2C里会被误判为一次停止传感器端的状态机就会错乱。sbit SHT30_SCL P1^0; sbit SHT30_SDA P1^1; void I2C_Delay(void) { unsigned char i 5; while (i--); } void I2C_Start(void) { SHT30_SDA 1; SHT30_SCL 1; I2C_Delay(); SHT30_SDA 0; // SCL高电平期间SDA拉低 起始 I2C_Delay(); SHT30_SCL 0; }这段代码里先把SDA和SCL都拉高保证总线空闲然后SDA拉低再让SCL拉低。I2C_Delay用的是普通空循环12MHz晶振下大约产生3到5us延时留足了余量。SCL拉低之后总线进入占用状态后面才能开始传字节。2.2 SHT30的I2C从机地址和写命令格式SHT30的7位从机地址由ADDR引脚决定ADDR接地时是0x44接VDD时是0x45。注意0x44是7位地址左移一位后才是8位写地址0x88。发送命令时主机先发0x88等ACK然后再发命令的高8位和低8位。很多人第一次调试时写成了发送0x44之后直接发0x2C06结果传感器没有任何响应因为I2C总线上根本不认0x44这个地址。这里的要点是I2C地址字节的最低位是读写标志位0x88表示接下来写0x89表示接下来读。SHT30的操作命令都是16位先发MSB后发LSB。驱动中最常用的命令集中在下表命令名称操作码功能说明Soft Reset0x30A2软件复位恢复默认状态单次测量-高重复度0x2C06读一次温湿度精度最高单次测量-低重复度0x2C10转换快噪声略大周期测量-10Hz0x2737数据流模式适合连续采样读取周期测量结果0xE000读取最新一次的测量数据单次测量模式下发完命令后SHT30会自动开始转换主机不需要再发命令等转换完成直接读即可。周期测量模式则不同传感器会持续以设定频率刷新内部数据主机随时可以发0xE000去取最新结果。2.3 初始化SHT30的最小顺序上电延时-复位-测量命令SHT30上电后内部需要一段时间稳定手册上给的是最大1ms左右但实际工程中建议至少等10ms尤其是电源纹波较大的51开发板延时不足会出现第一次读回的数据全为0xFF。初始化函数里应该包含下面这个完整顺序。void SHT30_Init(void) { unsigned char i; DelayMs(10); // 上电稳定10ms以上 for (i 0; i 2; i) // 连续写两次复位命令 { I2C_Start(); I2C_SendByte(0x88); // SHT30地址写位 I2C_WaitAck(); I2C_SendByte(0x30); // Soft Reset命令高8位 I2C_WaitAck(); I2C_SendByte(0xA2); // Soft Reset命令低8位 I2C_WaitAck(); I2C_Stop(); DelayMs(2); } }为什么Soft Reset命令要发两次SHT30的数据手册中有一条不太起眼的说明soft reset命令无法被识别为重复起始后的命令连续发送两次能够确保传感器在任何异常状态下都被复位成功。这是我实际调试时踩过的坑只发一次时成功率大约只有八成。每次复位命令结束后要等至少1.5ms代码里延时2ms比较稳妥。复位完成后不要立刻发测量命令让传感器内部的状态机回到空闲状态然后再执行单次测量命令。2.4 5V单片机和3.3V传感器之间的电平适配51单片机的IO口输出高电平接近VCC如果VCC是5V直接和3.3V的SHT30连接即使传感器没被立即烧坏长期工作也会导致内部电路损伤。稳妥的方案有三种一是买带电平转换的SHT30模块板上已经用MOS管或转换芯片处理过二是用两个NMOS做双向电平转换三是在SDA和SCL上串330Ω电阻同时确保SHT30的VDD用3.3V供电。第三种方案最简单但要检查51IO口的高电平阈值部分51在输入模式下高电平阈值是2.0V3.3V直接输入也能被识别。提示尽量让SHT30和单片机共地。如果模块是独立供电而且没有和单片机共地I2C总线上的电压参考点不一致读回来的数据大概率是乱码。3. SHT30温湿度数据读取与计算从6字节原始数据到温度和湿度3.1 单次测量模式下完整读时序执行完0x2C06命令之后SHT30会进入忙状态转换时间在低重复度下约1.5ms高重复度下约12.5ms。这段时间内如果发读取命令传感器可能不响应ACK。所以驱动里在读取前要插入延时高重复度建议保险起见延时20ms。读取时主机先发一个起始信号然后发送0x89也就是地址加读位接着依次读取温度MSB、温度LSB、温度CRC、湿度MSB、湿度LSB、湿度CRC六个字节最后一字节返回NACK后发停止信号。I2C读字节的函数里要特别注意最后一个字节必须回NACK否则传感器会认为主机还要继续读数据总线状态就被挂在读模式里。下面是完整的读取函数unsigned char SHT30_ReadData(unsigned char *buf) { unsigned char i; I2C_Start(); I2C_SendByte(0x89); // 地址读位 if (I2C_WaitAck()) return 0; // 无ACK读取失败 for (i 0; i 6; i) { buf[i] I2C_RecvByte(); if (i 5) I2C_SendAck(0); // 前5字节回ACK else I2C_SendAck(1); // 最后一字节回NACK } I2C_Stop(); return 1; }逻辑说明I2C_WaitAck返回值是总线上的ACK电平如果传感器没有拉低SDA说明设备不响应。前5个字节给ACK表示“继续传”第6个字节给NACK表示“我只要6个字节”。如果全部字节都回ACK数据读取会陷入死循环因为传感器会一直发数据。这属于逻辑设计里最常见的I2C读操作失误。3.2 SHT30温湿度计算公式SHT30用16位无符号数表示原始测量值直接线性映射到物理量。温度计算公式是T -45 175 * rawTemp / 65535湿度计算公式是RH 100 * rawHumidity / 65535。这里有两个坑第一个是运算顺序如果先做除法再乘175整数除法会直接把小数丢掉得到的结果会差好几度第二个是数据类型rawTemp是0到65535175 * rawTemp最大约1146万int类型只有16位会溢出必须用unsigned long。unsigned long temp_raw, humi_raw; unsigned int temp_x10, humi_x10; temp_raw (buf[0] 8) | buf[1]; humi_raw (buf[3] 8) | buf[4]; temp_x10 (unsigned int)((temp_raw * 1750) / 65535 - 450); humi_x10 (unsigned int)((humi_raw * 1000) / 65535);参数说明temp_x10保存的是温度乘以10后的整数值比如223表示22.3摄氏度这样避免了浮点库带来的代码体积膨胀。湿度humi_x10同理183表示18.3%RH。temp_raw * 1750先做一个较大的乘法再除法保持精度。用long类型做中间变量51的Keil C51里unsigned long是32位最大能到42亿完全不会溢出。3.3 CRC8校验防止I2C数据线干扰导致错误温湿度SHT30在温湿度数据后面各跟一个CRC8校验字节。多项式是0x31初始值是0xFF。51驱动里最好把CRC校验写进去否则在电机驱动、继电器开关这类电磁干扰多的环境里偶尔读回的温度会出现跳变几十度的离谱值。CRC计算是对每个字节逐位处理的核心代码很短但对连续多个字节计算时要保持一个累加状态。unsigned char SHT30_CRC8(unsigned char crc, unsigned char byte) { unsigned char i; crc ^ byte; for (i 0; i 8; i) { if (crc 0x80) crc (crc 1) ^ 0x31; else crc 1; } return crc; } unsigned char SHT30_CheckCRC(unsigned char *buf) { unsigned char crc 0xFF; crc SHT30_CRC8(crc, buf[0]); // 温度MSB crc SHT30_CRC8(crc, buf[1]); // 温度LSB if (crc ! buf[2]) return 0; crc 0xFF; crc SHT30_CRC8(crc, buf[3]); // 湿度MSB crc SHT30_CRC8(crc, buf[4]); // 湿度LSB if (crc ! buf[5]) return 0; return 1; }逻辑说明第一个参数crc是上一次计算的累积值初始为0xFF对温度两个字节连续计算后和第2字节比较湿度同样从0xFF重新开始。多项式0x31对应x^8 x^5 x^4 1这在SHT30、SHT31、SHTC3里是共用的以后换传感器可以复用这段代码。校验失败时建议丢弃本次数据连续三次失败则触发重新初始化传感器以处理传感器卡死的情况。3.4 用逻辑分析仪验证读取到的数据调试51sht30驱动时我一般会用逻辑分析仪抓取SDA和SCL的波形确认每次读操作是不是完整地走了“Start-地址-命令-Stop”的流程。逻辑分析仪上能直接看到0x88、0x2C、0x06三帧数据如果只看到0x30和0xA2说明发送地址那一步被跳过了要检查I2C_SendByte内部是否在高电平期间把数据位放在了SDA上。还有一点SDA和SCL在空闲时都应该是高电平如果测量到其中一个为低先查上拉电阻是否虚焊。提示SHT30模块上的上拉电阻一般是4.7kΩ到10kΩ。如果自己画板SCL和SDA必须各接一个上拉电阻到VDD不要只接一个也不要接到5V上。4. 周期测量模式、总线恢复和连续读取的边界处理4.1 周期测量模式下如何正确读数单次测量适合低频采样的场景比如每10秒读一次温湿度。如果要做闭环温度控制比如基于51的温控风扇每秒钟要读好几次这时应该用周期测量模式。初始化时发送0x2737命令让传感器以10Hz频率自动测量。之后主机不再发测量命令只需要每次读数据前发0xE000然后读取6字节数据。注意周期测量模式下传感器的测量动作是后台持续的两次读取之间不需要等待转换时间但传感器可能刚好在刷新数据的瞬间被读到这时候CRC校验就能把毛刺数据挡掉。4.2 I2C总线卡死时用9个时钟脉冲恢复I2C总线最讨厌的情况是SDA被某个设备一直拉低。SHT30在掉电、复位异常或者干扰下可能出现这种状态。总线卡死后51的IO口因为开漏输出设置只能拉低不能主动拉高必须在外部上拉电阻的帮助下恢复。常见做法是让SCL连续翻转9次每次翻转后检查SDA是否释放。void I2C_Recover(void) { unsigned char i; SHT30_SCL 0; SHT30_SDA 1; for (i 0; i 9; i) { SHT30_SCL 1; I2C_Delay(); SHT30_SCL 0; I2C_Delay(); } I2C_Start(); I2C_Stop(); }这段代码的思路是通过SCL时钟脉冲让挂在总线上的设备完成当前字节传输释放SDA。发完后用一个起始、停止信号重置所有设备的状态机。要确认恢复是否成功可以在最后测一下SDA是不是高电平如果还是低大概率是SHT30硬件损坏或总线上有其他设备短路。4.3 连续读取时三个容易被忽略的参数第一个是上拉电阻。总线频率越高上拉电阻不能太大否则信号上升沿太缓读回来的数据错位。100kHz下用10kΩ没问题400kHz下建议4.7kΩ。第二个是引线长度51和SHT30之间的连线超过20cm时会出现波形振铃和数据错误这时候降低I2C延时让总线跑100kHz以下。第三个是VDD电压纹波SHT30的电源引脚对纹波敏感和继电器驱动电路共用5V电源时建议在VDD和GND之间加100nF电容必要时再加10uF电解电容。这三个参数在proteus仿真51单片机时看不出来但实物一上电就暴露问题。4.4 用两个SHT30做双点温湿度采集如果项目需要在两个位置测温湿度可以利用SHT30的ADDR引脚。第一个传感器ADDR接地地址0x44第二个ADDR接VDD地址0x45。两条总线可以挂在同一组SCL和SDA上只需要在驱动里把从机地址做成参数分别初始化读数。这样硬件改动最小驱动代码里传地址参数给读写函数即可。唯一的限制是I2C总线的电容负载会随着设备增多而上升总线长度要控制得更短上拉电阻适当调小到3.3kΩ左右以保证信号质量。本文还有配套的精品资源点击获取
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进