一直想制作一个车内气体环境监控系统。通过传感器监控车辆内温湿度、二氧化碳浓度、PM2.5,以及VOC的浓度。用来提醒驾驶员开关窗、开关空调等动作。保障车内人员健康,确保行车安全。这次正好借助这款空气质量评估板来实现该项目。
AAS-AQS-UNO 空气质量评估套件有两个版本:
1、AAS-AQS-UNO 基础版(本款):只有粉尘传感器;2、AAS-AQS-UNO-RH-CO2:额外还有T9602 温度和湿度传感器、T6713 二氧化碳传感器;
这次到手评测的是基础版,只有粉尘传感器,自己手头有一个MH-Z19B的二氧化碳传感器,但是没有有机物浓度(VOC)的传感器,所以只能实现空气中颗粒物、CO2浓度的测量。
开发环境:开发环境试用Vscode+PlatformIO,试用arduino编程。UNO做为一个老牌开发板,在platformIO中支持的非常全面。
传感器读取:
粉尘传感器。SM-PWM-01C 是一款利用光学方法检测空气中粉尘浓度的传感器。在传感器中一个红外LED 和一个光接收器光轴相交,当带粉尘的气流通过交叉区域,产生折射光。光接收器检测到粉尘反射的红外 LED光线,根据输出信号的强弱判断粉尘的浓度。此款粉尘传感器能检测像香烟颗粒大小的颗粒物与室内灰尘等大颗粒,通过输出 PWM 脉冲信号宽度区分。
这个传感器有两个输出管脚,分别对应1~2um、3~10um两种尺寸的颗粒物的浓度。输出信号试用PWM输出,通过计算PWM波中低电平占比来计算浓度信息。
参考着文档,第一个想到读取该传感器数据的方法就是试用外部中断方式,来记录粉尘传感器信号管脚进入低电平的时长。
UNO的外部中断管脚为2、3两个管脚,但是粉尘传感器的两个信号输出管脚却接到了7、8两个脚上,Arduino uno并不能实现任意管脚的中断,这个思路行不通。
第二个方法是使用arduino中的pulseIn()函数。这个函数为读引脚的脉冲信号, 被读取的脉冲信号可以是 HIGH 或 LOW. 例如我们要检测HIGH脉冲信号, Arduino将在引脚变为高电平时开始计时, 当引脚变为低电平时停止记时,并返回脉冲持续时长(时间单位:微秒)。如果在超时时间内没有读到脉冲信号的话, 将返回0。用这个方法来测量输出的PWM的低电平时长。
#include <Arduino.h> #include <MsTimer2.h> #define TIMEOUT 5000 static uint8_t stat = 0; // 0 测量 1 显示 static uint32_t sumtime = 0; void flash() { if (stat == 0) { stat = 1; digitalWrite(13, 1); } else { stat = 0; sumtime = 0; digitalWrite(13, 0); } } void setup() { pinMode(13, OUTPUT); pinMode(8, INPUT_PULLUP); Serial.begin(115200); MsTimer2::set(TIMEOUT, flash); MsTimer2::start(); } void loop() { unsigned long duration; if (stat == 0) { duration = pulseIn(8, LOW, TIMEOUT); sumtime += duration; } if (stat == 1) { Serial.print(sumtime / TIMEOUT); Serial.print(" sumtime:"); Serial.println(sumtime); stat = 3; } }
但是实际测试中发现无法统计到粉尘传感器输出管脚的低电平。pulseIn()函数对固定频率的PWM测量很好用,但是这个粉尘传感器输出的PWM波,并不是固定频率的,pulseIn()函数无法准确测量。
最后的解决方案为,通过时间中断,每1ms检查一次粉尘传感器输出管脚的电平,累加低电平时长。按文档说明30秒为一个周期,所以以1ms为时间片检查还是蛮合理的。
// 每秒中断1000次 void sysinterrupt() { systick++; if (systick >= SYSTICK_PERIOD) { // 系统满了时间片 systick = 1; sp_val = 100.00 * sp_sumtime / SYSTICK_PERIOD; lp_val = 100.00 * lp_sumtime / SYSTICK_PERIOD; sp_sumtime = 0; lp_sumtime = 0; disp_flash = true; } // 判断输入脚电平 对应计数 每计数为1ms if (digitalRead(INPUT_PINSP) == LOW) { sp_sumtime++; } if (digitalRead(INPUT_PINLP) == LOW) { lp_sumtime++; } } void setup() { pinMode(13, OUTPUT); pinMode(INPUT_PINSP, INPUT_PULLUP); pinMode(INPUT_PINLP, INPUT_PULLUP); Serial.begin(115200); mySerial.begin(BAUDRATE); u8g2.begin(); // 选择U8G2模式 MsTimer2::set(1, sysinterrupt); // 开始计时 MsTimer2::start(); }
OLED显示屏。这里的oled显示屏为0.96寸的单色液晶屏,使用SPI总线。这里使用软件模拟SPI总线,使用U8G2库来驱动这个屏幕。屏幕中显示颗粒物浓度、二氧化碳浓度、温度等信息。
void loop() { char m_str[6]; if (disp_flash) { // 读取一次二氧化碳传感器 MHZ19_RESULT response = mhz.retrieveData(); if (response == MHZ19_RESULT_OK) { co2 = mhz.getCO2(); temp = mhz.getTemperature(); } // 刷新屏幕 u8g2.firstPage(); do { Serial.print("SP:"); Serial.print(sp_val); Serial.print(" LP:"); Serial.print(lp_val); Serial.print(" CO2:"); Serial.print(co2); Serial.print(" Temperature:"); Serial.println(temp); u8g2.setFont(u8g2_font_ncenB14_tf); u8g2.drawStr(0, 16, "Tsp:"); // 显示小颗粒浓度 dtostrf(sp_val, 5, 1, m_str); u8g2.drawStr(54, 16, m_str); // 显示大颗粒浓度 u8g2.drawStr(0, 32, "Tlp:"); dtostrf(lp_val, 5, 1, m_str); u8g2.drawStr(54, 32, m_str); u8g2.drawStr(0, 48, "CO2:"); // 显示二氧化碳浓度 dtostrf(co2, 6, 0, m_str); u8g2.drawStr(54, 48, m_str); u8g2.drawStr(0, 64, "T:"); // 显示温度 dtostrf(temp, 5, 1, m_str); u8g2.drawStr(54, 64, m_str); } while (u8g2.nextPage()); disp_flash = false; } else delay(1); }
二氧化碳传感器。这里使用的是MH-Z19B二氧化碳传感器。使用串口连接。使用软件模拟串口,与传感器连接。
编程与实现:
#include <Arduino.h> #include <MsTimer2.h> #include <SPI.h> #include <U8g2lib.h> #include <MHZ19.h> #include <SoftwareSerial.h> #define SYSTICK_PERIOD 30000 // 每个时间片的大小,按文档应该为30秒 即30000 #define INPUT_PINSP 8 // 1~2um小颗粒管脚 对应4脚 #define INPUT_PINLP 7 //>3um大颗粒管脚 对应2脚 // 软串口引脚 #define RX_PIN 2 #define TX_PIN 3 #define BAUDRATE 9600 static uint32_t systick = 1; static uint32_t sp_sumtime = 0, lp_sumtime = 0; static float sp_val = 0, lp_val = 0, temp = 0; // 每次时间片满了即会更新该值 static uint16_t co2 = 0; static bool disp_flash = true; // 是否刷新屏幕 U8G2_SSD1306_128X64_NONAME_F_4W_SW_SPI u8g2(U8G2_R0, /* clock=*/12, /* data=*/11, /* cs=*/U8X8_PIN_NONE, /* dc=*/9, /* reset=*/10); // Native to the sensor (do not change) SoftwareSerial mySerial(RX_PIN, TX_PIN); MHZ19 mhz(&mySerial); // 二氧化碳传感器 使用软串口 // 每秒中断1000次 void sysinterrupt() { systick++; if (systick >= SYSTICK_PERIOD) { // 系统满了时间片 systick = 1; sp_val = 100.00 * sp_sumtime / SYSTICK_PERIOD; lp_val = 100.00 * lp_sumtime / SYSTICK_PERIOD; sp_sumtime = 0; lp_sumtime = 0; disp_flash = true; } // 判断输入脚电平 对应计数 每计数为1ms if (digitalRead(INPUT_PINSP) == LOW) { sp_sumtime++; } if (digitalRead(INPUT_PINLP) == LOW) { lp_sumtime++; } } void setup() { pinMode(13, OUTPUT); pinMode(INPUT_PINSP, INPUT_PULLUP); pinMode(INPUT_PINLP, INPUT_PULLUP); Serial.begin(115200); mySerial.begin(BAUDRATE); u8g2.begin(); // 选择U8G2模式 MsTimer2::set(1, sysinterrupt); // 开始计时 MsTimer2::start(); } void loop() { char m_str[6]; if (disp_flash) { // 读取一次二氧化碳传感器 MHZ19_RESULT response = mhz.retrieveData(); if (response == MHZ19_RESULT_OK) { co2 = mhz.getCO2(); temp = mhz.getTemperature(); } // 刷新屏幕 u8g2.firstPage(); do { Serial.print("SP:"); Serial.print(sp_val); Serial.print(" LP:"); Serial.print(lp_val); Serial.print(" CO2:"); Serial.print(co2); Serial.print(" Temperature:"); Serial.println(temp); u8g2.setFont(u8g2_font_ncenB14_tf); u8g2.drawStr(0, 16, "Tsp:"); // 显示小颗粒浓度 dtostrf(sp_val, 5, 1, m_str); u8g2.drawStr(54, 16, m_str); // 显示大颗粒浓度 u8g2.drawStr(0, 32, "Tlp:"); dtostrf(lp_val, 5, 1, m_str); u8g2.drawStr(54, 32, m_str); u8g2.drawStr(0, 48, "CO2:"); // 显示二氧化碳浓度 dtostrf(co2, 6, 0, m_str); u8g2.drawStr(54, 48, m_str); u8g2.drawStr(0, 64, "T:"); // 显示温度 dtostrf(temp, 5, 1, m_str); u8g2.drawStr(54, 64, m_str); } while (u8g2.nextPage()); disp_flash = false; } else delay(1); }
这里使用定时器中断来做多任务处理。定时器每1ms中断一次。每次中断后,开始检查粉尘传感器两个输出管脚的电平,并累加低电平时长。每30秒钟为一个周期,统计周期内低电平占比时长,然后获取CO2浓度信息,温度信息,刷新OLED屏幕,通过OLED屏幕做信息展示。
效果演示:
使用烧纸的方式污染一下空气。
对着二氧化碳传感器吹口气。
视频: