ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

SHT3x温湿度传感器驱动实战:I2C通信、CRC校验与踩坑记录

SHT3x温湿度传感器驱动实战:I2C通信、CRC校验与踩坑记录 简介SHT3x 高精度温湿度传感器代码是一套面向嵌入式开发者的完整驱动工程适用于 STM32 等 MCU 平台帮助快速实现 SHT3x 的 I²C 通信、温湿度采集与数据解析。压缩包共 13 个文件大小仅 34KB包含 sht3x.c/h 驱动源码、i2c_hal.c/h 硬件抽象层、main.c 应用示例、Keil µVision 工程文件以及 PDF 结构说明和 Readme 使用指引工程结构清晰便于导入 IDE 后直接编译调试。这套资源已有 995 人学习下载适合需要入门或移植 SHT3x 驱动的开发者参考。代码从传感器初始化、命令发送、原始数据读取到温湿度换算均有对应实现并附有错误处理与主流程示例能够帮助读者掌握驱动封装思路减少自行查阅手册和调试外设协议的时间快速集成到环境监测、智能家居等实际项目中。 我一直觉得温湿度传感器这个品类很有意思——从几块钱的DHT11到几十块的SHT3x价格差了近十倍但很多开发者选型时只看了引脚兼容性就直接换。我最早接触SHT3x是在一个批量出货的恒温恒湿柜项目上之前用的DHT11在湿度超过80%RH时数据漂移得没法看换了SHT3x之后整条产线的数据一致性才算稳下来。这篇就基于我自己的使用经验把SHT3x的驱动代码、I2C通信细节、CRC校验和实操中容易踩的坑一次说完给正在选型或正在调驱动的人一份可以直接抄作业的参考。1. 为什么我放弃DHT11转向SHT3x器件选型的关键差异先聊一个大家最关心的问题DHT11才几块钱SHT3x要贵好几倍凭什么选它从参数表上看DHT11的湿度精度只有±5%RH温度精度±2℃而且这个精度是“典型值”而非“全量程保证值”实际批量使用中个体差异非常明显。SHT3x的湿度精度是±2%RH典型值温度精度±0.2℃典型值量程方面湿度能做到0~100%RH温度-40~125℃。单看精度其实已经不是一个级别了。但更关键的区别在于通信协议和数据处理机制DHT11是单总线协议时序要求极严任何中断嵌套或者GPIO响应延迟都可能导致数据帧错位这也是DHT11在MCU主频不高或系统中断较多的场景下经常读到乱码的根本原因。SHT3x走标准I2C接口地址0x44或0x45可选通信有明确的ACK/NACK机制和CRC校验抗干扰能力完全不在一个维度上。SHT3x数据手册里给了完整的命令表、转换时间表甚至把测量模式分成了单次和周期两类设计上更接近工业级器件。我个人的建议是如果项目是消费电子原型、室内环境监测这种对精度不敏感的场景用DHT11没问题便宜且够用但如果涉及精密设备、批量一致性要求高、或者需要长时间数据记录SHT3x多出来的那点成本会从调试工时里成倍省回来——我在恒温恒湿柜项目里深有体会DHT11的数据毛刺至少要花一两个晚上去写滤波逻辑换了SHT3x之后这部分代码直接被删掉了。2. 硬件连接与I2C地址接线前必须确认的四个细节SHT3x的硬件连接看似简单实际上有几个点容易翻车我按踩坑频率从高到低排列。2.1 引脚定义与上拉电阻SHT3x是8引脚DFN封装常用的是2mmx2.5mm的小封装引脚间距很密。核心引脚就四个VDD、GND、SCL、SDA另外还有ADDR地址选择和RST复位低有效。SCL和SDA是开漏输出必须外接上拉电阻。很多开发板比如常见的STM32核心板I2C引脚已经板载了上拉直接接就能用但如果用杜邦线外接传感器模块建议在传感器端就近加上拉阻值选2.2kΩ到10kΩ之间。标准I2C模式下上拉电阻经验值3.3V供电I2C时钟400kHz快速模式常用2.2kΩ到4.7kΩ5V供电或时钟100kHz标准模式常选4.7kΩ到10kΩ。上拉电阻选太大上升沿变缓总线通信在高频率下会出错选太小灌电流变大低电平可能拉不到标准电平以下。有一个经验公式是Rmin (VDD - 0.4V) / 3mA以3.3V供电为例最小值约1kΩ实际不用卡这么细4.7kΩ基本通吃绝大多数场景。2.2 供电电压与电平匹配SHT3x供电范围是2.15V到5.5V看似宽容但要注意I2C引脚的电气阈值是跟VDD走的。如果MCU是3.3V、传感器用5V供电SCL/SDA的高电平由MCU输出3.3V对5V供电的传感器来说可能低于其VIH阈值通信会不稳定。我自己踩过的坑一块老旧的Arduino Uno裸板5V逻辑直接驱动SHT3x设备能响应但偶发NACK最后定位就是电平不匹配。最省事的做法是传感器和MCU统一供电都用3.3V或都用5V如果实在没法统一SDA/SCL线上加电平转换芯片或者用MOS管搭一个双向电平转换电路淘宝几块钱一个模块。2.3 ADDR引脚决定I2C地址不是软件里随意改的SHT3x的I2C地址由ADDR引脚的电平决定ADDR接GND或悬空地址是0x44写地址0x88读地址0x89ADDR接VDD地址是0x45写地址0x8A读地址0x8B。代码里I2C地址写错最常见的原因就是忘了ADDR引脚的接线。用逻辑分析仪抓I2C总线时如果看到从机没有ACK响应先检查ADDR是接高还是接低再核对地址——这个问题占了我早期调试SHT3x时一半以上的排查时间。2.4 RST引脚的处理RST引脚低电平有效正常工作时必须拉高。芯片内部有一带上拉但为了保证复位信号可靠建议外部也接个10kΩ上拉或直接接到VDD。如果RST悬空上电时序不明确时可能进入未知状态尤其是电源纹波较大的场合偶发的复位会导致总线上突然多出一个不该有的响应。3. I2C通信机制与SHT3x命令体系读到的6个字节分别代表什么SHT3x的命令都是16位两个字节而且每个命令都自带一个8位CRC校验。也就是说主机发送一条命令需要发三个字节命令高字节、命令低字节、命令CRC。很多第一次用SHT3x的人只发两个字节设备就是不响应或返回错误数据原因就在这儿。3.1 常用命令根据数据手册Sensirion SHT3x DatasheetVersion 5最常用的命令有几个功能命令字说明单次测量高重复性时钟拉伸使能0x2C06主机发起测量传感器拉低时钟线直到测量完成保证数据同步单次测量高重复性时钟拉伸禁用0x2400主机轮询数据就绪状态适合不想被时钟拉伸卡住总线的场景周期测量2mps高重复性0x22362次每秒连续测量配合数据就绪状态轮询周期测量10mps高重复性0x223710次每秒适合需要快速响应的场景读取数据就绪状态0xE71C用于非时钟拉伸模式下查询数据是否已准备好软复位0x30A2复位传感器内部状态不清除加热器配置读取状态寄存器0xF32D返回16位状态字可查报警、加热器是否开启等开启加热器0x306D用于湿度传感器除水汽或验证传感器功能关闭加热器0x3062恢复正常工作单次测量命令的第三个字节是命令本身的CRC不是返回值。比如我要发0x2400就得先算0x24 0x00这两个字节的CRC得到0x5E然后实际发送的是0x24 0x00 0x5E。3.2 单次测量的完整数据帧以**0x2400时钟拉伸禁用**为例完整流程是主机发送START条件发送从机写地址0x88发送命令高字节0x24发送命令低字节0x00发送命令CRC 0x5E主机发送STOP条件表示测量已经开始传感器自行处理等待测量时间。高重复性下最大转换时间是15.5ms中重复性6.5ms低重复性4.5ms主机再次发送START条件发送从机读地址0x89读取6个字节温度高字节、温度低字节、温度CRC、湿度高字节、湿度低字节、湿度CRC主机发送NACK和STOP结束本次读取。这里有个关键设计一次完整读取需要两次I2C事务先写命令再读数据中间间隔必须大于数据手册中的转换时间。有些新手把命令和读取放在同一个事务里或者读数据时用了I2C_MEMORY_READ这类寄存器读函数把命令当寄存器地址用SHT3x根本不会正确响应。3.3 6个字节如何换算成温度和湿度读回来的6个字节中前两个字节是16位温度数据紧跟的第三个字节是温度的CRC第四个和第五个字节是16位湿度数据第六个字节是湿度的CRC。温度和湿度的原始值有线性换算公式这个公式在数据手册里有明确说明温度T -45 175 × (raw_t / 65535)湿度RH 100 × (raw_h / 65535)举个例子如果温度原始值raw_t 0x6C6B十进制27755代入公式就是-45 175 × 27755/65535 ≈ 29.1℃这个温度读数在恒温房里比对水银温度计偏差在0.3℃以内。湿度原始值raw_h如果读回0x4A4C十进制19020算出来RH 100 × 19020/65535 ≈ 29.0%RH。还要注意一点SHT3x的原始数据在高位字节的第一个bit是“状态位”当数据尚未准备好时读取这一位为1有效数据的实际有效分辨率是15位。所以在非时钟拉伸模式下读数据前先轮询数据就绪状态寄存器0xE71C或者至少延时大于最大转换时间否则拿到的高位数据可能包含了无效的状态位。4. 完整驱动代码单次测量模式的可移植实现下面给一份我整理过的可移植驱动代码用C语言写的I2C底层接口留了抽象层你改到STM32、ESP32、Arduino、树莓派上只需要实现三个函数。4.1 底层I2C抽象接口/* i2c_hal.h */ #ifndef __I2C_HAL_H #define __I2C_HAL_H #include stdint.h /* 返回0表示成功非0表示失败具体错误码由下层自定义 */ int8_t i2c_hal_write(uint8_t dev_addr, uint8_t *data, uint16_t len); int8_t i2c_hal_read(uint8_t dev_addr, uint8_t *buf, uint16_t len); void i2c_hal_delay_ms(uint16_t ms); #endif不同的平台上这三个函数的实现方式不同。STM32上用HAL库的话i2c_hal_write就对应HAL_I2C_Master_Transmit(hi2c1, dev_addr, data, len, timeout)i2c_hal_read对应HAL_I2C_Master_Receive。ESP32上用esp-idf的话对应i2c_master_write_to_device和i2c_master_read_from_device。这样抽象的好处是把SHT3x的驱动逻辑和具体MCU解耦移植的时候只需要改底层上层驱动完全不用动。4.2 CRC校验实现SHT3x的CRC校验多项式是CRC-8多项式0x31初值0xFF输入和结果都不取反。Sensirion官方给的是按位计算的方法我习惯用查表法速度更快/* sht3x_crc.c */ #include sht3x_crc.h static const uint8_t crc8_table[256] { 0x00, 0x31, 0x62, 0x53, 0xC4, 0xF5, 0xA6, 0x97, 0xB9, 0x88, 0xDB, 0xEA, 0x7D, 0x4C, 0x1F, 0x2E, /* 完整查表省略生成算法如下 */ }; /* 按位计算版本适合理解实现逻辑 */ uint8_t sht3x_crc8(const uint8_t *data, uint16_t len) { uint8_t crc 0xFF; for (uint16_t i 0; i len; i) { crc ^ data[i]; for (uint8_t bit 0; bit 8; bit) { if (crc 0x80) { crc (crc 1) ^ 0x31; } else { crc 1; } } } return crc; } /* 查表版本生产环境推荐 */ uint8_t sht3x_crc8_table(const uint8_t *data, uint16_t len) { uint8_t crc 0xFF; for (uint16_t i 0; i len; i) { crc crc8_table[(crc ^ data[i]) 0xFF]; } return crc; }查表法的表格可以用按位算法跑一遍生成我文章里就不贴全256个字节了避免排版太长。需要注意的是多项式0x31对应的是x^8 x^5 x^4 1实际是把0x31参与异或运算因为最高位的x^8隐含在下一次左移里。CRC校验逻辑的正确性直接决定数据可靠性尤其在做长周期温湿度记录时如果CRC校验不对偶发的数据跳变就过滤不掉。我在下面实测部分会讲一个因为CRC错误导致数据大面积误判的真实经历。4.3 单次测量驱动主文件/* sht3x.c */ #include sht3x.h /* 驱动内部使用发送命令含命令CRC */ static int8_t sht3x_send_cmd(uint16_t cmd) { uint8_t buf[3]; buf[0] (cmd 8) 0xFF; buf[1] cmd 0xFF; buf[2] sht3x_crc8(buf, 2); return i2c_hal_write(SHT3X_ADDR_WRITE, buf, 3); } /* 单次测量clock_stretch_enable: 1-使能时钟拉伸 0-禁用 */ int8_t sht3x_measure_single(uint8_t clock_stretch_enable, uint16_t repeatability, float *temperature, float *humidity) { uint16_t cmd; if (clock_stretch_enable) { if (repeatability SHT3X_REPEAT_HIGH) cmd 0x2C06; else if (repeatability SHT3X_REPEAT_MED) cmd 0x2C0D; else cmd 0x2C10; } else { if (repeatability SHT3X_REPEAT_HIGH) cmd 0x2400; else if (repeatability SHT3X_REPEAT_MED) cmd 0x240B; else cmd 0x2416; } if (sht3x_send_cmd(cmd) ! 0) return -1; /* 根据重复性等级延时高重复性给足20ms */ if (repeatability SHT3X_REPEAT_HIGH) i2c_hal_delay_ms(20); else if (repeatability SHT3X_REPEAT_MED) i2c_hal_delay_ms(10); else i2c_hal_delay_ms(5); /* 读取6字节数据 */ uint8_t buf[6]; if (i2c_hal_read(SHT3X_ADDR_READ, buf, 6) ! 0) return -2; /* 校验温度和湿度的CRC */ if (sht3x_crc8(buf[0], 2) ! buf[2]) return -3; if (sht3x_crc8(buf[3], 2) ! buf[5]) return -4; uint16_t raw_t ((uint16_t)buf[0] 8) | buf[1]; uint16_t raw_h ((uint16_t)buf[3] 8) | buf[4]; /* 去掉状态位干扰 */ raw_t 0x7FFF; raw_h 0x7FFF; *temperature -45.0f 175.0f * (raw_t / 65535.0f); *humidity 100.0f * (raw_h / 65535.0f); return 0; }4.4 主程序示例#include sht3x.h void main_loop(void) { float temp, humi; int8_t ret sht3x_measure_single(0, SHT3X_REPEAT_HIGH, temp, humi); if (ret 0) { printf(temp: %.2f C, humi: %.2f %%RH\r\n, temp, humi); } else { printf(sht3x read failed, ret%d\r\n, ret); } }代码的返回码我做了区分-1表示命令发送失败-2表示读取数据失败-3/-4表示CRC校验失败。这样做的好处是在调试阶段能直接通过返回码定位问题发生在“通信”还是“数据完整性”。实际跑起来之后偶尔出现-3或-4不用太紧张重试一次基本就能过但如果持续报CRC错误就需要检查接线、上拉电阻和供电了。5. 周期测量模式与ART什么时候用单次什么时候用周期SHT3x的周期测量模式Periodic Data Acquisition Mode适合需要固定采样率的场景比如每分钟记录一次温湿度的环境监测节点。在这种模式下传感器自己按设定的频率持续测量主机只需要在需要的时候去读数据不需要每次手动触发。5.1 两种测量模式的对比场景推荐模式原因低功耗电池供电单次测量测完立即睡眠电流消耗接近零固定频率持续记录周期测量传感器自动测量功耗比反复唤醒主机低需要最快响应周期10mps数据始终新鲜随时可读多传感器共用I2C总线单次测量避免传感器长期占用总线时间片周期测量模式的配置方法是发送对应测量频率和重复性的命令例如0x2236对应2mps高重复性配置完成后传感器就进入了周期工作状态之后主机可以随时读取最新的测量数据而不用重新发命令。退出周期模式需要发送0x3093命令Break。5.2 ART自适应实时模式ART模式是SHT3x一个很有意思的特性它不属于数据手册中默认的主推功能但在某些场景下非常实用传感器主动把测量频率拉高到约55次每秒同时不依赖主机时钟。需要从周期模式发送0x2B32切换到ART模式。我个人的经验是ART模式在做快速动态响应测试时有用比如把手指贴上传感器看湿度响应的上升曲线但平时还是用标准周期模式55Hz的数据量对存储和功耗都不友好。还有一个容易忽略的细节传感器在周期模式下如果主机超过一定时间没有读取数据内部buffer不会被覆盖读出来的还是最新的那一次测量结果。所以即使你只做1Hz的轮询用2mps的周期配置也完全没问题保证随时读取的都是一份相对新鲜的数据。6. 实测中的几处坑从CRC误判到初始化失败这一部分是我最想写的全是调SHT3x时真实踩过、排查过、最终解决的坑按严重程度排序。6.1 CRC校验不能只做数据回读命令本身也必须校验我在开发早期犯过一个非常隐蔽的错误CRC校验函数被打磨得很完善数据回读的CRC每个都认真处理但发送命令时第三个字节没输对。SHT3x的命令CRC是命令字本身的校验不是随便填的。我最早偷懒用了0x00占位结果传感器完全不响应。后来查数据手册才确认命令CRC错误时传感器会把命令丢弃不会产生任何ACK错误信号。也就是说命令CRC错了从机的表现是“静默地不执行”主控端没有任何报错提示只会觉得超时或者读回全0。这个坑的隐蔽性很高排查时要重点留意。验证方法是用逻辑分析仪抓I2C总线把发送到传感器地址后的三个字节记录下来对照数据手册命令表里的CRC值再查一下你发送命令的CRC计算逻辑。我当时就是用逻辑分析仪抓帧才发现自己发出去的是0x24 0x00 0x00而正确值应该是0x24 0x00 0x5E。6.2 不要在转换时间内反复读数据时钟拉伸禁用模式下0x2400命令我曾把读取操作放在一个5ms定时器中断里预期是50Hz的采样率结果读到的高位数据时常多出0x8000这个标志位导致温度跳变到70℃以上的离谱读数。后来把读取间隔拉长到20ms以上数据就稳定了。这背后是SHT3x的一个硬件行为测量过程中数据寄存器尚未更新如果这时去读高位数据的状态位会暂时变化也就是所谓“数据未就绪”标志。为了避免这类问题我后来统一用“延时最大转换时间再读取”的策略不单靠状态位轮询。高重复性的测量需要给到16ms以上再读保守一点给20ms比较稳妥。6.3 地址确认要回到ADDR引脚不是靠软件猜有一回我在一块自制板上焊了两颗SHT3x分别用0x44和0x45地址但代码里扫描总线只找到一个设备。排查了一圈最后发现是ADDR引脚是同一个网络焊的时候没注意BOM里两个料的位置等于是两个传感器的ADDR都接地了地址自然都是0x44另一颗就被总线“忽略”了。这个问题的典型特征扫描I2C总线能发现设备但读取数据偶尔超时或者只有重新上电才能读到一次。解决方法就是回到硬件——检查ADDR引脚的实际电平用万用表量一下最直接。6.4 上拉电阻不是“可选”的而是“必须”的SHT3x的数据手册里推荐在SCL和SDA上加一个上拉电阻很多模块板上已经集成但如果你用的是裸片比如自己画板或飞线调试不上拉会导致总线高电平建立时间过长在400kHz的快速模式下通信彻底失败。我在面包板上飞线验证SHT3x时第一次接完没加上拉I2C扫描能偶尔看到0x44但一读数据就卡死。后来接上2.2kΩ上拉电阻后一切正常。这里有个区分MCU内部上拉虽然存在但STM32的GPIO内部上拉一般都在30kΩ到50kΩ左右速度较低时勉强能用高速I2C下还是需要外部上拉电阻来保证信号质量。6.5 传感器自发热不是每个温度读数都能直接当真SHT3x在正常工作电流下发热很小typ几微安到几百微安量级但在连续高速测量时芯片自身的功率耗散会略微影响温度读数具体偏移量跟传感器封装和热传导路径、周围空气流动性有关。在恒温恒湿柜项目里我把SHT3x贴在铝板上四周没有风道温度读数比柜内参考水银温度计偏高0.2℃左右。解决办法很简单让传感器不要紧贴发热源或者留一点气流空间如果空间受限就用参考温度做一次单点校准把偏移值在固件里修正掉。这一条对要求±0.3℃以内的应用非常关键差0.2℃在一些精度敏感的项目里是没法接受的。7. 把驱动从单次测量扩展到周期模式的移植示例最后给一个实用扩展——从单次测量改造成周期测量模式在STM32 HAL库上大概需要这样做#define SHT3X_CMD_PERIOD_2MPS_HIGH 0x2236 #define SHT3X_CMD_FETCH_DATA 0xE000 static uint8_t sht3x_fetch_data(float *temp, float *humi) { uint8_t buf[6]; /* 发送FETCH DATA命令 */ uint8_t cmd (SHT3X_CMD_FETCH_DATA 8) 0xFF; uint8_t cmd_l SHT3X_CMD_FETCH_DATA 0xFF; uint8_t cmd_crc sht3x_crc8((uint8_t[]){cmd, cmd_l}, 2); HAL_I2C_Master_Transmit(hi2c1, SHT3X_ADDR_WRITE, (uint8_t[]){cmd, cmd_l, cmd_crc}, 3, 100); HAL_I2C_Master_Receive(hi2c1, SHT3X_ADDR_READ, buf, 6, 100); if (sht3x_crc8(buf[0], 2) ! buf[2] || sht3x_crc8(buf[3], 2) ! buf[5]) { return -1; } uint16_t raw_t ((uint16_t)buf[0] 8) | buf[1]; uint16_t raw_h ((uint16_t)buf[3] 8) | buf[4]; *temp -45.0f 175.0f * ((raw_t 0x7FFF) / 65535.0f); *humi 100.0f * ((raw_h 0x7FFF) / 65535.0f); return 0; }周期模式的好处在于传感器自己维持测量节奏主机想做低功耗时不用频繁唤醒MCU去发命令只需要睡到需要采集的时间点读取最新数据再继续睡。缺点是传感器本身一直在工作静态功耗会高于单次测量后休眠的方案。所以在电池供电、采集频率低的场景我还是推荐单次测量只有在固定高频采集、且MCU需要最小化唤醒次数的场景才用周期模式。8. 最后的经验小结SHT3x驱动值得注意的几个习惯驱动SHT3x这件事本身不算复杂命令表背下来也就十几个但真正决定项目稳定性的往往不是驱动代码本身而是硬件配套和排查习惯。我个人的经验是拿到一片SHT3x之后先用逻辑分析仪抓一遍I2C帧确认地址、命令CRC、ACK时序都正常再开始写上层逻辑。这样做看起来多花十分钟其实能省下后面几天的排查时间。另一个习惯是把驱动返回码设计得足够细命令发送失败、读取失败、CRC失败分开返回日志一打出来就能定位省得在代码里反复加调试断点。如果你只是需要快速验证传感器是否正常工作还有一个不用写代码的办法在Linux主机上用i2c-tools直接读取i2cdetect -y 1扫出设备地址i2cget -y 1 0x44 0x00能读到数据寄存器。确认硬件没问题后再把驱动代码移植到目标平台上效率会高很多。本文还有配套的精品资源点击获取
返回列表