尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

STM32驱动SCD40 CO2传感器:I2C时序、CRC校验与标定全攻略

STM32驱动SCD40 CO2传感器:I2C时序、CRC校验与标定全攻略 简介本资源是一套面向嵌入式开发初学者与物联网项目实践者的STM32平台二氧化碳监测方案源码聚焦SCD40传感器驱动与数据解析核心问题适用于智能建筑环境监控、农业温室气体管理及工业安全检测等实际场景。压缩包共11个文件6个.h头文件、5个.c源文件总大小仅24KB结构精简包含Sensirion官方I2C底层驱动sensirion_i2c.c/h、i2c_hal.c/h、SCD40专用协议封装scd4x_i2c.c/h及通用配置模块sensirion_common.c/h、sensirion_config.h便于快速移植与调试。已有440人学习下载代码严格遵循SCD40数据手册时序与CRC校验规范内置初始化配置、周期测量触发、原始数据转换为ppm值及基础错误处理逻辑无需额外依赖库即可在主流STM32 HAL/StdPeriph工程中直接集成。 老哥们最近在折腾一个室内空气质量监测的小板子主控用的STM32F103传感器选了瑞士Sensirion家的SCD40二氧化碳传感器。这块芯片比同门的SCD30体积小了一大圈功耗低到离谱5秒一个采样周期的时候平均电流也就0.5mA左右非常适合做电池供电的便携设备。这篇文章把我从零开始调通SCD40的完整过程整理出来包括I2C时序怎么处理、CRC校验怎么算、测量数据怎么换算、ASC和FRC标定怎么用再把我实际调试中踩过的坑一并交代清楚。这套源程序的核心思路并不复杂STM32通过I2C总线与SCD40通信上电后发送启动周期测量命令然后每隔5秒轮询一次数据就绪状态就绪后读取9个字节的原始数据经过CRC校验和公式换算得到二氧化碳浓度、温度、湿度三个物理量。如果你正准备做新风系统联动、教室/办公室空气质量监测、智能家居CO2提醒这类项目这篇文章可以直接当参考文档用。1. 项目设计与方案选型1.1 为什么选SCD40而不是SCD30或者其他传感器先聊选型。市面上常见的CO2传感器方案其实不少国产的MH-Z19系列用的比较多价格便宜串口输出但体积大、功耗高基本告别电池供电。Sensirion自家的SCD30是上一代主力NDIR原理精度不错但同样存在体积和功耗偏大的问题。而SCD40用的是光声传感原理不是传统的NDIR所以能做到非常小的封装尺寸只有8.5mm x 8.5mm x 5.8mm比SCD30小了差不多5倍。从参数上看SCD40的CO2测量范围是0到40000ppm精度为±(50ppm 5%读数)这个精度级别用于室内空气质量监测完全够用。温度精度±0.8°C相对湿度精度±6%中规中矩但胜在传感器直接把这几个量一起测了省去了额外挂温湿度芯片的麻烦。SCD40还集成了自动自校准算法ASC在400ppm左右的基线环境下连续运行约一周就能自动修正长期漂移这一点对产品维护非常友好。如果说缺点主要有两个一是I2C接口对时序有一定要求不能随便乱读二是传感器本身不含MCU功能所有数据处理都要主机端自己搞定。这两个缺点其实也谈不上大问题写入死数据手册里的示例代码就能解决这也是我写这篇源程序记录的目的。1.2 硬件连接与电路设计SCD40的硬件连接非常标准化总共只需要5个引脚VDD、GND、SDA、SCL、SEL。我的板上用3.3V给传感器供电SDA和SCL分别接到STM32F103的PB7和PB6上这两个引脚复用为I2C1。SEL引脚是地址选择脚接地时I2C地址是0x627位地址如果接VDD则会变为0x63。我做的是单传感器方案所以SEL直接接地。这里有个容易被忽略的细节SCD40虽然是光声式传感器功耗比NDIR低很多但它在测量周期内的瞬时电流峰值依然有几十毫安如果供电端滤波不足测量值会跳动得非常厉害。我实测在VDD和GND之间加了100nF陶瓷电容再加10uF钽电容之后数据稳定性明显改善。I2C总线的上拉电阻我用的是4.7k如果总线上挂了多个设备比如OLED、Flash芯片之类建议根据实际总线长度和速率调整4.7k是常规起步值。另外SCD40的数据手册明确推荐使用短而直的总线布线传感器尽量远离大功率电感、射频天线和发热元件尤其是发热元件。光声式传感器的温湿度测量会被热源干扰进而影响CO2浓度补偿后的数值。如果你的PCB板上有DC-DC电源或者WIFI模块布局时一定要留出足够的隔离距离。2. SCD40驱动层实现I2C时序与CRC校验2.1 SCD40的I2C协议基础SCD40的I2C通信协议遵循标准I2C框架但由于传感器内部状态机比较特殊它有一些区别于普通I2C从设备的特性需要注意。首先是时钟拉伸SCD40在接收到部分命令后会拉低SCL时钟线以等待内部处理完成这属于正常现象。STM32的硬件I2C外设在遇到时钟拉伸时部分型号或库版本可能表现不稳定这也是很多人在网上吐槽STM32硬件I2C“有bug”的根源。实际使用下来如果选用HAL库的阻塞式传输并适当增加超时时间通常不会有大问题但实在不行就换软件模拟I2C。第二点是命令帧结构。SCD40的命令有两类一类是只有2字节命令码的无参数命令比如启动周期测量、软复位另一类是命令码后跟2字节参数再加1字节CRC校验的命令比如强迫自校准、设置温度偏移等。所有返回数据也都按“2字节数据1字节CRC”的格式组织每两个字节的数据后跟一个CRC-8字节。命令功能命令码启动周期测量0x21B1读取数据就绪状态0xE4B8读取测量结果0xEC05停止周期测量0x3F86软复位0x3646强迫自校准FRC0x362F设置温度偏移0x241D这个命令码表建议收藏写驱动的时候非常有用。以上命令码均以官方数据手册为准实际项目中可以直接抄用。2.2 CRC-8校验为什么必须自己写SCD40的I2C总线本身不像SPI那样有CRC硬件支持而环境电磁干扰、总线竞争、时序毛刺都可能导致传输数据出错。为了避免读到错误的CO2数值Sensirion在协议层面加入了CRC-8校验多项式为0x31初始值为0xFF。这个多项式和SHT30、SHT40等Sensirion自家传感器完全一致写一遍校验函数以后所有型号都能复用。CRC校验的具体规则是每个数据字段拆分为2字节紧接着的第三个字节就是这个2字节数据的CRC校验值。读取测量结果时传感器返回9个字节拆解为CO2原始值2字节CRC、温度原始值2字节CRC、湿度原始值2字节CRC。我早期刚开始接触时想偷懒不校验结果室内开着空气净化器的时候数值偶尔会跳到几千甚至上万加了校验之后才定位到是总线干扰导致的数据错误。所以这个校验一定不要省。uint8_t scd40_crc8(const uint8_t *data, size_t len) { uint8_t crc 0xFF; for (size_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; }注意这个函数的输入是2个字节返回1个字节调用方式是把每组数据的前两个字节传入与返回的第三个字节对比。如果CRC不匹配这一帧数据直接丢弃等待下一个测量周期再读取。实际操作中CRC校验失败的概率其实很低最常出现的原因还是供电问题和I2C速率过快后面我会展开说。2.3 核心驱动函数实现SCD40的上电初始化流程有一个固定顺序顺序搞错了传感器可能一直无响应。我实际验证过的流程是上电后先等待至少10ms然后发送软复位命令0x3646再等待100ms让传感器内部完成复位初始化接着发送停止周期测量命令0x3F86这一步是为了确保传感器处于已知状态最后发送启动周期测量命令0x21B1。完成以上步骤后传感器进入5秒周期的测量模式。读取数据的流程同样有讲究不能在启动测量后立刻读取必须先轮询数据就绪状态寄存器。SCD40的测量周期是5秒但内部实际完成一次准确测量需要的时间取决于环境条件所以主机端必须通过0xE4B8命令读取就绪标志位只有标志位置位后才能读取测量结果。如果强行在未就绪时读取得到的数据可能是上一次周期的旧值或者干脆是0xFFFF这样的异常值。下面是HAL库下的核心读取函数我注释写得比较详细方便直接移植到自己的工程里#define SCD40_ADDR (0x62 1) #define SCD40_CMD_START (0x21B1) #define SCD40_CMD_READY (0xE4B8) #define SCD40_CMD_READ_DATA (0xEC05) #define SCD40_CMD_RESET (0x3646) typedef struct { uint16_t co2_ppm; float temp_c; float humidity_rh; } scd40_data_t; HAL_StatusTypeDef scd40_read_data_ready(uint8_t *ready) { uint8_t cmd[2] { (SCD40_CMD_READY 8) 0xFF, SCD40_CMD_READY 0xFF }; uint8_t raw[2] {0}; if (HAL_I2C_Master_Transmit(hi2c1, SCD40_ADDR, cmd, 2, 100) ! HAL_OK) return HAL_ERROR; if (HAL_I2C_Master_Receive(hi2c1, SCD40_ADDR, raw, 2, 100) ! HAL_OK) return HAL_ERROR; *ready (raw[1] 0x01); return HAL_OK; } HAL_StatusTypeDef scd40_read_measurement(scd40_data_t *data) { uint8_t cmd[2] { (SCD40_CMD_READ_DATA 8) 0xFF, SCD40_CMD_READ_DATA 0xFF }; uint8_t buf[9] {0}; if (scd40_read_data_ready((uint8_t){0}) HAL_ERROR) // 先查就绪位 return HAL_ERROR; if (HAL_I2C_Master_Transmit(hi2c1, SCD40_ADDR, cmd, 2, 100) ! HAL_OK) return HAL_ERROR; if (HAL_I2C_Master_Receive(hi2c1, SCD40_ADDR, buf, 9, 100) ! HAL_OK) return HAL_ERROR; // 每组数据前2字节第3字节是CRC for (int i 0; i 3; i) { uint8_t crc scd40_crc8(buf[i * 3], 2); if (crc ! buf[i * 3 2]) { return HAL_ERROR; } } uint16_t raw_co2 (buf[0] 8) | buf[1]; uint16_t raw_temp (buf[3] 8) | buf[4]; uint16_t raw_hum (buf[6] 8) | buf[7]; style="width:16px;margin-left:4px;vertical-align:text-bottom;cursor:text;" />
返回列表