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