STM32驱动SCD4X读取CO2和温湿度:I2C通信与CRC校验详解
2026/9/23 14:07:06 网站建设 项目流程

简介:面向嵌入式开发者的STM32F103C8T6与Sensirion SCD4X传感器I2C通信完整工程包,适用于智能家居、空气监测等需要CO₂/温湿度采集的场景。资源包共143个文件,约3.86MB,以C源文件、头文件、HAL库文件及编译生成文件为主,另含项目配置文档和PDF数据手册,可直接在Keil工程中编译调试。已有698人学习使用。内容覆盖GPIO(PB6/PB7)I2C模式初始化、HAL_I2C主从通信、传感器命令时序及原始数据解析,并附串口输出调试方法与常见坑点说明;配合随包的Sensirion数据手册和示例工程,开发者可对照完成驱动移植与参数校准,有效缩短开发周期。 最近帮朋友调了一个小项目:用一块STM32F103C8T6最小系统板,把Sensirion盛思睿的SCD4X二氧化碳传感器数据读出来,实时显示温度和湿度。整个过程踩了不少坑,从I2C通信不稳定、CRC校验失败,到数据读出来明显不合理,最后总算稳定跑起来了。这里把完整过程整理一下,包含选型考虑、硬件接线、协议细节、HAL库代码实现和常见问题排查,给后面要做同类项目的人一个能直接抄作业的参考。

SCD4X这个传感器最吸引人的地方,是把CO2、湿度、温度三个测量集成了在一个超小体积的光学传感器里,精度也不错,功耗还低。配合STM32F103C8T6这种几块钱就能买到的最小系统板,非常适合做室内空气质量监测、新风联动、温室大棚环境采集这类项目。无论你是刚接触单片机的学生,还是已经在做产品选型的工程师,这篇内容都值得看一下。

1. 项目整体设计与选型思路

1.1 为什么用STM32F103C8T6做主控

STM32F103C8T6可以说是国内玩嵌入式最常用的芯片之一了。Cortex-M3内核,72MHz主频,64KB Flash、20KB RAM,虽然参数放在今天不算亮眼,但做传感器数据采集和简单控制完全够用。关键是它有两个硬件I2C外设,支持100kHz和400kHz两种速率,接SCD4X这种I2C接口的传感器非常方便。

更实际的原因是生态成熟。CubeMX配置外设、HAL库调用接口,网上的资料一抓一大把,遇到问题很容易找到解决方案。如果是做毕设或者快速验证方案,用这块板子能把开发周期压缩到最短。而且F103C8T6的国产替代型号很多,将来要批量做产品,成本也能压得很低。

1.2 SCD4X系列怎么选:SCD40、SCD41、SCD42对比

Sensirion的SCD4X系列分为三个型号,很多人一开始搞不清楚区别,直接买错了。这里先整理一个对比表:

型号CO2精度寿命典型用途价格区间
SCD40±(50ppm + 5%读数)40000小时室内空气质量监测较低
SCD41±(40ppm + 5%读数)40000小时高精度环境监测较高
SCD42±(50ppm + 5%读数)20000小时低成本消费类产品最低

我这次用的是SCD41,精度最高,适合做数据记录分析。如果你只是做个LED联动或者给新风系统一个粗略参考,SCD40就够用了。SCD42价格最低,但寿命短了一半,工业场景慎选。另外市场上有些模块打着SCD41的旗号卖,实际是SCD40,到手可以读一下序列号或者对比一下数据,能看出一些端倪。

还有一个关键点,SCD4X的工作电压范围是2.4V到5.5V,但I2C电平会跟随VDD。如果你给传感器供5V,SCL/SDA的高电平也是5V,STM32F103C8T6的GPIO不一定吃得消。稳妥的做法是统一用3.3V供电,和STM32逻辑电平一致,省掉电平转换电路。

2. 硬件准备与接线

2.1 材料清单

实际用到的东西非常精简,这也是这个项目的优点之一:

  • STM32F103C8T6最小系统板一块
  • SCD4X传感器模块一个(我买的是Sensirion官方评估板,接口已经引出来)
  • ST-Link V2下载器一个
  • USB转串口模块一个(用于查看调试信息)
  • 杜邦线若干
  • 面包板或洞洞板

如果你的SCD4X是纯芯片需要自己焊,注意它底部有暴露的感光孔,焊接时不要堵住,也不要让助焊剂残留在上面。官方评估板会好很多,引脚已经做好,直接连杜邦线就行。

2.2 接线图和注意事项

STM32F103C8T6的I2C1默认在PB6(SCL)和PB7(SDA)上,这个组合最常用。接线表如下:

SCD4X模块引脚连接到说明
VDD3.3V供电,不要接5V
GNDGND共地
SCLPB6I2C时钟线
SDAPB7I2C数据线

注意:I2C总线上必须有上拉电阻。SCD4X官方模块板载了上拉电阻,直接能工作。如果你是自己画板或者买的模块没带上拉,需要在SCL和SDA上各接一个4.7kΩ到3.3V的电阻,否则I2C通信会时好时坏,甚至完全扫描不到设备。

另外,实际接线时保持杜邦线尽量短,I2C在100kHz下长线也能跑,但线太长或者面包板接触不良时,容易出现随机CRC错误或者NACK。我第一次调试时就是因为杜邦线松动,导致偶尔能读到数据偶尔读不到,排查了很久才发现是物理接触问题。

3. 通信协议与核心原理拆解

3.1 SCD4X的I2C地址和命令体系

SCD4X的7位I2C地址固定是0x62,换算成8位写地址是0xC4,读地址是0xC5。这个地址是芯片出厂写死的,不能像EEPROM那样用引脚配置。在HAL库中,调用HAL_I2C_Master_Transmit时地址参数要填0xC4。

SCD4X的寄存器不是普通的内存地址,而是通过命令码来操作。常用命令码整理在下表:

功能命令码(16位)说明
启动周期测量0x21B1默认模式,约5秒更新一次数据
启动低功耗周期测量0x21AC约30秒更新一次数据
停止周期测量0x3F86进入待机模式
读取测量结果0xEC05读取9字节数据
读取数据就绪状态0xE4B8判断是否有新数据
单次测量0x219D测完自动待机,适合电池供电
读取序列号0x3690返回芯片序列号

这里有个容易搞混的地方:执行任何命令之前,芯片必须处于空闲状态。如果你一直在跑周期测量模式,想发送单次测量命令是没用的,必须先发停止命令0x3F86,等待大约500ms后再发新命令。

3.2 数据帧格式与CRC校验

SCD4X返回的数据帧格式很固定。执行读测量结果命令后,芯片返回9个字节,排列如下:

字节索引内容
0-1CO2浓度,16位无符号整数,单位ppm
2CO2数据CRC校验
3-4温度原始值,16位无符号整数
5温度数据CRC校验
6-7湿度原始值,16位无符号整数
8湿度数据CRC校验

每两个数据字节后面跟着一个CRC校验字节。这个CRC算法是CRC-8,多项式是0x31,初始值是0xFF,和SHT3x传感器是一套算法。别小看这个校验,I2C线路上稍有干扰,CO2数据就可能从600ppm跳到99999ppm,如果不做CRC校验,这种错误数据会直接污染你的统计结果。

CRC代码实现如下:

uint8_t SCD4X_CRC8(uint8_t *data, uint8_t len) { uint8_t crc = 0xFF; for (uint8_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 = (crc << 1); } } return crc; }

3.3 原始数据如何换算成物理量

SCD4X返回的温度和湿度都是16位原始值,需要经过线性变换才能得到实际值。公式如下:

  • CO2浓度:不需要转换,原始值就是ppm。比如返回0x0258,就是600ppm。
  • 温度(℃)= -45 + 175 × 原始值 / 65535
  • 相对湿度(%RH)= 100 × 原始值 / 65535

举个例子,如果温度原始值是0x6666(十进制的26214),代入公式:-45 + 175 × 26214 / 65535 ≈ 25.0℃,正好是室温。湿度原始值如果接近14745,换算出来大约22.5%RH。

这几个公式是Sensor数据手册里明确给的,不要自己发明算法。网上有些改版代码会先移位再换算,结果差得离谱,都是没看懂数据手册导致的。

4. CubeMX配置与HAL库代码实现

4.1 CubeMX关键配置

用STM32CubeMX生成工程时,只需要配置两个外设:

第一个是I2C1。在Pinout视图里把PB6设为I2C1_SCL,PB7设为I2C1_SDA,然后在Parameter Settings里把Speed Mode设为Standard Mode(100kHz),其他保持默认。这里千万不要图快选Fast Mode,SCD4X规格书上明确写了最大支持100kHz,我之前试过400kHz,十次里有两三次会NACK,靠不住。

第二个是USART1,PB6/PB7没有冲突,USART1引脚是PA9(TX)、PA10(RX)。配置成异步模式,波特率115200,用来输出调试日志。如果你有OLED显示屏,可以再开一个软件I2C或者SPI接口,不过那是后话,先把串口打通。

时钟这块,直接用HSE 8MHz晶振,系统时钟配到72MHz,APB1总线时钟36MHz。I2C1挂在APB1上,36MHz的频率分频到100kHz,CubeMX会自动帮你算好,不用手工干预。

4.2 驱动代码实现

工程生成后,我习惯新建一个scd4x.cscd4x.h,把传感器相关的操作封装起来,主程序看起来干净,也方便以后复用。

先看发送命令和读取数据的底层函数:

#include "scd4x.h" #include "crc.h" extern I2C_HandleTypeDef hi2c1; #define SCD4X_ADDR_WRITE 0xC4 #define SCD4X_ADDR_READ 0xC5 uint8_t SCD4X_SendCommand(uint16_t cmd) { uint8_t buf[2]; buf[0] = cmd >> 8; buf[1] = cmd & 0xFF; return HAL_I2C_Master_Transmit(&hi2c1, SCD4X_ADDR_WRITE, buf, 2, 1000); } uint8_t SCD4X_ReadData(uint8_t *data, uint8_t len) { return HAL_I2C_Master_Receive(&hi2c1, SCD4X_ADDR_READ, data, len, 1000); }

然后封装一个读取测量结果的函数,读完直接做CRC校验和物理量换算:

uint8_t SCD4X_ReadMeasurement(float *co2, float *temperature, float *humidity) { uint8_t buf[9]; uint16_t co2_raw, temp_raw, hum_raw; if (SCD4X_SendCommand(0xEC05) != HAL_OK) return 1; HAL_Delay(2); if (SCD4X_ReadData(buf, 9) != HAL_OK) return 2; if (buf[2] != SCD4X_CRC8(&buf[0], 2) || buf[5] != SCD4X_CRC8(&buf[3], 2) || buf[8] != SCD4X_CRC8(&buf[6], 2)) return 3; co2_raw = (buf[0] << 8) | buf[1]; temp_raw = (buf[3] << 8) | buf[4]; hum_raw = (buf[6] << 8) | buf[7]; *co2 = co2_raw; *temperature = -45.0f + 175.0f * temp_raw / 65535.0f; *humidity = 100.0f * hum_raw / 65535.0f; return 0; }

注意一个细节:SCD4X执行完读测量结果命令之后,内部需要一点时间准备数据,我实测加2ms延时比较稳。你在网上看到的很多代码不延时就直读,偶尔也行,但在芯片刚启动或者刚从低功耗唤醒时,容易读到0xFF。加上这2ms,问题就消失了。

4.3 主流程和串口输出

主程序逻辑很清晰:上电延时,启动周期测量,然后在循环里等数据就绪标志,就绪后读取数据并打印。

int main(void) { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_I2C1_Init(); MX_USART1_UART_Init(); float co2, temp, hum; uint8_t data_ready[3]; printf("SCD4X Test Start\r\n"); if (SCD4X_SendCommand(0x21B1) != HAL_OK) { printf("Start periodic measurement failed\r\n"); while (1); } while (1) { HAL_Delay(1000); if (SCD4X_SendCommand(0xE4B8) != HAL_OK) continue; HAL_Delay(2); if (SCD4X_ReadData(data_ready, 3) != HAL_OK) continue; if (data_ready[0] & 0x01) { if (SCD4X_ReadMeasurement(&co2, &temp, &hum) == 0) { printf("CO2: %.0f ppm, Temp: %.2f C, Hum: %.2f %%RH\r\n", co2, temp, hum); } } } }

串口重定向printf的代码我用的是标准方式,在usart.c里补一个fputc,把输出落到USART1就行,这块比较基础就不展开说了。

实际运行效果符合预期:上电约5秒后串口打出第一组数据,之后每5秒更新一次。CO2数值稳定在450~550ppm左右(室内开窗通风时),温度和湿度跟家用温湿度计对比,误差在可接受范围内。

5. 数据校准与补偿实战

5.1 自动自校准和强制校准的选择

SCD4X默认开启了自动自校准(ASC)功能。它的原理是传感器长期运行中,会持续跟踪一段时间内的最低CO2读数,默认认为这个最低值接近400ppm(即室外新鲜空气背景值),然后自动修正零点。

这个功能适合长时间运行的室内空气质量监测仪,因为室内CO2浓度经常波动到室外水平,传感器可以自己慢慢修正。但在温室大棚、动物养殖场这类CO2浓度长期高于1000ppm的场景,最低值也远高于400ppm,ASC反而会把零点拉偏,这时候建议关闭自动校准,改用强制校准。

强制校准的流程是先让传感器在400ppm的标定环境下运行几分钟,然后发送强制校准命令,芯片内部会计算出零点偏移并保存。对普通玩家来说,最简单的方法是把设备拿到室外通风处,让传感器吹10分钟新鲜空气,然后执行校准命令,效果就很不错。我没法在这里写出绝对可靠的操作步骤,因为官方手册也强调需要稳定的400ppm标准气体才能做到绝对准确,但家用场景用室外空气替代基本够用。

5.2 温度偏移修正和海拔设置

SCD4X芯片在工作时自身会发热,虽然发热量很小,但放在密封外壳里,实测读数会比环境温度高1到3摄氏度。这个问题在数据手册里有明确说明,也提供了温度偏移修正命令,偏移值单位是摄氏度,乘以2的16次方再写进去。

如果你只是读取温度和湿度做参考,这个偏移可以忽略。但如果你用SCD4X算露点,或者做环境温度记录,那1℃的误差就很明显了。我自己的做法是:把传感器放在通风良好的位置,和下图的参考温度计对比,找一个稳定的差值作为偏移量写入。

另外,SCD4X内部使用气压来修正CO2读数,默认值是海拔高度对应的标准大气压。如果你在高海拔地区使用,比如海拔2000米以上,最好把当地海拔设置进去。这个命令相对冷门,很多教程都不提,但对测量结果的准确性影响还是挺明显的。

6. 踩坑实录与排查速查表

6.1 最常见的几个问题

我这次调试过程中遇到的坑,以及网上被问得最多的问题,集中汇总如下:

现象根本原因解决方法
I2C扫描不到0x62设备SDA/SCL接反或上拉电阻缺失核对接线,补4.7kΩ上拉
上电后一直读不到数据没有先启动周期测量,或启动后等待时间不足启动测量后至少等5秒再读
数据全是0xFF读测量结果命令后没有延时发完命令后加2ms延时
CRC校验频繁失败杜邦线接触不良或I2C速率过高换短线牢固连接,确认100kHz
CO2读数跳动巨大传感器被误拾(上拉电阻被拔掉)或者受风直吹检查焊接,避免放置在风口
温度比室温高2℃以上传感器自身发热且外壳密封用温度偏移命令修正

6.2 STM32F103硬件I2C的死锁问题

关于STM32F1系列硬件I2C,业界流传的说法是“有bug”,容易死锁。我实际体验下来,死锁确实遇到过,但大多发生在通信出错后没有恢复机制的情况下。SCD4X是慢速设备,只要严格按照命令时序来,HAL库的I2C基本稳定。

如果真遇到死锁,最简单的恢复办法是把I2C外设DeInit再Init一次,或者直接对GPIO做一次拉高脉冲,强制释放总线。这算是最简单可靠的兜底方案,代码如下:

void I2C_Recovery(void) { GPIO_InitTypeDef GPIO_InitStruct = {0}; __HAL_I2C_DISABLE(&hi2c1); GPIO_InitStruct.Pin = GPIO_PIN_6 | GPIO_PIN_7; GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_OD; GPIO_InitStruct.Pull = GPIO_PULLUP; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; HAL_GPIO_Init(GPIOB, &GPIO_InitStruct); for (int i = 0; i < 9; i++) { HAL_GPIO_WritePin(GPIOB, GPIO_PIN_6, GPIO_PIN_RESET); HAL_Delay(1); HAL_GPIO_WritePin(GPIOB, GPIO_PIN_6, GPIO_PIN_SET); HAL_Delay(1); } MX_I2C1_Init(); }

这个方法在从机意外拉低SDA导致总线锁死时特别管用。不过最好还是在通信错误后加错误重试逻辑,不要每次都靠硬件复位来解决。

6.3 快速排查清单

我在调完这个项目后给自己总结了一套固定的排查顺序,每次换新板、换新传感器,全部走一遍,基本几分钟就能定位问题:

  1. 用逻辑分析仪或示波器抓SCL和SDA波形,确认有信号。
  2. 用I2C扫描代码确认0x62设备在线。
  3. 用串口打印启动周期测量的返回值,确认命令成功。
  4. 等够5秒后打印数据就绪状态,确认芯片在工作。
  5. 再读测量结果,看CRC有没有报错。
  6. 如果数据还是异常,检查供电电压和上拉电阻。

这套流程看起来简单,但真的能省下大量瞎猜的时间。

个人实际使用体会和扩展建议

SCD4X和之前常用的SCD30相比,体积小了很多,功耗也更低,待机时电流几乎可以忽略,非常适合做电池供电的便携式空气质量记录仪。但要注意的是,SCD4X的通信协议比SCD30稍繁琐一些,启动命令、等待就绪、读数据三步缺一不可,刚开始接触时需要耐心读一下数据手册。

我在实际使用中最满意的场景是用SCD41做了一个桌面空气监测小玩意,CO2超过1000ppm时蜂鸣器提醒开窗通风,冬天实测下来确实能提醒自己在门窗紧闭的房间里及时换气。SCD4X的体积和功耗让我可以把它塞进各种小外壳里,再加上OLED显示屏,整机电流也不过几十毫安,用一块1000mAh锂电池就能跑很长时间。

如果你手头已经有STM32F103C8T6板子,不妨直接按本文步骤试一下。顺利的话从零到读到第一组数据,两个小时以内就能搞定。后面还可以玩出更多花样,比如用FreeRTOS多任务定期采集数据、把数据通过ESP8266模块上传到云平台做历史曲线,甚至把这个驱动移植到国产替代的GD32F103上,代码基本上不用改。SCD4X这个传感器给我的整体印象是:性能稳定、资料规范、开发难度适中,是一个非常适合入门到进阶的传感器项目。

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询