Термо/гигрометр + архив данных.
- Войдите на сайт для отправки комментариев
Первый мой основательный проект, код весьма корявый, но рабочий. Буду рад если укажете на недочеты, ну и вдруг кому-нибудь пригодится.
Термометр-гигрометр для помещения:
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);
}
