接上回
逐个分析
m_TVOC.h
#include <Arduino.h>#include <SoftI2C.h>#include "DFRobot_SGP40.h"// TVOC指数
// 型号:sgp_40
// 接口:VCC->VCC(5V)、GND->GND、SDA->D6、SCL->D7、WAK->GND
// 协议:I2C(软)
// 地址:0x5A#define _Pin_TVOC_SDA 6
#define _Pin_TVOC_SCL 7
#define _Address_TVOC 0x59namespace Module {SoftI2C SoftWire = SoftI2C(_Pin_TVOC_SDA, _Pin_TVOC_SCL); //sda, scl
DFRobot_SGP40 _m_sgp_40(&SoftWire);struct _TVOC {void init() {int timeout = 0;// 预热10秒_m_sgp_40.begin(10000);}bool isWarmUp = false;void warmUp() {if (!isWarmUp) {isWarmUp = _m_sgp_40.warmUp();}}void adjust(float temp, float hum) {_m_sgp_40.setRhT(hum, temp);}int getValue() {return isWarmUp ? _m_sgp_40.getVoclndex() : 0;}char unit[6] = "index";
} TVOC;}
通讯方式是 I2C
DFRobot_SGP40 是一个第三方库,本来可以直接在 Arduino IDE 搜索并安装使用。
但是 Arduino Nano 仅有一对 I2C 接口 A4、A5 引脚,而这硬件级的 I2C 接口,需要留个 OLED 显示屏使用。
因此,这里使用了另外一个第三方库 SoftI2C,利用其它引脚通过软件模拟的方式实现I2C通讯。
而且,DFRobot_SGP40 本身并不支持 SoftI2C 类型,所以这里复制了一个副本,并把源码中 Wire 替换成 SoftI2C。
这里根据 I2C 的理论,I2C总线是共享的总线系统,这意味着只要模块的寻址地址不一样,应该可以共用一对 I2C 接口。
只是,小白我在实际的测试使用中,发现会影响 OLED 显示屏导致显示不稳定,不知道究竟是 OLED 库的问题 还是 I2C 信号干扰问题。
所以,读取传感器本身通讯压力较小,实测使用 SoftI2C 基本没问题。
特别的,参考这里的文档SGP40,该传感器是需要预热的,且建议 10 秒以上。
这里有个问题,示例代码中,是通过 while 的方式等待 10 秒预热的,对本项目来说,是不能接受阻塞其它传感器那么久的。
因此,修改 DFRobot_SGP40 源码,把预热操作分离出来方法 warmUp。
源码:
bool DFRobot_SGP40::begin(uint32_t duration)
{_pWire->begin();VocAlgorithm_init(&_vocaAgorithmParams);unsigned long timestamp = millis();// 它在这里通过 while 预热while(millis()-timestamp<duration){getVoclndex();}return sgp40MeasureTest();
}
改为:
long timestamp;
uint32_t duration;
bool DFRobot_SGP40::begin(uint32_t d)
{_pWire->begin();VocAlgorithm_init(&_vocaAgorithmParams);duration = d;timestamp = millis();return true;
}bool DFRobot_SGP40::warmUp(){if(millis()-timestamp<duration){getVoclndex();return false;}return sgp40MeasureTest();
}
预热操作,交给外部调用,本项目是在 程序入口 的 loop 中反复执行:
// arduino-air-monitor.ino// ...略void loop() {
// ...略if (!Module::TVOC.isWarmUp) {Module::TVOC.warmUp();}
// ...略
}// ...略
最后,DFRobot_SGP40 拥有 setRhT 方法,用于设置当前环境中的相对湿度和温度,貌似可以获得更精确的数值。
// arduino-air-monitor.ino// ...略void process(bool display) {// 设置当前环境中的相对湿度和温度,获得更精确的数值if (Module::TVOC.isWarmUp) {Module::TVOC.adjust(Module::Temperature.getValue(), Module::Humidity.getValue());}
}// ...略
m_CO2.h
#include <Arduino.h>// 基于 https://github.com/RobTillaart/MTP40F/blob/master/examples/MTP40F_PWM_demo/MTP40F_PWM_demo.ino
// 由于上面的示例,关于PWM计算,不符合文档下列所属,因此修改之。// 【PWM 功能详解】
// PWM 的周期是 1004ms
// 起始阶段高电平输出 2ms
// 中部周期 1000ms
// 结束阶段低电平输出 2ms
// 通过 PWM 获得当前 CO2
// 浓度值的计算公式:
// Cppm = 5000*(TH-2ms)/(TH+TL-4ms)
// Cppm 为计算得到的 CO2
// 浓度值,单位是 ppm
// TH 为一个输出周期中输出为高电平的时间
// TL 为一个输出周期中输出为低电平的时间// PWM端口:3、5、6、9、10、11、13// 二氧化碳
// 型号:mtp_40_f
// 接口:G+->VCC(5V)、G->GND、PWM->D3
// 协议:PWM#define _Pin_CO2 3namespace Module {namespace MTP_40_F {unsigned long start = 0;
unsigned long duration = 0;// 计算高电平持续时间
void Interrupt() {if (digitalRead(_Pin_CO2) == HIGH) start = millis();else duration = millis() - start;
}// 浓度值的计算公式:
// Cppm = 5000*(TH-2ms)/(TH+TL-4ms)
// 单位是 ppm
unsigned int DurationToPPM() {return 5000 * (float)(duration - 2) / (1004 - 4);
}}struct _CO2 {void init() {pinMode(_Pin_CO2, INPUT_PULLUP);// 中断服务例程(ISR),当连接到引脚电平变化时,Arduino会调用这个函数。attachInterrupt(digitalPinToInterrupt(_Pin_CO2), MTP_40_F::Interrupt, CHANGE);}unsigned int getValue() {unsigned int value = MTP_40_F::DurationToPPM();// 量程 400ppm ~ 5000ppm// 首次计算结果有可能是 65535,因此做一个判断修正。if (value > 10000) {value = 0;}return value;}char unit[6] = "ug\/m";
} CO2;}
通讯方式是 PWM
MTP40F 文档是这样说的:
【PWM 功能详解】
PWM 的周期是 1004ms
起始阶段高电平输出 2ms
中部周期 1000ms
结束阶段低电平输出 2ms
通过 PWM 获得当前 CO2
浓度值的计算公式:
Cppm = 5000*(TH-2ms)/(TH+TL-4ms)
Cppm 为计算得到的 CO2
浓度值,单位是 ppm
TH 为一个输出周期中输出为高电平的时间
TL 为一个输出周期中输出为低电平的时间
关于PWM计算,是基于 RobTillaart/MTP40F改写的,由于计算逻辑不符合上面文档说的计算公司,因此修改之。
源码:
// ppm == 0 is a pulse length of 2 millis.
// ppm == 2000 is a pulse length of 1002 millis.
// every 2 ppm adds 1 millis
uint16_t duration2PPM(uint16_t d)
{return (d - 2) * 2;
}
本项目:
// 浓度值的计算公式:
// Cppm = 5000*(TH-2ms)/(TH+TL-4ms)
// 单位是 ppm
unsigned int DurationToPPM() {return 5000 * (float)(duration - 2) / (1004 - 4);
}
这里最关键的代码应该是:
attachInterrupt(digitalPinToInterrupt(_Pin_CO2), MTP_40_F::Interrupt, CHANGE);
中断服务例程(ISR),当连接到引脚电平变化时,Arduino会调用这个函数。电平变化是 PWM 的关键,文档中举了个例子:
公式中可以看出,已知周期是 1004 ms,公式中的关键参数是 TH,只要得出 TH,代入公司,即可得出具体数值:
unsigned long start = 0;
unsigned long duration = 0;// 计算高电平持续时间
void Interrupt() {if (digitalRead(_Pin_CO2) == HIGH) start = millis();else duration = millis() - start;
}
未完待续。。。
多多支持其它文章
https://blog.csdn.net/xachary2又或者请我喝杯奶茶😍
vue3-zoom-drag