ARTICLE DETAIL

资讯详情

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

基于Arduino Uno与JW01传感器搭建室内CO2监测系统全攻略

基于Arduino Uno与JW01传感器搭建室内CO2监测系统全攻略 JW01二氧化碳传感器搭配一块几十块钱的Arduino Uno是我做环境监测方案时最常用、也最不容易翻车的一套组合。这个项目说白了就三件事传感器把CO2浓度通过串口发出来Arduino把一帧一帧的数据拆开最后在OLED屏上显示出来。整个逻辑不复杂特别适合刚接触Arduino、想做个有实际用途的小设备的人练手也适合在办公室、教室这种封闭空间里快速搭一个空气质量参考终端。这篇就按我平时的操作习惯从选型思路、接线、代码解析到排查问题完整走一遍。代码是可直接编译烧录的完整版还加了DHT22温湿度模块如果你手头没有DHT22也能直接忽略那一部分不影响CO2读取。1. 先把思路理清楚JW01到底适合做什么1.1 为什么CO2监测要用NDIR传感器很多人第一次选CO2传感器时会纠结电化学的便宜NDIR的贵到底买哪种。我的建议是如果做室内空气质量监测预算允许的情况下尽量选NDIR比如JW01。NDIR的原理是红外非色散吸收传感器里有一个红外光源和一个探测器二氧化碳分子会对特定波长的红外光产生吸收通过检测光强的衰减程度来反推浓度。这个过程不涉及化学反应所以传感器寿命长、响应稳定、不容易被其他气体干扰。电化学传感器虽然便宜但寿命通常只有两三年而且会受温度湿度影响用于长时间监测需要频繁校准。JW01常见的模块分两类一类是UART串口输出一类是PWM占空比输出。我个人更推荐串口版因为读取逻辑简单一条线发数据Arduino用SoftwareSerial就能读解析帧也就十几行代码。PWM版需要用pulseIn去测高电平时间还要依赖公式换算精度和调试成本都高一些。1.2 Arduino Uno的串口资源怎么分配Arduino Uno只有一个硬件串口也就是接USB的那组通常在烧录程序后用来跟电脑通信。如果直接把JW01接到硬件串口的RX/TX上烧录程序时可能会有冲突调试时也不方便同时看串口监视器的输出。所以我一般会把JW01接到软串口上用SoftwareSerial库模拟一个串口。软串口对时序要求比较高但只要波特率是9600且代码里没有长时间阻塞操作数据读取是很稳定的。如果以后换成ESP32这类多硬件串口的芯片那更简单直接用Serial1、Serial2就行代码结构基本不用变。1.3 串口版和PWM版怎么选串口版的优势是数据已经处理成浓度值直接解析帧结构就能得到ppm精度和稳定性由传感器保证我们只需要做校验和判断防止乱码影响显示。PWM版的优势是接线更少一根信号线就能读数但Arduino读PWM精度受pulseIn测量误差影响特别是在多任务循环里容易测得不准。如果你已经买了PWM版的JW01也可以做后面我会单独讲一下思路和公式但如果你还没买直接买串口版能省不少事。2. 动手前准备物料清单、接线和库2.1 物料清单与接线表这个项目的硬件非常精简核心元器件有这些物料型号/规格数量备注主控板Arduino Uno R31块其他兼容板也可以CO2传感器JW01串口版1个注意区分串口/PWM丝印OLED显示屏0.96寸 I2C SSD13061块128x64四针或六针均可温湿度传感器DHT22可选项1个增强环境监测功能蜂鸣器有源/无源均可1个可选用于浓度超标提醒面包板400孔1块方便快速搭建杜邦线公对公、公对母若干尽量短一点减少干扰接线方面我按Arduino Uno的标准引脚来列一份表照着接基本不会错JW01引脚连接到VCCArduino 5VGNDArduino GNDTXD传感器发送Arduino 数字引脚2软串口RXRXD传感器接收Arduino 数字引脚3软串口TXOLED屏用的是I2C接口OLED引脚连接到VCCArduino 5VGNDArduino GNDSCLArduino A5SDAArduino A4DHT22的数据脚我接在数字引脚4。蜂鸣器接数字引脚13正极接引脚负极接GND如果是有源蜂鸣器直接给高电平就会响。注意JW01模块上不同批次的丝印可能会标成TXD/RXD也可能标成T/R接线前一定先看模块背面的说明。TXD是发送端要接Arduino的接收端也就是我上面说的引脚2接反了读不到数据。2.2 库文件安装代码里用到了几个第三方库如果你之前没装过在Arduino IDE的“库管理器”里搜索并安装即可。库名称搜索关键字用途SoftwareSerialArduino自带的库模拟串口Adafruit SSD1306adafruit/SSD1306SSD1306驱动Adafruit GFXadafruit/GFXOLED绘图DHT sensor libraryadafruit/DHTDHT22驱动一个容易踩的坑是只装了Adafruit SSD1306是不够的它依赖Adafruit GFX如果没装GFX库编译时会在很多莫名其妙的地方报错。装上之后记得重启IDE让库被正确扫描。DHT库也可以不装只要你不用DHT22。2.3 供电和电平的几点提醒JW01传感器的工作电压通常是5V启动瞬间电流稍大Arduino Uno的板载5V稳压芯片可以提供足够电流直接从USB供电就行。但如果你同时接了OLED、DHT22、蜂鸣器和JW01总电流其实差不多到200mA上下USB口完全撑得住。另外一个容易忽略的问题如果你不是用Arduino Uno而是用ESP32或其他3.3V主控板JW01的串口输出电平是5V直接接到3.3V引脚上有可能烧引脚。这种情况要么选支持3.3V供电的变体型号要么加一个电平转换模块。Uno就完全不用担心这个问题。3. 完整代码与逐段解析3.1 完整可直接编译烧录的代码下面这份代码是我实际在用的版本包含CO2读取、DHT22温湿度读取、OLED显示和浓度报警。数据刷新间隔是2秒刚好够人眼观察也不会让软串口和DHT读取之间产生明显阻塞。#include SoftwareSerial.h #include Wire.h #include Adafruit_GFX.h #include Adafruit_SSD1306.h #include DHT.h #define CO2_RX_PIN 2 #define CO2_TX_PIN 3 #define DHT_PIN 4 #define BUZZER_PIN 13 #define ALARM_CO2 1000 #define OLED_ADDR 0x3C SoftwareSerial co2Serial(CO2_RX_PIN, CO2_TX_PIN); Adafruit_SSD1306 display(128, 64, Wire, -1); DHT dht(DHT_PIN, DHT22); uint8_t rxFrame[9]; uint8_t rxIndex 0; uint16_t co2PPM 0; unsigned long lastScreenUpdate 0; unsigned long lastFrameAt 0; void setup() { Serial.begin(9600); co2Serial.begin(9600); dht.begin(); pinMode(BUZZER_PIN, OUTPUT); digitalWrite(BUZZER_PIN, LOW); if (!display.begin(SSD1306_SWITCHCAPVCC, OLED_ADDR)) { Serial.println(F(SSD1306 init failed)); while (1); } display.clearDisplay(); display.setTextSize(1); display.setTextColor(WHITE); display.setCursor(20, 25); display.print(F(CO2 Monitor)); display.display(); delay(1000); } void loop() { while (co2Serial.available()) { uint8_t b co2Serial.read(); if (rxIndex 0 b ! 0xFF) { continue; } rxFrame[rxIndex] b; if (rxIndex 9) { rxIndex 0; if (parseCO2Frame()) { co2PPM getCO2PPM(); lastFrameAt millis(); } } } if (millis() - lastFrameAt 10000) { digitalWrite(BUZZER_PIN, HIGH); } else { digitalWrite(BUZZER_PIN, LOW); } if (millis() - lastScreenUpdate 2000) { lastScreenUpdate millis(); float t dht.readTemperature(); float h dht.readHumidity(); if (isnan(t) || isnan(h)) { t 0; h 0; } updateOLED(t, h); } } bool parseCO2Frame() { uint8_t sum 0; for (int i 0; i 8; i) { sum rxFrame[i]; } if (rxFrame[0] ! 0xFF || rxFrame[1] ! 0x04) { return false; } if ((sum 0xFF) ! rxFrame[8]) { return false; } return true; } uint16_t getCO2PPM() { uint16_t ppm ((uint16_t)rxFrame[2] 8) | rxFrame[3]; if (ppm 5000) { ppm 5000; } return ppm; } void updateOLED(float t, float h) { display.clearDisplay(); display.setTextSize(2); display.setTextColor(WHITE); display.setCursor(0, 0); display.print(F(CO2)); display.setTextSize(3); display.setCursor(18, 24); display.print(co2PPM); display.print(F(ppm)); display.setTextSize(1); display.setCursor(0, 56); display.print(t, 1); display.print(F(C )); display.print(h, 1); display.print(F(%)); display.display(); if (co2PPM ALARM_CO2) { tone(BUZZER_PIN, 2000, 200); } }如果屏幕上的ppm数值超过三位显示可能会超出边界我在实际使用时会把数字字号从3下调到2或者在代码里判断一下位数再决定显示位置。这个不影响读取逻辑你按自己屏幕效果微调就行。3.2 JW01数据帧解析与校验原理JW01串口输出的串口参数是9600波特率、8位数据、无校验、1位停止位。它每秒钟会输出一帧数据一帧刚好9个字节格式如下字节位置含义示例值0帧头0xFF1气体类型0x04CO22浓度高字节0x003浓度低字节0xC84保留字0x005保留字0x006保留字0x007保留字0x008校验和0xCD浓度计算方式是把第2字节左移8位再和第3字节按位或起来得到的就是ppm值。比如高字节是0x00、低字节是0xC8那浓度就是0x00C8也就是200ppm。校验和的计算逻辑是前8个字节求和取低8位然后和第9字节比较。比如前面的示例帧0xFF 0x04 0x02 0x00 0xC8 0x00 0x00 0x00 0x1CD取低8位就是0xCD刚好匹配。代码里parseCO2Frame()做的事情就是这个。校验这一步非常关键因为软串口在读取过程中偶尔会吃到干扰字节如果不做校验屏幕上可能出现乱跳的离谱数值。3.3 显示逻辑与报警阈值设计报警阈值我默认设成了1000ppm这个数值其实是参考室内空气质量标准的典型值。超过1000ppm很多人会觉得头闷、注意力下降所以作为报警点是合理的。你也可以根据自己的需求改ALARM_CO2比如家里有小宝宝可以设到800ppm更保守一些。报警逻辑里有一个细节值得说一下lastFrameAt是用来记录最后一次成功解析到有效帧的时间如果超过10秒没有收到有效帧我会让蜂鸣器一直响。为什么这么做因为传感器如果松线了或者模块坏了用户往往看着屏幕上的旧数据还以为是正常这个超时报警能快速提醒你检查接线。蜂鸣器我用的无源蜂鸣器用tone()函数可以发出2000Hz的短音如果是5V有源蜂鸣器直接digitalWrite(BUZZER_PIN, HIGH)就能响。3.4 如果你手上的版本是PWM输出有的JW01模块标的是PWM输出这类模块通常有一个OUT引脚输出脉冲宽度和CO2浓度成比例。读取思路是用pulseIn()测量高电平和低电平的持续时间然后根据模块手册上的公式换算成ppm。大致思路是这样#define PWM_PIN 5 void setup() { Serial.begin(9600); pinMode(PWM_PIN, INPUT); } void loop() { unsigned long tHigh pulseIn(PWM_PIN, HIGH); unsigned long tLow pulseIn(PWM_PIN, LOW); unsigned long tCycle tHigh tLow; if (tCycle 0) { return; } // 占空比 - ppm具体公式以你的模块手册为准 float duty (float)tHigh / tCycle; int ppm duty * 5000; Serial.print(ppm); Serial.println(ppm); delay(1000); }这里我特别提醒一句不同批次JW01 PWM模块的占空比范围和对应量程可能不同有的对应0~2000ppm有的对应0~5000ppm你拿到模块先查手册确认别直接套网上公式否则误差会很大。这也是我更推荐串口版的原因串口版不需要关心这些细节。4. 实操实录与问题排查4.1 从零到出数的一次完整过程我第一次搭这个项目时从拆开模块到屏幕出数大约花了一个小时。如果材料齐、库装好5分钟真的够用。完整流程是这样先把面包板上的VCC/GND排好JW01、OLED、DHT22都先不插单独给面包板供上5V电源然后用万用表确认几个位置电压正常避免短路。接着插OLED先单独跑一下I2C扫描程序确认屏幕地址是0x3C还是0x3D。然后用我刚才那份代码先把DHT22不接跑一个纯读CO2的版本打开串口监视器波特率调成9600等一两秒就能看到传感器输出。我第一次测试时串口监视器一片空白排查了一圈发现是JW01的TXD和RXD两个引脚接反了。模块上TXD要接Arduino的RX引脚RXD要接Arduino的TX引脚也就是交叉接线很多新手都在这里栽过。4.2 常见问题速查表我把实操中遇到概率最高的几个问题整理成了一张表方便你照着排查现象可能原因解决办法串口监视器无输出JW01接线反了检查TXD是否接Arduino引脚2串口监视器大量乱码波特率不对或软串口引脚不是2/3确认代码中9600波特率屏幕显示CO2但是数值不变JW01未上电/数据线松重新插一遍最好用热熔胶固定数值出现5000封顶且不稳定校验失败或模块预热不足上电预热5分钟再看OLED不亮或白屏I2C地址不是0x3C用I2C扫描改地址为0x3D空气中数值低于300ppm模块还没预热完等待10~30分钟稳定4.3 读数不稳定的背后原因很多人在串口监视器里看到CO2数值来回跳第一反应是传感器坏了。其实JW01串口版本身输出是每秒一次相对平滑如果数值在一个固定范围内波动比如420~480ppm之间浮动那是正常现象空气中CO2浓度本来就是动态变化的。如果数值出现离谱跳变比如从480直接跳到2000再跳回500多半是数据传输被干扰了。面包板上的长杜邦线相当于天线Arduino的数字引脚2和3在接收软串口时最怕高频噪声。解决方法是把杜邦线剪短、让JW01尽量靠近Arduino还可以在Arduino的5V和GND之间并联一个100uF电解电容帮助过滤电源毛刺。还有一种常见情况是代码里长时间调用delay()比如我一开始在loop里加了一个delay(1000)导致软串口读取时时序错乱数据帧被切碎。后来改成用millis()做定时刷新问题就消失了。这也是我上面的代码里没有死等delay的原因。4.4 一个容易忽略的细节传感器预热NDIR传感器上电后需要预热JW01模块刚通电的前几分钟读数往往明显偏低或者偏高等热稳定之后才会进入正常状态。我第一次用的时候上电就读显示380ppm还挺高兴结果过了二十分钟再看变成430ppm才知道刚才根本没读完。所以如果你要做对比实验比如测开窗前后浓度变化记得先让设备稳定运行至少10分钟再做记录。放在固定位置长期通电比每次用完断电、用前再开更稳定。5. 扩展方向让监测系统真正可用5.1 接ESP32把监测数据送到手机上看Arduino Uno适合做原型但如果想长期部署我更推荐把代码迁移到ESP32上。ESP32有多个硬件串口直接把JW01的TXD接到ESP32的RX2引脚就行代码里把SoftwareSerial改成Serial2其余解析逻辑完全复用。ESP32支持WiFi可以把CO2、温度、湿度数据通过HTTP请求发送到局域网内的网页服务手机浏览器直接访问。我后来做的一个版本就是把数据传到一个轻量的内网网页上在手机上随时能看到当前浓度曲线不需要额外装App。实际上如果你已经是Arduino IDE的环境在开发板管理器中添加ESP32支持后选型时选“ESP32 Dev Module”烧录流程和Uno一样代码兼容性很好。这也是从入门到进阶最平滑的一条路。5.2 数据记录和趋势曲线Arduino Uno自身没有时间戳能力断电后时间会丢所以长期连续记录数据不如用电脑或者树莓派来收。我常用的做法是Arduino每隔几秒通过硬件串口发送一行CSV格式的文本比如1622550000,430,24.5,55电脑端用Python的pyserial读取并落盘再用matplotlib或者Grafana画曲线。如果你不想碰代码以外的东西也可以直接用Arduino IDE自带的“串口绘图器”选好波特率和串口它能把串口输出里的数值自动绘制成实时波形图。不过串口绘图器一次只能显示一个数值流我一般会让CO2单独一行输出这样图表看起来最直观。5.3 多场景部署办公室、教室、卧室我在办公室放了一台实测效果很明显。工位坐满人、门窗关闭一小时后CO2经常突破1500ppm人会有明显的闷胀感。开窗通风5分钟浓度能降到500ppm左右。这个过程如果你只用体感去感受根本说不清什么时候该开窗屏幕上一目了然。在卧室用的话我建议把设备放在床头附近但不要正对空调出风口或者窗户。放在书桌上、距离人体0.5到1米左右最有参考价值。需要注意的是JW01传感器本身有轻微发热不要把它直接贴墙或者塞进柜子里否则局部空气流动不畅读数会偏高。把它架空放置周围留一点空间数据才真实。如果要做成一个小盒子长期使用可以再加一个0.5A的USB电源适配器给Arduino供电不用一直插着电脑。OLED屏幕建议关掉默认的屏幕常亮改成每隔几秒刷新一次长期运行能减少屏幕老化。这几天我把这套系统放在办公桌上连续跑了48小时整体非常稳定没有出现一次数据断流。中间唯一一次蜂鸣器报警是下午会议室人多、门关了十几分钟CO2冲到1000ppm以上挺好用的。最后再分享一个小技巧当你想验证传感器读数是否正常可以对着传感器正常呼气一小口数值会在几秒内明显上升然后慢慢回落这说明整个链路是通的。如果连这个反应都没有就回头查接线和串口配置。数据可靠了这套5分钟搭好的环境监测系统才算真正能用。
返回列表