Ардуино на службе здоровья / Хабр

Ардуино на службе здоровья / Хабр Анемометр

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

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

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

Про анемометры:  Газовый котел PROTHERM МЕДВЕДЬ 30 PLO (26 кВт) – характеристики, отзывы, плюсы-минусы, конкуренты и все цены в обзоре

Датчик углекислого газа mq-135 [база знаний “умныеэлементы”]

Вам нужно оценить качество воздуха? Воспользуйтесь датчиком газа MQ135, который хорошо обнаруживает концентрацию углекислого газа (CO2) в воздухе. Помимо углекислого газа датчик обнаруживает в воздухе угарный газ (CO), ацетон, аммоний, спирт (Alcohol), пыль, дым.

Датчик является одним из популярных датчиков серии MQ. Обнаружение концентрации газов в датчике происходит за счёт измерения сопротивления чувствительного материала при взаимодействии газов с этим чувствительным материалом. Датчик обнаруживает концентрации углеводородных газов, паров спирта, пропана, метана, водорода и дыма примерно от 300 до 10000 миллионных долей (ppm – parts per million).

Для описания количества газа в воздухе чаще всего пользуются либо процентным соотношением газа или миллионными долями. К примеру, Сто миллионных долей дыма означает, что если мы разобьём весь объём воздуха на 1’000’000 частей, то 100 из этих частей будут являться частями дыма, а 999’900 частей какие-то другие газы.

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

Перед началом использования рекомендуется прогреть датчик, т.е. оставить его включенным на сутки. Это поможет достичь стабильных показаний в процессе его дальнейшей работы.

Обратите внимание, что показания датчика подвержены влиянию температуры и влажности окружающего воздуха. Поэтому в случае использования датчика в изменяющейся среде, будет необходима компенсация этих параметров.

Основным элементом датчика – является нагреватель, он же является и основным потребителем энергии. Ввиду этого, на плате предусмотрен вывод питания нагревателя. Таким образом, можно управлять нагревателем для экономии энергопотребления. Если же такой задачи не требуется, просто объедините выводы VDD и VH с помощью перемычки, и нагреватель будет запитан от сигнала .

Детектор углекислого газа на ардуино. * coderr

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

     Для понимания я представлю допустимые и опасные уровне СО2:

  • 300 – 400 ppm (идеальное, улица)
  • 400 – 600 ppm ( отличное, проветриваемое помещение )
  • 600 – 800 ppm ( хорошее, 800 не очень то и хорошо но и не плохо :)))
  • 800 – 1000 ppm ( среднее для квартиры, вялость, душнавато )
  • 1000 – 1400 ppm ( низкое, обязательно стоит проветривать )
  • > 1400 ppm ( плохое, нарушение нормальной роботы организма и получения достаточного уровня кислорода )
  • => 2000 ppm (критическое, опасно для здоровья)

     Так как мне часто и по долгу приходиться проводить время в своей комнате за компьютером то я решил позаботиться о своём здоровье и работоспособности. Для анализа ситуации нам поможет станция собранная на основе Ардуино нано и датчика mh-z19b ( да он стоит дорого, но хорошие датчики дёшево стоить и не могут, дешевле брать настоятельно не рекомендую !!! ).  Описывать цену всех компонентов не стану, так как цены постоянно меняются относительно времени и курса доллара. Поэтому сразу приступим к главному, а именно к тому  что я использовал в своём проекте: 

  •  Ардуино нано (чёрная с хорошими кварцами и стабилизаторами напряжения до 600 Ma как по 5V так и по 3.3V, советую).
  • Дисплей st7789-240240-IPS ( один из лучших за такие деньги для контроллеров )
  • Датчик mh-z19b-co2 (на момент моей покупки стоил 1300 р)
  • Датчик температуры и влажности DHT22 ( не самое лучшее решение но и не назвать плохим, вы можете заменить на BME280 – придётся внести правки в код )
  • Часы с памятью eeprom на 4K. DS3231-AT24C32-IIC (добавил до жиру)
  • Сенсорная кнопка TTP223
  • Аккумуляторы 18650 – берите на своё усмотрение но желательно объёмом 3200 mah.
  • Зарядка и защита от разряда Micro-USB-1A-18650
  • Преобразователь питания повышающий до 5V / 600-DC-DC-1-5-5
  • Преобразователь логических уровней 8 канальный
  • Два резистора по 10К. 

     Схема подключения:

Ардуино на службе здоровья / Хабр

     В схеме учитывайте что бы датчик MH-Z19b был подключен к питанию на 5V отдельно ( не питать от стабилизатора Ардуинки ), допускается параллельная запитка с учётом тока не меньше 0,6 – 0,8 Ампера, так как датчик при мерцании лазера в пике потребляет до 176 миллиампер, могут возникнуть просадки в напряжении Ардуинки. Ещё обратите внимание что у преобразователя логических уровней контакт OE притянут к питанию низкого уровня 3.3 V  – это необходимо что бы подать сигнал на включение рабочего режима, рекомендуется ещё притянуть его к земле через резистор как у меня в схеме что бы стабильно отрабатывать при выключении и включении устройства. 

     Кнопка здесь играет пока единственную роль, пробуждение от спящего режима. Ещё чуть не забыл предупредить – все логические контакты mh-z19b работают только на уровне 3.3 V – очень будьте осторожны с подключением, спалить такой датчик большой грех !!.. 

     Главная часть, прошивка: 

#include <Adafruit_GFX.h>    // Core graphics library
#include <SoftwareSerial.h>
#include <Arduino_ST7789_Fast.h>
//--------------------- подключаем часы и энергонезависемую память в них -------------------------
#include "uRTCLib.h"
//#include "uEEPROMLib.h"
uRTCLib rtc(0x68);   // адрес часов      
//uEEPROMLib eeprom(0x57); // адрес EEPROM
//unsigned int addr_EEPROM; // переменная для хранения текущего адреса EEPROM    
//-------------------------------------------------------------------
//------------------------------- подключаем датчик DHT22 ---------------------------------------
#include "DHT.h"
#define DHTPIN 8     
#define DHTTYPE DHT22   
DHT dht(DHTPIN, DHTTYPE);
unsigned long PM_DHT = 0;
//-------------------------------------------------------------------
//------------------------------- подключаем дисплей --------------------------------------------
#define ST7789_DRIVER     // Configure all registers
#define TFT_WIDTH  240
#define TFT_HEIGHT 240
#define TFT_CS    -1  // define chip select pin
#define TFT_DC     10  // define data/command pin
#define TFT_RST    9
#define TFT_MOSI 11  // Data out
#define TFT_SCLK 13  // Clock out
Arduino_ST7789 tft = Arduino_ST7789(TFT_DC, TFT_RST); // создаём объект дисплея
//-------------------------------------------------------------------
//float p = 3.1415926;
//---------------------------- Спящий режим ---------------------------------------
volatile bool buttonBLK = false; // кнопка подсветки
unsigned long PM_BLK = 0; // таймер спящего режима
bool sleep_BLK=false; // флаг спящего режима false - отключен спящий режим
//----------------------- датчик углекислого газа --------------------------------------------
SoftwareSerial mySerial(A0, A1); // A0 - к TX сенсора, A1 - к RX
byte cmd[9] = {0xFF,0x01,0x86,0x00,0x00,0x00,0x00,0x00,0x79}; // переменная и команда для чтения СО2 сенсора
byte noABC[9] = {0xFF,0x01,0x79,0x00,0x00,0x00,0x00,0x00,0x86}; // переменная и команда для отключения автокалибровки
unsigned char response[9]=""; // буфер для хронения полученных данных и сколько бит он занимает
int c_ppm=0; //переменная для присвоения значения со2
unsigned long PM = 0; //переменная для времени чтоб лишний раз делай не использовать
//-------------------------------------------------------------------
#define NUM_VAL (14 1) // размерность массива показаний в графике
int co2ppm[NUM_VAL]; // массив показаний 
int ght=85; // ght - высота графика
#define MIN_co2 0  // минимальный уровень ppm
#define MAX_co2 2000 // максимальный уровень ppm
//====================================================
void buttonTick() {
buttonBLK=true;  // нажатие
}
//==============================================================================================================================================================
void setup(void) {
Serial.begin(9600); //инициализируем УАРТ
dht.begin(); // инициализируем датчик DHT22
Wire.begin(); // I2C
tft.init(TFT_WIDTH, TFT_HEIGHT);
tft.fillScreen(BLACK);
tft.cp437(true);
tft.setRotation(2); // поворот экрана на 180 градусов
pinMode(6, OUTPUT);
analogWrite(6, 255);
pinMode(2, INPUT_PULLUP); // кнопка 
attachInterrupt(0, buttonTick, FALLING);
// rtc.set(0, 54, 11, 7, 18, 4, 20);  // Format:  секунды(0-59), минуты(0-59), часы(0-23), день недели (6 - пятница), день(1-31), месяц(1-12), год(00-99)
zastavka(20000); // заставка и время в секундах 20
delay(500);
mySerial.begin(9600); //инициализируем програмный УАРТ
mySerial.write(noABC, 9); //отключение автокалибровки, когда уверен что датчик показывает правильно эту строчку разкомментировать  (убрать "//" вначале или добавить для калибровки) 
}
//====================================================
void(* resetFunc) (void) = 0;
float flag=false,flag_dni=false;
char arr[30]=""; // буфер для текста... 
//===============================================================================================================================================================
void loop() {
if(buttonBLK) // пробуждаем подсветку
{analogWrite(6, 255);
sleep_BLK=false; // отключаем спящий режим
PM_BLK = millis(); // сбрасываем таймер
buttonBLK=false;}
//--------------------------------------- датчик MH-Z19b
if (millis() < PM) PM = millis(); // если устройство будет работать долго (около 52 дней), то millis обнулится, чтоб это не повлияло на работу
if (millis() - PM >= 60000) //раз в 60 секунд ( это время отклика по документации )
{
PM = millis(); //чтоб начать отчет следующих нескольких секунд 
mySerial.write(cmd, 9); //отправляем запрос на датчик
memset(response, 0, 9); //обнуляем предыдущие ответы с датчика
mySerial.readBytes(response, 9); //получаем ответ с датчика
byte crc = 0; //объявляем переменную для работы
//-------------дальше анализируем ответ датчика
for (int ic = 1; ic < 8; ic  ) crc =response[ic];
crc = 255 - crc;
crc  ;
if ( !(response[0] == 0xFF && response[1] == 0x86 && response[8] == crc) ) {
delay(2000); //ждём когда датчик прогреется
if(flag){
flag=false;
resetFunc(); // перезагружаемся
}
flag=true;
} else { //если всё ок, анализируем полученные данные и узнаем ppm co2 в воздухе
unsigned int responseHigh = (unsigned int) response[2];
unsigned int responseLow = (unsigned int) response[3];
unsigned int ppm = (256*responseHigh)   responseLow;
c_ppm = ppm; //приравниваем переменную "c_ppm" к полученному результату
} 
sprintf(arr, "%sM%s",utf8rus("Воздух:").c_str(),c_ppm," ppm"); // показываем текущий уровень углекислого газа
drawtext(arr,WHITE,BLACK,0,5,2);
co2_ppm(); // сбор данных для графика
drawGraph(0,25,co2ppm); // формируем график
if(!sleep_BLK)
{vozduch(c_ppm); // отценка качества воздуха
PM_DHT = millis(); // после опроса датчика СО2 - обязательно выжидаем время для DHT22
}
}
//---------------------------------------
//--------------------------------------- датчик DHT22
if(!sleep_BLK) // проверяем время только если не спящий режим 
{ 
if (millis() < PM_DHT) PM_DHT = millis(); 
if (millis() - PM_DHT >= 3000) // опросить датчик через 3 сек после опроса СО2 и все следующие разы 
{PM_DHT = millis();
char buff[6]=""; 
sprintf(arr, "%s%s%s",utf8rus("Влажность:   ").c_str(),dtostrf(dht.readHumidity(), 2, 2, buff)," %");
drawtext(arr,WHITE,BLACK,0,115,2);
sprintf(arr, "%s%s%s",utf8rus("Температура: ").c_str(),dtostrf(dht.readTemperature(), 2, 2, buff)," C");
drawtext(arr,GREEN,BLACK,0,135,2);
}
}
//---------------------------------------
delay(900); 
//----------------------- вывод даты и времени
if(!sleep_BLK)
{
rtc.refresh(); // обнавляем время 
// обнавлять только в определённое время когда в 00:00
if(!flag_dni||rtc.hour()==0)
{flag_dni=true;
DayAsString(rtc.dayOfWeek());
drawtext(arr,CYAN,BLACK,0,200,2);
}
sprintf(arr, "%d%s%d%s%d%s%d%s%d%s%d%s",rtc.hour(),":",rtc.minute(),":",rtc.second(),"   ",rtc.day(),".",rtc.month(),".",rtc.year(),"    ");  
drawtext(arr,CYAN,BLACK,0,220,2);
}
//----------------------------- спящий режим
if(!sleep_BLK) // не нужно делать проверку если мы уже в спящем режиме
{
if (millis() < PM_BLK) PM_BLK = millis(); 
if (millis() - PM_BLK >= 300000) //раз в 5 мин (300000)
{
PM_BLK = millis();
analogWrite(6, 0);
sleep_BLK=true; // переводим всё в спящий режим и медленную работу
}
}
}
//==================================================== Конец LOOP
//==================================================== заставка
void zastavka(int timeS){
PM = millis();
bool f_revers=false;
int kk=0;
uint16_t cubik = RGBto565(0,255,0);
for(int j=0; j<120; j  )
{
if (millis() - PM >= timeS)
{break;}
if(j % 2 == 0)
{
if(!f_revers)
{tft.drawRect(120-j, 120-j, j j, j j, cubik);}
else
{ tft.drawRect(j, j, kk-j-j, kk-j-j, cubik);}}
delay(10);
if(j==119 && !f_revers)
{j=0;
kk=240;
cubik = RGBto565(0,0,0);
f_revers=true;
}
if(j==119 && f_revers)
{j=0;
kk=0;
cubik = RGBto565(0,255,0);
f_revers=false;}}
PM = millis();
tft.fillScreen(BLACK);
}
//==================================================== конец заставки
//==================================================== отценка качества воздуха
void vozduch(int c_ppm)
{
int Vtext1=155,Vtext2=175; // позиция текстовых полей по вертикали
int FontSiz=2; // размер шрифта
if(c_ppm>=300 && c_ppm<=400)
{
sprintf(arr, "%s%s",utf8rus("Воздух: ").c_str(),utf8rus("идеальное          ").c_str());
drawtext(arr,GREEN,BLACK,0,Vtext1,FontSiz);
}
else{
if(c_ppm>=400 && c_ppm<=600)
{
sprintf(arr, "%s%s",utf8rus("Воздух: ").c_str(),utf8rus("отличное           ").c_str());
drawtext(arr,GREEN,BLACK,0,Vtext1,FontSiz);
}
else{
if(c_ppm>=600 && c_ppm<=800)
{
sprintf(arr, "%s%s",utf8rus("Воздух: ").c_str(),utf8rus("хорошее           ").c_str());
drawtext(arr,WHITE,BLACK,0,Vtext1,FontSiz);
}
else{
if(c_ppm>=800 && c_ppm<=1000)
{
sprintf(arr, "%s%s",utf8rus("Воздух: ").c_str(),utf8rus("среднее           ").c_str());
drawtext(arr,CYAN,BLACK,0,Vtext1,FontSiz);
}
else{
if(c_ppm>=1000 && c_ppm<=1400)
{
sprintf(arr, "%s%s",utf8rus("Воздух: ").c_str(),utf8rus("низкое            ").c_str());
drawtext(arr,MAGENTA,BLACK,0,Vtext1,FontSiz);
}
else{
if(c_ppm>=1400 && c_ppm<=1800)
{
sprintf(arr, "%s%s",utf8rus("Воздух: ").c_str(),utf8rus("плохое            ").c_str());
drawtext(arr,RED,BLACK,0,Vtext1,FontSiz);
}
else{
if(c_ppm>=1800)
{
sprintf(arr, "%s%s",utf8rus("Воздух: ").c_str(),utf8rus("кретическое          ").c_str());
drawtext(arr,RED,BLACK,0,Vtext1,FontSiz);
}}}}}}}
if(c_ppm>600&&c_ppm<5000)
{
sprintf(arr, "%sM%s",utf8rus("Превышение: ").c_str(),c_ppm-600," ppm");  
drawtext(arr,YELLOW,BLACK,0,Vtext2,FontSiz);
}
else
{
if(c_ppm<600&&c_ppm!=0)
{
sprintf(arr, "%s",utf8rus("Превышение: нет        ").c_str());  
drawtext(arr,YELLOW,BLACK,0,Vtext2,FontSiz);
}
}
}
//==================================================== конец отценки качества воздуха
//==================================================== Дни недели
void DayAsString(int day) {
switch (day) {
case 1: {strcpy(arr,utf8rus("     Воскресенье").c_str()); break;} 
case 2: {strcpy(arr,utf8rus("     Понедельник").c_str()); break;} 
case 3: {strcpy(arr,utf8rus("     Вторник    ").c_str()); break;} 
case 4: {strcpy(arr,utf8rus("     Среда      ").c_str()); break;} 
case 5: {strcpy(arr,utf8rus("     Четверг    ").c_str()); break;}  
case 6: {strcpy(arr,utf8rus("     Пятница    ").c_str()); break;} 
case 7: {strcpy(arr,utf8rus("     Суббота    ").c_str()); break;} 
}
}
//====================================================
//==================== создаём массив значений для графика ================================
void co2_ppm()
{
for(int i=0;i<NUM_VAL-1;i  ) co2ppm[i]=co2ppm[i 1];
int vv = constrain(c_ppm, MIN_co2, MAX_co2);
co2ppm[NUM_VAL-1] = (long)(vv-MIN_co2)*ght/(MAX_co2-MIN_co2);
}
//====================================================
//================== график углекислого газа ==================================
void drawGraph(int xg, int yg, int valTab[])
{
int hh=0, nx=17; // nx ширина графика
uint8_t r,g,b;
for(int i=0;i<NUM_VAL-1;i  ) {
int dy = valTab[i 1]-valTab[i];
for(int j=0;j<nx;j  ) {
hh = valTab[i] dy*j/nx;
tft.rgbWheel(2*85*(ght-hh-30)/ght,&r,&g,&b);
// tft.rgbWheel(2*60*(ght-hh-30)/ght,&r,&g,&b);
tft.drawFastVLine(1 xg i*nx j, yg 1, ght-hh, RGBto565(240,240,240));
tft.drawFastVLine(1 xg i*nx j, yg 1 ght-hh, hh, RGBto565(r,g,b));
}
tft.drawLine(1 xg i*nx,yg 1 ght-valTab[i],1 xg (i 1)*nx,yg 1 ght-valTab[i 1],BLACK);
}
}
//====================================================
//====================================================
void drawtext(char *text, uint16_t color, uint16_t color2, int x, int y, int sizeF) {
tft.setCursor(x, y);
tft.setTextColor(color,color2);
tft.setTextSize(sizeF);
tft.setTextWrap(true);
tft.print(text);
}
//====================================================
//====================================================
/* Recode russian fonts from UTF-8 to Windows-1251 */
String utf8rus(String source)
{
int i,k;
String target;
unsigned char n;
char m[2] = { '0', '' };
k = source.length(); i = 0;
while (i < k) {
n = source[i]; i  ;
if (n >= 0xC0) {
switch (n) {
case 0xD0: {
n = source[i]; i  ;
if (n == 0x81) { n = 0xA8; break; }
if (n >= 0x90 && n <= 0xBF) n = n   0x30;
break;
}
case 0xD1: {
n = source[i]; i  ;
if (n == 0x91) { n = 0xB8; break; }
if (n >= 0x80 && n <= 0x8F) n = n   0x70;
break;
}
}
}
m[0] = n; target = target   String(m);
}
return target;
}
//====================================================

     В основном код хорошо прокомментирован, поэтому добавлю лишь пару нюансов. rtc.set(0, 54, 11, 7, 18, 4, 20); – функция установки времени, в неё нужно передать время, дату и скомпилировать проект. Когда часы настроились то закомментируйте эту строчку и перепрошейте устройство снова. mySerial.write(noABC, 9);  – функция калибровки датчика mh-z19b, для этого вам понадобиться закомментировать её, прошить устройство, положить его на улице  к примеру в сарае или гараже (если погода не сильно холодная) и оставить на 24 часа, после этого датчик уличный уровень углекислого газа будет считать за 400 ppm – и уже относительно этого уровня измерять ваш углекислый газ. Когда датчик откалибровали, то раскомментируйте эту функцию и прошейте устройство снова.  

Про анемометры:  Датчик качества воздуха CCS811: инструкция по использованию и примеры [Амперка / Вики]

     Вот что приблизительно получилось у меня, корпус на момент написания статьи я ещё не успел сделать но думаю это и не так важно.

Ардуино на службе здоровья / Хабр

У Ардуинки где находиться разъём USB я отпаял диод-предохранитель что бы не подавать на неё питание с разъёма, это питание 5V я взял что бы подать на модуль зарядки. ( Прошивать устройство только при включенном переключателе с аккумуляторами ). Так же на всех модулях я выпаил все ненужные светодиоды что бы сэкономить заряд аккумуляторов. Конденсатор параллельно питанию Ардуино я поставил на 470 микрофарад 25 вольт – чисто для успокоения души, вы можете его не ставить или заменить на другой.  

Ардуино на службе здоровья / Хабр
Ардуино на службе здоровья / Хабр

И снова датчик углекислого газа (co2) "mh-z19b". обзор – предостережение

// Подключение LCD (1602 с контроллером HD44780)

// LCD RS pin to digital pin 7

// LCD Enable pin to digital pin 6

// LCD D4 pin to digital pin 5

// LCD D5 pin to digital pin 4

// LCD D6 pin to digital pin 3

// LCD D7 pin to digital pin 2

// LCD R/W pin to ground

// LCD VSS pin to ground

// LCD VCC pin to 5V

// 10K resistor: ends to 5V and ground, wiper to LCD VO pin

/*
— Подключение датчиков————-
первый подключается к: второй подключается к:
Arduino | 1 CO2 sensor MH-Z19B Arduino | 2 CO2 sensor MH-Z19B
pin A0 | RX pin A2 | RX
pin A1 | TX pin A3 | TX
pin A4 | Vo pin A5 | Vo
pin 10 | PWM pin 11 | PWM
GND и Vcc (земля и питание) датчиков подключаем соответственно к массе и к 5В ардуины
ВНИМАНИЕ! UART шину данных к Ардуино подключаем только через конвертер уровней 5V <=> 3.3V
*/

#include <SoftwareSerial.h>
#include <LiquidCrystal.h>
LiquidCrystal lcd(7, 6, 5, 4, 3, 2);
#define zm1_pwm_pin 10
#define zm1_a_pin A4
#define zm2_pwm_pin 11
#define zm2_a_pin A5
SoftwareSerial zm1(A0, A1); // RX, TX
SoftwareSerial zm2(A2, A3); // RX, TX

int zm1_t = 0;
int zm1_ss = 0;
int zm1_co2 = 0;
int zm2_t = 0;
int zm2_ss = 0;
int zm2_co2 = 0;
byte cmd[9] = {0xFF,0x01,0x86,0x00,0x00,0x00,0x00,0x00,0x79};
byte abc[9] = {0xFF,0x01,0x79,0x00,0x00,0x00,0x00,0x00,0x86};
unsigned char response2[9];
unsigned char response1[9];
int ppm1 = 0;
int ppm2 = 0;
int anlg1 = 0;
int anlg2 = 0;

void setup() {
// Serial.begin(9600); // нужно только для дебага, в этом скетче нету вывода в консоль, не спользуется.
lcd.begin(16, 2);
pinMode(zm1_pwm_pin, INPUT);
pinMode(zm1_a_pin, INPUT);
pinMode(zm2_pwm_pin, INPUT);
pinMode(zm2_a_pin, INPUT);
while(zm1_pwm_pin == LOW || zm2_pwm_pin == LOW ){
lcd.setCursor(0, 0);
lcd.print(“*Now Warming Up*”);
lcd.setCursor(0, 1);
lcd.print(“**Please wait!**”);
}
//———– конфигурируем 1 датчик
zm1.begin(9600);
delay(100);
zm1.write(abc, 9); // запрещаем автокалибровку
//——– Задаем принудительно работу в диапазоне 5000ppm
byte setrangeA_cmd[9] = {0xFF, 0x01, 0x99, 0x00, 0x00, 0x00, 0x13, 0x88, 0xCB};
unsigned char setrangeA_response[9];
zm1.write(setrangeA_cmd,9);
zm1.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;
lcd.setCursor(0, 0);
if ( !(setrangeA_response[0] == 0xFF && setrangeA_response[1] == 0x99 && setrangeA_response[8] == setrangeA_crc) ) {
lcd.print(«RangeERR» String(setrangeA_crc) “/” String(setrangeA_response[8]));
} else {
lcd.print(“*Range was set!*”);
}
zm1.end();
delay(100);
//———– конфигурируем 2 датчик
zm2.begin(9600);
delay(100);
zm2.write(abc, 9); // запрещаем автокалибровку
//——– Задаем принудительно работу в диапазоне 5000ppm
byte setrangeB_cmd[9] = {0xFF, 0x01, 0x99, 0x00, 0x00, 0x00, 0x13, 0x88, 0xCB};
unsigned char setrangeB_response[9];
zm2.write(setrangeB_cmd,9);
zm2.readBytes(setrangeB_response, 9);
int setrangeB_i;
byte setrangeB_crc = 0;
for (setrangeB_i = 1; setrangeB_i < 8; setrangeB_i ) setrangeB_crc =setrangeB_response[setrangeB_i];
setrangeB_crc = 255 — setrangeB_crc;
setrangeB_crc = 1;
lcd.setCursor(0, 1);
if ( !(setrangeB_response[0] == 0xFF && setrangeB_response[1] == 0x99 && setrangeB_response[8] == setrangeB_crc) ) {
lcd.print(«RangeERR» String(setrangeB_crc) “/” String(setrangeB_response[8]));
} else {
lcd.print(“*Range was set!*”);
}
zm2.end();
delay(2000);
}

void loop() {
// ***** узнаём концентрацию CO2 через UART: *****
zm1.begin(9600);
zm1.write(cmd, 9);
memset(response1, 0, 9);
zm1.readBytes(response1, 9);
int i;
byte crc = 0;
for (i = 1; i < 8; i ) crc =response1[i];
crc = 255 — crc;
crc ;
if ( !(response1[0] == 0xFF && response1[1] == 0x86 && response1[8] == crc) ) {
lcd.setCursor(0, 0);
lcd.print(«zm1 ERR: » String(crc) “/” String(response1[8]));
} else {
unsigned int responseHigh = (unsigned int) response1[2];
unsigned int responseLow = (unsigned int) response1[3];
unsigned int responseTT = (unsigned int) response1[4];
unsigned int responseSS = (unsigned int) response1[5];
zm1_t = responseTT-40;
zm1_ss = responseSS;
zm1_co2 = (256*responseHigh) responseLow;
};
// ***** узнаём концентрацию CO2 через PWM: *****
unsigned long th, tl;
do {
th = pulseIn(zm1_pwm_pin, HIGH, 1004000) / 1000;
tl = 1004 — th;
ppm1 = 5000 * (th-2)/(th tl-4); // расчёт для диапазона от 0 до 5000ppm
} while (th == 0);

Про анемометры:  Падает в котле отопления давление – что делать? - SteepMEN

// ***** читаем по аналогу: *****
anlg1 = analogRead(zm1_a_pin); // показания на экране отображаются в АЦП «попугаях», а точнее в 5/1024 В, для вывода в вольтах не хватило разрешения дисплея.
zm1.end();
delay(1000);
// ***** узнаём концентрацию CO2 через UART: *****
zm2.begin(9600);
zm2.write(cmd, 9);
memset(response2, 0, 9);
zm2.readBytes(response2, 9);
crc = 0;
for (i = 1; i < 8; i ) crc =response2[i];
crc = 255 — crc;
crc ;
if ( !(response2[0] == 0xFF && response2[1] == 0x86 && response2[8] == crc) ) {
lcd.setCursor(0, 1);
lcd.print(«zm2 ERR: » String(crc) “/” String(response2[8]));
} else {
unsigned int responseHigh = (unsigned int) response2[2];
unsigned int responseLow = (unsigned int) response2[3];
unsigned int responseTT = (unsigned int) response2[4];
unsigned int responseSS = (unsigned int) response2[5];
zm2_t = responseTT-40;
zm2_ss = responseSS;
zm2_co2 = (256*responseHigh) responseLow;
};
// ***** узнаём концентрацию CO2 через PWM: *****
unsigned long th2, tl2;
do {
th2 = pulseIn(zm2_pwm_pin, HIGH, 1004000) / 1000;
tl2 = 1004 — th2;
ppm2 = 5000 * (th2-2)/(th2 tl2-4); // расчёт для диапазона от 0 до 5000ppm
} while (th2 == 0);

// ***** читаем по аналогу: *****
anlg2 = analogRead(zm2_a_pin); // показания на экране отображаются в АЦП «попугаях», а точнее в 5/1024 В, для вывода в вольтах не хватило разрешения дисплея.
zm2.end();

//———————- Выводим на дисплей — // первое значение на экране «u» юзначает данные полученные по UART, второе — «w» по PWM, третье — «a» по Analog портам.

lcd.clear();
lcd.setCursor(0, 0);
lcd.print(«u»); lcd.print(zm1_co2);
lcd.setCursor(5, 0);
lcd.print(” w”); lcd.print(ppm1);
lcd.setCursor(11, 0);
lcd.print(” a”); lcd.print(anlg1);
lcd.setCursor(0, 1);
lcd.print(«u»); lcd.print(zm2_co2);
lcd.setCursor(5, 1);
lcd.print(” w”); lcd.print(ppm2);
lcd.setCursor(11, 1);
lcd.print(” a”); lcd.print(anlg2);

delay(5000);
}

Исходный код программы (скетча)

Модуль oled дисплея (0.96’ oled display module)

OLED (Organic Light-Emitting Diodes, органический светоизлучающий диод) – это светоизлучающая технология, которая применяется в большинстве современных телевизоров. В OLED дисплеях используется тот же принцип формирования изображения, что и в современных телевизорах, только количество пикселей в них значительно меньше.

Для нашего проекта мы использовали монохромный 7-ми контактный OLED дисплей SSD1306 с диагональю 0.96”. Он может использовать 3 различных коммуникационных протокола: 3-х проводный SPI, 4-х проводный SPI и I2C.

Назначение его контактов (распиновка) приведены в следующей таблице.

Номер контактаНазвание контактаАльтернативное название контактаНазначение контакта
1GndGroundземля
2VddVcc, 5Vнапряжение питания (в диапазоне 3-5 В)
3SCKD0, SCL, CLKиспользуется как контакт синхронизации (clock pin). Применяется в интерфейсах I2C и SPI
4SDAD1, MOSIконтакт данных. Применяется в интерфейсах I2C и SPI
5RESRST, RESETконтакт сброса модуля. Применяется в интерфейсе SPI
6DCA0контакт команд (Data Command pin). Применяется в интерфейсе SPI
7CSChip Select (выбор чипа)используется когда несколько устройств взаимодействуют по интерфейсу SPI

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

Технические характеристики OLED дисплея SSD1306:

  • драйвер микросхемы OLED: SSD1306;
  • разрешение: 128 x 64;
  • угол зрения: >160°;
  • входное напряжение: 3.3V ~ 6V;
  • цвет пикселов: синий;
  • диапазон рабочих температур: -30°C ~ 70°C.

Подключение mh-z19 к ардуино

Наша задача на сегодня – разобрать подключение MH-Z19 к Ардуино, а также базовые характеристики и практичность применения PIR датчика углекислого газа. В принципе с последним пунктом более-менее все понятно. Мы все понимаем, что в целях безопасности не помешает постоянно отслеживать концентрацию CO2 в квартире ∕ доме ∕ офисе. Конечно, можно приобрести готовую бытовую систему, но она обойдется вам в круглую сумму, а вот так называемый комнатный прибор, сделанный своими руками, сэкономит семейный бюджет и принесет огромную пользу.

Само устройство инфракрасного измерителя концентрации CO2, с которым мы сегодня знакомимся, создано для определения удельного содержания газа. Сенсор не требует высокой мощности, стабилен в работе (до 5 лет эксплуатации), имеет малое энергопотребление и высокую чувствительность, способен передавать данные через 2 выходных интерфейса: PWM и UART.

Теперь обратимся к техническим особенностям модуля:

  • напряжение: 3.6 – 5.5V;
  • потребляемый ток: < 18 мА;
  • диапазон измерений: 0 – 0.5 %;
  • время измерения: T90 < 60 сек.;
  • диапазон рабочих температур: 0-50 С;
  • допустимая влажность: 0 – 95% RH;
  • габариты: 33 ×20 × 9 мм;
  • вес: около 21 грамм

Рассмотрим схему подключения инфракрасного датчика к Arduino:
Ардуино на службе здоровья / Хабр

В ней мы задействовали такие аппаратные компоненты: плату расширения Arduino Nano (можно использовать другие совместимые микроконтроллеры), модуль MH-Z19, комплект соединительных проводов.

Наша следующая задача – написать коды для чтения значений с устройства (и проверки его работоспособности). Пример такого скетча:

#define pwmPin 5
#define LedPin 13
int prevVal = LOW;
long th, tl, h, l, ppm;
void setup() {
Serial.begin(9600);
pinMode(pwmPin, INPUT);
pinMode(LedPin, OUTPUT);
}
void loop() {
long tt = millis();
int myVal = digitalRead(pwmPin);
//Если обнаружили изменение
if (myVal == HIGH) {
digitalWrite(LedPin, HIGH);
if (myVal != prevVal) {
h = tt;
tl = h - l;
prevVal = myVal;
}
}  else {
digitalWrite(LedPin, LOW);
if (myVal != prevVal) {
l = tt;
th = l - h;
prevVal = myVal;
ppm = 5000 * (th - 2) / (th   tl - 4);
Serial.println("PPM = "   String(ppm));
}
}
}

Управление осуществляется через монитор COM-порта (Ctrl Shift M).

На базе указанного образца можно модернизировать всю систему, дополнив ее. Например, при желании можно собрать портативный девайс для измерения уровня CO2 в воздухе. Для него будут использоваться дисплей (типа Nokia 5110), источник питания на 5 В, а также несколько специализированных библиотек.

Для справки: существует несколько типов или классов «загазованности» помещений согласно ГОСТ:
Ардуино на службе здоровья / ХабрИменно на эти параметры мы ориентируемся при тестировании.
Надеемся, вы оцените преимущества подобного прибора и будете им пользоваться на постоянной основе! До новых встреч!

Схема проекта

Схема подключения инфракрасного датчика CO2 к плате Arduino представлена на следующем рисунке.

Схема подключения инфракрасного датчика CO2 к плате ArduinoКак видите, схема достаточно простая. Инфракрасный датчик CO2 и OLED дисплей запитываются от контактов 5V и GND платы Arduino. Сигнальный аналоговый контакт датчика CO2 подключен к контакту A0 платы Arduino Nano. OLED дисплей подключен к плате Arduino по интерфейсу SPI. Соединения между OLED дисплеем и платой Arduino представлены в следующей таблице:

OLED дисплейПлата Arduino
GNDGround
VCC5V
D010
D19
RES13
DC11
CS12

После сборки проекта на макетной плате у нас получилась конструкция следующего вида:

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

Adblock
detector