资讯动态

STM32F103C8T6驱动SCD4X二氧化碳传感器:从接线到校准的完整实战

发布时间:2026/9/2 9:54:38 来源:尧图企业网站定制
简介面向嵌入式初学者的STM32F103C8T6驱动Sensirion SCD4X传感器工程示例重点解决I²C总线采集二氧化碳、湿度与温度时的协议配置、命令读取与数据换算问题覆盖从GPIO初始化、I²C主设备收发到传感器数据解析的完整链路也适合作为智能家居或环境监测项目的基础模板。压缩包共143个文件大小约3.86MB内含35个h头文件和34个c源码文件涵盖I2C驱动、系统时钟、延时以及RCC、USART等标准外设库Keil工程配置uvprojx/uvoptx可直接打开编译hex/axf等输出便于烧录验证另有PDF数据手册和调试文本辅助查阅。已有698人浏览学习。工程在SCD4X驱动之外还附带LED控制、USART串口打印等示例读者可按PB6/PB7接线搭建环境监测节点也可将I2C主机收发代码移植到其他Sensirion传感器上同时支持J-Link下载与9600波特率串口输出实时测量值方便联调验证。 一块几十块钱的STM32F103C8T6最小系统板一颗采用光声传感原理的Sensirion SCD4X二氧化碳传感器模块成本不到一百多块钱就能搭出一套能读CO2浓度、温湿度的空气质量监测节点。我去年做室内新风控制项目时就是这么干的SCD4X只需要I2C两根线5秒钟出一个带CRC校验的测量帧表面看是“开箱即用”的典型代表。但真把程序调起来才发现这颗传感器对电源纹波、时序稳定性和校准逻辑都有隐藏要求我前后折腾了快一整天从读不到数据、CRC校验狂报错到数值纹丝不动每一步都有对应的坑。这篇文章就以STM32F103C8T6调用SCD4X为主线把硬件接线、软I2C驱动、测量值转换、校准流程和完整排错链路一次写透给后面做室内空气监测、新风联动、孵化箱环境监控的兄弟省点时间。1. 为什么是SCD4X搭配F103C8T6这套组合的含金量在哪1.1 SCD4X在CO2传感器里算什么水平市面上常见的CO2传感器大致分三类老牌NDIR传感器如MH-Z19、S8、金属氧化物半导体传感器如SGP30、以及SCD4X这种微型光声传感器。MH-Z19和S8虽然功耗大、体积大、价格便宜但需要自己处理温度补偿和高度补偿SGP30测的是eCO2本质是算法估算值长期漂移明显SCD4X则在极小封装里集成了CO2、温度、湿度三个测量项并且内置了湿度补偿和温度补偿逻辑出厂校准精度标称40ppm ± 5%在消费级产品里属于相当能打的存在。SCD4X最让我满意的是它把复杂度封装得很好。对外只暴露一组I2C寄存器通过几个命令就可以完成周期测量、单次测量、数据就绪查询、强制校准、海拔设置等操作不需要像老式NDIR那样去调模拟电路或者处理复杂的PWM输出。它的测量间隔是固定的5秒左右数据帧格式也简单CO2两个字节、温度两个字节、湿度两个字节每两个数据后面跟一个CRC8校验字节共9字节解析起来非常直观。1.2 为什么拿F103C8T6最小系统板而不是更贵的板子STM32F103C8T6这颗芯片虽然已经“老”了但它的定位非常准确64KB Flash、20KB RAM、主频72MHz对于传感器采集这种轻量级任务绰绰有余。如果只是跑一个裸机循环读SCD4X、刷个OLED、走串口打印Flash占用一般在30%以内。现在市面上还有不少引脚兼容的国产替代型号代码不用改成本还能再压一截做小批量产品非常合适。我见过很多新手一上来就上F407甚至H743实际上对于SCD4X这种I2C从设备F103的处理能力已经严重过剩。SCD4X的通信速率支持100kHz和400kHzF103的硬件I2C或者GPIO模拟都毫无压力。另外它的I2C地址是0x62如果同一总线上还要挂OLED一般是0x3C地址完全不会冲突这也是我选择这套组合的原因之一。从功能验证到产品化的路径非常平滑后期想加FreeRTOS、LVGL也都能塞进这颗芯片只是要注意Flash和RAM的规划。2. 接线、供电与最小系统板上最容易被忽略的三件事2.1 引脚分配与上拉电阻SCD4X模块一共只引出四根线VDD、GND、SCL、SDA没有额外的复位、报警或者PWM引脚需要操心。我用的引脚分配很简单SCL接PB6、SDA接PB7对应STM32F103的I2C1。如果打算用软件模拟I2C这两个引脚也可以随便换但建议保持这个习惯因为后期如果切到硬件I2CPB6/PB7可以直接复用。上拉电阻是第一个容易翻车的地方。SCD4X模块一般板载了上拉电阻但如果买的是散装传感器、自己画板子就必须在SCL和SDA上各接一个4.7kΩ到10kΩ的上拉电阻到3.3V。STM32内部虽然有上拉但内部上拉的阻值偏大在400kHz模式下边沿不够陡容易导致通信不稳定。我有个兄弟图省事没加上拉结果I2C偶尔能通、偶尔卡死排查了半天才发现是这个问题。如果模块已经有上拉那就不要再重复加两个上拉并联会拉低总线电平反而更糟。2.2 供电、去耦电容与电平匹配SCD4X官方手册里供电范围比较宽但稳妥起见我建议直接给它供3.3V这样和STM32的IO电平完全匹配不需要额外做电平转换。我自己的板子上在传感器的VDD和GND之间放了一个10μF的钽电容和一个0.1μF的陶瓷电容紧挨着电源引脚放置这个细节对SCD4X尤其重要。它的内部光声测量腔对电源噪声比较敏感供电纹波大了之后CO2读数会出现明显的跳动甚至会直接拉高CRC错误率。另外一个经常被忽略的问题是不要用驱动继电器、电机或者加热丝的那路电源给传感器供电。我用一个12V风扇做风道测试时风扇一启动SCD4X的CO2数值就开始上下乱飘最后把传感器供电单独用了一路LDO问题才消失。风道方面SCD4X虽然体积小但它的气体交换口不能被完全堵死外壳上至少要留出对流孔否则传感器内部CO2浓度会被局部环境“困住”读数会虚高。3. 驱动实现软I2C为主、硬I2C为辅的取舍与核心代码3.1 为什么我最终选了软件I2C而不是硬件I2C网上关于STM32F103硬件I2C的争议很多有人说它的硬件I2C有bug其实严格来说不是芯片完全不能用而是ST的HAL库在F1上处理I2C事件中断的方式比较绕配合DMA、超时、总线忙标志的组合容易出问题。我在测试过程中用HAL_I2C_Master_Transmit读SCD4X确实遇到过调用返回HAL_BUSY、需要复位I2C外设才能恢复的情况。对于SCD4X这种读一帧数据要经过“发送命令、停止、等待、重发起始、读9字节”的流程用软件模拟I2C反而更清晰。每位时序自己控制逻辑分析仪一抓就能看出问题在哪排错成本低很多。SCD4X默认工作在100kHz或者400kHz软件I2C只要延时合理跑400kHz完全没问题对传感器数据量来说性能绰绰有余。我也建议你先把软I2C跑通确认传感器正常后再去折腾硬件I2C否则硬件I2C的坑和传感器的坑叠在一起排查起来非常痛苦。3.2 HAL库手工建工程与IO初始化我习惯用STM32CubeMX建工程。F103C8T6配置好几处关键项SYS里的Debug选择Serial Wire不然后面用ST-Link下载第二次就报找不到设备RCC的HSE选择外部晶振时钟树配到72MHzGPIO里把PB6、PB7配置为开漏输出模式上拉使能。开漏模式配合外部上拉这样读引脚和写引脚都不需要来回切换模式SDA释放后由外部上拉拉高符合I2C总线的线与逻辑。IO初始化代码实际上是标准HAL的写法void Soft_I2C_Init(void) { GPIO_InitTypeDef GPIO_InitStruct {0}; __HAL_RCC_GPIOB_CLK_ENABLE(); 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_HIGH; HAL_GPIO_Init(GPIOB, GPIO_InitStruct); HAL_GPIO_WritePin(GPIOB, GPIO_PIN_6 | GPIO_PIN_7, GPIO_PIN_SET); }开漏输出模式下将引脚拉高就是释放总线拉低就是主动驱动低电平这正是I2C协议需要的。时钟频率不用配到太快我实际测试中延时函数用的一个空循环大概在几百纳秒级别SCD4X就能稳定通信。3.3 命令帧、时序与CRC8校验SCD4X的命令帧非常简洁每条命令都是2字节部分命令后面需要跟参数和CRC。我常用到的命令整理如下功能命令字说明启动周期测量0x21B1每约5秒产生一组新数据读取测量值0xEC05读出9字节CO2、温度、湿度各带CRC查询数据就绪0xE4B8返回2字节状态低11位非零表示就绪停止周期测量0x3F86退出周期测量进入待机单次测量0x219D兼顾低功耗的单次触发模式低功耗周期测量0x21AC约30秒间隔的周期测量强制校准FRC0x362F400ppm基准气体下修正读数设置自动自校准0x2416开关ASC校准功能设置海拔0x2427高海拔环境补偿恢复出厂0x3632清除全部校准状态CRC8校验是驱动里最核心的一段。SCD4X使用的多项式是0x31初始值为0xFF对每段2字节数据进行校验生成1字节CRC。实现代码只有十几行uint8_t scd4x_crc8(const 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; }这个函数接收2字节原始数据返回1字节CRC和SCD4X返回的校验字节做比较。我写驱动的时候先单独写了个小函数验证CRC算法用官方文档里的示例数据跑了一遍然后再接入工程避免驱动上了线之后分不清是通信问题还是校验问题。4. 数据读取与标定拿到CO2读数只是第一步4.1 数据就绪位判断与测量值转换SCD4X周期测量模式下不能上电读完就马上取数必须等它内部完成一次光声测量。正确的顺序是先发0x21B1启动周期测量之后每次读数据前先查0xE4B8的数据就绪状态如果返回状态字的低11位非零再发0xEC05读测量值。直接跳过就绪判断去读大概率会拿到0xFFFF或者上一轮的旧数据。读取9字节数据的核心代码长这样uint8_t scd4x_read_measurement(uint16_t *co2, float *temp, float *rh) { uint8_t buf[9]; // 发送 0xEC05 读取测量值 Soft_I2C_Start(); if (Soft_I2C_WriteByte(0xC4)) { // 0x62 1写 Soft_I2C_Stop(); return 1; } Soft_I2C_WriteByte(0xEC); Soft_I2C_WriteByte(0x05); Soft_I2C_Stop(); delay_ms(2); // 读 9 字节 Soft_I2C_Start(); if (Soft_I2C_WriteByte(0xC5)) { // 0x62 1 | 1读 Soft_I2C_Stop(); return 1; } for (int i 0; i 9; i) { // 最后一字节回NACK其余回ACK buf[i] Soft_I2C_ReadByte(i 8 ? 0 : 1); } Soft_I2C_Stop(); // CRC校验CO2[0:1] CRC在[2]温度[3:4] CRC在[5]湿度[6:7] CRC在[8] if (scd4x_crc8(buf, 2) ! buf[2]) return 2; if (scd4x_crc8(buf 3, 2) ! buf[5]) return 3; if (scd4x_crc8(buf 6, 2) ! buf[8]) return 4; *co2 (buf[0] 8) | buf[1]; uint16_t t_raw (buf[3] 8) | buf[4]; *temp -45.0f 175.0f * t_raw / 65535.0f; uint16_t rh_raw (buf[6] 8) | buf[7]; *rh 100.0f * rh_raw / 65535.0f; return 0; }温度转换公式是 -45 175 * raw / 65535湿度转换公式是 100 * raw / 65535这两个公式必须记好很多人会把SCD30的转换公式套过来SCD30的数据顺序是湿度、温度、CO2而SCD4X是CO2、温度、湿度直接套用就会拿到完全对不上的数。4.2 自动自校准ASC与强制校准FRC的使用时机SCD4X的校准策略直接决定长期读数是否可信。传感器出厂时有一个基准校准但使用一段时间后因为老化、污染、环境变化读数会产生偏移。ASC自动自校准功能适合那些设备能定期接触到新鲜空气的场景比如室内空气质量监测仪放在通风良好的房间里它会把测到的最低CO2浓度认定为接近外界基线自动修正传感器漂移。但如果你的设备长期放在密闭空间比如孵化箱、大棚、地下仓库千万别开ASC它会不断把偏高的基线当成参考越校越偏。FRC强制校准则适合已知当前环境CO2浓度的情况具体做法是让设备在目标浓度下稳定运行至少几分钟然后通过0x362F命令写入预期的CO2浓度值比如在室外新鲜空气下写入400ppm。执行FRC后传感器内部会完成一次重新校准返回的2字节结果如果是0xFFFF则说明参数无效或执行失败。我一般在新项目首次部署时做一次FRC之后正常使用就交给ASC。还要注意海拔对CO2读数的影响SCD4X内部有海拔补偿参数住在高原地区的话用0x2427设置一下当地海拔否则读数会比实际浓度偏大这个细节很多人不知道。5. 实测中踩过的坑从全是FF到数值漂移的完整排查5.1 现象一读出来全是0xFF偶尔还卡死在I2C等待我第一次上电时串口打印的CO2一直是65535也就是两个字节全是0xFF。我的排查链路是这样的先用万用表量传感器VDD对GND电压确认3.3V正常然后检查SCL和SDA有没有接反这是最容易发生的问题模块上的丝印可能标注不清插反了之后I2C完全无法工作再就是把SCL和SDA都拉到3.3V确认上拉电阻存在。这些都没问题时搬出逻辑分析仪抓波形发现SDA上的地址字节发送后第九个时钟周期压根没有ACK低电平说明传感器没有应答——问题在传感器自身或者供电质量。后来发现原因非常蠢我的USB转TTL模块和传感器共用一个劣质3.3V LDO传感器在上电瞬间拉低了电压导致它没有完成正常的内部初始化。换了独立LDO给传感器供电并在VDD附近补上10μF电容后ACK就正常了。如果你也遇到全是0xFF建议按这条链路走一遍电压、接线、上拉、ACLK波形最后再怀疑芯片。I2C函数里务必加超时保护不然总线被拉死时程序会一直卡在while循环里整个系统看起来就像死机了。5.2 现象二CRC校验错误频发甚至一帧里三个CRC全错当传感器有ACK、数据也能读出来但CRC校验大量失败时基本上可以断定是通信时序或者信号完整性问题。我第一次把I2C延时调得特别激进想在400kHz下跑满速度结果SCL高电平时间太短传感器端的输入建立时间不够CRC就开始随机错误。把延时函数里的空循环次数从5调到10之后问题大幅缓解。另一个常见诱因是杜邦线太长。SCD4X使用环境如果隔着20厘米以上的飞线信号边沿会被线缆电容拖慢干扰一上来CRC就会挂。我在测试台上用10厘米的短杜邦线完全没问题换到30厘米后就开始偶发CRC错误。还有一个不起眼的原因调试器ST-Link同时接着SWD而SWD的时钟线如果离SCL太近高频干扰会耦合到I2C总线上。实在排查不出来的时候把所有调试线拔掉只保留供电和I2C再测试一轮。5.3 现象三CO2数值一直在400左右不动像没通电一样这个现象容易让人误以为传感器坏了其实很可能是读数还没更新或者读错了寄存器位置。我遇到过一种情况代码里没有查询数据就绪位每100毫秒就去读一次0xEC05结果大部分时间读到的都是同一条旧数据看起来就是数值“卡死”了。SCD4X在周期测量模式下5秒才更新一次读得再频繁也不会出新数据。还有一种情况是把SCD30的寄存器顺序套到SCD4X上SCD30读出来的第一项是湿度而SCD4X的第一项是CO2。如果把湿度当成CO2打印自然看不出数值变化。确认这一点最直接的方法是朝传感器哈一口气CO2读数应该在几秒内从400多升到1000以上。如果怎么哈气都不动再去抓I2C波形看看0xEC05之后传感器是不是真的返回了新数据。5.4 现象四CO2数值跳变剧烈从400直接蹦到900数值跳动大首先要排除是不是真的环境变化。SCD4X的分辨率是1ppm本身不会产生这么大的随机跳动所以问题大概率出在供电或者风道上。我一个测试中把传感器放在风扇直吹的位置气流变化导致传感器内部温度场波动读数就会周期性起伏。解决方法是给传感器加一个防风罩或者把它放在进风口稳定的位置。如果供电正常、风道也没问题可以做一个软件滑动平均把最近5个有效值做平均再输出能明显平滑曲线。要注意的是滑动窗口不要超过10个点否则5秒一个数据点、窗口50秒实时性就太差了做新风联动时会觉得系统反应迟钝。6. 显示、RTOS与低功耗化从功能验证到产品化的扩展6.1 给传感器数据加一块OLED显示和串口调试读SCD4X的数据最终是要给人看的。我在工程里用0.96寸的I2C OLED做本地显示SCD4X和OLED挂在同一条I2C总线上地址分别是0x62和0x3C互不干扰。需要注意的是用软件I2C时读SCD4X和写OLED的操作要串行执行不要在传感器读数据的半中间去切换总线操作。显示刷新率不用太高SCD4X本身5秒一个新数据OLED每2到3秒刷一次就够了刷新太频繁反而会占I2C带宽影响传感器通信的稳定性。串口方面我一般用UART1的PA9/PA10做调试输出波特率115200把原始的CO2、温度、湿度带单位打印出来。调试时建议把裸数据也打出来比如原始16位CO2值和CRC校验结果这样一帧数据出问题时能立刻判断是通信层的问题还是转换层的问题。加了OLED之后Flash占用会多出十几KB对F103C8T6的64KB Flash来说还在安全范围内但如果你同时上了FreeRTOS和LVGL就要开始仔细裁剪字体和图片资源了。6.2 挂FreeRTOS后传感器任务怎么设计我后来把工程迁移到了FreeRTOS上SCD4X读取被单独拆成一个任务。任务里不能简单粗暴地“延时5秒读一次”因为传感器从启动到数据就绪的间隔不是绝对精确的5秒正确写法是循环里每100毫秒查询一次0xE4B8就绪位一旦就绪立即读取。这样既不会漏数据也不会在传感器还没准备好时发出无效读命令。任务栈大小给到256个word就足够了但要注意一个关键点I2C总线上同时挂着SCD4X和OLED两个任务如果都去访问总线必须加一个互斥量。我给I2C总线建了一个二值信号量任何任务在发起I2C操作前先获取信号量操作完释放。没有这个保护OLED刷新和SCD4X读取交叉进行时偶发的数据错乱和CRC错误就会卷土重来。还有个地方容易被坑FreeRTOS的vTaskDelay延时和裸机delay_ms混用所有短延时尽量都走vTaskDelay长延时用系统节拍避免阻塞空闲任务导致看门狗或者低功耗逻辑出问题。6.3 电池供电场景下的低功耗方案如果你的目标产品要用电池供电SCD4X的周期测量模式就不太合适了5秒一个点虽然电流不大但24小时连续跑还是会造成电量浪费。更适合的做法是使用单次测量模式发0x219D命令触发一次测量然后传感器内部完成测量后进入待机再查询就绪位读取数据。搭配STM32的STOP模式主控每5分钟醒来一次触发生成一次测量读完数据继续睡整机平均电流可以压得非常低。低功耗周期模式0x21AC也是一个折中选项约30秒出一个数据点适合那些需要保持实时性但又不能太耗电的场景。这里有个工程细节从STOP模式唤醒后HAL库的时钟重新初始化需要一点时间不能立刻就去发I2C命令先要稳定时钟、重新配置系统时钟源否则传感器可能收不到正确时序。SCD4X本身还有存储校准参数的持久化命令0x3615在设置完海拔、温度补偿、校准参数后执行一次芯片掉电重启也不会丢。这个项目做完之后我最大的体会是SCD4X的驱动代码其实也就几百行真正的难点在工程细节上——干净的电源、稳定的上拉、合理的风道这些决定传感器能不能长期可靠地工作。如果你也打算用STM32F103C8T6搭配SCD4X做室内空气质量监测、新风联动或者环境数据采集完全可以按我这条链路先点亮传感器再加显示、加系统、做低功耗一步步往产品形态靠。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价