らいふぷろせす

たまーに、できごときろく

CO2モニター を作ってみた

2020-05-09 17:35:02 | 電子工作
以前から部屋の二酸化炭素濃度はどうなっているんだろう?と気になっていて、CO2モニターを作ろうと材料は用意していたのですが・・・
一年越しの作成です。
コロナウイルスの件で、改めて換気の重要性が数値化できるのではないかと思い、やっとやる気が出ました(^^;)

材料は
・マイコン:Arduino UNO
・表示器:LCD Shield
・台:100均のスマホスタンドを使ってみました(いい感じです)


センサーとマイコンはUARTで接続できます。ソフトウエアシリアルで接続しました。
ハードのほうでLogを取ったりするのもよいと思います。
センサーから0~5000ppmまでの値が出力されます。
Arduinoのライブラリも公開されていて簡単に作成できました。
温度も一応表示しましたが、ライブラリのコメントにもあるように精度は良くないですが参考になります。


CO2モニターを目の前に置き、作業をしているとみるみる数値が上がっていきます。


ちなみに、室内のCO2濃度が1000ppmになると、思考力、集中力が減少 するとか・・
だから冬、ファンヒーターたいて作業してるとすぐ眠くなるのか...な?

下の表はここから引用
空気中のCO2濃度有害ガスが人体に作用する時間
250-350ppm大気中における通常濃度
250-1000ppm換気が十分実施されている屋内の通常数値
2000-5000ppm
換気の悪い部屋
頭痛、眠気、倦怠感、注意力散漫、心拍数の増加、吐き気の発生
5,000ppm以上作業場所としての限界値(8時間-TWA)
>40,000ppm以上酸素障害誘発、脳へのダメージによる昏睡、最悪死に至る

作ったCO2モニターで観察してみると、外では440ppmくらい。
室内の換気は結構こまめに行う必要があることがわかりました。
呼気も顔周辺にこもるようです。暖房や冷房を使ってなくてもサーキュレーターは効果があるかもしれません。
植物を部屋に置くことも良いかもしれません。
以前聞いた話ですが「室内で野菜を栽培しようとして光合成に必要な二酸化炭素量は足りているのか?と測定してみたところ思いのほか二酸化炭素量が少なかった」とのことでした。

センサーの校正は必要かもしれませんが相対量としては参考になると思います。
(絶対値としてみてもそんなに悪い気はしませんが...知識不足でわかりません(^^;))

密になりそうな部屋にモニターを置いて換気を促すきっかけにするのもよいと思いました。

ーーーーー Arduino スケッチ -----

/* CO2モニタ
* LCDモニターにCO2濃度(ppm)を表示)
* CO2センサー(MH-Z19B)
* 接続  センサー     Arduino
*       赤   →   5V
*      黒   →   GND
*      緑   →   A0
*      青   →   A1
*/

// include the library code:
#include <LiquidCrystal.h>
#include <SoftwareSerial.h>
#include<Wire.h>


// initialize the library by associating any needed LCD interface pin
// with the arduino pin number it is connected to
const int rs = 12, en = 11, d4 = 5, d5 = 4, d6 = 3, d7 = 2;
LiquidCrystal lcd(rs, en, d4, d5, d6, d7);


SoftwareSerial MHZ19Serial(A0, A1);//sensor green→A0 blue→A1


byte Request[9] = {0xFF, 0x01, 0x86, 0x00, 0x00, 0x00, 0x00, 0x00, 0x79};

struct Response {
word header;
byte vh;
byte vl;
byte temp;
byte tails[4];
} __attribute__((packed));;


void setup() {
MHZ19Serial.begin(9600);
Serial.begin(9600);
// initialize digital pin LED_BUILTIN as an output.
pinMode(LED_BUILTIN, OUTPUT);
// set up the LCD's number of columns and rows:
lcd.begin(16, 2);
}


void loop() {
struct Response response;

MHZ19Serial.write(Request, sizeof Request);
MHZ19Serial.readBytes((char *)&response, 9);

if (response.header != 0x86ff) {
return;
}

word ppm = (response.vh << 8) + response.vl;
byte temp = response.temp - 41;


Serial.print(ppm);
Serial.print(" ");
// Serial.println(temp);
Serial.print(temp); // XL232Logger にデータを転送する場合は改行しない
Serial.print(" ");


// set the cursor to (0,0):
lcd.setCursor(0, 0);
lcd.print("CO2 ");
lcd.print(ppm);
lcd.print("ppm");

lcd.setCursor(12, 0);
// lcd.print("Temp ");
lcd.print("T");
lcd.print(temp);
lcd.print("\xdf");
// lcd.print("C");



if (ppm>3000){       //3000ppを超えると危険を表示およびLEDを長めに点灯
lcd.setCursor(0,1);
lcd.print(" ");
for (int i=0; i <=3; i++){
digitalWrite(LED_BUILTIN, HIGH); // turn the LED on (HIGH is the voltage level)
// delay(5); // wait for a second
// digitalWrite(LED_BUILTIN, LOW); // turn the LED off by making the voltage LOW
lcd.setCursor(0,1);
lcd.print("Danger!");
delay(500);
lcd.setCursor(0,1);
lcd.print(" ");
delay(500);
lcd.setCursor(0,1);
lcd.print("Danger!!");
delay(500);
lcd.setCursor(0,1);
lcd.print(" ");
delay(500); // wait for a second
}
}else if (ppm>2000){       //2000ppmを超えると警告LED点滅
lcd.setCursor(0,1);
lcd.print(" ");
for (int i=0; i <=3; i++){
digitalWrite(LED_BUILTIN, HIGH); // turn the LED on (HIGH is the voltage level)
delay(5); // wait for a second
digitalWrite(LED_BUILTIN, LOW); // turn the LED off by making the voltage LOW
lcd.setCursor(0,1);
lcd.print("Caution!");
delay(900);
lcd.setCursor(0,1);
lcd.print(" ");
delay(100);
lcd.setCursor(0,1);
lcd.print("Caution!!");
delay(900);
lcd.setCursor(0,1);
lcd.print(" ");
delay(100); // wait for a second
}

}else if (ppm>1000){       //1000ppmを超えると換気を促す
lcd.setCursor(0,1);
lcd.print(" ");
for (int i=0; i <=3; i++){
lcd.setCursor(0,1);
lcd.print("Ventilate now!");
delay(1500);
lcd.setCursor(0,1);
lcd.print(" ");
delay(500);

}
}else{
lcd.setCursor(0,1);
lcd.print("It's safe. (^^)");
digitalWrite(LED_BUILTIN, LOW); // turn the LED off by making the voltage LOW
delay(6015);
}

// clear screen for the next loop:
lcd.clear();


}


コメント
  • X
  • Facebookでシェアする
  • はてなブックマークに追加する
  • LINEでシェアする

臭いモニター を作ってみた

2020-05-09 15:16:52 | 電子工作
久しぶりの電子工作。
今回は[TGS2450]というガスセンサーを使って、臭いモニターを作ってみました。




使用した材料
・マイコン:Arduino Pro Mini 互換品
・表示器:OLD(SSD1306)※I2cのタイプを使いました

電源は5V。回路は秋月電子の資料そのままです。
Arduinoのスケッチはラジオペンチさんの「0.96インチOLEDにトレンドグラフを表示 (Arduino)」を参考にさせていただきました。
オートスケールのグラフは見ていて楽しいです。
ラジオペンチさんこのようなソースコードを公開していただいてありがとうございます。

表示は中央上にリアルタイムの信号値、右上上段「D」はグラフが表示されている最中の最大値と最小値の差、右上下段「M」はグラフが表示している最大値を表示しています。
電源を入れてから安定するまで(数値が下がるまで)ちょっと時間がかかります。
臭いモニターのフルスケールは1000くらいです。
実際に色々反応を試してみたところ、揮発性の高い気体(サインペンやアルコール)は敏感に反応(900超え)します。


にんにくみそもしっかり反応(800くらい)しました。←この反応の数値では耐えられないくらいの臭いですorz...

気になる体臭や口臭ではどうだったかというと・・・
反応は低かったです。周囲の環境にもよるかもしれませんが、よほど他に反応する気体が存在しないところでないと相対的には変化量は見れないのかもしれません。
気が付かないだけで、室内ではセンサーに反応する気体が結構存在するように思います。(私や私の部屋が臭いわけではないですよ!たぶん・・・)

空気が澄んでいるときの庭で少し様子を見てセンサーの値が低くなったの確認してから「ハァ~」ってやると多少の値の変化が見られます。
楽しいけどちょっと傷つく(^^;)

まー、臭いの感覚は人それぞれなのであまり気にすることはないのですが...
ほんとに臭ったら自分でも気づきますよね!?
ちなみに歯磨き後はほとんど変化なしでした。

様々な臭いのするものや条件で数値を取って研究してみないと、臭いモニターとしては使いこなすのは難しいと思いました。


ーーーーー Arduino スケッチ -----

/* データートレンドモニタ(20190127_OLEDgraphTest.ino)
* 0.96インチOLEDにアナログ入力の変化グラフを書く
* 2019/01/27 ラジオペンチ http://radiopench.blog96.fc2.com/
*
*/

#include
#include // adafruitのライブラリを使用
#include
#include // 定周期割込みに使用

#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 64 // OLED display height, in pixels


#define PIN_HEATER 14 // D14(A0)
#define PIN_SENSOR 15 // D15(A1)
#define PIN_OUTPUT 3 // A3

// Declaration for an SSD1306 display connected to I2C (SDA, SCL pins)
#define OLED_RESET -1 // Reset pin # (or -1 if sharing Arduino reset pin)
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);

int latestData;
int latestData_rev;
int latestData_Max;
int latestData_Min;
int latestData_Deff;
int dataBuff[110]; // データーバッファ
char chrBuff[20]; // 表示フォーマットバッファ
int dataMin;
int dataMax;
volatile boolean timeFlag = LOW;

void setup() {
// pinMode(13, OUTPUT);
// Serial.begin(9600);

if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { // Address 0x3C for 128x64
Serial.println(F("SSD1306 allocation failed"));
for (;;); // Don't proceed, loop forever
}

for (int i = 0; i <= 99; i++) {
dataBuff[i] = -1; // バッファを未定義フラグ(-1)で埋める
}
drawCommons(); // 共通部分を描画
MsTimer2::set(60, timeUp); // 60ms秒毎にタイマー割込み
MsTimer2::start();


pinMode(PIN_HEATER,OUTPUT);
pinMode(PIN_SENSOR,OUTPUT);
digitalWrite(PIN_HEATER,HIGH); // Heater Off
digitalWrite(PIN_SENSOR,LOW); // Sensor Pullup Off


}

void loop() {
while (timeFlag == LOW) { // MsTimer2割込み待ち
}
timeFlag = LOW;




int val=0;
delay(185);
digitalWrite(PIN_SENSOR,HIGH); // Sensor Pullup On
delay(3);
latestData = analogRead(PIN_OUTPUT); // Get Sensor Voltage
delay(2);
digitalWrite(PIN_SENSOR,LOW); // Sensor Pullup Off
digitalWrite(PIN_HEATER,LOW); // Heater On
delay(8);
digitalWrite(PIN_HEATER,HIGH); // Heater Off
latestData = 1024 - latestData;
//Serial.println(latestData);



// digitalWrite(13, HIGH);
// latestData = analogRead(0); // アナログポート0のデーターを読む(115us)




saveBuff(); // 表示バッファに書き込み(190us)
dispNewData(); // 最新値を表示(1.42ms)
dispPeekData();
plotData(); // グラフプロット(7.4ms)
scaleLine(); // 目盛り線表示(1.9ms)
dispVscale(); // 縦軸目盛り表示(3.9ms)
display.display(); // バッファの値を転送して表示(37ms)
// digitalWrite(13, LOW); // 処理時間合計=52ms
}

void drawCommons() { // 共通図形の作画
display.clearDisplay();
display.setTextSize(1); // Normal 1:1 pixel scale
display.setTextColor(WHITE); // Draw white text
display.setCursor(0, 0); // Start at top-left corner
display.println("Smell");
display.setCursor(0, 8);
display.println(" monitor");
display.drawRect(26, 16, 102, 48, WHITE); // グラフ領域枠の作画
display.drawFastHLine(24, 16, 2, WHITE); // Max値の補助マーク
display.drawFastHLine(24, 39, 2, WHITE); // center
display.drawFastHLine(24, 63, 2, WHITE); // Min
display.display(); // VRAM転送
}

void dispVscale() { // 縦軸目盛り表示
display.fillRect(0, 16, 24, 55, BLACK); // 前の表示をまとめて消す(24x55ドット)(460us)
display.setCursor(0, 16);
sprintf(chrBuff, "%4d", dataMax); display.print(chrBuff); // Max値表示

display.setCursor(0, 36);
sprintf(chrBuff, "%4d", (dataMax + dataMin) / 2); display.print(chrBuff); // 中心値表示

display.setCursor(0, 57);
sprintf(chrBuff, "%4d", dataMin); display.print(chrBuff); // Min値表示
}

void saveBuff() { // データバッファの更新と最大・最小値の決定
int d;
dataMin = 1023; // 最小
dataMax = 0; // 最大値記録変数を初期化

for (int i = 98; i >= 0; i--) { // 配列に値を保存しながら最大と最小値を求める
d = dataBuff[i];
dataBuff[i + 1] = d; // 配列のデーターを一つ後ろにずらし
if (d != -1) { // ずらしたデータが有効値だったら、
if (d
dataMin = d;
}
if (d > dataMax) { // 最大値を記録
dataMax = d;
}
}
}


latestData_Max = dataMax;
latestData_Min = dataMin;

dataBuff[0] = latestData; // 配列の先頭には最新データーを記録し、
if (latestData
dataMin = latestData;
}
if (latestData > dataMax) { // 最大値を再確認
dataMax = latestData;
}
dataMin = dataMin - 20; // 最小値を-20下に設定
dataMin = (dataMin / 10) * 10; // 10ステップに丸め
if (dataMin < 0) {
dataMin = 0; // 但し下限は0
}
dataMax = dataMax + 20; // 最大値を+20上に設定
dataMax = ((dataMax / 10) + 1) * 10; // 切り上げで10ステップに丸め
if (dataMax > 1020) {
dataMax = 1023; // 但し1020以上なら1023で抑える
}
// Serial.print(dataMin); Serial.print(", "); Serial.println(dataMax);
}

void dispNewData() { // 最新データーの値を画面の中央に表示
display.fillRect(48, 0, 46, 16, BLACK); // 前の表示値を消す(24x8ドット)(320us)
display.setCursor(48, 0);
display.setTextSize(2,2);
sprintf(chrBuff, "%4d", latestData); // 4桁右詰めで
display.print(chrBuff); // 最新測定値を表示
}


void dispPeekData() { // Maxデーターの値を画面の右上に表示
if (latestData_Max >= latestData_Min){
latestData_Deff = latestData_Max - latestData_Min;
}
display.fillRect(100, 0, 47, 16, BLACK); // 前の表示値を消す(24x8ドット)(320us)
display.setCursor(102, 0);
display.setTextSize(1);
display.println("D");
display.setCursor(103, 0);
sprintf(chrBuff, "%4d", latestData_Deff); // 4桁右詰めで

display.print(chrBuff); // 最新測定値を表示
display.setCursor(102, 8);
display.println("M");
display.setCursor(102, 8);
sprintf(chrBuff, "%4d", latestData_Max); // 4桁右詰めで
display.print(chrBuff);
}

void plotData() { // 配列の値に基づきデーターをプロット
long yPoint;
display.setTextSize(1);
display.fillRect(27, 17, 100, 46, BLACK); // グラフ表示領域をクリア(100x53ドット)(1.9ms)
for (int i = 0; i <= 98; i++) {
if (dataBuff[i] == -1) { // データーが未定(-1)なら
break; // プロット中止
}
yPoint = map(dataBuff[i], dataMin, dataMax, 63, 15); // プロット座標へ変換
display.drawPixel(125 - i, yPoint, WHITE); // データをプロット
}
}

void scaleLine() { // 目盛り線を作画
for (int x = 26; x <= 128; x += 4) {
display.drawFastHLine(x, 39, 1, WHITE); // 中心線を点線で描く
}
for (int x = (127 - 33); x > 30; x -= 33) {
for (int y = 16; y
display.drawFastVLine(x, y, 1, WHITE); // 縦線を点線で2本描く
}
}
display.setTextSize(1);
display.setCursor(96, 55);
display.print("< 8s>"); // 横軸スケール表示
}

void timeUp() { // MsTimer2割込み処理
timeFlag = HIGH;
}

コメント
  • X
  • Facebookでシェアする
  • はてなブックマークに追加する
  • LINEでシェアする