刚拿到JW01二氧化碳传感器那块小模块时,我心想这不就是个红外面板加一颗小单片机的事吗,结果一查资料,发现它居然直接走串口输出数据,和Arduino搭起来简直是无脑接。这个桌面级环境监测系统我用了不到半小时就搭完了,真正动手接线加烧录代码,确实5分钟内能搞定。套装非常简单:JW01二氧化碳传感器负责测CO2浓度,Arduino UNO负责读数据,OLED屏负责把数值摆在你眼前。新手想学串口协议解析,或者单纯想给家里卧室加个CO2仪表,这篇文章可以直接照着做,代码我完整贴在下面了。
很多人在室内待久了觉得头晕、犯困,总以为是自己没睡够,其实大概率是二氧化碳浓度涨上来了。室外一般400~450ppm,门窗紧闭的卧室开一会儿空调,分分钟飙到1500ppm以上,这时候人是真的会变迟钝。所以把CO2浓度可视化成一个小仪表,放在书桌、婴儿房、会议室或者自习室,都是很实在的用途。下面从头拆解这个项目。
1. 项目思路拆解:为什么是JW01搭配Arduino
1.1 室内CO2监测到底解决了什么问题
先把这个需求说清楚。人体呼出的气体里CO2浓度非常高,长时间待在不通风的空间,室内的二氧化碳浓度会快速上升。按照常见室内空气标准,长期居住和办公建议控制在1000ppm以内,超过这个值以后,人会觉得闷、注意力下降、甚至头晕。夏天开空调关窗、冬天开暖气关窗的时候最容易出现这种状况。
这套系统解决的就是“看不见摸不着”的问题。你可以把它理解成一个CO2的“温度计”,只不过量的是空气质量。放在书桌上,抬头就能看到当前浓度;超过阈值,LED灯和屏幕提示就会提醒你“该开窗了”。对过敏人群、家里有小孩老人的用户来说,这个设备比空气净化器上的数值更直接,因为净化器只显示PM2.5,不显示CO2。
这个项目也适合刚接触Arduino的玩家。它不涉及复杂的PWM、中断或者无线通信,核心就一件事:从串口读取传感器数据,解析成能看懂的数字。学会了这套串口协议解析流程,以后换任何UART接口的传感器(PM2.5、甲醛、温湿度等)思路都一样。这也是我把这个项目写出来的另一个原因——它是入门串口通信的最短路径之一。
1.2 传感器选型对比:JW01凭什么几十块钱还能用
现在市面上的CO2传感器方案不少,我一开始也纠结过要不要上更高端的模块。把常见的几款放在一起对比一下,你就能理解为什么最终选了JW01。
| 型号 | 原理 | 接口 | 量程 | 参考价格 | 特点 |
|---|---|---|---|---|---|
| JW01系列 | NDIR红外 | UART | 0-2000 / 0-5000ppm | 较低 | 成本低、数字输出稳定、适合桌面仪表 |
| MH-Z14A | NDIR红外 | UART / PWM | 0-5000ppm | 中等 | 功能更全,支持自校准 |
| SGP30 | MOS金属氧化物 | I2C | 400-60000ppm | 中等 | eCO2是估算值,首次开机需校准 |
| SCD30 | NDIR红外 | I2C / UART | 0-40000ppm | 较高 | 高精度,还集成温湿度 |
JW01最大的优势是“NDIR红外原理却卖到了很便宜的价格”。NDIR就是非分散红外检测,简单说就是利用CO2分子对特定波长红外光的吸收特性来算浓度,这才是真正的CO2传感器,不像SGP30那样靠金属氧化物电阻变化估算,SGP30对酒精、有机物气体会有干扰。
如果你的项目预算充足、精度要求高,那SCD30毫无疑问更强。但做桌面环境监测,JW01完全够用——它的精度标称一般在±50ppm ±5%读数附近,家用场景下这个误差完全可以接受。而且JW01走UART串口,直接输出数字量,不需要ADC转换,也不用自己校准电压和浓度曲线。相比之下,老式模拟量输出的传感器还要自己去标定,麻烦不少。
1.3 为什么是Arduino而不是树莓派或STM32
Arduino UNO在这个项目里属于“杀鸡用牛刀”,但也正因为如此,才更适合大多数人来复现。树莓派虽然性能强,但你要多准备电源、SD卡、操作系统,还得处理串口权限问题,一个简单的桌面仪表没必要整这么复杂。STM32则是另一条学习曲线,光环境搭建就能劝退不少新手。
Arduino生态最大的福利就是库和资料。SoftwareSerial库解决了UNO只有一个硬件串口的问题,OLED显示用Adafruit SSD1306库直接调用,整个代码量控制在150行以内。而且Arduino UNO输出的是5V电平,正好和常见的JW01模块匹配,不需要额外加电平转换。如果你手头只有Arduino Nano,代码也完全通用,引脚定义一样就行。
这背后的选型逻辑很简单:能用最少的时间跑通、让读者把注意力放在传感器协议这个核心知识点上,方案就是好方案。如果一上来就上STM32+RTOS,那就跑偏了。
2. 硬件准备、接线与JW01串口协议
2.1 物料清单和接线表
这个项目需要的硬件很少,多数是Arduino玩家手里现成的东西。
| 模块 | 型号/规格 | 数量 | 说明 |
|---|---|---|---|
| 主控板 | Arduino UNO / Nano | 1 | 其他兼容板也行 |
| CO2传感器 | JW01-CO2系列 | 1 | 建议量程0-2000或0-5000ppm |
| OLED屏 | 0.96寸 SSD1306 I2C | 1 | 128x64分辨率 |
| LED | 普通5mm发光二极管 | 1 | 做阈值报警指示灯 |
| 电阻 | 330Ω | 1 | 给LED限流 |
| 杜邦线 | 母对母、公对母若干 | 若干 | 推荐不同颜色方便区分 |
接线看上去不难,但有一点特别容易搞反:传感器的TX要接到Arduino的RX引脚,传感器的RX要接到Arduino的TX引脚,也就是交叉连接。我的SoftwareSerial定义是SoftwareSerial co2Serial(11, 10),其中11号引脚负责接收,10号引脚负责发送,所以JW01的TX必须接Arduino的11脚,JW01的RX必须接Arduino的10脚。OLED用的是I2C接口,UNO上SDA是A4,SCL是A5,这两个不用记,库默认会用Wire库找对应引脚。
还需要强调一个点:D0和D1是Arduino的硬件串口,也就是USB转串口复用的那一组。如果你把传感器接到D0/D1上,上传程序的时候会被干扰,而且连电脑串口监视器也会冲突。所以尽量用D10/D11这类普通数字引脚,通过SoftwareSerial来跑传感器。这样电脑的串口监视器还能正常打印调试数据,两不误。
2.2 JW01串口协议:看懂一帧数据再写代码
JW01这类模块上电后会周期性主动上报数据,常见的是9600波特率、8位数据位、无校验、1位停止位。数据帧一般是9个字节,格式大致如下。
| 字节位置 | 值 | 含义 |
|---|---|---|
| Byte0 | 0xFF | 帧头,固定为FF |
| Byte1 | 0x04 | 数据上报标识 |
| Byte2 | 0x00 | 保留位 |
| Byte3 | XX | CO2浓度高字节 |
| Byte4 | XX | CO2浓度低字节 |
| Byte5 | XX | 温度高字节(部分固件支持,否则为0x00) |
| Byte6 | XX | 温度低字节 |
| Byte7 | 0x00 | 保留位 |
| Byte8 | XX | 校验和 |
浓度值是按大端方式拼出来的:CO2 = (Byte3 << 8) | Byte4。比如Byte3是0x01、Byte4是0x2C,那CO2浓度就是0x012C,换算成十进制是300ppm。校验和常见有两种算法:一种是Byte1到Byte7之和取低8位,另一种是Byte1到Byte7之和取反再加1,也就是补码。不同批次或不同固件版本可能有差异,这在低价CO2模块里很常见。
我写代码的时候干脆把两种校验都兼容了,这样无论模块用的哪一种都能通过校验。但这不代表每个型号的帧格式都一样,有的版本第5、6字节可能不是温度而是别的数据,有的版本帧长也不一定是9字节。最稳妥的做法是拿到新模块先抓一次原始数据,确认帧头和浓度字节位置再解析。这也是我下面专门写一节串口抓包的原因。
2.3 上板之前先抓包:用USB-TTL验证传感器
很多新手犯的错误是焊好线就写代码,结果屏幕上一直显示0或者乱码,然后开始怀疑人生。我的习惯是:任何串口传感器到手,先不接Arduino,直接用USB-TTL转串口模块接电脑,打开串口助手看一下原始输出。
接线方式和接Arduino一样,USB-TTL的TX接传感器的RX,USB-TTL的RX接传感器的TX,然后共地、供电。打开串口助手,波特率设9600,正常情况下每1到2秒能看到一帧数据,类似FF 04 00 01 2C 00 00 00 5B这样的十六进制串。如果没有主动上报,可以尝试发送一组查询指令FF 01 86 00 00 00 00 00 79,部分模块固件兼容这类查询帧,收到指令后会回一帧数据。
这一步花不了两分钟,但能帮你确认三件事:模块供电是否正常、波特率是否正确、数据帧格式到底是怎样的。如果串口助手都读不到数据,那说明模块本身就有问题,就不用费劲去查Arduino代码了。做了这步再往下走,你的Arduino代码会写得非常有底气。
2.4 接线避坑:供电、共地、电平这三件事
第一,供电。JW01常见版本需要5V供电,如果接到Arduino的3.3V上,模块可能能亮但输出数据不稳定,甚至完全不工作。所以老老实实从Arduino的5V引脚取电。第二,共地。如果传感器不是由Arduino直接供电,而是用外部电源,那传感器的GND和Arduino的GND必须连在一起,否则串口信号没有参考电平,数据就是乱码。共地算是串口通信里最基础也最容易被忽略的问题。
第三,电平。Arduino UNO是5V逻辑电平,常见JW01版本也是TTL电平,所以直连没问题。但如果你后面换ESP32这类3.3V主控,就要确认传感器输出电平是否兼容,很多模块直接用3.3V也能读到数据,但稳妥起见还是加个电平转换模块或者用分压电阻。第四,OLED的I2C地址,常见是0x3C,也有少部分模块是0x3D,如果OLED初始化失败,先改地址再检查接线。
3. Arduino完整代码:从串口解析到屏幕显示
3.1 开发环境与依赖库准备
代码要在Arduino IDE里编译上传。IDE版本用1.8.x或2.x都行,2.x也有挺好用的自动补全。需要安装两个库:Adafruit SSD1306和Adafruit GFX。打开Arduino IDE,菜单栏找到“工具 -> 管理库”,在搜索框里输入Adafruit SSD1306,找到后安装;再输入Adafruit GFX,同样安装。Adafruit GFX是图形库,SSD1306负责驱动OLED,两者是搭配使用的。
安装完库最好重启一下IDE,避免编译时找不到头文件。如果你用的不是Adafruit库而是U8g2库,代码会有差异,但功能类似。我这篇文章统一用Adafruit库,因为相对更常见一些。
3.2 代码设计思路和模块划分
这个项目的代码可以分成几个模块:串口读取函数、校验函数、OLED刷新函数、LED报警逻辑。整个主循环不用delay()卡死,而是用millis()控制显示刷新频率,这样串口读取始终是及时的,不会因为刷新屏幕把传感器数据漏掉。
读取串口的思路是:先检查缓冲区里是否至少有9个字节,然后用peek()看第一个字节是不是0xFF帧头,如果是就整帧读出来做校验,不是就丢弃当前字节继续找。这样即使串口里混入了干扰字节,也能慢慢“爬”回正确位置。校验通过后,提取Byte3和Byte4拼成浓度值,再判断数值是否在合理范围内(0~5000ppm),避免把错误数据刷上屏幕。
OLED刷新我控制在200毫秒一次,肉眼看起来非常流畅,又不会让传感器读取被拖累。LED报警逻辑放在最后,阈值超过1000ppm就点亮,低于1000ppm就熄灭,这个数值你可以自己改。
3.3 可直接复制的完整Arduino代码
以下是完整代码,复制粘贴即可用。我在关键位置都加了注释,方便对照理解。
#include <Wire.h> #include <Adafruit_GFX.h> #include <Adafruit_SSD1306.h> #include <SoftwareSerial.h> // JW01 接线定义 #define CO2_RX_PIN 11 // 接 JW01 TX,注意这里是软件的RX #define CO2_TX_PIN 10 // 接 JW01 RX #define ALARM_LED_PIN 8 // 报警LED // OLED 尺寸 #define OLED_I2C_ADDR 0x3C #define OLED_WIDTH 128 #define OLED_HEIGHT 64 // 阈值和刷新间隔 #define CO2_THRESHOLD 1000 // 超过这个值点亮LED,单位ppm #define DISPLAY_INTERVAL 200 // 屏幕刷新间隔,单位ms SoftwareSerial co2Serial(CO2_RX_PIN, CO2_TX_PIN); Adafruit_SSD1306 display(OLED_WIDTH, OLED_HEIGHT, &Wire, -1); uint8_t frame[9]; uint16_t co2PPM = 0; bool co2Valid = false; unsigned long lastValidMs = 0; unsigned long lastDisplayMs = 0; void setup() { Serial.begin(9600); // 电脑串口监视器 co2Serial.begin(9600); // JW01传感器串口 pinMode(ALARM_LED_PIN, OUTPUT); digitalWrite(ALARM_LED_PIN, LOW); if (!display.begin(SSD1306_SWITCHCAPVCC, OLED_I2C_ADDR)) { Serial.println(F("[ERR] OLED init failed, check address 0x3C/0x3D and wiring.")); while (1) delay(10); } showText("CO2 Monitor", "warming up..."); } void loop() { readCO2Frame(); // 读取并解析串口数据 if (millis() - lastDisplayMs >= DISPLAY_INTERVAL) { lastDisplayMs = millis(); updateDisplay(); } bool isFresh = co2Valid && (millis() - lastValidMs < 6000); bool alarm = isFresh && (co2PPM > CO2_THRESHOLD); digitalWrite(ALARM_LED_PIN, alarm ? HIGH : LOW); } // 从软件串口读取并解析一帧数据 void readCO2Frame() { while (co2Serial.available() >= 9) { if (co2Serial.peek() != 0xFF) { co2Serial.read(); // 不是帧头,丢弃 continue; } co2Serial.readBytes(frame, 9); if (checkFrame(frame)) { uint16_t v = (frame[3] << 8) | frame[4]; if (v > 0 && v < 5000) { // 合理性检查 co2PPM = v; co2Valid = true; lastValidMs = millis(); } } } } // 校验帧数据,兼容两种常见校验和算法 bool checkFrame(uint8_t f[9]) { if (f[0] != 0xFF) return false; uint8_t sum = 0; for (int i = 1; i < 8; i++) { sum += f[i]; } // 方法1:和直接取低8位;方法2:取反再加1 bool crcOk = (f[8] == sum) || (f[8] == (uint8_t)(~sum + 1)); return crcOk; } // 刷新OLED显示 void updateDisplay() { display.clearDisplay(); display.setTextColor(SSD1306_WHITE); display.setTextSize(2); display.setCursor(0, 6); display.print("CO2 ppm"); display.setTextSize(4); display.setCursor(8, 30); bool isFresh = co2Valid && (millis() - lastValidMs < 6000); if (isFresh) { display.print(co2PPM); } else { display.print("---"); } if (isFresh && (co2PPM > CO2_THRESHOLD)) { display.setTextSize(1); display.setCursor(0, 56); display.print(">1000 WARN"); } display.display(); } // 两行提示文字 void showText(const char* line1, const char* line2) { display.clearDisplay(); display.setTextColor(SSD1306_WHITE); display.setTextSize(2); display.setCursor(0, 12); display.println(line1); display.setCursor(0, 36); display.setTextSize(1); display.println(line2); display.display(); }3.4 代码关键点说明:这几个地方别改错
SoftwareSerial co2Serial(11, 10)这一行,第一个参数是接收引脚,必须接JW01的TX,第二个参数是发送引脚,接JW01的RX。有人习惯记成“软件串口RX接传感器TX”,这没错,但最容易搞混的地方是引脚编号和实际接线对不上,烧录后一直没数据。如果遇到这种情况,第一件事就是检查TX和RX有没有交叉接对。
checkFrame()里兼容了两种校验算法,这是针对JW01不同固件版本做的妥协。如果你收到的数据一直校验不过,可以把校验逻辑临时去掉,直接打印出frame[3]和frame[4]看一眼浓度是否合理。不过说实话,我碰到的大部分模块用sum直接取低8位就能通过,极少遇到取反加一的版本。
OLED刷新用了DISPLAY_INTERVAL=200,不要改太小,否则屏幕会有闪烁感;也不要太大,不然CO2数值更新看起来卡顿。200毫秒是比较舒服的体验。LED报警阈值CO2_THRESHOLD=1000是参考室内空气质量标准定的,如果你放在会议室或者教室,可以考虑调到800甚至更低,提前提醒开窗。
4. 实测、校准与常见问题排查
4.1 首次上电:先预热再判断数据
我第一次上电时,OLED显示了一个很低的数值,大概100多ppm,我心想这室内空气也太好了吧,后来才反应过来传感器还没稳定。CO2传感器尤其是NDIR原理的,通电后需要一段时间让红外光源和探测器进入稳定状态,常见建议是预热5到15分钟再参考读数。所以代码里开机显示的“warming up...”并不是随便写的,是真的需要等。
预热之后再怎么看数据是不是正常?一个很有效的验证方法:对着传感器轻轻吹一口气,但别太近,保持20厘米左右的距离,呼出的CO2浓度高达数万ppm,屏幕上数值会瞬间冲到1500到3000ppm。吹完以后数值又会慢慢回落到正常范围。这个过程基本可以确定传感器、接线和代码全链路都通了。
需要注意一点,别真的把口水喷到传感器上,NDIR模块的进气口如果被水汽或者灰尘堵住,响应会变慢,数值也会漂移。验证完之后,把它放在通风正常、没人对着吹气的位置,让它好好跑一会儿。前5分钟数值上下波动都是正常的,别急着下结论说数据不准。
4.2 数据合理性判断:哪些数值是正常的
传感器稳定后,可以按这几个参照来判断数据是否正常。开窗通风状态下,室内CO2浓度应该和室外接近,一般在400到600ppm之间。关上门窗正常呼吸半小时,浓度会缓慢上升到800到1200ppm。如果房间里待了两个人或者卧室开了一整夜空调,早上测到1500ppm甚至2000ppm也不奇怪,这时候人通常已经觉得闷得不行了。
如果你家里有新风系统或者空气净化器带CO2显示,可以把两者的数值放在一起对比。品牌设备的传感器经过出厂校准,数值可以作为参考,两者的差异如果在50到100ppm以内,说明JW01工作得相当正常。如果差异很大,比如JW01显示800ppm、参考设备显示1400ppm,那就要考虑是不是模块摆放位置不同造成的空气不流通,或者传感器本身需要校准了。
放置位置也很影响读数。不要贴墙安装,不要放在暖气片、空调出风口旁边,也不要放在窗口气流直吹的地方。传感器需要和室内空气充分接触,背后能留一点空隙是最好的。另外,人呼出的气流如果直接吹到传感器进气口,数值会异常偏高,所以放在桌面侧后方比放在面前更接近真实室内浓度。
4.3 校准与维护经验:什么时候需要动它
家用场景下,JW01这类模块一般不需要频繁校准。但如果用了大半年以后发现数值系统性偏高,或者明显不合理,可以试着做一次简单的“背景校准”。方法是把传感器拿到室外通风处,让它在新鲜空气中运行10分钟以上,然后记录稳定后的数值,理论上应该无限接近室外环境浓度(约400到450ppm)。如果偏差很大,就需要查手册看是否支持外部标定指令。
很多低价CO2模块出厂时固件里带有自动基线校准,但这套算法通常假设传感器每周至少有一次机会接触到新鲜空气(比如开窗通风时),如果你长期在密闭空调房运行,自动校准可能反而不准。这也是为什么我的代码里保留了“最后有效帧时间”的判断,一旦传感器长时间掉线,屏幕会显示三个横杠,不会拿旧值骗你。
维护方面倒不用太操心。每隔一两个月用气吹清理一下外壳和进气口就行,别用水洗,红外传感器模组内部有精密的光学结构,沾水基本就报废了。如果你发现响应速度明显变慢,先检查是不是进气口被灰尘堵了,再考虑传感器老化。
4.4 常见问题排查速查表:照着表格就能定位问题
| 现象 | 可能原因 | 解决办法 |
|---|---|---|
| 串口监视器无任何输出 | TX/RX接反、供电不足、波特率不对 | 确认交叉接线,JW01接5V,波特率9600 |
| 屏幕显示“---” | 传感器未上报数据、线松、没预热 | 检查接线是否牢固,用USB-TTL抓包确认模块有输出 |
| OLED不亮或花屏 | I2C地址不对、SDA/SCL反了 | 换0x3D试试,检查A4/A5接线 |
| 数值一直为0或不变 | 校验未通过、模块固件帧格式不同 | 临时去掉校验,打印frame[3]和frame[4]看数值 |
| 数值明显偏高 | 模块离出风口太近、被呼吸气流直吹 | 挪到空气流通但无直吹的位置 |
| 数值开机乱跳 | 传感器未充分预热 | 稳定运行15分钟后再判断 |
| 有数据但刷新很慢 | 每次读数间隔较长 | 确认模块是否为主动上报模式,或尝试发查询指令 |
排查的时候优先从物理层开始:供电、共地、TX/RX,这三项占故障原因的一大半。串口通信最怕的就是“感觉都接对了”,结果问题出在电平不匹配或者共地没做好。用Arduino的5V和GND给传感器供电是最省心的方案,外部供电务必记得共地。
5. 玩法延伸:从桌面仪表变成智能联动装置
5.1 加继电器联动排风扇:自动通风怎么实现
OLED显示CO2只是第一步,更实用的玩法是让它自动控制排风扇或者新风机。思路很简单:CO2浓度超过某个阈值就打开继电器,浓度降下来再关闭。但有一点必须提醒:继电器控制220V强电有风险,新手建议先用5V小风扇做演示,熟悉了原理再考虑强电设备。
代码上就是在主循环里加了一个变量,控制一个继电器引脚。为了避免阈值附近频繁开关,我加了两档滞回逻辑:浓度升到1200ppm以上才开启,降到1000ppm以下再关闭,中间这段“死区”能有效防止继电器不停跳动。
#define RELAY_PIN 7 bool relayOn = false; void loopRelayControl() { if (!relayOn && co2PPM > 1200) { digitalWrite(RELAY_PIN, HIGH); relayOn = true; } else if (relayOn && co2PPM < 1000) { digitalWrite(RELAY_PIN, LOW); relayOn = false; } }把这个函数加在主循环里,每次读完传感器就调用一次。如果你用的是低电平触发继电器模块,记得把HIGH和LOW反过来,具体看继电器模块的说明书。控制220V设备时,继电器必须选有安全标识的正规产品,电路做好绝缘,不建议新手在没有指导的情况下直接上手强电。
5.2 用ESP32或ESP8266上报到手机
Arduino UNO串口监视器只能在电脑上看,想把数据发到手机上,思路有两个。一个是在UNO上用Serial.println(co2PPM)把解析好的数值发到默认串口,然后让ESP8266的串口去读,ESP8266再把数据通过Wi-Fi上报到手机。另一个更直接的方案是绕开UNO,直接用ESP32的硬件串口去读JW01,然后把数据推到MQTT或HTTP服务器上。
用ESP32的时候要注意一个工程问题:JW01常见版本是5V供电,而ESP32的GPIO只有3.3V耐压。如果不做电平转换,长期使用可能损坏ESP32的引脚。最简单的做法是给传感器RX线路接一个分压电阻,比如串联1kΩ再接一个2kΩ到地,把5V电平降到3.3V左右。TX方向的输出相对安全,但最好也确认一下模块是否兼容3.3V逻辑。
如果你的ESP32开发板带有5V输出引脚,供电没问题。代码逻辑和Arduino版本几乎一样,只是把SoftwareSerial换成Serial1或Serial2,这样读取更稳定,而且不占用GPIO模拟串口。数据上报到手机以后,你还可以搭配定时记录、历史曲线、远程开窗这些更高级的功能,那就是一个完整的中控系统了。
5.3 更多可玩方向:串口绘图、数据记录、语音提醒
想先不联网就看数据趋势,Arduino IDE自带的串口绘图器(Serial Plotter)就够用。把代码里改成只向Serial打印co2PPM,然后打开工具里的串口绘图器,就能看到实时曲线,方便观察开窗前后浓度变化。这个方法很适合做对比实验,比如测试关窗多久CO2会超标。
想要长期记录数据,可以给Arduino加一个Micro SD卡模块,定时把CO2浓度、时间戳写进CSV文件。有了历史数据,就能分析晚上卧室CO2是不是超标、白天办公室哪个时段最闷。再进阶一点,还可以把OLED换成彩色TFT屏,在一个界面上同时显示CO2、温度和湿度,搭配DHT11或DHT20传感器就能扩展成完整的环境监测站。
我个人这两周用下来最深的体会是:串口协议真没想象中难,一旦理解了“帧头 + 数据域 + 校验位”这个套路,遇到任何UART传感器都能迅速上手。最后再分享一个小技巧:新模块到手一定先接USB-TTL抓包,看清楚每一帧长什么样再写代码,这个习惯能帮你省掉90%的排查时间。这个项目跑通之后,下一步可以尝试把CO2数据接入家里的小爱同学或者Home Assistant,玩法会一下子打开很多。