Датчики углекислого газа CO2 – виды, преимущества и недостатки

Датчики углекислого газа CO2 – виды, преимущества и недостатки Анемометр

Далее следует…

На этом история не закончилась, а все только начиналось, потому что когда прибор перешел на автономное питание ему резко поплохело и история его лечения заслуживает отдельного топика, вот осциллограмма из истории болезни:

UPD:Схема подключения типичная для указанных датчиков, более детально о распиновке в скетче.

Рабочий скетч для Arduino UNO

Скетч собирался (копипастился) с разных кусков, так что сильно не ругайте, надеюсь разобраться в нем можно

#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]='';
  outstr[0]='';

  if(val < 0.0){
    strcpy(outstr,"-");
    val = -val;
  }

  val  = roundingFactor;

  strcat(outstr, itoa(int(val),temp,10));  //prints the int part
  if( precision > 0) {
    strcat(outstr, "."); // 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");

    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  ] = '';
    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 failnn");
    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 measurementn");
      }
      else LcdString("error starting pressure measurementn");
    }
    else LcdString("error retrieving temperature measurementn");
  }
  else LcdString("error starting temperature measurementn");

  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);
  
}

Про анемометры:  Liquid & amp; Газ ультразвуковых кислорода датчики расхода -

Четыре датчика co2 в одном устройстве: сверяем показания.

1. MH-Z14A — самый заслуженный, но вполне рабочий датчик. Отличается крупным размером.

2. MH-Z19 — «народный» датчик, бюджетный вариант.

3. MH-Z19В — Это исправленная и дополненная версия MH-Z19.

4. SenseAir S8 — самый дорогой и самый авторитетный.

Я уже делал обзор трех из четырёх сегодняшних датчиков. Там можно найти их технические характеристики. Все они более-менее одинаковы. Так что сегодня остановимся на невошедшим в тот отбор MH-Z19B.

Поставляется я антистатическом пакете:
Датчики углекислого газа CO2 – виды, преимущества и недостатки

Внутри сам датчик, ножки уже припаяны:
Датчики углекислого газа CO2 – виды, преимущества и недостатки

Корпус датчика такой же, как и у MH-Z19, но платы немного отличаются:
Датчики углекислого газа CO2 – виды, преимущества и недостатки

Наклейка сбоку позволяет их не перепутать:

Датчики углекислого газа CO2 – виды, преимущества и недостатки

Мое устройство выглядит как новогодняя елка:

Датчики углекислого газа CO2 – виды, преимущества и недостатки

В прошлом обзоре его уже довольно обсмеяли, так что я не буду на нем останавливаться подробно.Там просто ардуина, экран, часы, четыре датчика, логгер, записывающий на флешку и блютус адаптер.

Если кому интересен код — вот код:

при нажатии выпадет несколько экранов


/*
© Tykhon, 2022.

MH-Z19
TX: 3<->5 A0
RX: 3<->5 A1
Gnd:      Gnd
Vin:       5v

MH-Z19b
TX: 3<->5 4
RX: 3<->5 A1
Gnd:      Gnd
Vin:       5v


MH-Z14A
1_ 5v:     5v
2_Gnd:    Gnd
11_RX: 3<->5 7
10_TX: 3<->5 6


S8:
G :        5v
G0:       Gnd
Rx: 3<->5 A3
Tx: 3<->5 A2

DS3231
SCL:      A5
SDA:      A4
Gnd:      Gnd
VDD:      5v


TFT
Led: -> 150 Ohm -> 5v
SCK: -> 1 KOhm -> 13
SDA: -> 1 KOhm -> 11
A0:  -> 1 KOhm -> 8
Reset: -> 1 KOhm ->  9
CS:  -> 1 KOhm -> 10
Gnd:      Gnd
Vcc:      5v

*/

#include "Adafruit_GFX.h"                 // for LCD Core graphics library
#include <Adafruit_ST7735.h>              // for LCD  Hardware-specific library
#include <SoftwareSerial.h>
#include "Wire.h"

/////////////////////// for LCD //////////////////////////////

#define TFT_CS     10
#define TFT_RST    9  // you can also connect this to the Arduino reset in which case, set this #define pin to -1!
#define TFT_DC     8

Adafruit_ST7735 tft = Adafruit_ST7735(TFT_CS,  TFT_DC, TFT_RST);  //for LCD 

#define DS1307_ADDR 0x68                     // RTC address

int s8_co2;
int z14_co2;
int z19_co2;
int z19b_co2;
int z19_t;
int z19_ss;
int z19b_t;
int z19b_ss;
int s8_co2_last;
int z14_co2_last;
int z19_co2_last;
int z19b_co2_last;
int s8_co2_mean_last;
int z14_co2_mean_last;
int z19_co2_mean_last;
int z19b_co2_mean_last;
int s8_co2_mean;
int z14_co2_mean;
int z19_co2_mean;
int z19b_co2_mean;
int s8_co2_mean2;
int z14_co2_mean2;
int z19_co2_mean2;
int z19b_co2_mean2;


SoftwareSerial mySerial_z19(A0, A1);
SoftwareSerial mySerial_z19b(4, 5);
SoftwareSerial mySerial_s8(A2, A3);
SoftwareSerial mySerial_z14(7, 6);



byte cmd_z14[9] = {0xFF,0x01,0x86,0x00,0x00,0x00,0x00,0x00,0x79}; 
unsigned char response_z14[9]; 
String ppmString = " ";


byte cmd_z19[9] = {0xFF,0x01,0x86,0x00,0x00,0x00,0x00,0x00,0x79}; 
byte cmd_s8[7] = {0xFE,0x44,0x00,0x08,0x02,0x9F,0x25}; 
unsigned char response_z19[9];
unsigned char response_z19b[9];
byte response_s8[] = {0,0,0,0,0,0,0};

unsigned int current_minute, lastminute;
int second_delay = 30000;                     // delay between measurements, ms
int work_period = 2;                          // period for recording data, min
float smoothing_factor = 0.5;                 // coefficient for Kalman filter
float smoothing_factor2 = 0.15;               // coefficient for Kalman filter

////////////////////////////////////////   functions    //////////////////////////////////////////////


byte bcdToDec(byte val)  {
  return ( (val/16*10)   (val) );
}


String getdate(){
  Wire.beginTransmission(DS1307_ADDR);      // Reset the register pointer
  byte zero = 0x00;
  Wire.write(zero);
  Wire.endTransmission();
  Wire.requestFrom(DS1307_ADDR, 7);
  int secondint = bcdToDec(Wire.read());
  int minuteint = bcdToDec(Wire.read());
  current_minute = minuteint;
  int hour = bcdToDec(Wire.read() & 0b111111); //24 hour time
  int weekDay = bcdToDec(Wire.read()); //0-6 -> sunday - Saturday
  int monthDay = bcdToDec(Wire.read());
  int month = bcdToDec(Wire.read());
  int year = bcdToDec(Wire.read());
  String second = String(secondint); if (secondint < 10) {second ="0" second;};
  String minute = String(minuteint); if (minuteint < 10) {minute ="0" minute;};
  return String(hour) ":" minute ":" second " " String(monthDay) "/" String(month) "/" String(year);
}



void sendRequest_s8(byte packet[]){ 
 mySerial_s8.begin(9600);
 while(!mySerial_s8.available()){                       //keep sending request until we start to get a response 
    mySerial_s8.write(cmd_s8,7); 
    delay(50); 
 }
 int timeout=0;                                         //set a timeout counter 
  while(mySerial_s8.available() < 7 ) {                 //Wait to get a 7 byte response 
    timeout  ; 
    if(timeout > 10) {                                  //if it takes to long there was probably an error 
        while(mySerial_s8.available())                  //flush whatever we have 
        mySerial_s8.read(); 
        break;                                          //exit and try again 
    } 
    delay(50); 
  } 
 for (int i=0; i < 7; i  ) { 
      response_s8[i] = mySerial_s8.read(); 
 }
  mySerial_s8.end();
}                      



unsigned long getValue_s8(byte packet[]) 
{ 
 int high = packet[3];                                   //high byte for value is 4th byte in packet in the packet 
 int low = packet[4];                                    //low byte for value is 5th byte in the packet 
 unsigned long val = high*256   low;                     //Combine high byte and low byte with this formula to get value 
 return val; 
}


void sendRequest_z14(){
  mySerial_z14.begin(9600);
  mySerial_z14.write(cmd_z14, 9);
  memset(response_z14, 0, 9);
  mySerial_z14.readBytes(response_z14, 9);

  int i;
  byte crc = 0;
  for (i = 1; i < 8; i  ) crc =response_z14[i];
  crc = 255 - crc;
  crc  ;
  if ( !(response_z14[0] == 0xFF && response_z14[1] == 0x86 && response_z14[8] == crc) ) {
    Serial.println("CRC error z14: "   String(crc)   " / "  String(response_z14[8]));
  } else { 
    unsigned int responseHigh = (unsigned int) response_z14[2];
    unsigned int responseLow = (unsigned int) response_z14[3];
  z14_co2 = (256*responseHigh)   responseLow;
  };
  mySerial_z14.end();
}



void sendRequest_z19(){
  mySerial_z19.begin(9600);
  mySerial_z19.write(cmd_z19, 9);
  memset(response_z19, 0, 9);
  mySerial_z19.readBytes(response_z19, 9);
  int i;
  byte crc = 0;
  for (i = 1; i < 8; i  ) crc =response_z19[i];
  crc = 255 - crc;
  crc  ;
  if (response_z19[0] != 0xFF) {Serial.println("CRC error z19: response_z19[0] != 0xFF: "   String(response_z19[0]));};
  if (response_z19[1] != 0x86) {Serial.println("CRC error z19: response_z19[1] != 0x86: "   String(response_z19[1]));};
  if (response_z19[8] != crc) {Serial.println("CRC error z19: response_z19[8] != crc: "   String(response_z19[8])   " / "  String(crc));};
  
  if ( !(response_z19[0] == 0xFF && response_z19[1] == 0x86 && response_z19[8] == crc) ) {
    Serial.println("CRC error z19: "   String(crc)   " / "  String(response_z19[8]));
  } else { 
    unsigned int responseHigh = (unsigned int) response_z19[2];
    unsigned int responseLow = (unsigned int) response_z19[3];
    unsigned int responseTT = (unsigned int) response_z19[4];
    unsigned int responseSS = (unsigned int) response_z19[5];
    z19_t = responseTT-40;
    z19_ss =  responseSS;
    z19_co2 = (256*responseHigh)   responseLow;
   };
  mySerial_z19.end();
}

void sendRequest_z19b(){
  mySerial_z19b.begin(9600);
  mySerial_z19b.write(cmd_z19, 9);
  memset(response_z19b, 0, 9);
  mySerial_z19b.readBytes(response_z19b, 9);
  int i;
  byte crc = 0;
  for (i = 1; i < 8; i  ) crc =response_z19b[i];
  crc = 255 - crc;
  crc  ;
  if (response_z19b[0] != 0xFF) {Serial.println("CRC error z19b: response_z19b[0] != 0xFF: "   String(response_z19b[0]));};
  if (response_z19b[1] != 0x86) {Serial.println("CRC error z19b: response_z19b[1] != 0x86: "   String(response_z19b[1]));};
  if (response_z19b[8] != crc) {Serial.println("CRC error z19b: response_z19b[8] != crc: "   String(response_z19b[8])   " / "  String(crc));};
  
  if ( !(response_z19b[0] == 0xFF && response_z19b[1] == 0x86 && response_z19b[8] == crc) ) {
    Serial.println("CRC error z19b: "   String(crc)   " / "  String(response_z19b[8]));
  } else { 
    unsigned int responseHigh = (unsigned int) response_z19b[2];
    unsigned int responseLow = (unsigned int) response_z19b[3];
    unsigned int responseTT = (unsigned int) response_z19b[4];
    unsigned int responseSS = (unsigned int) response_z19b[5];
    z19b_t = responseTT-40;
    z19b_ss =  responseSS;
    z19b_co2 = (256*responseHigh)   responseLow;
   };
  mySerial_z19b.end();
}


void tft_form(){
  tft.setTextSize(2);
  tft.setTextColor(0x177F);

  tft.setCursor(2, 4);
  tft.print("z14");
  tft.setCursor(2, 33);
  tft.print("z19");
  tft.setCursor(2, 62);
  tft.print("z19b");
  tft.setCursor(2, 91);
  tft.print("s8");
  
}


void tft_output(){

  tft.setTextSize(2);

  tft.setCursor(56, 4);
  tft.setTextColor(ST7735_BLACK);
  tft.print(String(z14_co2_mean_last));

  tft.setCursor(56, 4);
  tft.setTextColor(ST7735_WHITE);
  tft.print(String(z14_co2_mean));
  z14_co2_mean_last = z14_co2_mean;

  tft.setCursor(56, 33);
  tft.setTextColor(ST7735_BLACK);
  tft.print(String(z19_co2_mean_last));

  tft.setCursor(56, 33);
  tft.setTextColor(ST7735_WHITE);
  tft.print(String(z19_co2_mean));
  z19_co2_mean_last = z19_co2_mean;

  tft.setCursor(56, 62);
  tft.setTextColor(ST7735_BLACK);
  tft.print(String(z19b_co2_mean_last));

  tft.setCursor(56, 62);
  tft.setTextColor(ST7735_WHITE);
  tft.print(String(z19b_co2_mean));
  z19b_co2_mean_last = z19b_co2_mean;

  tft.setCursor(56, 91);
  tft.setTextColor(ST7735_BLACK);
  tft.print(String(s8_co2_mean_last));

  tft.setCursor(56, 91);
  tft.setTextColor(ST7735_WHITE);
  tft.print(String(s8_co2_mean));
  s8_co2_mean_last = s8_co2_mean;


  
  tft.setTextSize(1);
  tft.setCursor(110, 4);
  tft.setTextColor(ST7735_BLACK);
  tft.print(String(z14_co2_last));

  tft.setCursor(110, 4);
  tft.setTextColor(ST7735_WHITE);
  tft.print(String(z14_co2));
  z14_co2_last = z14_co2;

  tft.setCursor(110, 33);
  tft.setTextColor(ST7735_BLACK);
  tft.print(String(z19_co2_last));

  tft.setCursor(110, 33);
  tft.setTextColor(ST7735_WHITE);
  tft.print(String(z19_co2));
  z19_co2_last = z19_co2;

  tft.setCursor(110, 62);
  tft.setTextColor(ST7735_BLACK);
  tft.print(String(z19b_co2_last));

  tft.setCursor(110, 62);
  tft.setTextColor(ST7735_WHITE);
  tft.print(String(z19b_co2));
  z19b_co2_last = z19b_co2;

  tft.setCursor(110, 91);
  tft.setTextColor(ST7735_BLACK);
  tft.print(String(s8_co2_last));

  tft.setCursor(110, 91);
  tft.setTextColor(ST7735_WHITE);
  tft.print(String(s8_co2));
  s8_co2_last = s8_co2;


  
  tft.setTextSize(1);
  tft.fillRect(0, 117, 160, 120, ST7735_BLACK);
  tft.setCursor(0, 117);
  tft.print(String(getdate()));

}



void z19setup() {

/*  2     3    6    7
  {0x86,0x00,0x00,0x00},   // mhz_cmnd_read_ppm
  {0x79,0xA0,0x00,0x00},   // mhz_cmnd_abc_enable
  {0x79,0x00,0x00,0x00},   // mhz_cmnd_abc_disable
  {0x87,0x00,0x00,0x00},   // mhz_cmnd_zeropoint
  {0x8D,0x00,0x00,0x00},   // mhz_cmnd_reset
  {0x99,0x00,0x03,0xE8},   // mhz_cmnd_set_range_1000
  {0x99,0x00,0x07,0xD0},   // mhz_cmnd_set_range_2000
  {0x99,0x00,0x0B,0xB8},   // mhz_cmnd_set_range_3000
  {0x99,0x00,0x13,0x88}};  // mhz_cmnd_set_range_5000
*/

//  byte setrangeA_cmd[9] = {0xFF, 0x01, 0x99, 0x00, 0x00, 0x00, 0x07, 0xD0, 0x8F};     // set range 0 - 2000ppm
  byte setrangeA_cmd[9] = {0xFF, 0x01, 0x99, 0x00, 0x00, 0x00, 0x13, 0x88, 0xCB};     // set range 0 - 5000ppm
//  byte setrangeA_cmd[9] = {0xFF, 0x01, 0x79, 0x00, 0x00, 0x00, 0x00, 0x00, 0x86},     // mhz_cmnd_abc_disable
//  byte setrangeA_cmd[9] = {0xFF, 0x01, 0x79, 0xA0, 0x00, 0x00, 0x00, 0x00, 0xE6};     // mhz_cmnd_abc_enable
//  byte setrangeA_cmd[9] = {0xFF, 0x01, 0x86, 0x00, 0x00, 0x00, 0x00, 0x00, 0x79};     // data request
  unsigned char setrangeA_response[9]; 

  mySerial_z19.begin(9600);

  mySerial_z19.write(setrangeA_cmd,9);
  mySerial_z19.readBytes(setrangeA_response, 9);
  int setrangeA_i;
  byte setrangeA_crc = 0;
  for (setrangeA_i = 1; setrangeA_i < 8; setrangeA_i  ) setrangeA_crc =setrangeA_response[setrangeA_i];
  setrangeA_crc = 255 - setrangeA_crc;
  setrangeA_crc  = 1;
  if ( !(setrangeA_response[0] == 0xFF && setrangeA_response[1] == 0x99 && setrangeA_response[8] == setrangeA_crc) ) {
    Serial.println("Range CRC error: "   String(setrangeA_crc)   " / "  String(setrangeA_response[8])   " (bytes 6 and 7)");
  } else {
    Serial.println("Range was set! (bytes 6 and 7)");
  }
  delay(1000);
  mySerial_z19.end();
}

///////////////////////////////////////  setup   /////////////////////////////////////////

void setup(void) {
  Serial.begin(38400);
  Wire.begin();
//  z19setup();
  tft.initR(INITR_BLACKTAB);   // initialize a ST7735S chip, black tab
  Serial.println("init");
    
  tft.setTextWrap(false); // Allow text to run off right edge
  tft.setRotation(3);
  tft.fillScreen(ST7735_BLACK);
  tft_form();
 
}

////////////////////////////////////////////////   loop    /////////////////////////////////////////////////////

void loop() {

  sendRequest_z14();
  sendRequest_z19();
  sendRequest_z19b();
  sendRequest_s8(cmd_s8); 
  s8_co2 = getValue_s8(response_s8); 

  if (!s8_co2_mean) s8_co2_mean = s8_co2;
  if (!z14_co2_mean) z14_co2_mean = z14_co2;
  if (!z19_co2_mean) z19_co2_mean = z19_co2;
  if (!z19b_co2_mean) z19b_co2_mean = z19b_co2;

  s8_co2_mean = s8_co2_mean - smoothing_factor*(s8_co2_mean - s8_co2);
  z14_co2_mean = z14_co2_mean - smoothing_factor*(z14_co2_mean - z14_co2);
  z19_co2_mean = z19_co2_mean - smoothing_factor*(z19_co2_mean - z19_co2);
  z19b_co2_mean = z19b_co2_mean - smoothing_factor*(z19b_co2_mean - z19b_co2);

  if (!s8_co2_mean2) s8_co2_mean2 = s8_co2;
  if (!z14_co2_mean2) z14_co2_mean2 = z14_co2;
  if (!z19_co2_mean2) z19_co2_mean2 = z19_co2;
  if (!z19b_co2_mean2) z19b_co2_mean2 = z19b_co2;

  s8_co2_mean2 = s8_co2_mean2 - smoothing_factor2*(s8_co2_mean2 - s8_co2);
  z14_co2_mean2 = z14_co2_mean2 - smoothing_factor2*(z14_co2_mean2 - z14_co2);
  z19_co2_mean2 = z19_co2_mean2 - smoothing_factor2*(z19_co2_mean2 - z19_co2);
  z19b_co2_mean2 = z19b_co2_mean2 - smoothing_factor2*(z19b_co2_mean2 - z19b_co2);

  
  getdate();
  tft_output();
//  if (((current_minute)%work_period == 0)&&(lastminute != current_minute)) {

String dataString = getdate() "  s8~~:  " String(s8_co2_mean2) "  s8~:  " String(s8_co2_mean) "  s8:  " String(s8_co2) "  z14~~:  " String(z14_co2_mean2) "  z14~:  " String(z14_co2_mean) "  z14:  " String(z14_co2) "  z19~~:  " String(z19_co2_mean2) "  z19~:  " String(z19_co2_mean) "  z19:  " String(z19_co2) "  z19b~~:  " String(z19b_co2_mean2) "  z19b~:  " String(z19b_co2_mean) "  z19b:  " String(z19b_co2);
//  "  z19_t:  " String(z19_t) "  z19b_t:  " String(z19b_t);
    dataString.replace(".",",");
    dataString.replace("  ","t");
    Serial.println(dataString);
  lastminute = current_minute;
//  };

  delay(second_delay);
}

Что же я увидел как только подключил датчик и показания прибора устаканились? Датчик завышал уровень CO2 примерно на 200-250 ppm. Это много, этого нельзя не заметить. Я выставил свою елку на улицу и она там простояла ночь.

Про анемометры:  Кратность воздухообмена в жилых помещениях

Датчики углекислого газа CO2 – виды, преимущества и недостатки

На улице должно быть порядка 400 ppm, а никак не 650.

И я решил откалибровать датчик. В этом одно из главных преимуществ этой модели датчика относительно Z19 без «B»: она калибруется не по 0ppm, а по 400ppm. Т.е. вместо азотной камеры этот датчик для калибровки достаточно просто вынести на улицу.

Вообще, в датчике предусмотрена автоматическая калибровка. Время от времени датчик смотрит на свои показания за несколько прошедших дней и находит в них минимальное значение. Исходя из презумпции, что периодически помещение с датчиком проветривается и там оказывается 400 ppm CO2, датчик меняет свои внутренние коэффициенты с тем, чтобы отснятый минимум соответствовал как раз этому уровню. Так что самый простой вариант — подождать, пока калибровка произойдет автоматически.

Второй вариант — использовать специальную команду. Подать ее в порт и все произойдет.
Третий вариант — замкнуть один из выводов датчика на землю и продержать 7 секунд. Именно такой способ я и выбрал.

Показания сразу стали равны 400 ppm и вскоре я внес датчик домой.

Датчики углекислого газа CO2 – виды, преимущества и недостатки

Как говорится, «взяли вилку». Был перелет, теперь недолет. На те же 250 ppm теперь меньше. Ну что ж, придется ждать, пока сработает автокорректировка.
А пока посмотрим на совпадение показаний других датчиков. MH-Z14A все больше завышает. MH-Z19 путается в показаниях и график выглядит как кардиограмма инфарктника. Но она все-таки больше совпадает с S8, нежели график MH-Z14A. В прошлом тесте все было наоборот. Так что некоторый дрейф присутствует у всех.
Для сглаживания локальных всплесков и провалов снятых значений я применил фильтр Кальмана. Это простой и эффективный способ отбросить лишнее и сконцентрироваться на важном в показаниях приборов. Для настройки фильтра требуется определить его коэффициент, для чего я добавил в выводимую программой таблицу несколько колонок, а потом построил по ним графики.

Про анемометры:  Датчики давления. Виды и работа. Как выбрать и применение

Датчики углекислого газа CO2 – виды, преимущества и недостатки

Датчики углекислого газа CO2 – виды, преимущества и недостатки

Как по мне, 0,15 — отличный выбор.Если прибор нужен в основном для принятия решений уровня «открывать форточку или нет» — то такая точность вполне достаточна, а визуально такой график воспринимается легче.

Еще я захотел сравнить показания встроенных в датчики термометров.

Датчики углекислого газа CO2 – виды, преимущества и недостатки

Нельзя однозначно сказать, что рассматриваемый датчик завышает температуру. Термометр — недокументированная фича, и используется он только для выстраивания внутренних коэффициентов датчика. Так что он измеряет свою температуру, а не окружающую.

Пожалуй, надо проверить работу ШИМ- выхода датчика. В некоторых случаях неудобно опрашивать датчик, посылая байты в компорт. Тогда есть альтернатива: на pwm выходе присутствует прямоугольный сигнал с частотой порядка 1 Гц и со скважностью, пропорциональной концентрации СО2. Его довольно легко анализировать и такой способ весьма защищен от помех.

Датчики углекислого газа CO2 – виды, преимущества и недостатки

Работает. Впрочем, pwm выход есть и на старой версии этого датчика.

А вот аналоговый выход есть только на «B». Напряжение на нем пропорционально концентрации углекислого газа. Проверим:

Датчики углекислого газа CO2 – виды, преимущества и недостатки

Тоже работает. Такой выход позволяет мастерить совсем простые устройства. Буквально из транзистора, пары резисторов и светодиода можно собрать устройство уровня «загорелся красный светодиод — открой окно». Так что это очень полезное новшество.

Конечно, все подробности отличий MH-Z19B и MH-Z19 можно изучить по pdf.Мои же впечатления такие:
Калибровка по 400 ppm — штука работающая и полезная. Датчик выдает более гладкий сигнал, чем его собрат. Вероятно, внутри показания фильтруются. Датчик при запуске не выдает в порт техническую информацию о себе — предел измерений, точность, что-то еще. Так пожалуй лучше, а то если устройство, которое принимает сигнал, настроено что-то там запускать при превышении СО2, то при приходе «2000» может сработать запуск, а датчик просто информировал о своем пределе измерений.
Что еще сказать. Отслеживается зависимость показаний от качества питания. У MH-Z19B — в меньшей степени, у MH-Z19 — в чуть большей. Но больше всего дуреет S8 при недостаточном питании, тому вообще крышу сносит.

Пока я все это изучал, кажется сработала автокалибровка.

Датчики углекислого газа CO2 – виды, преимущества и недостатки

Причем сразу у двух датчиков: MH-Z14А, который завышал, стал слегка занижать. А MH-Z19B, который занижал, стал завышать.

В этом датчике калибровку можно определить по обнулению 6-го байта в ответе датчика на запрос данных. В первый час после запуска датчика у него нулевое значение, потом оно постепенно возрастает и за 24 часа дрходит до 142. На этой отметке происходит автокалибровка, байт сбрасывается на 0 и все начинается сначала. Выглядит это так:

Датчики углекислого газа CO2 – виды, преимущества и недостатки

Со временем они наверняка придут к почти совпадающим показаниям. У меня так уже было с тремя датчиками из прошлого обзора.

Датчики углекислого газа CO2 – виды, преимущества и недостатки

За неделю тогда они практически спелись в унисон.

Итог: все датчики работают. Все показывают более-менее адекватные цифры. Выбирать я бы стал по удобству применения в конкретном приборе.

Товар предоставлен для написания обзора магазином. Обзор опубликован в соответствии с п.18 Правил сайта.

Оцените статью
Анемометры
Добавить комментарий

Adblock
detector