Термо/гигрометр + архив данных.

Нет ответов
Мужик из полиуретана
Offline
Зарегистрирован: 12.09.2015

Первый мой основательный проект, код весьма корявый, но рабочий.  Буду рад если укажете на недочеты, ну и вдруг кому-нибудь пригодится.

Термометр-гигрометр для помещения:

 1)на двух dht22 и одном ds18b20, оба DHT22 расположены внутри помещения, на разных высотах (например, 1.5 и 9 метров), DS18b20 расположен на улице, вне помещения.

2)С LCD 1602 для отображения данных, правда без I2C (китайцы не положили в посылку, пришлось так делать).

3) С модулем часов DS3231 для записи данных с датчиков в определенное время .

4) С энкодером, чтобы эти данные выводить на экран и листать записи за различное время

У Термо-гигрометра два режима: 

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

2) При вращении энкодера, термо-гигрометр переходит в режим считывания из EEPROM ранее записанных данных, где отображается дата записи, час записи, средняя влажность, температура с нижнего DHT22, температура с верхнего DHT22 и с уличного DS18B20, памяти EEPROM (1024 кб) хватит на 5 суток, при условии записи данных раз в час (раз в час - сугубо для теста, у меня записывает два раза в сутки, т.е. памяти хватит на 63 дня, мне этого вполне хватает, поэтому я так щедро растянул одну запись аж на 8 байт)

Если положение энкодера не меняется, данные отображаются 10 секунд, а потом термо-гигрометр переходит в режим отображения данных в реальном времени. 

P.S. Для корректной работы записи необхожимо заранее прописать в ячейки 0 и 1 EEPROM значение 2, с помощью отдельного скетча с функцией EEPROMWriteInt

Подключение: Энкодер - на пины 2,3; оба DHT22 - на 8 и 9 пины, DS18B20 - на 13 пин, но учитывая наличие там встроенного светодиода, подтягивающий резистор берем поменьше, 2 кОма

LCD подключаем на все оставшиеся цифровые пины, DS3231 - на шину I2C (аналоговые входа 4 и 5). 

Питание от БП, поэтому с энергосбереганием тоже не заморачивался.

 

 */// подключаем все нужные библиотеки
#include <OneWire.h>
#include "DHT.h"                
#include <DallasTemperature.h>
#include <Time.h>
#include <Wire.h>
#include <DS1307RTC.h>
#include <EEPROM.h>
#include <LiquidCrystal.h>
#include <Encoder.h>

#define ENCODER_USE_INTERRUPTS

#define ONE_WIRE_BUS 13        //номер пина ds18b20
#define DHTPINa 8                // номер пина первого dht22
#define DHTPINb 9           // номер пина второго dht22

OneWire oneWire(ONE_WIRE_BUS);   //создаем обьект для ds18b20

DHT dhta(DHTPINa, DHT22);        // и для dht22
DHT dhtb(DHTPINb, DHT22); 

LiquidCrystal lcd(12, 11, 10, 5, 4, 6, 7);  //подключаем экран
Encoder myEnc(3, 2);                       // энкодер, на пины с прерыванием
DallasTemperature sensors(&oneWire);  //подключаем ds18b20
DeviceAddress Thermometer1 = { 0x28, 0xFF, 0xEE, 0xBB, 0x62, 0x15, 0x01, 0x92 }; //адрес термометра

int addressWrite;            // переменная ячейки записи       
int addressRead = 2;             // переменная ячейки чтения, 2 - т.к. ячейки 0 и 1 зарезервируем под значение addressWrite, чтобы оно не сбивалось при каждом включении
boolean counterWrite = 0;            // переменная счетчик записи
byte value;                      // переменная значения ячейки
boolean counter_temp = true;     // счетчик отображения температуры
long oldPosition  = -999;        // предыдущее значение энкодера
long previousMillis = 0;         
long interval = 10000;           // время показа данных, считанных по энкодеру

// РИСУЕМ РУССКИЕ БУКВЫ И СИМВОЛЫ

byte L_kr_lit[8] =               //рисуем нужную кириллицу, буква л
 {
   B00000,
   B00000,
   B01111,
   B00101,
   B00101,
   B10101,
   B01001,
   B00000,
 };
 
byte J_kr_lit[8] =               //рисуем нужную кириллицу, буква ж
 {
   B00000,
   B00000,
   B10101,
   B10101,
   B01110,
   B10101,
   B10101,
   B00000,
 };
 
byte N_kr_lit[8] =               //рисуем нужную кириллицу, буква н
 {
   B00000,
   B00000,
   B10001,
   B10001,
   B11111,
   B10001,
   B10001,
   B00000,
 }; 
 
byte P_kr_lit[8] =               //рисуем нужную кириллицу, буква п
 {
   B00000,
   B00000,
   B11111,
   B10001,
   B10001,
   B10001,
   B10001,
   B00000,
 }; 
 
byte M_kr_lit[8] =               //рисуем нужную кириллицу, буква м
 {
   B00000,
   B00000,
   B10001,
   B11011,
   B10101,
   B10001,
   B10001,
   B00000,
 }; 

byte U_kr_lit[8] =               //рисуем нужную кириллицу, буква У
 {
   B10001,
   B10001,
   B10001,
   B01010,
   B00100,
   B01000,
   B10000,
   B00000,
 }; 
 
byte Cels_kr_lit[8] =            //рисуем значок градуса и С
 {
   B11000,
   B11000,
   B00010,
   B00101,
   B00101,
   B00100,
   B00101,
   B00010,
 }; 


void setup() 
{
  lcd.createChar(2, L_kr_lit);  // создаем чары для наших нарисованных букв и символов
  lcd.createChar(3, J_kr_lit);
  lcd.createChar(4, N_kr_lit);
  lcd.createChar(5, P_kr_lit);
  lcd.createChar(6, M_kr_lit);
  lcd.createChar(7, Cels_kr_lit);
  lcd.createChar(1, U_kr_lit);
  lcd.begin(16, 2);             // инициализируем экран       
  sensors.begin();              // запускаем датчики
  sensors.setResolution(Thermometer1, 10); // и задаем разрешение датчика(от 8 до 12(бит),при 12 лагает)
  dhta.begin();                 // запускаем оба DHT
  dhtb.begin();
  setSyncProvider(RTC.get);     // и модуль часов
}

void loop() 
  {
  long newPosition = myEnc.read();     // считываем с энкодера позицию, записываем в переменную
  
  if (newPosition != oldPosition)      // если энкодер изменил свое положение, то
  {
    unsigned long currentMillis = millis();   // запоминаем время начала таймера
    if(newPosition < oldPosition )            // если энкодер крутили по часовой
      {
  
      value = EEPROM.read(addressRead);         // то выводим на экран ранее записанные в еепром данные
      lcd.clear();                                    // чтобы ничего не мешало - очистим экран
      lcd.print(value, DEC);                                   // первая ячейка - число
      lcd.print(".");
      addressRead = addressRead + 1;
      
      value = EEPROM.read(addressRead);
      lcd.print(value, DEC);
      lcd.print(".");                                         // вторая - месяц
      addressRead = addressRead + 1;
 
      value = EEPROM.read(addressRead);
      lcd.print(value, DEC);                           // третья - год, 
      addressRead = addressRead + 1;
      lcd.print(" ");
 
      value = EEPROM.read(addressRead);
      if (value < 10)                                 // для красивого отображения часов меньше 10 (например, не 2:5, а 02:05)
      lcd.print("0");
      lcd.print(value, DEC);                                  // четвертая - час записи.
      addressRead = addressRead + 1;
      lcd.print(" ");
 
      value = EEPROM.read(addressRead);
      lcd.print("B");
      lcd.print(value, DEC);                                 // влажность
      lcd.print("%");                                     // и значок процента
      addressRead = addressRead + 1;
    
      lcd.setCursor(0, 1);                               // переходим на вторую строку
      value = EEPROM.read(addressRead);
      lcd.print("\x5e");                                 // что-то типа символа стрелки вверх
      lcd.print(value-127, DEC);                   // шестая - температура с первого DHT22, за ноль берем значение 127, чтобы записывать в один байт и отрицательную температуру.
      lcd.print( "\7" );                // наш значок градуса
      lcd.print(" ");
      addressRead = addressRead + 1;
 
      value = EEPROM.read(addressRead);
      lcd.print("v");                                // аналог стрелки вниз
      lcd.print(value-127, DEC);
      lcd.print( "\7" ); 
      lcd.print(" ");                                 // седьмая - температура с второго DHT22
      addressRead = addressRead + 1;
 
      value = EEPROM.read(addressRead);
      lcd.print("\x7e");                              //стрелка вправо, как бы температура снаружи
      lcd.print(value-127, DEC);
      lcd.print( "\7" );                             // восьмая - температура с ds18b20
      addressRead = addressRead + 1;
 

      if(addressRead == EEPROM.length())                        // если достигли конца EEPROM - начинаем сначала
      addressRead = 2;                                   // 2 - т.к. 0 и 1 уже используюся, напоминаю      
      oldPosition = newPosition;                             //текущее значение становится предыдущим
      while( (currentMillis - previousMillis < interval) && (newPosition == oldPosition) )    // цикл, во время которого мы просто ждем 10 секунд(показывая считанные данные на экране)
      {                                                                                       // поглядывая, не прошли ли они и слушаем, не изменилось ли положение энкодера
       currentMillis = millis();
       newPosition = myEnc.read();
       }
       previousMillis = currentMillis;                                                       //обновляем значение
      }
    else
    {
      addressRead = addressRead - 16;                                                       //для листания в обратную сторону все аналогично, только отходим на 2 записи(по 8 байт) назад,     
      value = EEPROM.read(addressRead);                                                     // чтобы первая прочитанная была предыдущей по сравнению с листанием вперед
      lcd.clear();
      lcd.print(value, DEC);                                   // первая ячейка - число
      lcd.print(".");
      addressRead = addressRead + 1;
      
      value = EEPROM.read(addressRead);
      lcd.print(value, DEC);
      lcd.print(".");                                         // вторая - месяц
      addressRead = addressRead + 1;
 
      value = EEPROM.read(addressRead);
      lcd.print(value, DEC);                           // третья - год, 
      addressRead = addressRead + 1;
      lcd.print(" ");
 
      value = EEPROM.read(addressRead);
      if (value < 10)                                 // для красивого отображения часов меньше 10 (например, не 2:5, а 02:05)
      lcd.print("0");
      lcd.print(value, DEC);                                  // четвертая - час записи.
      addressRead = addressRead + 1;
      lcd.print(" ");
 
      value = EEPROM.read(addressRead);
      lcd.print("B");
      lcd.print(value, DEC);                                 // влажность
      lcd.print("%");                                     // 
      addressRead = addressRead + 1;
    
      lcd.setCursor(0, 1);
      value = EEPROM.read(addressRead);
      lcd.print("\x5e");
      lcd.print(value-127, DEC);
      lcd.print( "\7" );                // шестая - температура с первого DHT22
      lcd.print(" ");
      addressRead = addressRead + 1;
 
      value = EEPROM.read(addressRead);
      lcd.print("v");
      lcd.print(value-127, DEC);
      lcd.print( "\7" ); 
      lcd.print(" ");                                 // седьмая - температура с второго DHT22
      addressRead = addressRead + 1;
 
      value = EEPROM.read(addressRead);
      lcd.print("\x7e");
      lcd.print(value-127, DEC);
      lcd.print( "\7" ); // восьмая - температура с ds18b20
      addressRead = addressRead + 1;
 

      if(addressRead == EEPROM.length())                        // если достигли конца EEPROM - начинаем сначала
      addressRead = 2; 
      oldPosition = newPosition;
      while( (currentMillis - previousMillis < interval) && (newPosition == oldPosition) ) 
      {
       currentMillis = millis();
       newPosition = myEnc.read();
       }
       previousMillis = currentMillis;
      }
    } 
  
  sensors.requestTemperatures();   // запрашиваем данные с датчиков, пишем в переменные
  float h1 = dhta.readHumidity();
  float h2 = dhtb.readHumidity();
  float t3 = sensors.getTempC(Thermometer1);                                                          
  float t1 = dhta.readTemperature();
  float t2 = dhtb.readTemperature();

  if (isnan(h1) || isnan(t1) || isnan(h2) || isnan(t2) || isnan (t3) ) 
  // на случай если не удалось считать данные с датчика - выдадим моргающее 10 раз сообщение о ошибке
  {
    for (int i=0; i < 10; i++)
    {
      lcd.clear();
      lcd.setCursor(0, 0);
      lcd.print( "Sensor Error" );
      delay(200);
      lcd.clear();
      delay(200);
    }
  return;
  }

  byte humydity;                    // переменная для средней влажности
  humydity = ((h1+h2)/2);           // получаем и записываем среднюю влажность
 
  int temp;                         // переменная для средней температуры
  temp = ((t1+t2)/2);               // получаем и записываем среднюю влажность

  lcd.clear();                      // очистим для начала экран
  lcd.setCursor(0, 0);              // зададим координаты 
  lcd.print( "B\2a\3\4.:" );        // пишем "Влаж.:", самое значение влажности и процент
  lcd.print(humydity);                                         
  lcd.print("%");                                                             
    
  lcd.setCursor(11, 0);             // переходим на вторую строку
  if (hour() < 10)                  // для красивого отображения часов и минут меньше 10 (например, не 2:5, а 02:05)
    {lcd.print("0");}
  lcd.print( hour() );              // пишем часы и минуты
  lcd.print( ":" );
  if (minute() < 10)
    {lcd.print("0");}
  lcd.print( minute() );

  if (counter_temp)
  // в зависимости от счетчика показываем либо температуру внутри и меняем счетчик, либо уличную и меняем счетчик (время, за которое ардуино пробегает луп - около секунды, получается
  {                                                                                // чередование температуры внутри и снаружи раз в секунду.
    counter_temp = false;
    lcd.setCursor(0, 1);
    lcd.print( "Te\6\5.:" );        // пишем "Темп.:"
    lcd.print(temp);
    lcd.print( "\7" );              // и наш значок градусов
   
  }
  else
  {
    counter_temp = true;
    lcd.setCursor(0, 1);           // аналогично для уличной температуры, только пишем "Темп.Ул.:"
    lcd.print( "Te\6\5.\1\2.:" );                                            
    lcd.print(t3);
    lcd.print("\7");
  }  

  if (counterWrite == 0 && minute() == 0 )                              // условия для записи, одна запись в нужную минуту
  {
   lcd.clear();
   lcd.print("Data write");                                            // просто для индикации, что запись прошла

   addressWrite = EEPROMReadInt(0);                                    // считывает значение, в котором хранится запись о прошлом адресе записи
   delay(50);
      
   EEPROM.write(addressWrite, day());
   addressWrite = addressWrite + 1;                                     // переход на следующий адрес
   if (addressWrite == EEPROM.length())                                 // если заполнилось - начинаем сначала
    addressWrite = 2;
   delay(50);                                                           // на всякий случай
   
   EEPROM.write(addressWrite, month());
   addressWrite = addressWrite + 1;
   if (addressWrite == EEPROM.length())
    addressWrite = 2;
   delay(50);

   EEPROM.write(addressWrite,(byte) year() - 2000 );                    // записываем год минус 2000 чтобы влезло в один байт
   addressWrite = addressWrite + 1;
   if (addressWrite == EEPROM.length())
    addressWrite = 2;
   delay(50);
   
   EEPROM.write(addressWrite, hour());
   addressWrite = addressWrite + 1;
   if (addressWrite == EEPROM.length())
    addressWrite = 2;
   delay(50);
   
   EEPROM.write(addressWrite, (byte) humydity);
   addressWrite = addressWrite + 1;
   if (addressWrite == EEPROM.length())
    addressWrite = 2;
   delay(50);
                                                      
   EEPROM.write(addressWrite, (byte) t1 + 127);                         // значение 127 берется за ноль (для отрицательных температур)
   addressWrite = addressWrite + 1;
   if (addressWrite == EEPROM.length())
    addressWrite = 2;
   delay(50);
   
   EEPROM.write(addressWrite, (byte) t2 + 127);
   addressWrite = addressWrite + 1;
   if (addressWrite == EEPROM.length())
    addressWrite = 2;
   delay(50);

   EEPROM.write(addressWrite, (byte) t3 + 127);
   addressWrite = addressWrite + 1;
   if (addressWrite == EEPROM.length())
    addressWrite = 2;
   delay(50);

   EEPROMWriteInt(0, addressWrite);                                    //записываем на какой ячейке остановились
   delay(50);
   
   counterWrite++;
   }
   
   if ( counterWrite > 0 && minute() == 1 )                                // когда прошла первая минута - обнуляем счетчик, не сделали это сразу чтобы не было несколько записей                  
   counterWrite =0;                                                                       // в одну и ту же минуту

  
  }

void EEPROMWriteInt(int p_address, unsigned int p_value)                  // функция для записи int в два байта 
{
  byte lowByte = ((p_value >> 0) & 0xFF);
  byte highByte = ((p_value >> 8) & 0xFF);
  EEPROM.write(p_address, lowByte);
  EEPROM.write(p_address + 1, highByte);
}
 
unsigned int EEPROMReadInt(int p_address)                                // и для считывания
{
  byte lowByte = EEPROM.read(p_address);
  byte highByte = EEPROM.read(p_address + 1);
  return ((lowByte << 0) & 0xFF) + ((highByte << 8) & 0xFF00);
}