エントリー
ネットには、人を殺す冷酷な黒いカビについてのさまざまな悪夢があり、カビは濡れた部屋を愛しています。 カビに対処するのは難しいですが、最初に監視する必要があるのは空気の湿度です。 冬の始まりには湿度も覚えておく必要があります。冷たい空気を加熱すると湿度が下がり、乾燥した空気が粘膜に悪影響を及ぼし、免疫力が大幅に低下するためです。
このように主張して、Arduinaベースにそのような湿度インジケータを構築することは悪くないという結論に達しました。 デジタルセンサーの機会と安価さを利用して、気圧計、温度計、そして重要なことに空気中の二酸化炭素の量を測定する機能を追加しないことは罪でした(古い高層ビルでは、アパートの換気が悪いことが多く、アパート自体は冬の間密閉されています)。 二酸化炭素、パフォーマンスの低下、頭痛に関する多くの恐怖物語もあります。これらは空気中の高濃度のCO2の症状です。
インターネット上では、大気中のCO2の許容濃度の次のプレートも見つかりました。
必要なのは:
- 1. Arduino Pro Mini価格2.15ドル
- 2.湿度および温度センサーDHT-22別名AM2302(DHT-11と比較して精度が向上)、価格$ 4
- 3.圧力センサーBMP180、価格2.98 $
- 4.二酸化炭素センサーTGS4161、価格$ 60のモジュール
- 5.ディスプレイタイプNokia 5110、価格$ 2.35
合計 :71.48ドル(同様のセンサーセットを備えた産業用デバイスの価格は300ドルから)
二酸化炭素センサー
二酸化炭素センサーは個別に停止する必要があります。 圧力および湿度センサーはすでに数値を読み取りますが、何らかの電源安定化回路を備えています。完全デジタルCO2センサーのコストは120ドルからで、「遊ぶ」ことに少し似ています。 Arduinのモジュールの形で最も安い、36ドルのMG811に基づいて中国の提供。 私はその特性を調べましたが、湿度、空気中の外来ガスの存在に大きな依存関係があり、より正確なものを探す必要があると判断しましたが、これはTGS4161であることがわかりました:
測定値の安定性を犠牲にして落ち着いて、私は購入し、センサーは2週間後に到着しました。
デバイスの組み立て
モジュールが私に届いたとき、私はすでにすべてがブレッドボード上に組み立てられていましたが、それだけが欠落していました。 はんだごてで武装して、私は広告写真に目を向けました(上記参照)、彼らはそれを接続する方法を言いますか? ここで私は少しびっくりしなければなりませんでした、 Doutがデジタル出力であるという希望は実現しませんでした。このモジュールのドキュメントはどこにもありません。 どうする? 私はテスターでモジュールの足をつついて、センサーに息を吹きかけ、そのような母親を描き、ウサギに書き込みをしなければなりませんでした... pokeメソッドを使用して、モジュールに2つのオペアンプが含まれていることが確立されました。 それらの最初のものは1.5オームの入力インピーダンスを持っています(テラオーム、つまり1500ギガオームと正確に間違えられませんでしたが、従来のデジタルマルチメーターは20メガオーム以下の入力インピーダンスを持っています)、2番目のデュアルオペアンプはコンパレータとして機能しますそしてリピーター。 Aoutはリピーターからの特定のアナログ信号、 Doutはコンパレーターからの信号です(CO2がスケールから外れるとアクティブになります)。 TCMピン割り当てを確立できませんでした。
センサーはどのように機能しますか?
センサーは、動作に高温を必要とする電気化学セルです。 温度は、約0.2Wの電力でセンサーに組み込まれたヒーターによって提供されます。 350ppm以下のCO2濃度でのセルの電圧は一定の安定値を持ち、CO2の濃度が増加すると、セルの電圧も変化(減少)します。 セルの高出力インピーダンスを一致させ、電圧を増幅するために、オペアンプが使用されます。
センサーを使用するには、ボルトをCO2濃度に変換する必要があります。 センサーのドキュメントには、センサーの初期電圧は任意であると主張されていますが、この電圧の絶対的な変化はデータシートに厳密に従っています。 そのため、少なくとも新鮮な空気でゼロの開始点を測定し、測定値を基準点として設定することにより、感度のグラフに基づいてセンサーのモデルを構築する必要があります。 一般に、少なくとも、モデルが構築され、センサーが較正され、何らかの種類の建物ですべてを組み立てることができました。
新たに較正されたセンサーの感度は、300メートルにわたって公園内のより多くの新鮮な空気とガスで満たされた通りを確実に区別できるほどでした。
残りのセンサーについては、湿度が完全に測定され、デバイスを拾うとすぐに湿度の増加に気付くことができます。ちょうど同じジャンプです(アパートの湿度が29%になった場合でも)。 また、圧力は絶対値と相対値の両方で完全に測定され、デバイスを上下させることで、圧力の違いに気付くことができます。
以下は...
物語はそこで終わりませんでしたが、デバイスが自律型電源に切り替わったときに病気になり、治療の歴史は別のトピックに値するため、それはまだ始まったばかりでした。ここに医学史のオシログラムがあります:
UPD:
配線図はこれらのセンサーの典型的なもので、スケッチのピン配置について詳しく説明しています。
Arduino UNOの作業スケッチ
スケッチは別の部分から(コピーされた)ものだったので、あまりscらないでください。
#include <SFE_BMP180.h> #include <Wire.h> #include "DHT.h" #define PIN_SCE 7 // LCD CS .... Pin 2 #define PIN_RESET 6 // LCD RST .... Pin 1 #define PIN_DC 5 // LCD Dat/Com. Pin 3 #define PIN_SDIN 4 // LCD SPIDat . Pin 4 #define PIN_SCLK 3 // LCD SPIClk . Pin 5 // LCD Gnd .... Pin 8 // LCD Vcc .... Pin 6 // LCD Vlcd ... Pin 7 #define LCD_C LOW #define LCD_D HIGH #define LCD_X 84 #define LCD_Y 48 #define LCD_CMD 0 SFE_BMP180 pressure; #define DHTPIN 10 #define DHTTYPE DHT22 DHT dht(DHTPIN, DHTTYPE); #define ALTITUDE 113.0 // Altitude of SparkFun's HQ in Boulder, CO. in meters int a = 0; int sensorPin = A0; // Aout 2 int vBattPin = A1; // int sensorValue = 0; // variable to store the value coming from the sensor float adc_step=5/1024.0f; // float a_k=5.0894E-7; // 2 float b_k=6.7303E-4;// 2 float v_0=1.09; // int ledPin=13; static const byte ASCII[][5] = { {0x00, 0x00, 0x00, 0x00, 0x00} // 20 ,{0xff, 0xff, 0xff, 0xff, 0xff} // 21 ! //,{0x00, 0x00, 0x5f, 0x00, 0x00} // 21 ! ,{0x00, 0x07, 0x00, 0x07, 0x00} // 22 " ,{0x14, 0x7f, 0x14, 0x7f, 0x14} // 23 # ,{0x24, 0x2a, 0x7f, 0x2a, 0x12} // 24 $ ,{0x23, 0x13, 0x08, 0x64, 0x62} // 25 % ,{0x36, 0x49, 0x55, 0x22, 0x50} // 26 & ,{0x00, 0x05, 0x03, 0x00, 0x00} // 27 ' ,{0x00, 0x1c, 0x22, 0x41, 0x00} // 28 ( ,{0x00, 0x41, 0x22, 0x1c, 0x00} // 29 ) ,{0x14, 0x08, 0x3e, 0x08, 0x14} // 2a * ,{0x08, 0x08, 0x3e, 0x08, 0x08} // 2b + ,{0x00, 0x50, 0x30, 0x00, 0x00} // 2c , ,{0x08, 0x08, 0x08, 0x08, 0x08} // 2d - ,{0x00, 0x60, 0x60, 0x00, 0x00} // 2e . ,{0x20, 0x10, 0x08, 0x04, 0x02} // 2f / ,{0x3e, 0x51, 0x49, 0x45, 0x3e} // 30 0 ,{0x00, 0x42, 0x7f, 0x40, 0x00} // 31 1 ,{0x42, 0x61, 0x51, 0x49, 0x46} // 32 2 ,{0x21, 0x41, 0x45, 0x4b, 0x31} // 33 3 ,{0x18, 0x14, 0x12, 0x7f, 0x10} // 34 4 ,{0x27, 0x45, 0x45, 0x45, 0x39} // 35 5 ,{0x3c, 0x4a, 0x49, 0x49, 0x30} // 36 6 ,{0x01, 0x71, 0x09, 0x05, 0x03} // 37 7 ,{0x36, 0x49, 0x49, 0x49, 0x36} // 38 8 ,{0x06, 0x49, 0x49, 0x29, 0x1e} // 39 9 ,{0x00, 0x36, 0x36, 0x00, 0x00} // 3a : ,{0x00, 0x56, 0x36, 0x00, 0x00} // 3b ; ,{0x08, 0x14, 0x22, 0x41, 0x00} // 3c < ,{0x14, 0x14, 0x14, 0x14, 0x14} // 3d = ,{0x00, 0x41, 0x22, 0x14, 0x08} // 3e > ,{0x02, 0x01, 0x51, 0x09, 0x06} // 3f ? ,{0x32, 0x49, 0x79, 0x41, 0x3e} // 40 @ ,{0x7e, 0x11, 0x11, 0x11, 0x7e} // 41 A ,{0x7f, 0x49, 0x49, 0x49, 0x36} // 42 B ,{0x3e, 0x41, 0x41, 0x41, 0x22} // 43 C ,{0x7f, 0x41, 0x41, 0x22, 0x1c} // 44 D ,{0x7f, 0x49, 0x49, 0x49, 0x41} // 45 E ,{0x7f, 0x09, 0x09, 0x09, 0x01} // 46 F ,{0x3e, 0x41, 0x49, 0x49, 0x7a} // 47 G ,{0x7f, 0x08, 0x08, 0x08, 0x7f} // 48 H ,{0x00, 0x41, 0x7f, 0x41, 0x00} // 49 I ,{0x20, 0x40, 0x41, 0x3f, 0x01} // 4a J ,{0x7f, 0x08, 0x14, 0x22, 0x41} // 4b K ,{0x7f, 0x40, 0x40, 0x40, 0x40} // 4c L ,{0x7f, 0x02, 0x0c, 0x02, 0x7f} // 4d M ,{0x7f, 0x04, 0x08, 0x10, 0x7f} // 4e N ,{0x3e, 0x41, 0x41, 0x41, 0x3e} // 4f O ,{0x7f, 0x09, 0x09, 0x09, 0x06} // 50 P ,{0x3e, 0x41, 0x51, 0x21, 0x5e} // 51 Q ,{0x7f, 0x09, 0x19, 0x29, 0x46} // 52 R ,{0x46, 0x49, 0x49, 0x49, 0x31} // 53 S ,{0x01, 0x01, 0x7f, 0x01, 0x01} // 54 T ,{0x3f, 0x40, 0x40, 0x40, 0x3f} // 55 U ,{0x1f, 0x20, 0x40, 0x20, 0x1f} // 56 V ,{0x3f, 0x40, 0x38, 0x40, 0x3f} // 57 W ,{0x63, 0x14, 0x08, 0x14, 0x63} // 58 X ,{0x07, 0x08, 0x70, 0x08, 0x07} // 59 Y ,{0x61, 0x51, 0x49, 0x45, 0x43} // 5a Z ,{0x00, 0x7f, 0x41, 0x41, 0x00} // 5b [ ,{0x02, 0x04, 0x08, 0x10, 0x20} // 5c ¥ ,{0x00, 0x41, 0x41, 0x7f, 0x00} // 5d ] ,{0x04, 0x02, 0x01, 0x02, 0x04} // 5e ^ ,{0x40, 0x40, 0x40, 0x40, 0x40} // 5f _ ,{0x00, 0x01, 0x02, 0x04, 0x00} // 60 ` ,{0x20, 0x54, 0x54, 0x54, 0x78} // 61 a ,{0x7f, 0x48, 0x44, 0x44, 0x38} // 62 b ,{0x38, 0x44, 0x44, 0x44, 0x20} // 63 c ,{0x38, 0x44, 0x44, 0x48, 0x7f} // 64 d ,{0x38, 0x54, 0x54, 0x54, 0x18} // 65 e ,{0x08, 0x7e, 0x09, 0x01, 0x02} // 66 f ,{0x0c, 0x52, 0x52, 0x52, 0x3e} // 67 g ,{0x7f, 0x08, 0x04, 0x04, 0x78} // 68 h ,{0x00, 0x44, 0x7d, 0x40, 0x00} // 69 i ,{0x20, 0x40, 0x44, 0x3d, 0x00} // 6a j ,{0x7f, 0x10, 0x28, 0x44, 0x00} // 6b k ,{0x00, 0x41, 0x7f, 0x40, 0x00} // 6c l ,{0x7c, 0x04, 0x18, 0x04, 0x78} // 6d m ,{0x7c, 0x08, 0x04, 0x04, 0x78} // 6e n ,{0x38, 0x44, 0x44, 0x44, 0x38} // 6f o ,{0x7c, 0x14, 0x14, 0x14, 0x08} // 70 p ,{0x08, 0x14, 0x14, 0x18, 0x7c} // 71 q ,{0x7c, 0x08, 0x04, 0x04, 0x08} // 72 r ,{0x48, 0x54, 0x54, 0x54, 0x20} // 73 s ,{0x04, 0x3f, 0x44, 0x40, 0x20} // 74 t ,{0x3c, 0x40, 0x40, 0x20, 0x7c} // 75 u ,{0x1c, 0x20, 0x40, 0x20, 0x1c} // 76 v ,{0x3c, 0x40, 0x30, 0x40, 0x3c} // 77 w ,{0x44, 0x28, 0x10, 0x28, 0x44} // 78 x ,{0x0c, 0x50, 0x50, 0x50, 0x3c} // 79 y ,{0x44, 0x64, 0x54, 0x4c, 0x44} // 7a z ,{0x00, 0x08, 0x36, 0x41, 0x00} // 7b { ,{0x00, 0x00, 0x7f, 0x00, 0x00} // 7c | ,{0x00, 0x41, 0x36, 0x08, 0x00} // 7d } ,{0x10, 0x08, 0x08, 0x10, 0x08} // 7e ← ,{0x00, 0x06, 0x09, 0x09, 0x06} // 7f → }; void LcdCharacter(char character) { LcdWrite(LCD_D, 0x00); for (int index = 0; index < 5; index++) { LcdWrite(LCD_D, ASCII[character - 0x20][index]); } LcdWrite(LCD_D, 0x00); } void LcdClear(void) { for (int index = 0; index < LCD_X * LCD_Y / 8; index++) { LcdWrite(LCD_D, 0x00); } } void LcdInitialise(void) { pinMode(PIN_SCE, OUTPUT); pinMode(PIN_RESET, OUTPUT); pinMode(PIN_DC, OUTPUT); pinMode(PIN_SDIN, OUTPUT); pinMode(PIN_SCLK, OUTPUT); digitalWrite(PIN_RESET, LOW); // delay(1); digitalWrite(PIN_RESET, HIGH); LcdWrite( LCD_CMD, 0x21 ); // LCD Extended Commands. LcdWrite( LCD_CMD, 0xBf ); // Set LCD Vop (Contrast). //B1 LcdWrite( LCD_CMD, 0x04 ); // Set Temp coefficent. //0x04 LcdWrite( LCD_CMD, 0x14 ); // LCD bias mode 1:48. //0x13 LcdWrite( LCD_CMD, 0x0C ); // LCD in normal mode. 0x0d for inverse LcdWrite(LCD_C, 0x20); LcdWrite(LCD_C, 0x0C); } void LcdString(char *characters) { while (*characters) { LcdCharacter(*characters++); } } void LcdWrite(byte dc, byte data) { digitalWrite(PIN_DC, dc); digitalWrite(PIN_SCE, LOW); shiftOut(PIN_SDIN, PIN_SCLK, MSBFIRST, data); digitalWrite(PIN_SCE, HIGH); } // gotoXY routine to position cursor // x - range: 0 to 84 // y - range: 0 to 5 void gotoXY(int x, int y) { LcdWrite( 0, 0x80 | x); // Column. LcdWrite( 0, 0x40 | y); // Row. } char * floatToString(char * outstr, double val, byte precision, byte widthp){ char temp[16]; byte i; // compute the rounding factor and fractional multiplier double roundingFactor = 0.5; unsigned long mult = 1; for (i = 0; i < precision; i++) { roundingFactor /= 10.0; mult *= 10; } temp[0]='\0'; outstr[0]='\0'; if(val < 0.0){ strcpy(outstr,"-\0"); val = -val; } val += roundingFactor; strcat(outstr, itoa(int(val),temp,10)); //prints the int part if( precision > 0) { strcat(outstr, ".\0"); // print the decimal point unsigned long frac; unsigned long mult = 1; byte padding = precision -1; while(precision--) mult *=10; if(val >= 0) frac = (val - int(val)) * mult; else frac = (int(val)- val ) * mult; unsigned long frac1 = frac; while(frac1 /= 10) padding--; while(padding--) strcat(outstr,"0\0"); strcat(outstr,itoa(frac,temp,10)); } // generate space padding if ((widthp != 0)&&(widthp >= strlen(outstr))){ byte J=0; J = widthp - strlen(outstr); for (i=0; i< J; i++) { temp[i] = ' '; } temp[i++] = '\0'; strcat(temp,outstr); strcpy(outstr,temp); } return outstr; } void drawLine(void) { unsigned char j; for(j=0; j<84; j++) // top { gotoXY (j,0); LcdWrite (1,0x01); } for(j=0; j<84; j++) //Bottom { gotoXY (j,5); LcdWrite (1,0x80); } for(j=0; j<6; j++) // Right { gotoXY (83,j); LcdWrite (1,0xff); } for(j=0; j<6; j++) // Left { gotoXY (0,j); LcdWrite (1,0xff); } } float VoltageToPPM(float voltage) { return pow(b_k, voltage)/a_k; } void setup(void) { //analogReference(INTERNAL); LcdInitialise(); LcdClear(); gotoXY(0,0); if (pressure.begin()) LcdString("BMP180 init success"); else { // Oops, something went wrong, this is usually a connection problem, // see the comments at the top of this sketch for the proper connections. LcdString("BMP180 init fail\n\n"); while(1); // Pause forever. } dht.begin(); } void loop(void) { sensorValue = analogRead(sensorPin); delay(50); sensorValue = analogRead(sensorPin); delay(50); char buf[20]; float value=sensorValue*adc_step; gotoXY(0,0); LcdString("CO2:"); LcdString(floatToString(buf, VoltageToPPM(value),1,5)); LcdString("ppm"); gotoXY(0,1); LcdString("CO2 V:"); LcdString(floatToString(buf, value,4,5)); gotoXY(0,2); delay(50); sensorValue = analogRead(vBattPin); delay(50); sensorValue = analogRead(vBattPin); value=sensorValue*adc_step; LcdString("V batt:"); LcdString(floatToString(buf, value,3,4)); // static bool led_on_off=true; // digitalWrite(ledPin, led_on_off); // led_on_off=!led_on_off; char status; double T,P,p0,a; // Loop here getting pressure readings every 10 seconds. // If you want sea-level-compensated pressure, as used in weather reports, // you will need to know the altitude at which your measurements are taken. // We're using a constant called ALTITUDE in this sketch: // If you want to measure altitude, and not pressure, you will instead need // to provide a known baseline pressure. This is shown at the end of the sketch. // You must first get a temperature measurement to perform a pressure reading. // Start a temperature measurement: // If request is successful, the number of ms to wait is returned. // If request is unsuccessful, 0 is returned. status = pressure.startTemperature(); if (status != 0) { // Wait for the measurement to complete: delay(status); // Retrieve the completed temperature measurement: // Note that the measurement is stored in the variable T. // Function returns 1 if successful, 0 if failure. status = pressure.getTemperature(T); if (status != 0) { // Print out the measurement: //gotoXY(0,3); // LcdString("temp: "); //LcdString(floatToString(buf, T,1,4)); // LcdString(" C"); // Start a pressure measurement: // The parameter is the oversampling setting, from 0 to 3 (highest res, longest wait). // If request is successful, the number of ms to wait is returned. // If request is unsuccessful, 0 is returned. status = pressure.startPressure(2); if (status != 0) { // Wait for the measurement to complete: delay(status); // Retrieve the completed pressure measurement: // Note that the measurement is stored in the variable P. // Note also that the function requires the previous temperature measurement (T). // (If temperature is stable, you can do one temperature measurement for a number of pressure measurements.) // Function returns 1 if successful, 0 if failure. status = pressure.getPressure(P,T); if (status != 0) { // Print out the measurement: gotoXY(0,5); //lcd.print("ap "); LcdString(floatToString(buf, P*0.7501,1,4)); //lcd.print(" mb"); // The pressure sensor returns abolute pressure, which varies with altitude. // To remove the effects of altitude, use the sealevel function and your current altitude. // This number is commonly used in weather reports. // Parameters: P = absolute pressure in mb, ALTITUDE = current altitude in m. // Result: p0 = sea-level compensated pressure in mb p0 = pressure.sealevel(P,ALTITUDE); // we're at 1655 meters (Boulder, CO) LcdString("-"); LcdString(floatToString(buf, p0*0.7501,1,4)); //Serial.print(" mb, "); //Serial.print(p0*0.0295333727,2); //Serial.println(" inHg"); // On the other hand, if you want to determine your altitude from the pressure reading, // use the altitude function along with a baseline pressure (sea-level or other). // Parameters: P = absolute pressure in mb, p0 = baseline pressure in mb. // Result: a = altitude in m. a = pressure.altitude(P,p0); } else LcdString("error retrieving pressure measurement\n"); } else LcdString("error starting pressure measurement\n"); } else LcdString("error retrieving temperature measurement\n"); } else LcdString("error starting temperature measurement\n"); float h = dht.readHumidity(); float t = dht.readTemperature(); gotoXY(0,4); if (isnan(t) || isnan(h)) { LcdString("Failed to read from DHT"); } else{ LcdString(floatToString(buf, h,1,4)); LcdString("%"); } gotoXY(0,3); LcdString("temp: "); LcdString(floatToString(buf, t,1,4)); LcdString(" C"); delay(1000); }