Контроль влажности подвала Arduino pro mini

pav2000
Offline
Зарегистрирован: 15.12.2014
Есть в загородном доме два подвала которые гидроизолированы от земли (нет поступления воды из грунта). Зимой на стенах появляется конденсат, предметы покрываются влагой. Температура в подвалах не опускается ниже 0...+2 градусов, т.е. вода не замерзает. Это достигнуто утеплением стен подвала снаружи ЭППС и нагревом пола подвала (он тепло не изолирован) землей.
Задача убрать выпадение конденсата в подвалах.
1. Принцип работы
Идея контроля влажности подвала была подсмотрена на http://geektimes.ru (http://geektimes.ru/post/255298/)
Вся идея состоит в том чтобы измерить температуру и относительную влажность в подвале и на улице, на основании температуры и относительной влажности рассчитать абсолютную влажность и принять решение о включении вытяжного вентилятора в подвале. Теория для расчета изложена здесь - https://carnotcycle.wordpress.com/2012/08/04/how-to-convert-relative-humidity-to-absolute-humidity/
2. Выбор элементной базы.
В качестве контроллера было выбрано Arduino pro mini.  Это мой второй проект (первый - контроль работы теплового насоса был сделан на uno). Изучив предлагаемые контроллеры Arduino  пришел к выводу что существует  всего три варианта - DUE (отдельная песня и мне кажется тупик),  MEGA и все остальное (сильно схожие контроллеры на одном чипе 328).  Задача достаточно простая, по этому выбор пал на pro mini (не на мегу). 
В pro mini был прошит загрузчик optiboot 5 версии, это дало возможность использования сторожевого таймер и увеличением максимального размера кода.
Устройство индикации - до этого были испытаны монохромные дисплеи 4х20 символов и графический 128х64 точки на 9372. Появилось желание подключить  дисплей цветной ЖК. В виду малого количества ножек у МК было использовано подключение по SPI .  Ebay  - имеет большое количество предложений на контроллере ILI9341 с размерами диагонали 1.8-2.8 дюйма и разрешением 320х240 точек. В эту конструкцию был куплен дисплей 2.4 дюйма.
Т.к. устройство будет устанавливаться в подвале то для наблюдения я хочу сделать выносной пульт со связью по радиоканалу. Для организации радиоканала были использованы модули на основе nrf24. Планируется система типа "умного дома" каждый блок (а сейчас есть уже два блока в подвалах) посылает пакет о своем состоянии в головное устройство.
Исполнительное устройство. В начале был использован классический одно канальный релейный шилд для ардуино. Корпус проектировался под него (есть даже выступы для крепления). После установки устройства  в подвале был выявлен косяк - зависание контроллера дисплея при выключении вентилятора (25 вт). Сразу были увеличены конденсаторы по питанию, поставлен LC фильтр по 5 вольтам, дисплей был переведен на питание 3.3 вольта от внешнего стабилизатора (он уже был и использовался для питания nrf24). Все эти меры ничего не дали.
Затем возникла идея сброса контроллера дисплея при зависании. Программный сброс обеспечил. А вот как читать регистры дисплея так и не разобрался (типа прочел регистр все ок не прочел надо перегружать). Был организован периодический сброс - раз в час. Решение кривое. Все это делалось за городом из того что было. Решение меня не удовлетворило. Пришлось везти блок в Москву, там было установлено твердотельное реле шарп S202S02 ( с зеро кросс блоком) и помехогасящий конденсатор на 0.1 мкф. После этого устройсво заработало без зависаний.
Используется датчик темперуры и влажности DHT-22, штатное включение, подтягивающий резистор 5 кОм, длина провода до 6 метров (что испробовал).
Для согласования уровней 5 вольт ардуино и дисплея используются делители из резисторов 2к+4к. Согласвоания уровне й для nrf24 не требуется.
 
2. Программа.
У меня есть достаточно богатый опыт программирования (с 89 года) но все это касается полноценных компьютеров, последнее время занимаюсь разработкой управляющих систем на arm под самосборной допиленной Linux,  пишу либо на голом с или qt. Опыта программирования МК не имел, но старался перенести свой опыт на них.
Использую шедуллер задач leos ver 1 (первая версия использует таймер вторая использует сторожевой таймер), это "обертка" таймера для организации периодического вызова функций (у меня вызов опроса датчика).
Сторожевой таймер используется для борьбы с зависанием МК (я их не наблюдал но пусть будет).
Для работы с дисплеем используется библиотека ucglib версии не позднее 1.02. D более поздних версиях переработан формат шрифтов и есть проблемы с русификацией в кодировке UTF-8, допиливать было лень.
Для nrf24 используется библиотека от маньяка.
Для чтения датчиков DHT используется самая свежая библиотека с официального сайта. Там кодов ошибок больше.
Реализован подсчет мото часов блока и вентилятора.Кнопка повешена на прерывание и позволяет менять настройки включения вентилятора, смотреть информацию об устройстве (длительное нажатие) и сбрасывать счетчики (нажатие при включении).
Реализован вывод ошибок чтения DHT-22 (верхний правый угол цифры), с чтением были проблемы, после оптимизации кода они ушли.
На графике выводится не весь диапазон темперур (вывод -20...+20) для увеличения разрешающей способности графика в области допустимых темератру в подвале (0...+20) .

3. Конструктив.

Корпус напечатан на 3Д принтере (нижняя часть склеена из двух частей). Очень удобно, все отверстия сразу сделаны, только заусецы счистить и можно использовать. Получилось достаточно компактно. Вся электроника кроме входных цепей распаена на макетке, проводом (по времени один вечер).

Замечанные косяки -

1. На дисплее появляются "не штатные"  точки на графике (скоре всего помехи по SPI после перерисовке экрана они проподают),

2. Кнопка иногода "не четко" срабатывает проблемы с длительным нажатием.

3. nrf24 в блоке ожидает "квитанции" о приеме пакета иногда "квиатнция" не приходит (30-40%) но пакет доставляется до приемного устройства.

4. Уличный датчик DHT-22  показания влажности прыгают утром (ветер, солнце?). Датчик менял.

Конструктивная критика приветсвуется, особенно по коду.

Фото готового устройсва для пояснения:

Код программы:

#include <SPI.h>
#include <EEPROM.h>
#include <avr/wdt.h>
#include <leOS.h>      // Шедуллер задач
#include <dht.h>       // Дачик влажности и температуры
#include "Ucglib.h"    // справка https://code.google.com/p/ucglib/wiki/
#include "rusFont.h"   // Русские шрифты
#include "nRF24L01.h"  // Беcпроводной модуль
#include "RF24.h"      // Беcпроводной модуль

//#define DEBUG                                   // Отладочную  информацию в ком порт посылает  
//#define DEMO                                    // Признак демонстрации - данные с датчиков генерятся данные
#define BEEP                                    // Использовать пищалку
#define RADIO                                   // Признак использования радио модуля

#define dH_OFF          15                      // Гистерезис абсолютной влажности в сотых грамма на куб
#define dT_OFF          25                      // Гистерезис температуры в сотых градуса

#define TIME_SCAN_KEY 120                       // Время опроса кнопок мсек
#ifdef DEMO                                     // Для демо все быстрее и случайным образом
    #define NUM_SAMPLES      2                  // Число усреднений измерений датчика
    #define TIME_SCAN_SENSOR 2000               // Время опроса датчиков мсек, для демки быстрее
    #define TIME_PRINT_CHART 8000               // Время вывода точки графика мсек, для демки быстрее
    #define TIME_HOUR        50000              // Число мсек в часе, для демки быстрее   
#else   
   #define NUM_SAMPLES      5                   // Число усреднений измерений датчика
   #define TIME_SCAN_SENSOR 3000                // Время опроса датчиков мсек
   #define TIME_PRINT_CHART 300000              // Время вывода точки графика мсек
   #define TIME_HOUR        3600000             // Число мсек в часе
#endif

#define VERSION "Version: 0.51 03/08/15"        // Текущая версия
#define ID             0x22                     // уникально Идентификатор устройства - старшие 4 бита, вторые (младшие) 4 бита серийный номер устройства
#define NUM_SETTING    6                        // Число вариантов настроек - 1
#define MOTOR_OFF      0                        // Мотор выключен
#define MOTOR_ON       1                        // Мотор включен
#define LONG_KEY      2000                      // Длительное нажатие кнопки мсек, появляется Экран инфо
#define SHORT_KEY     100                       // Короткое нажатие кнопки более мсек
#define NRF24_CHANEL  100                       // Номер канала nrf24

// СИСТЕМАТИЧЕСКИЕ ОШИБКИ ДАТЧИКОВ для ID 0x21
#define TOUT_ERR      40                         // Ошибка уличного датчика температуры в сотых долях градуса
#define TIN_ERR       40                         // Ошибка домового датчика температуры в сотых долях градуса
#define HOUT_ERR      -30                        // Ошибка уличного датчика влажности в сотых долях %
#define HIN_ERR       +30                        // Ошибка домового датчика влажности в сотых долях %

//  НОГИ к которым прицеплена переферия (SPI используется для TFT и NRF24 - 11,12,13)
#ifdef BEEP
    #define PIN_BEEP      15                         // Ножка куда повешена пищалка
#endif
#define PIN_RELAY     14                         // Ножка на которую повешено реле (SSR) вентилятора - аналоговый вход A0 через резистор 470 ом
#define PIN_CS        10                         // TFT дисплей spi
#define PIN_CD        9                          // TFT дисплей spi
#define PIN_RESET     8                          // TFT дисплей spi
#define PIN_CE        7                          // nrf24 ce
#define PIN_CSN       6                          // nrf24 cs
#define PIN_DHT22a    5                          // Первый датчик DHT22   IN  ДОМ
#define PIN_DHT22b    4                          // Второй датчик DHT22   OUT УЛИЦА
#define PIN_KEY       3                          // Кнопка, повешена на прерывание, что бы ресурсов не тратить
#define PIN_IRQ_NRF24 2                          // Ножка куда заведено прерывание от NRF24 (не используется)


// АЦП ----------------------------------------
const long ConstADC=1126400;                    // Калибровка встроенного АЦП (встроенный ИОН) по умолчанию 1126400 дальше измеряем питание и смотрим на дисплей    

Ucglib_ILI9341_18x240x320_HWSPI ucg(/*cd=*/PIN_CD, /*cs=*/PIN_CS, /*reset=*/PIN_RESET); // Аппаратный SPI на дисплей ILI9341
leOS myOS;                                      // многозадачность
dht DHT;                                        // Датчики
bool infoScreen=false;                          // Признак отображениея иформационного экрана  1 - на экран ничего не выводится кроме информационного экрана
bool flagKey=false;                             // Флаг нажатия клавиши
bool pressKey=false;                            // Флаг необходимости обработки кнопки

unsigned long time_key=0;                       // Время нажатия копки
byte last_error=100;                            // Предыдущая ошибка<packet.error

 struct type_setting_eeprom                     // Структура для сохранения данных в eeprom
 {
     byte fStart = 0;                           // Какой набор настроек используется
     unsigned long hour_unit;                   // мото часы блок измеряется в интервалах вывода графика TIME_PRINT_CHART
     unsigned long hour_motor;                  // мото часы мотор измеряется в интервалах вывода графика TIME_PRINT_CHART
 }; 
volatile type_setting_eeprom settingRAM;        // Рабочая копия счетчиков в памяти
type_setting_eeprom settingEEPROM EEMEM;        // Копия счетчиков в eeprom - туда пишем 

// пакет передаваемый, используется также для хранения результатов. 
 struct type_packet_NRF24                       // Структура передаваемого пакета 32 байта - это максимум
    {
        byte id=ID;                                  // Идентификатор типа устройства - старшие 4 бита, вторые (младшие) 4 бита серийный номер устройства
        int  tOut=-5000,tIn=-5000;                   // Текущие температуры в сотых градуса !!! место экономим
        int  absHOut=55555,absHIn=55555;             // Абсолютные влажности в сотых грамма на м*3 !!! место экономим
        int  relHOut=55555,relHIn=55555;             // Относительные влажности сотых процента !!! место экономим
        byte error;                                  // Ошибка разряды: 0-3 первый датчик (00-ок) 4-7 второй датчик (00-ок)
        byte dH;                                     // Порог включения вентилятора по абсолютной влажностив сотых грамма на м*3
        int  T;                                      // Порог выключения вентилятора по температуре в сотых градуса
        bool  motor=false;                           // Статус вентилятора 
        char note[12] = "Arduino Pro";               // Примечание не более 11 байт + 0
    } packet;
   
struct type_sensors                               // структура для усреднения
{
       int  num;                                    // сколько отсчетов уже сложили не болле NUM_SAMPLES
       long  sum_tOut=-5000,sum_tIn=-5000;          // Сумма для усреднения Текущие температуры в сотых градуса !!! место экономим
       long  sum_relHOut=55555,sum_relHIn=55555;    // Сумма для усреднения Относительные влажности сотых процента !!! место экономим
       int  tOut=-5000,tIn=-5000;                   // Текущие температуры в сотых градуса !!! место экономим
       int  absHOut=55555,absHIn=55555;             // Абсолютные влажности в сотых грамма на м*3 !!! место экономим
       int  relHOut=55555,relHIn=55555;             // Относительные влажности сотых процента !!! место экономим
} sensors;
    
// Массивы для графиков
byte tOutChart[120];
byte tInChart[120];
byte absHOutChart[120];
byte absHInChart[120];
byte posChart=0;       // Позиция в массиве графиков - начало вывода от 0 до 120-1
byte TimeChart=0;      // Время до вывода очередной точки на график. 

#ifdef  RADIO    // Радио модуль NRF42l  
   RF24 radio(PIN_CE, PIN_CSN);  //определение управляющих ног
   const uint64_t pipes[2] = { 0xF0F0F0F0E1LL,0xF0F0F0F0D2LL};
   bool send_packet_ok=false;                // признак удачной отправки последнего пакета 
#endif 
///////////////////////////////////////////////////////////////////////////////////////////////////////////
// ПРОГРАММА
///////////////////////////////////////////////////////////////////////////////////////////////////////////
void setup(){
#ifdef  DEBUG  
   Serial.begin(9600); 
   Serial.println(F("DEBUG MODE")); 
   #ifdef  BEEP  
     Serial.println(F("BEEP ON")); 
   #else         
     Serial.println(F("BEEP OFF"));
   #endif
   #ifdef  RADIO 
     Serial.println(F("RADIO ON")); 
   #else         
     Serial.println(F("RADIO OFF"));
   #endif
#endif

reset_sum();

#ifdef  RADIO    // Радио модуль NRF42l  
   radio.begin();
   radio.setDataRate(RF24_250KBPS); // выбор скорости
   radio.setPALevel(RF24_PA_MAX);
   radio.setChannel(NRF24_CHANEL);  //тут установка канала
   radio.setCRCLength(RF24_CRC_16);
// radio.setAutoAck(false);       // выключить аппаратное потверждение
// radio.enableDynamicPayloads(); // разрешить Dynamic Payloads
// radio.enableAckPayload();      // разрешить AckPayload
  radio.setRetries(15,15);
  radio.openWritingPipe(0xF0F0F0F0E1LL);
  radio.openReadingPipe(1,0xF0F0F0F0D2LL);
  radio.startListening();
#endif
  #ifdef BEEP
    pinMode(PIN_BEEP, OUTPUT);          //  Настройка ноги для динамика
    digitalWrite(PIN_BEEP, LOW);
  #endif
  pinMode(PIN_KEY, INPUT);              //  Включена кнопка
  digitalWrite(PIN_KEY, HIGH);  
  pinMode(PIN_RELAY, OUTPUT);           //  Реле
  digitalWrite(PIN_RELAY, LOW);  
  pinMode(PIN_DHT22a, OUTPUT);          //  Датчик 1 
  digitalWrite(PIN_DHT22a, HIGH);  
  pinMode(PIN_DHT22b, OUTPUT);          //  Датчик 2
  digitalWrite(PIN_DHT22b, HIGH);  
  
  reset_ili9341();                      // сброс дисплея
  
  readEeprom();                          // Прочитать настройки
  if (digitalRead(PIN_KEY)==0)           // Если при включении нажата кнопка то стираем все
  {
      int i;
      settingRAM.fStart=0;
      settingRAM.hour_unit=0;
      settingRAM.hour_motor=0;
      ucg.setColor(255, 255, 255);
      print_StrXY(10,50,F("Сброс настроек и счетчиков"));
      for(i=0;i<3;i++)
        {
         delay(1000);
         ucg.print(F(" ."));
        }
      writeEeprom();       // Запись в EEPROM  
      delay(1000);
      ucg.clearScreen();
  } 
 
  wdt_enable(WDTO_8S); // Сторожевой таймер Для тестов не рекомендуется устанавливать значение менее 8 сек.
  // Запуск задач по таймеру
  myOS.begin();
  myOS.addTask(measurement,TIME_SCAN_SENSOR);    // Измерение 
  attachInterrupt(1, scanKey, CHANGE);           // КНОПКА Прерывания по обоим фронтам
  print_static();                                // распечатать таблицу
  Setting();                                     // Применить настройки
  measurement();                                 // Считать данные
  #ifdef BEEP
     beep(100);
  #endif
}

void loop()
{
// Обработка нажатия кнопки
if (pressKey==true) // Кнопка 
{
 myOS.pauseTask(measurement);                                 // Остановить задачи
 if (time_key > LONG_KEY)                                     // Длительное нажатие кнопки
   printInfo();                                               // Вывод информационного экрана
 else 
   if (time_key > SHORT_KEY)                                  // Короткое нажатие кнопки
     { 
        #ifdef BEEP
          beep(30);                                            // Звук нажатия на клавишу
        #endif
       if (infoScreen==true)  clearInfo();                    // если информационный экран то стереть его
       else {  if (settingRAM.fStart >= NUM_SETTING) settingRAM.fStart=0; //  Кольцевой счетчик настроек
               else settingRAM.fStart++;                      // В противном случае следующая настройка
               Setting(); }  
     }
  flagKey=false;                                              // Подготовка к следующему нажатию
  time_key=0;                                                
  pressKey=false;
  myOS.restartTask(measurement);                             // Запустить задачи
}

}

void print_static()  // Печать статической картинки 
{
   cli();
  // Заголовок 
  ucg.setColor(0, 0, 180);  // 
  ucg.drawBox(0, 0, 320-1, 23);
  ucg.setColor(250, 250, 250);
  ucg.setFontMode(UCG_FONT_MODE_TRANSPARENT);
  print_StrXY(2,19,F("ОСУШИТЕЛЬ ID: 0x"));
  ucg.print( hex(packet.id >> 4));
  ucg.print( hex(packet.id&0x0f));
   
  #ifdef DEMO
    ucg.print(F(" demo"));
  #endif
  
  // Таблица для данных
  ucg.setColor(0, 200, 0);
  ucg.drawHLine(0,25,320-1);
  ucg.drawHLine(0,25+23*1,320-1);
  ucg.drawHLine(0,25+23*2,320-1);
  ucg.drawHLine(0,25+23*3,320-1);
  ucg.drawHLine(0,25+23*4,320-1);
  ucg.drawVLine(200-4,25,24+23*3);
  ucg.drawVLine(260,25,24+23*3);

  // Заголовки таблиц
  ucg.setColor(255, 255, 0);
  print_StrXY(180+30,25+0+18,F("Дом"));
  print_StrXY(250+20,25+0+18,F("Улица"));
  print_StrXY(0,25+23*1+18,F("Температура градусы C")); 
  print_StrXY(0,25+23*2+18,F("Относительная влаж. %")); 
  print_StrXY(0,25+23*3+18,F("Абсолют. влаж. г/м*3")); 

  // Графики
  ucg.setColor(200, 200, 200);
  ucg.drawHLine(1,240-1,130);
  ucg.drawVLine(1,135,105);
  ucg.drawHLine(10+150,240-1,130);
  ucg.drawVLine(10+150,135,105);
  print_StrXY(10,135+0,F("Температура")); 
  print_StrXY(20+150,135+0,F("Абс. влажность")); 
  
  // надписи на графиках
  print_StrXY(128,154,F("+20")); 
  print_StrXY(135,194,F("0")); 
  print_StrXY(128,233,F("-20"));
  
  print_StrXY(296,164,F("15"));
  print_StrXY(296,194,F("10"));
  print_StrXY(296,223,F("5"));
   sei();
}

void print_status() // Печать панели статуса Значки на статус панели
{
  if (infoScreen==true) return;        // если отображен информационный экран то ничего не выводим  
   cli();
 // 1. печать ошибки чтения датчиков
   print_error_DHT();
 // 2. Признак включения мотора
  if (packet.motor==true)  ucg.setColor(0, 240, 0); 
  else                     ucg.setColor(0, 40, 0);
  ucg.drawBox(290-32, 5, 14, 14);
  
  #ifdef  RADIO 
  // 3. Признак удачной передачи информации по радиоканалу
      if (send_packet_ok==true)  ucg.setColor(0, 240, 0); 
      else                       ucg.setColor(0, 40, 0);
      ucg.setPrintDir(3);
      ucg.setFontMode(UCG_FONT_MODE_TRANSPARENT);
      print_StrXY(290-40,20,F(">>")); 
      ucg.setPrintDir(0);
      ucg.setFontMode(UCG_FONT_MODE_SOLID);
  #endif
  sei();
}  

void print_error_DHT() // Печать ошибки чтения датчиков выводится при каждом чтении датчика
{
  if (infoScreen==true) return;        // если отображен информационный экран то ничего не выводим  
 // 1. печать ошибки чтения датчиков
  if (packet.error!=last_error)        // если статус ошибки поменялся то надо вывести если нет то не выводим - экономия время и нет мерцания
  {
      cli();
      last_error=packet.error; 
      ucg.setColor(0, 0, 180);         // Сначала стереть
      ucg.drawBox(290, 0, 26, 18);
      ucg.setPrintPos(290,18); 
      ucg.setFontMode(UCG_FONT_MODE_TRANSPARENT);
      if (packet.error>0) 
        { 
        ucg.setColor(255, 100, 100); 
        print_StrXY(280,19,F("0x"));
        ucg.print( hex(packet.error >> 4));
        ucg.print( hex(packet.error & 0x0f)); 
        }
      else  { ucg.setColor(200, 240, 0);   ucg.print(F("ok")); } 
     sei(); 
   }   
} 
//  вывод на экран данных (то что меняется)
void print_data()
{ 
 if (infoScreen==true) return;                  // если отображен информационный экран то ничего не выводим  
  cli();
 // Печать значений для дома
  ucg.setFontMode(UCG_FONT_MODE_SOLID);
  ucg.setColor(250, 0, 100);  // Цвет ДОМА
  print_floatXY(200+0,25+23*1+18,((float)packet.tIn)/100);
  print_floatXY(200+0,25+23*2+18,((float)packet.relHIn)/100);
  print_floatXY(200+0,25+23*3+18,((float)packet.absHIn)/100);
  ucg.setColor(0, 250, 100);  // Цвет УЛИЦЫ
  print_floatXY(260+4,25+23*1+18,((float)packet.tOut)/100);
  print_floatXY(260+6,25+23*2+18,((float)packet.relHOut)/100);
  print_floatXY(260+6,25+23*3+18,((float)packet.absHOut)/100);
  sei();
}  

// Печать графика на экране, добавляется одна точка и график сдвигается 
void printChart() 
{
byte i,x=0;
byte tInLast,absHInLast,tOutLast,absHOutLast;

// Статистика по моточасам, время ведется в тиках графика а потом пересчитывается в часы при выводе.
settingRAM.hour_unit++;
if (packet.motor==true) settingRAM.hour_motor++;

// Работаем через кольцевой буфер
// Добавить новую точку в кольцевой буфер
     // Температура в доме. диапазон -25 . . . +25 растягиваем на 100 точек
     tInLast=tInChart[posChart];                       // Сохранить точку для стирания на графике 
     if (packet.tIn<=-2500) tInChart[posChart]=0;      // Если температура меньше -25 то округляем до -25
     if (packet.tIn>=2500)  tInChart[posChart]=100;    // Если температура больше 25  то округляем до 25
     if ((packet.tIn>-2500)&&(packet.tIn<2500)) tInChart[posChart]=((2*packet.tIn)+5000)/100;  // внутри -25...+25 растягиваем в два раза
     // Температура на улице. диапазон -25 . . . +25 растягиваем на 100 точек
     tOutLast=tOutChart[posChart];                       // Сохранить точку для стирания на графике 
     if (packet.tOut<=-2500) tOutChart[posChart]=0;      // Если температура меньше -25 то округляем до -25
     if (packet.tOut>=2500)  tOutChart[posChart]=100;    // Если температура больше 25  то округляем до 25
     if ((packet.tOut>-2500)&&(packet.tOut<2500)) tOutChart[posChart]=((2*packet.tOut)+5000)/100;  // внутри -25...+25 растягиваем в два раза
     // Абсолютная влажность в доме диапазон от 0 до 20 грамм на кубометр, растягиваем на 100 точек
     absHInLast=absHInChart[posChart];                   // Сохранить точку для стирания на графике 
     if (packet.absHIn>=2000) absHInChart[posChart]=100;
     else absHInChart[posChart]=(5*packet.absHIn)/100;   // внутри 0...20 растягиваем в пять  раз
     // Абсолютная влажность на улицу диапазон от 0 до 20 грамм на кубометр, растягиваем на 100 точек
     absHOutLast=absHOutChart[posChart];                   // Сохранить точку для стирания на графике 
     if (packet.absHOut>=2000) absHOutChart[posChart]=100;
     else absHOutChart[posChart]=(5*packet.absHOut)/100;   // внутри 0...20 растягиваем в пять раз
     
  if (infoScreen==false)                 // если отображен информационный экран то ничего не выводим
   {
   cli();  
   for(i=0;i<120;i++)    // График слева на право
     { 
     // Вычислить координаты текущей точки x в кольцевом буфере. Изменяются от 0 до 120-1
     if (posChart<i) x=120+posChart-i; else x=posChart-i;

     ucg.setColor(0, 0, 0); // Стереть предыдущую точку
     if (x-1<0) ucg.drawPixel(5+120-i,237-tInLast);
     else       ucg.drawPixel(5+120-i,237-tInChart[x-1]);
     if (x-1<0) ucg.drawPixel(5+120-i,237-tOutLast);
     else       ucg.drawPixel(5+120-i,237-tOutChart[x-1]);
     if (x-1<0) ucg.drawPixel(6+120-i+158,237-absHInLast);
     else       ucg.drawPixel(6+120-i+158,237-absHInChart[x-1]); 
     if (x-1<0) ucg.drawPixel(6+120-i+158,237-absHOutLast);
     else       ucg.drawPixel(6+120-i+158,237-absHOutChart[x-1]); 

     // Вывести новую точку
     if ((tInChart[x]==0)||(tInChart[x]==100))   ucg.setColor(255, 255, 255); else ucg.setColor(250, 0, 100); 
     ucg.drawPixel(5+120-i,237-tInChart[x]);
     
     if (absHInChart[x]==100) ucg.setColor(255, 255, 255); else ucg.setColor(250, 0, 100); 
     ucg.drawPixel(6+120-i+158,237-absHInChart[x]);
     
     if ((tOutChart[x]==0) || (tOutChart[x]==100)) ucg.setColor(255, 255, 255); else ucg.setColor(0, 250, 100); 
     ucg.drawPixel(5+120-i,237-tOutChart[x]);
     
     if (absHOutChart[x]==100) ucg.setColor(255, 255, 255); else ucg.setColor(0, 250, 100); 
     ucg.drawPixel(6+120-i+158,237-absHOutChart[x]);
      }
  
     // Пунктирные линии графика
    ucg.setColor(100, 100, 100);  
    for(i=1;i<=120;i=i+5)
    {
       ucg.drawPixel(5+120-i,237-10);
       ucg.drawPixel(5+120-i,237-50);
       ucg.drawPixel(5+120-i,237-90);
       
       ucg.drawPixel(6+120-i+158,237-25);
       ucg.drawPixel(6+120-i+158,237-50);
       ucg.drawPixel(6+120-i+158,237-75);
     }  
    sei(); 
   } 
 if (posChart<120-1) posChart++; else posChart=0;            // Изменили положение в буфере и Замкнули буфер
} 


// ---ПЕРЕДАЧА ДАННЫХ ЧЕРЕЗ РАДИОМОДУЛЬ -----------------------------
void send_packet()
{
#ifdef  RADIO    // Радио модуль NRF42l  
        radio.stopListening();     // Остановить приемник
        send_packet_ok = radio.write(&packet,sizeof(packet));
         #ifdef BEEP
           if (send_packet_ok==true) beep(40);
         #endif
         #ifdef  DEBUG  
           if (send_packet_ok==true)  Serial.println(F("Packet sending ++++++++++"));
           else                       Serial.println(F("Packet NOT sending -----------"));
         #endif   
        radio.startListening();    // Включить приемник
#endif  
 }  

// Чтение датчика возвращает код ошибки:
// DHTLIB_OK                   0
// DHTLIB_ERROR_CHECKSUM       1
// DHTLIB_ERROR_TIMEOUT        2
// DHTLIB_ERROR_CONNECT        3
// DHTLIB_ERROR_ACK_L          4
// DHTLIB_ERROR_ACK_H          5
byte readDHT(byte pin)
{
delay(5);
cli();
  byte err=-1*DHT.read22(pin); // Чтение датчика
sei();  
return err; 
} 

// Измерение и обработка данных чтение датчиков --------------------------------------------------------------------------------
void measurement()
{ 
 myOS.pauseTask(measurement);        // Обязательно здесь, а то датчики плохо читаются мешает leos
 wdt_reset();                        // Сбросить сторожевой таймер
 
 packet.error=readDHT(PIN_DHT22a);   // ПЕРВЫЙ ДАТЧИК ДОМ  Новый пакет, сбросить все ошибки и прочитать первый датчик
 
 #ifdef  DEMO
   DHT.temperature=packet.tIn/100+random(-20,25)/10.0; 
   if (DHT.temperature>20) DHT.temperature=19;
   if (DHT.temperature<-10) DHT.temperature=-9;
   DHT.humidity=packet.relHIn/100+(float)random(-5,7);
   if (DHT.humidity>96) DHT.humidity=90;
   if (DHT.humidity<1) DHT.humidity=10;
   packet.error=0; // в Демо режиме
//   DHT.temperature=3.0;
//   DHT.humidity=21.0;
 #endif  
     sensors.tIn=(int)(DHT.temperature*100.0)+TIN_ERR;  // Запомнить результаты для суммирования
     sensors.relHIn=(int)(DHT.humidity*100.0)+HOUT_ERR;  
     
    #ifdef  DEBUG  
       Serial.print(F("Sensor read samples:")); Serial.println(sensors.num); 
       Serial.print(F("IN T="));Serial.print(sensors.tIn);Serial.print(F(" H=")); Serial.print(sensors.relHIn); Serial.print(F(" error=")); Serial.println(packet.error);
    #endif   
 
 packet.error=packet.error+16*readDHT(PIN_DHT22b);// ВТОРОЙ ДАТЧИК УЛИЦА  ошибки в старшие четыре бита
 #ifdef  DEMO
   DHT.temperature=packet.tOut/100+random(-40,45)/10.0; 
   if (DHT.temperature>30) DHT.temperature=29;
   if (DHT.temperature<-30) DHT.temperature=-29;
   DHT.humidity=packet.relHOut/100+random(-10,12);
   if (DHT.humidity>96) DHT.humidity=90;
   if (DHT.humidity<1)  DHT.humidity=10;
   packet.error=0;      // в Демо режиме
 //  DHT.temperature=-10.0;
 //  DHT.humidity=40.0;
 #endif  
     sensors.tOut=(int)(DHT.temperature*100.0)+TOUT_ERR;  // Запомнить результаты для суммирования
     sensors.relHOut=(int)(DHT.humidity*100.0)+HOUT_ERR;
 
    #ifdef  DEBUG  
       Serial.print(F("OUT T="));Serial.print(sensors.tOut);Serial.print(F(" H=")); Serial.print(sensors.relHOut); Serial.print(F(" error=")); Serial.println(packet.error);
    #endif   
 
 print_error_DHT();    // Вывод ошибки чтения датчика при каждом чтении контроль за качеством связи с датчиком
 
 if (packet.error==0)// Если чтение без ошибок у ДВУХ датчиков  копим сумму для усреднения
  {
     sensors.sum_tIn=sensors.sum_tIn+sensors.tIn;
     sensors.sum_relHIn=sensors.sum_relHIn+sensors.relHIn;
     sensors.sum_tOut=sensors.sum_tOut+sensors.tOut;
     sensors.sum_relHOut=sensors.sum_relHOut+sensors.relHOut;
     sensors.num++;
   }
 
 // набрали в сумме нужное число отсчетов рассчитываем усреднение и выводим
 if (sensors.num>=NUM_SAMPLES)  // Пора усреднять и выводить значения 
 {
         // вычисление средних значений
         packet.tIn=sensors.sum_tIn/NUM_SAMPLES;
         packet.relHIn=sensors.sum_relHIn/NUM_SAMPLES;
         packet.tOut=sensors.sum_tOut/NUM_SAMPLES;
         packet.relHOut=sensors.sum_relHOut/NUM_SAMPLES;
         reset_sum();       // Сброс счетчиков и сумм
         // вычисление абсолютной влажности
         packet.absHIn=(int)(calculationAbsH((float)(packet.tIn/100.0),(float)(packet.relHIn/100.0))*100.0);
         packet.absHOut=(int)(calculationAbsH((float)(packet.tOut/100.0),(float)(packet.relHOut/100.0))*100.0);
         
     #ifdef  DEBUG  
       Serial.println(F("Average value >>>>>>>>>>"));
       Serial.print(F("IN T="));Serial.print(packet.tIn);Serial.print(F(" H=")); Serial.print(packet.relHIn); Serial.print(F(" abs H=")); Serial.println(packet.absHIn);
       Serial.print(F("OUT T="));Serial.print(packet.tOut);Serial.print(F(" H=")); Serial.print(packet.relHOut); Serial.print(F(" abs H=")); Serial.println(packet.absHOut);
     #endif   
                     
         #ifdef  RADIO     // Радио модуль NRF42l 
          send_packet();   // Послать данные
         #endif
         CheckON();         // Проверка статуса вентилятора
         print_status();    // панель состояния
       
         print_data();       // вывод усредненных значений 
         if ((long)((long)TimeChart*TIME_SCAN_SENSOR*NUM_SAMPLES)>=(long)TIME_PRINT_CHART) // проврека не пора ли выводить график
            { printChart(); TimeChart=0; // Сдвиг графика и вывод новой точки
              #ifdef  DEBUG  
                 Serial.println(F("Point add chart ++++++++++++++++++++"));
              #endif  
              #ifdef BEEP
 //               beep(50);
              #endif
             } 
         else TimeChart++;
    }
    myOS.restartTask(measurement);     // Пустить задачи
}

// Функция переводит относительную влажность в абсолютную 
// t-температура в градусах Цельсия h-относительная влажность в процентах
float calculationAbsH(float t, float h)
{
 float temp;
 temp=pow(2.718281828,(17.67*t)/(t+243.5));
 return (6.112*temp*h*2.1674)/(273.15+t);
}

// Сканирование клавиш ------------------------------------------
void scanKey()
{  
    byte key;  
    cli(); 
    key=digitalRead(PIN_KEY);                                         // Прочитать кнопку 0 - нажата 
    if ((key==0)&&(flagKey==false))                                   // Если кнопка была нажата запомнить время и поставить флаг нажатия
    {
        flagKey=true;                                                 // Кнопка нажата  ждем обратного фронта
        time_key=millis();                                            // Время нажатия запомнили
    }
        
       
    if ((key==1)&&(flagKey==true))                                    // Если кнопка была отжата 
    {
         time_key=millis()-time_key;                                  // Рассчитать время нажатия
         pressKey=true;
    }
   sei();
 }

// Проверка статуса вытяжки, не пора ли переключится
void CheckON()
{
cli();
if (packet.motor==false) // Вентилятор выключен 
  {
  if (settingRAM.fStart!=MOTOR_OFF)
  if (settingRAM.fStart==MOTOR_ON) {packet.motor=true; digitalWrite(PIN_RELAY, HIGH);}  // Все время включен  
  else  if (( packet.tIn>=packet.T )&& ((packet.absHIn-packet.dH)> packet.absHOut)) {packet.motor=true; digitalWrite(PIN_RELAY, HIGH);} // ВКЛЮЧЕНИЕ
  }
else // Вентилятор включен  
  {
  if (settingRAM.fStart!=MOTOR_ON) 
  if (settingRAM.fStart==MOTOR_OFF) {packet.motor=false; digitalWrite(PIN_RELAY, LOW);}  // Все время выключен 
  else  if((packet.tIn<=packet.T-dT_OFF)||(packet.absHIn-packet.absHOut<packet.dH-dT_OFF)) {packet.motor=false; digitalWrite(PIN_RELAY, LOW);} // ВЫКЛЮЧЕНИЕ  
  } 
sei(); 
} 

// Вывод информации о настройках и сохрание индекса настроек в eeprom ---------------------------------
void Setting()
{
 // Настройка
  cli();
  ucg.setColor(0, 100, 255);
  ucg.setPrintPos(0,25+0+18); 
  switch (settingRAM.fStart)
        {
        case  MOTOR_OFF: ucg.print(F("Выключено              ")); packet.dH=255;packet.T=255; break; 
        case  MOTOR_ON:  ucg.print(F("Режим вытяжки         "));  packet.dH=0;  packet.T=0;   break; 
        case  2:         ucg.print(F("Включение T>+2 dH>0.2"));   packet.dH=20; packet.T=200; break;
        case  3:         ucg.print(F("Включение T>+2 dH>0.4"));   packet.dH=40; packet.T=200; break;
        case  4:         ucg.print(F("Включение T>+3 dH>0.3"));   packet.dH=30; packet.T=300; break;
        case  5:         ucg.print(F("Включение T>+3 dH>0.5"));   packet.dH=50; packet.T=300; break;
        case  6:         ucg.print(F("Включение T>+4 dH>0.6"));   packet.dH=30; packet.T=600; break;
        } 
 writeEeprom();       // Запись в EEPROM  
 CheckON();           // Возможно надо включить мотор
 print_status();      // панель состояния
 sei();   
}

// Вывод float  с одним десятичным знаком в координаты x y // для экономии места
void print_floatXY(int x,int y, float v)
{
 ucg.setPrintPos(x,y);
 ucg.print(v,2);
 ucg.print("  "); // Стереть хвост от предыдущего числа
} 

// Вывод строки константы в координаты x y // для экономии места
void print_StrXY(int x,int y, const __FlashStringHelper* b)
{
 ucg.setPrintPos(x,y);
 ucg.print(b);
} 

void printInfo() // Окно с информацией о блоке, появляется при длительном нажатии на кнопку
{
  infoScreen=true;
  cli();
  ucg.setColor(250, 250, 250);  // 
  ucg.drawBox(10, 10, 320-1-20, 240-1-20);
  ucg.setColor(0, 50, 250);
  ucg.drawFrame(10+5, 10+5, 320-1-20-10, 240-1-20-10);
  
  ucg.setFontMode(UCG_FONT_MODE_TRANSPARENT);
  ucg.setColor(0, 150, 10);
  print_StrXY(35,18+15,F("ОСУШИТЕЛЬ на Arduino Pro Mini"));
  
  ucg.setColor(0, 50, 50);
  print_StrXY(10+10,15+17*2,F("1 Напряжение питания В.")); 
  print_floatXY(10+230,15+17*2,readVcc()/1000.0);
 
  print_StrXY(10+10,15+17*3,F("2 Температура блока гр.")); 
  print_floatXY(10+230,15+17*3,GetTemp());
 
  print_StrXY(10+10,15+17*4,F("3 Свободная память байт")); 
  ucg.setPrintPos(10+230,15+17*4); 
  ucg.print(freeRam()); 
 
  print_StrXY(10+10,15+17*5,F("4 Мото часы блока")); 
  ucg.setPrintPos(10+230,15+17*5); 
  ucg.print(settingRAM.hour_unit/(TIME_HOUR/TIME_PRINT_CHART)); 
  
  print_StrXY(10+10,15+17*6,F("5 Мото часы вентилятора")); 
  ucg.setPrintPos(10+230,15+17*6); 
  ucg.print(settingRAM.hour_motor/(TIME_HOUR/TIME_PRINT_CHART)); 
  
  print_StrXY(10+10,15+17*7,F("6 Канал NRF24l01+")); 
  ucg.setPrintPos(10+230,15+17*7); 
  ucg.print(NRF24_CHANEL); 
 
  print_StrXY(10+10,15+17*8,F("7 Гистерезис абс. влажности")); 
  print_floatXY(10+255,15+17*8,(float)dH_OFF/100.0);
  
  print_StrXY(10+10,15+17*9,F("8 Гистерезис температуры")); 
  print_floatXY(10+255,15+17*9,(float)dT_OFF/100.0);
  
  print_StrXY(10+10,15+17*10,F("9 ERR Т/Н in:")); 
  ucg.print(TIN_ERR); 
  ucg.print(F("/")); 
  ucg.print(HIN_ERR); 
  ucg.print(F(" out:"));
  ucg.print(TOUT_ERR); 
  ucg.print(F("/")); 
  ucg.print(HOUT_ERR); 
 
  ucg.setColor(0, 0, 150);
  print_StrXY(10+10,16+17*11,F("СБРОС - Вкл. при нажатой кнопке.")); 
  
  ucg.setColor(250,80,80);
  print_StrXY(10+10,20+21+18*10,F(VERSION)); 
  sei();
} 

void clearInfo()  // Стереть информационный экран
{
      infoScreen=false;
      last_error=100;         // Признак обновления ошибки
      cli();
      ucg.setColor(0, 0, 0);  // залить черным
      ucg.drawBox(10, 10, 320-1-20, 240-1-20);
      print_static();
      Setting();  
      printChart();
      sei();
} 
// Чтение свободной памяти --------------------------------------------------------------------
int freeRam () {
  extern int __heap_start, *__brkval; 
  int v; 
  return (int) &v - (__brkval == 0 ? (int) &__heap_start : (int) __brkval); 
}
// Чтение внутреннего датчика температуры ---------------------------------------
double GetTemp(void)
{
  unsigned int wADC;
  double t;
  sei();  // Должны быть разрешены прерывания
  ADMUX = (_BV(REFS1) | _BV(REFS0) | _BV(MUX3));
  ADCSRA |= _BV(ADEN);  
  delay(20);           
  ADCSRA |= _BV(ADSC);  
  while (bit_is_set(ADCSRA,ADSC));
  wADC = ADCW;
  t = (wADC - 324.31 ) / 1.22;
  return (t); 
}
// Чтение напряжения питания ----------------------------------------------
long readVcc() {
  long result;
  // Read 1.1V reference against AVcc
  ADMUX = _BV(REFS0) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1);
  delay(2); // Wait for Vref to settle
  ADCSRA |= _BV(ADSC); // Convert
  while (bit_is_set(ADCSRA,ADSC));
  result = ADCL;
  result |= ADCH<<8;
  result = ConstADC / result; // Back-calculate AVcc in mV
  return result;
} 
// Запись счетчиков в Eeprom --------------------------------------------------
void writeEeprom()
{ 
cli(); 
  eeprom_write_block((const void*)&settingRAM, (void*) &settingEEPROM, sizeof(settingRAM)); 
sei();
}
// Чтение счетчиков из Eeprom --------------------------------------------------
void readEeprom()
{
cli(); 
   eeprom_read_block((void*)&settingRAM, (const void*) &settingEEPROM, sizeof(settingRAM)); 
sei();
}

void reset_sum()  // Сброс счетчиков накоплений
{
sensors.num=0;  // Рассчитать величину усреднения
sensors.sum_tOut=0;
sensors.sum_tIn=0;
sensors.sum_relHOut=0;
sensors.sum_relHIn=0;
}

char hex(byte x)  // Функция для вывода в hex
{
   if(x >= 0 && x <= 9 ) return (char)(x + '0');
   else      return (char)('a'+x-10);
}

bool reset_ili9341(void)
{
  pinMode(PIN_RESET, OUTPUT);                    // Сброс дисплея сигнал активным является LOW
 // digitalWrite(PIN_RESET, HIGH);  
  digitalWrite(PIN_RESET, LOW);  
  delay(100);
  digitalWrite(PIN_RESET, HIGH);  
  // Дисплей
  ucg.begin(UCG_FONT_MODE_TRANSPARENT);
  ucg.setFont(my14x10rus);  
  
 //  ucg.setFont(myfont);
//  ucg.setRotate90();
  ucg.setRotate270();
  ucg.clearScreen();
}

#ifdef BEEP  
  void beep(int x)  // Пищать х мсек
  {
    digitalWrite(PIN_BEEP, HIGH); 
    delay(x);
    digitalWrite(PIN_BEEP, LOW); 
  } 
#endif

файл фонтов

/ Русский шрифт 10х14 ------------------------------------------------------
const ucg_fntpgm_uint8_t my14x10rus[4157] UCG_SECTION(".progmem.my14x10") = {
  0,11,15,0,255,14,3,35,6,248,32,255,0,15,255,14,
  0,0,0,0,8,0,0,2,14,14,4,1,0,64,192,192,
  192,192,192,192,192,192,128,0,64,192,128,6,5,5,7,0,
  9,68,204,204,204,136,10,14,28,11,0,0,8,128,8,128,
  17,0,17,0,127,192,17,0,17,0,34,0,34,0,255,128,
  34,0,34,0,68,0,68,0,9,14,28,10,0,0,8,0,
  8,0,59,128,123,0,200,0,200,0,232,0,107,0,11,128,
  9,128,9,128,111,0,238,0,8,0,8,14,14,9,0,0,
  97,179,214,102,12,12,24,24,48,48,102,107,205,134,9,14,
  28,10,0,0,56,0,124,0,108,0,108,0,108,0,56,0,
  56,128,109,128,199,0,194,0,199,0,237,128,124,128,56,0,
  2,5,5,3,0,10,128,192,192,192,64,5,14,14,6,0,
  0,24,48,96,96,192,192,192,192,192,192,96,96,48,24,5,
  14,14,6,0,0,192,96,48,48,24,24,24,24,24,24,56,
  48,96,192,7,7,7,8,0,4,146,214,124,16,124,214,146,
  8,10,10,10,0,1,8,24,24,24,127,254,24,24,24,16,
  4,6,6,5,0,0,112,96,96,64,192,192,8,2,2,9,
  0,5,127,254,3,3,3,4,0,0,224,160,224,8,14,14,
  9,0,0,3,3,6,6,12,12,24,24,48,48,96,96,192,
  192,9,14,28,10,0,0,62,0,127,0,227,128,197,128,197,
  128,197,128,201,128,201,128,209,128,209,128,209,128,227,128,127,
  0,62,0,9,14,28,10,0,0,12,0,28,0,60,0,124,
  0,8,0,4,0,12,0,12,0,8,0,4,0,12,0,12,
  0,127,128,255,128,9,14,28,10,0,0,126,0,255,0,195,
  128,193,128,1,128,3,128,63,0,126,0,224,0,192,0,192,
  0,192,0,223,128,191,0,9,14,28,10,0,0,126,0,255,
  0,195,128,1,128,1,128,3,0,58,0,119,0,3,128,1,
  128,1,128,3,128,255,0,126,0,9,14,28,10,0,0,2,
  0,6,0,14,0,30,0,62,0,118,0,230,0,198,0,251,
  128,247,0,6,0,6,0,6,0,4,0,9,14,28,10,0,
  0,127,128,127,128,96,0,96,0,96,0,110,0,111,0,3,
  128,1,128,1,128,1,128,195,0,255,0,124,0,9,14,28,
  10,0,0,31,0,127,0,96,0,192,0,192,0,192,0,222,
  0,223,0,195,128,193,128,193,128,227,0,127,0,60,0,9,
  14,28,10,0,0,127,128,255,128,0,0,3,0,3,0,6,
  0,6,0,12,0,12,0,24,0,24,0,48,0,48,0,32,
  0,9,14,28,10,0,0,58,0,119,0,227,128,193,128,193,
  128,99,0,54,0,111,0,227,128,193,128,193,128,227,128,119,
  0,46,0,9,14,28,10,0,0,60,0,255,0,231,0,195,
  128,193,128,193,128,225,128,253,128,125,128,1,128,3,128,7,
  0,127,0,252,0,3,11,11,4,0,2,224,160,224,0,0,
  0,0,0,224,160,224,4,13,13,5,0,0,112,80,112,0,
  0,0,0,0,112,80,112,96,192,9,11,22,10,0,1,1,
  128,3,128,15,0,28,0,120,0,224,0,120,0,28,0,15,
  0,3,128,1,128,9,6,12,10,0,4,127,128,255,0,0,
  0,0,0,127,128,255,0,9,11,22,10,0,1,192,0,224,
  0,120,0,60,0,15,0,3,128,15,0,60,0,120,0,224,
  0,192,0,8,14,14,9,0,0,116,238,135,3,3,6,14,
  56,48,48,0,0,48,48,9,11,22,10,0,1,62,0,65,
  0,128,128,154,128,166,128,162,128,162,128,166,128,155,0,64,
  0,63,128,9,14,28,10,0,0,252,0,254,0,199,0,195,
  128,193,128,193,128,193,128,253,128,253,128,193,128,193,128,193,
  128,193,128,129,0,9,14,28,10,0,0,94,0,223,0,195,
  128,193,128,193,128,195,128,255,0,255,0,195,128,193,128,193,
  128,195,128,223,0,190,0,9,14,28,10,0,0,14,0,63,
  0,115,128,97,128,192,0,192,0,192,0,192,0,192,0,192,
  0,96,0,112,0,63,128,15,0,9,14,28,10,0,0,238,
  0,111,0,99,128,97,128,97,128,97,128,97,128,97,128,97,
  128,97,128,97,128,99,128,111,0,238,0,9,14,28,10,0,
  0,95,128,223,0,192,0,192,0,192,0,192,0,223,0,222,
  0,192,0,192,0,192,0,192,0,223,128,191,0,9,14,28,
  10,0,0,95,128,223,0,192,0,192,0,192,0,192,0,223,
  0,222,0,192,0,192,0,192,0,192,0,192,0,128,0,9,
  14,28,10,0,0,63,128,127,128,225,128,192,0,192,0,192,
  0,192,0,207,128,223,128,193,128,193,128,225,128,127,128,62,
  0,9,14,28,10,0,0,129,0,193,128,193,128,193,128,193,
  128,193,128,223,128,223,128,193,128,193,128,193,128,193,128,193,
  128,64,128,8,14,14,10,1,0,254,127,24,24,24,24,24,
  24,24,24,24,24,254,127,9,14,28,10,0,0,31,128,63,
  128,1,128,1,128,1,128,1,128,1,128,1,128,1,128,1,
  128,1,128,195,128,255,0,62,0,9,14,28,10,0,0,65,
  128,195,128,199,0,206,0,220,0,216,0,216,0,216,0,216,
  0,220,0,206,0,199,0,195,128,65,128,9,14,28,10,0,
  0,64,0,192,0,192,0,192,0,192,0,192,0,192,0,192,
  0,192,0,192,0,192,0,192,0,255,128,255,0,9,14,28,
  10,0,0,193,128,227,128,247,128,247,128,213,128,193,128,213,
  128,221,128,221,128,201,128,193,128,193,128,193,128,129,0,9,
  14,28,10,0,0,225,0,225,128,241,128,241,128,249,128,217,
  128,221,128,205,128,205,128,197,128,197,128,193,128,193,128,128,
  128,9,14,28,10,0,0,46,0,111,0,227,128,193,128,193,
  128,193,128,193,128,193,128,193,128,193,128,193,128,227,128,123,
  0,58,0,9,14,28,10,0,0,254,0,255,0,195,128,193,
  128,193,128,195,128,223,0,222,0,192,0,192,0,192,0,192,
  0,192,0,128,0,10,15,30,10,0,255,46,0,111,0,227,
  128,193,128,193,128,193,128,193,128,193,128,193,128,193,128,193,
  128,227,0,123,128,58,192,0,192,9,14,28,10,0,0,126,
  0,255,0,195,128,193,128,193,128,195,128,223,0,220,0,206,
  0,199,0,195,128,193,128,193,128,129,0,9,14,28,10,0,
  0,62,0,127,0,224,0,192,0,192,0,224,0,118,0,27,
  0,3,128,1,128,1,128,3,128,255,128,127,0,9,14,28,
  10,0,0,255,0,127,128,0,0,12,0,12,0,12,0,12,
  0,12,0,12,0,12,0,12,0,12,0,12,0,4,0,9,
  14,28,10,0,0,64,128,193,128,193,128,193,128,193,128,193,
  128,193,128,193,128,193,128,193,128,193,128,99,0,127,0,62,
  0,9,14,28,10,0,0,227,128,99,0,99,0,99,0,34,
  0,54,0,54,0,54,0,20,0,28,0,28,0,28,0,8,
  0,8,0,10,14,28,11,0,0,64,64,192,192,192,192,192,
  192,192,192,192,192,204,192,204,192,204,192,222,192,222,192,211,
  192,193,192,128,192,9,14,28,10,0,0,193,128,193,128,193,
  128,99,0,99,0,50,0,56,0,28,0,14,0,103,0,99,
  0,193,128,193,128,193,128,10,14,28,10,0,0,192,192,192,
  192,97,128,97,128,51,0,63,0,30,0,12,0,8,0,4,
  0,12,0,12,0,12,0,8,0,9,14,28,10,0,0,127,
  128,255,128,1,128,3,128,7,0,6,0,4,0,16,0,48,
  0,112,0,224,0,192,0,255,128,255,0,5,14,14,6,0,
  0,248,192,192,192,192,192,192,192,192,192,192,192,192,248,9,
  14,28,10,0,0,192,0,96,0,96,0,48,0,48,0,24,
  0,24,0,12,0,12,0,6,0,6,0,3,0,3,0,1,
  128,5,14,14,6,0,0,248,24,24,24,24,24,24,24,24,
  24,24,24,24,248,9,6,12,10,0,8,8,0,28,0,54,
  0,99,0,193,128,128,128,10,1,2,10,0,255,255,192,4,
  3,3,5,0,12,224,96,48,8,11,11,9,0,0,124,127,
  3,3,59,123,227,195,199,255,123,9,13,26,10,0,0,64,
  0,192,0,192,0,192,0,192,0,222,0,223,0,195,128,193,
  128,193,128,195,0,255,0,222,0,8,11,11,9,0,0,30,
  63,115,224,192,192,192,224,240,127,30,9,13,26,10,0,0,
  0,128,1,128,1,128,1,128,1,128,61,128,125,128,225,128,
  193,128,193,128,227,128,127,128,61,128,8,11,11,9,0,0,
  60,126,231,195,195,223,222,192,227,127,62,7,13,13,8,0,
  0,62,124,96,96,252,248,96,96,96,96,96,96,32,8,13,
  13,9,0,0,63,127,227,195,195,195,227,123,51,3,3,127,
  254,8,13,13,9,0,0,64,192,192,192,222,223,195,195,195,
  195,195,195,130,2,13,13,3,0,0,64,192,128,64,192,192,
  192,192,192,192,192,192,128,5,14,14,6,0,255,16,24,24,
  8,48,120,24,24,24,24,24,24,120,240,8,13,13,9,0,
  0,64,192,192,198,198,204,216,216,216,204,198,199,131,2,14,
  14,3,0,0,64,192,192,192,192,192,192,192,192,192,192,192,
  192,128,9,11,22,10,0,0,91,0,219,128,201,128,201,128,
  201,128,201,128,201,128,201,128,201,128,193,128,129,0,8,11,
  11,9,0,0,94,223,195,195,195,195,195,195,195,195,130,8,
  11,11,9,0,0,52,118,227,195,195,195,195,195,227,118,52,
  8,11,11,9,0,0,252,254,199,195,199,222,220,192,192,192,
  128,9,13,26,9,0,255,63,0,127,0,227,0,195,0,195,
  0,195,0,251,0,123,0,3,0,3,0,3,128,3,128,3,
  128,7,11,11,8,0,0,92,222,224,224,192,192,192,192,192,
  192,128,8,11,11,9,0,0,62,127,192,192,240,102,15,3,
  3,254,124,6,13,13,7,0,0,32,96,96,252,248,96,96,
  96,96,96,96,124,60,8,11,11,9,0,0,65,195,195,195,
  195,195,195,195,227,123,58,8,11,11,9,0,0,129,129,195,
  195,102,102,102,36,60,24,24,9,11,22,10,0,0,128,128,
  193,128,201,128,201,128,201,128,201,128,193,128,221,128,247,128,
  227,128,65,0,8,11,11,9,0,0,195,102,102,52,24,24,
  24,52,102,102,195,8,12,12,9,0,255,193,227,99,102,110,
  44,12,24,24,48,240,224,8,11,11,9,0,0,127,255,7,
  6,12,0,48,96,224,255,254,7,14,14,8,0,0,14,28,
  24,24,24,48,224,224,48,24,24,24,28,14,2,16,16,5,
  1,255,192,192,192,192,192,192,192,192,192,192,192,192,192,192,
  192,192,8,14,14,9,0,0,224,112,24,24,24,12,7,7,
  12,24,24,24,112,224,10,5,10,11,0,4,48,192,120,192,
  204,192,199,128,195,0,5,13,13,6,0,1,248,136,136,136,
  136,136,136,136,136,136,136,136,248,8,11,11,9,0,0,252,
  254,199,195,199,222,220,192,192,192,128,8,11,11,9,0,0,
  30,63,115,224,192,192,192,224,240,127,30,8,11,11,9,0,
  0,127,254,24,24,24,24,24,24,24,24,16,8,12,12,9,
  0,255,193,227,99,102,110,44,12,24,24,48,240,224,8,12,
  12,9,0,255,126,255,219,219,219,219,90,24,24,24,24,16,
  8,11,11,9,0,0,195,102,102,52,24,24,24,52,102,102,
  195,8,12,12,9,0,255,132,198,198,198,198,198,198,198,198,
  254,255,3,8,11,11,9,0,0,65,195,195,195,231,127,63,
  3,3,3,2,8,11,11,9,0,0,130,195,195,211,219,219,
  219,219,219,203,255,8,12,12,9,0,255,130,195,195,211,219,
  219,219,219,218,200,255,3,8,11,11,9,0,0,192,224,96,
  96,108,110,103,99,103,126,124,8,11,11,9,0,0,130,195,
  195,195,219,221,207,199,207,253,251,8,11,11,9,0,0,64,
  192,192,192,220,222,199,195,199,254,252,8,11,11,9,0,0,
  124,254,198,3,27,59,3,3,6,254,120,9,11,22,10,0,
  0,71,0,207,128,205,128,205,128,221,128,221,128,205,128,205,
  128,205,128,207,128,135,0,8,11,11,9,0,0,63,127,227,
  195,227,123,59,51,51,115,226,9,14,28,10,0,0,252,0,
  254,0,199,0,195,128,193,128,193,128,193,128,253,128,253,128,
  193,128,193,128,193,128,193,128,129,0,9,14,28,10,0,0,
  223,128,223,0,192,0,192,0,192,0,192,0,222,0,223,0,
  195,128,193,128,193,128,195,128,255,0,254,0,9,14,28,10,
  0,0,94,0,223,0,195,128,193,128,193,128,195,128,255,0,
  255,0,195,128,193,128,193,128,195,128,223,0,190,0,9,14,
  28,10,0,0,223,0,223,128,192,0,192,0,192,0,192,0,
  192,0,192,0,192,0,192,0,192,0,192,0,192,0,128,0,
  9,14,28,10,0,0,11,0,27,0,59,0,115,0,99,0,
  99,0,99,0,99,0,99,0,99,0,123,0,251,128,193,128,
  193,128,9,14,28,10,0,0,95,128,223,0,192,0,192,0,
  192,0,192,0,223,0,222,0,192,0,192,0,192,0,192,0,
  223,128,191,0,10,14,28,11,0,0,64,64,192,192,196,192,
  204,192,204,192,109,128,109,128,109,128,109,128,204,192,204,192,
  204,192,200,192,128,128,9,14,28,10,0,0,122,0,251,0,
  131,128,1,128,1,128,3,0,58,0,123,0,3,128,1,128,
  1,128,131,128,251,0,120,0,9,14,28,10,0,0,67,128,
  195,128,199,128,199,128,199,128,205,128,205,128,205,128,217,128,
  217,128,217,128,209,128,209,128,193,0,9,14,28,10,0,0,
  91,128,219,128,215,128,199,128,199,128,205,128,205,128,205,128,
  217,128,217,128,217,128,209,128,209,128,193,0,9,14,28,10,
  0,0,65,128,195,128,199,0,206,0,220,0,216,0,216,0,
  216,0,216,0,220,0,206,0,199,0,195,128,65,128,9,14,
  28,10,0,0,220,0,222,0,199,0,195,128,193,128,193,128,
  193,128,193,128,193,128,193,128,193,128,193,128,193,128,193,128,
  9,14,28,10,0,0,193,128,227,128,247,128,247,128,213,128,
  193,128,213,128,221,128,221,128,201,128,193,128,193,128,193,128,
  129,0,9,14,28,10,0,0,129,0,193,128,193,128,193,128,
  193,128,193,128,223,128,223,128,193,128,193,128,193,128,193,128,
  193,128,64,128,9,14,28,10,0,0,46,0,111,0,227,128,
  193,128,193,128,193,128,193,128,193,128,193,128,193,128,193,128,
  227,128,123,0,58,0,9,14,28,10,0,0,223,128,223,128,
  193,128,193,128,193,128,193,128,193,128,193,128,193,128,193,128,
  193,128,193,128,193,128,129,0,9,14,28,10,0,0,254,0,
  255,0,195,128,193,128,193,128,195,128,223,0,222,0,192,0,
  192,0,192,0,192,0,192,0,128,0,9,14,28,10,0,0,
  14,0,63,0,115,128,97,128,192,0,192,0,192,0,192,0,
  192,0,192,0,96,0,112,0,63,128,15,0,9,14,28,10,
  0,0,255,0,127,128,0,0,12,0,12,0,12,0,12,0,
  12,0,12,0,12,0,12,0,12,0,12,0,4,0,9,14,
  28,10,0,0,129,0,193,128,193,128,193,128,193,128,125,128,
  61,128,1,128,1,128,1,128,193,128,225,128,127,0,62,0,
  10,14,28,11,0,0,63,0,127,128,237,192,204,192,204,192,
  237,192,109,128,45,0,12,0,12,0,12,0,12,0,12,0,
  4,0,9,14,28,10,0,0,193,128,193,128,193,128,99,0,
  99,0,50,0,56,0,28,0,14,0,103,0,99,0,193,128,
  193,128,193,128,10,15,30,10,0,255,130,0,195,0,195,0,
  195,0,195,0,195,0,195,0,195,0,195,0,195,0,195,0,
  195,0,223,0,223,128,1,192,9,14,28,10,0,0,129,0,
  193,128,193,128,193,128,193,128,193,128,225,128,125,128,61,128,
  1,128,1,128,1,128,1,128,0,128,10,14,28,11,0,0,
  128,128,192,192,192,192,192,192,200,192,204,192,204,192,204,192,
  204,192,204,192,196,192,192,64,223,128,95,192,11,15,30,11,
  0,255,128,128,192,192,192,192,192,192,200,192,204,192,204,192,
  204,192,204,192,204,192,196,192,192,64,223,128,95,192,0,224,
  9,14,28,10,0,0,224,0,224,0,96,0,96,0,96,0,
  96,0,110,0,111,0,99,128,97,128,97,128,99,128,127,0,
  62,0,10,14,28,11,0,0,128,128,192,192,192,192,192,192,
  192,192,192,192,220,192,222,192,199,64,195,64,195,64,199,64,
  254,192,124,192,9,14,28,10,0,0,128,0,192,0,192,0,
  192,0,192,0,192,0,222,0,223,0,195,128,193,128,193,128,
  195,128,255,0,254,0,9,14,28,10,0,0,62,0,127,0,
  227,128,193,128,193,128,1,128,29,128,29,128,1,128,1,128,
  193,128,227,128,127,0,62,0,10,14,28,11,0,0,71,0,
  207,128,221,192,216,192,216,192,216,192,248,192,248,192,216,192,
  216,192,216,192,221,192,207,128,135,0,9,14,28,10,0,0,
  63,128,127,128,225,128,193,128,193,128,225,128,125,128,61,128,
  29,128,57,128,113,128,225,128,193,128,129,0,8,11,11,9,
  0,0,124,127,3,3,59,123,227,195,199,255,123,8,11,11,
  9,0,0,7,31,56,112,102,207,195,195,231,126,60,8,11,
  11,9,0,0,92,222,198,198,220,222,195,195,199,222,188,8,
  11,11,9,0,0,254,255,192,192,192,192,192,192,192,192,128,
  10,11,22,11,0,0,31,128,31,128,25,128,49,128,49,128,
  49,128,1,128,127,192,255,192,192,192,192,192,8,11,11,9,
  0,0,60,126,231,195,195,223,222,192,227,127,62,9,11,22,
  10,0,0,64,128,201,128,201,128,107,0,54,0,54,0,107,
  0,201,128,201,128,201,128,129,0,8,11,11,9,0,0,116,
  246,195,7,126,62,6,3,7,254,124,8,11,11,9,0,0,
  65,195,195,199,207,223,219,211,195,195,130,8,11,11,9,0,
  0,89,219,211,199,207,223,219,211,195,195,130,8,11,11,9,
  0,0,71,207,204,220,216,216,216,220,206,199,67,8,11,11,
  9,0,0,27,59,115,227,195,195,195,195,195,195,195,9,11,
  22,10,0,0,65,0,227,128,247,128,247,128,213,128,213,128,
  213,128,213,128,193,128,193,128,129,0,8,11,11,9,0,0,
  65,195,195,251,251,195,195,195,195,195,130,8,11,11,9,0,
  0,52,118,227,195,195,195,195,195,227,118,52,8,11,11,9,
  0,0,95,223,195,195,195,195,195,195,195,195,130,255,255,255,
  255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
  255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
  255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
  255,255,255,255,255,255,255,255,255,255,255,255,255};
  
 // ---------------------------------------------------------------------------

 

Andrey_Y_Ostanovsky
Offline
Зарегистрирован: 03.12.2012

Я бы делал wdt_reset в самом начале loop(). А то пока кнопки обрабатываете - оно и перегрузится.

pav2000
Offline
Зарегистрирован: 15.12.2014

Andrey_Y_Ostanovsky пишет:

Я бы делал wdt_reset в самом начале loop(). А то пока кнопки обрабатываете - оно и перегрузится.

Не вижу особого смысла сторежевой таймер настроен на 8 сек  прерывание по таймеру где сидит его сброс 3 сек запас есть.

Сейчас попробовал Вашу идею тоже все работает -))

jeka_tm
jeka_tm аватар
Offline
Зарегистрирован: 19.05.2013

как то все слишком просто. нормально оформлено (не считая того что код не свернул), и фото, и код. не к чему придраться вот так сразу, разве что лаком не покрыты платы, условия то не самые лучшие

хорошо получилось

vvadim
Онлайн
Зарегистрирован: 23.05.2012

понравилось, классно сделаный домашний проект.
респект автору!

Focus
Offline
Зарегистрирован: 05.04.2015

Подпишусь пожалуй

kash001
Offline
Зарегистрирован: 14.08.2015

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

2. Приточный вентилятор хорошо, но, как мне кажется, не очень эффективно. Хотя зимой при низкой температуре и при 100%-й влажности наружного воздуха, заходя и нагреваясь влажность будет, конечно, значительно ниже 100%. Но на подогрев воздуха потребуется расходовать энергию дома. Для подогрева входящего воздуха можно использовать самодельный рекуператор с выходящим воздухом.

3. Можно попробовать высушить воздух/стены осушителем еще до зимы. Зимой при температуре 0-2 градуса, кмк, от осушителя толку не будет никакого. Я себе купил б/у ballu bhd25 (примерно с таким названием) за 4т.р. пару лет назад, с производительностью до 25л/сутки, чтобы высушить погреб под гаражом, и при изучении своего вопроса мне сказали, что на зиму все вентиляционные отверстия нужно закрывать.

4. По вашему графику видно, что влажность в подвале снижается, но! до каких значений ее можно снизить? Пусть на улице минимальная абс. влажность 9г/м3, и до этого значения вы опустите влажность в подвале, но! при опускании температуры до 1-2градусов, относительная влажность 100% установится при 6г/м3, а у вас только 9?

pav2000
Offline
Зарегистрирован: 15.12.2014

kash001 пишет:

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

На сколько мне известно большинство датчиков измеряют ОТНОСИТЕЛЬНУЮ влажность (показания %). Конкретно DHT-22 выдает относительную влажность (согласно даташита на него). 0-100% И как раз перевод из относительной в абсолютную является "тонким" моментом и существует несколько эмпирических формул (ссылку на одну я приводил выше).

kash001 пишет:

2. Приточный вентилятор хорошо, но, как мне кажется, не очень эффективно. Хотя зимой при низкой температуре и при 100%-й влажности наружного воздуха, заходя и нагреваясь влажность будет, конечно, значительно ниже 100%. Но на подогрев воздуха потребуется расходовать энергию дома. Для подогрева входящего воздуха можно использовать самодельный рекуператор с выходящим воздухом.

Я рассчитываю не использовать энергию для подогорева приточного воздуха зимой. У меня подвал заглубленный, утепленный по периметру. Измерения прошлого года показали что даже при заботных температурах -30 (в течении 9 часов) у меня в подвале было +1+2. Средняя температура подвала +4+5, это объясняется подогревом подвала грунтом, который имеет температуру около +6 градусов.

Включение вентилятора происходит не только при определенной разнице абсолютных влажностей но и при темперетуре в подвале НЕ МЕНЬШЕ заданной (например +2 - см. надпись синим цветом). Система конечно будет охлаждать подвал но температура в подвале не упадет ниже заданной. В подвале зимой всегда будет теплее чем на улице. По этому конденсат не будет выпадать (поступающий воздух в абсолютных значениях более сухой и попадает в более теплое помещение)

Кстати рекуператор будет здесь мало эффективен - очень малая дельта температур входящего и выходящего воздуха. Либо надо использовать теплообменник лошадиных размеров.

kash001 пишет:

3. Можно попробовать высушить воздух/стены осушителем еще до зимы. Зимой при температуре 0-2 градуса, кмк, от осушителя толку не будет никакого. Я себе купил б/у ballu bhd25 (примерно с таким названием) за 4т.р. пару лет назад, с производительностью до 25л/сутки, чтобы высушить погреб под гаражом, и при изучении своего вопроса мне сказали, что на зиму все вентиляционные отверстия нужно закрывать.

Осушитель - большие энергозатраты + проблема отвода конденсата (если накопитель переполнится).  Как раз зимой осушитель будет работать очень эффективно. Допустим  - в подвале +4  влажность 7  за бортом -10 влажность 3 грамм (все цифры условны). Осушитель включается, внутри подвала влажность и температура падает допустим до +2 и 5 грамм. По температуре (+2) осушитель выключается  но за 5-10 часов подвал нагревается грунтом до +4 (влажность остается 5 грамм), осушитель включается и итерация повторяется. Постепенно влажность уменьшается до 3 при колебаниии температуры +2 +4 градуса.

При использовании осушителя конечно надо закрывать вентиляционные отверстия. Иначе вы будите осушать улицу.

kash001 пишет:

4. По вашему графику видно, что влажность в подвале снижается, но! до каких значений ее можно снизить? Пусть на улице минимальная абс. влажность 9г/м3, и до этого значения вы опустите влажность в подвале, но! при опускании температуры до 1-2градусов, относительная влажность 100% установится при 6г/м3, а у вас только 9?

см предыдущий пост. При уменьшении влажности осушитель включится и доведет влажность до 6 грамм. При  абсолютной герметичности подвала (с заслонкой на притоке) в подвале установится за зиму влажность соответсвующая температуре -20 -25 градусов.

Подведем итоги.

Принцип работы моего осушителя с точки зрения энерго затрат очень эффективен. Потребляемая мощность - блок ( 1вт) и вентилятор - 18 вт.

Проблемы с конденсатом будут если уличная температура и влажность  выше  внутренних длительный промежуток времени ( я думаю 1-2 суток, что маловероятно) тогда за счет не герметичности уличный воздух будет попадать в подвал и возможно выпадение конденсата. Для уменьшения этого эффекта надо ставить управляемую заслонку для вентиляции (сейчас у меня отверстие всегда открыто), которая будет открываться вместе с включением вентилятора.

 

pav2000
Offline
Зарегистрирован: 15.12.2014

Немного поправил исходник. В первую очередь это коснулось формата передаваемого пакета. Так как приемник будет на stm32, то в процессе отладки приемника выяснилось, что компилятор stm32 выравнивает переменные с точностью 2 байта  и int в stm32 занимает 4 байта. Структура пакета была переделана, чтобы она была одинаковой на обеих платформах.

Итоговый исходник:

#pragma GCC optimize ("-Os")
//#pragma pack(push, 1)     // выравнивание по одному байту ????????

#include <SPI.h>
#include <EEPROM.h>
#include <avr/wdt.h>
#include <leOS.h>      // Шедуллер задач
#include <dht.h>       // Дачик влажности и температуры
#include "Ucglib.h"    // ВНИМАНИЕ использовать библиотеку не познее 1.01 справка https://code.google.com/p/ucglib/wiki/
#include "rusFont.h"   // Русские шрифты
#include "nRF24L01.h"  // Беcпроводной модуль
#include "RF24.h"      // Беcпроводной модуль

//#define DEBUG                                   // Отладочную  информацию в ком порт посылает  
//#define DEMO                                    // Признак демонстрации - данные с датчиков генерятся данные
#define BEEP                                    // Использовать пищалку
#define RADIO                                   // Признак использования радио модуля

#define dH_OFF          15                      // Гистерезис абсолютной влажности в сотых грамма на куб
#define dT_OFF          25                      // Гистерезис температуры в сотых градуса

#ifdef DEMO                                     // Для демо все быстрее и случайным образом
    #define NUM_SAMPLES      2                  // Число усреднений измерений датчика
    #define TIME_SCAN_SENSOR 2000               // Время опроса датчиков мсек, для демки быстрее
    #define TIME_PRINT_CHART 8000               // Время вывода точки графика мсек, для демки быстрее
    #define TIME_HOUR        50000              // Число мсек в часе, для демки быстрее   
#else   
   #define NUM_SAMPLES      10                  // Число усреднений измерений датчика
   #define TIME_SCAN_SENSOR 2000                // Время опроса датчиков мсек
   #define TIME_PRINT_CHART 300000              // Время вывода точки графика мсек
   #define TIME_HOUR        3600000             // Число мсек в часе
#endif

#define VERSION "Version: 0.55 23/08/15"        // Текущая версия
#define ID             0x21                     // уникально Идентификатор устройства - старшие 4 бита, вторые (младшие) 4 бита серийный номер устройства
#define NUM_SETTING    6                        // Число вариантов настроек - 1
#define MOTOR_OFF      0                        // Мотор выключен
#define MOTOR_ON       1                        // Мотор включен
#define LONG_KEY      3000                      // Длительное нажатие кнопки мсек, появляется Экран инфо
#define SHORT_KEY     150                       // Короткое нажатие кнопки более мсек
#define NRF24_CHANEL  100                       // Номер канала nrf24

// СИСТЕМАТИЧЕСКИЕ ОШИБКИ ДАТЧИКОВ для ID 0x21
#define TOUT_ERR      40                         // Ошибка уличного датчика температуры в сотых долях градуса
#define TIN_ERR       40                         // Ошибка домового датчика температуры в сотых долях градуса
#define HOUT_ERR      -30                        // Ошибка уличного датчика влажности в сотых долях %
#define HIN_ERR       +30                        // Ошибка домового датчика влажности в сотых долях %


//  НОГИ к которым прицеплена переферия (SPI используется для TFT и NRF24 - 11,12,13)
#ifdef BEEP
    #define PIN_BEEP  15                         // Ножка куда повешена пищалка
#endif
#define PIN_RELAY     14                         // Ножка на которую повешено реле (SSR) вентилятора - аналоговый вход A0 через резистор 470 ом
#define PIN_CS        10                         // TFT дисплей spi
#define PIN_CD        9                          // TFT дисплей spi
#define PIN_RESET     8                          // TFT дисплей spi
#define PIN_CE        7                          // nrf24 ce
#define PIN_CSN       6                          // nrf24 cs
#define PIN_DHT22a    5                          // Первый датчик DHT22   IN  ДОМ
#define PIN_DHT22b    4                          // Второй датчик DHT22   OUT УЛИЦА
#define PIN_KEY       3                          // Кнопка, повешена на прерывание, что бы ресурсов не тратить
#define PIN_IRQ_NRF24 2                          // Ножка куда заведено прерывание от NRF24 (пока не используется)


// АЦП ----------------------------------------
const long ConstADC=1126400;                    // Калибровка встроенного АЦП (встроенный ИОН) по умолчанию 1126400 дальше измеряем питание и смотрим на дисплей    

Ucglib_ILI9341_18x240x320_HWSPI ucg(/*cd=*/PIN_CD, /*cs=*/PIN_CS, /*reset=*/PIN_RESET); // Аппаратный SPI на дисплей ILI9341
leOS myOS;                                      // многозадачность
dht DHT;                                        // Датчики
bool infoScreen=false;                          // Признак отображениея иформационного экрана  1 - на экран ничего не выводится кроме информационного экрана
bool flagKey=false;                             // Флаг нажатия клавиши
bool pressKey=false;                            // Флаг необходимости обработки кнопки

unsigned long time_key=0;                       // Время нажатия копки
byte last_error=100;                            // Предыдущая ошибка чтения датчиков

 struct type_setting_eeprom                     // Структура для сохранения данных в eeprom
 {
     byte fStart = 0;                           // Какой набор настроек используется
     unsigned long hour_unit;                   // мото часы блок измеряется в интервалах вывода графика TIME_PRINT_CHART
     unsigned long hour_motor;                  // мото часы мотор измеряется в интервалах вывода графика TIME_PRINT_CHART
 }; 
volatile type_setting_eeprom settingRAM;        // Рабочая копия счетчиков в памяти
type_setting_eeprom settingEEPROM EEMEM;        // Копия счетчиков в eeprom - туда пишем 

// пакет передаваемый, используется также для хранения результатов. 
 struct type_packet_NRF24        // Версия 2.0!! адаптация для stm32 Структура передаваемого пакета 32 байта - это максимум
    {
        byte id=0x22;                               // Идентификатор типа устройства - старшие 4 бита, вторые (младшие) 4 бита серийный номер устройства
        byte error;                                 // Ошибка разряды: 0-1 первый датчик (00-ок) 2-3 второй датчик (00-ок) 4 - радиоканал     
        int16_t   tOut=-500,tIn=-500;               // Текущие температуры в сотых градуса !!! место экономим
        uint16_t  absHOut=123,absHIn=456;           // Абсолютные влажности в сотых грамма на м*3 !!! место экономим
        uint16_t  relHOut=555,relHIn=555;           // Относительные влажности сотых процента !!! место экономим
        uint8_t   motor=false;                      // битовая маска пока используется один бит Статус вентилятора 
        uint8_t dH;                                 // Порог включения вентилятора по абсолютной влажностив сотых грамма на м*3
        uint16_t  T;                                // Порог выключения вентилятора по температуре в сотых градуса
        char note[12] = "ArduDry :-)";              // Примечание не более 11 байт + 0 байт
    } packet;  
struct type_sensors                                 // структура для усреднения
{
       int  num;                                    // сколько отсчетов уже сложили не болле NUM_SAMPLES
       long  sum_tOut=-5000,sum_tIn=-5000;          // Сумма для усреднения Текущие температуры в сотых градуса !!! место экономим
       long  sum_relHOut=55555,sum_relHIn=55555;    // Сумма для усреднения Относительные влажности сотых процента !!! место экономим
       int  tOut=-5000,tIn=-5000;                   // Текущие температуры в сотых градуса !!! место экономим
       int  absHOut=55555,absHIn=55555;             // Абсолютные влажности в сотых грамма на м*3 !!! место экономим
       int  relHOut=55555,relHIn=55555;             // Относительные влажности сотых процента !!! место экономим
} sensors;
    
// Массивы для графиков
byte tOutChart[120];
byte tInChart[120];
byte absHOutChart[120];
byte absHInChart[120];
byte posChart=0;       // Позиция в массиве графиков - начало вывода от 0 до 120-1
byte TimeChart=0;      // Время до вывода очередной точки на график. 

#ifdef  RADIO    // Радио модуль NRF42l  
   RF24 radio(PIN_CE, PIN_CSN);  //определение управляющих ног
   bool send_packet_ok=false;                // признак удачной отправки последнего пакета 
#endif 
///////////////////////////////////////////////////////////////////////////////////////////////////////////
// ПРОГРАММА
///////////////////////////////////////////////////////////////////////////////////////////////////////////
void setup(){
#ifdef  DEBUG  
   Serial.begin(9600); 
   Serial.println(F("DEBUG MODE")); 
   #ifdef  BEEP  
     Serial.println(F("BEEP ON")); 
   #else         
     Serial.println(F("BEEP OFF"));
   #endif
   #ifdef  RADIO 
     Serial.println(F("RADIO ON")); 
   #else         
     Serial.println(F("RADIO OFF"));
   #endif
#endif

reset_sum();

#ifdef  RADIO    // Радио модуль NRF42l  первичная настройка
   radio.begin();
   radio.setDataRate(RF24_250KBPS);         //  выбор скорости RF24_250KBPS RF24_1MBPS RF24_2MBPS
   radio.setPALevel(RF24_PA_MAX);           // выходная мощность передатчика
   radio.setChannel(NRF24_CHANEL);          //тут установка канала
   radio.setCRCLength(RF24_CRC_16);         // использовать контрольную сумму в 16 бит
// radio.setAutoAck(false);                 // выключить аппаратное потверждение
// radio.enableDynamicPayloads();           // разрешить Dynamic Payloads
// radio.enableAckPayload();                // разрешить AckPayload
  radio.setRetries(10,30);                  // Количество повторов и пауза между повторами
  radio.openWritingPipe(0xF0F0F0F0E1LL);    // передатчик
  radio.openReadingPipe(1,0xF0F0F0F0D2LL);  // приемник
  radio.startListening();
#endif
  #ifdef BEEP
    pinMode(PIN_BEEP, OUTPUT);          //  Настройка ноги для динамика
    digitalWrite(PIN_BEEP, LOW);
  #endif
  pinMode(PIN_KEY, INPUT);              //  Включена кнопка
  digitalWrite(PIN_KEY, HIGH);  
  pinMode(PIN_RELAY, OUTPUT);           //  Реле
  digitalWrite(PIN_RELAY, LOW);  
  pinMode(PIN_DHT22a, OUTPUT);          //  Датчик DHT22 #1 
  digitalWrite(PIN_DHT22a, HIGH);  
  pinMode(PIN_DHT22b, OUTPUT);          //  Датчик DHT22 #2
  digitalWrite(PIN_DHT22b, HIGH);  
  
  reset_ili9341();                      // сброс дисплея
  readEeprom();                          // Прочитать настройки
  if (digitalRead(PIN_KEY)==0)           // Если при включении нажата кнопка то стираем все
  {   int i;
      settingRAM.fStart=0;
      settingRAM.hour_unit=0;
      settingRAM.hour_motor=0;
      ucg.setColor(255, 255, 255);
      print_StrXY(10,50,F("Сброс настроек и счетчиков"));
      for(i=0;i<3;i++)
        {
         delay(1000);
         ucg.print(F(" ."));
        }
      writeEeprom();       // Запись в EEPROM  
      delay(1000);
      ucg.clearScreen();
  } 
 
  wdt_enable(WDTO_8S); // Сторожевой таймер Для тестов не рекомендуется устанавливать значение менее 8 сек.
  // Запуск задач по таймеру
  myOS.begin();
  myOS.addTask(measurement,TIME_SCAN_SENSOR);    // Измерение 
  attachInterrupt(1, scanKey, CHANGE);           // КНОПКА Прерывания по обоим фронтам
  print_static();                                // распечатать таблицу
  Setting();                                     // Применить настройки
  measurement();                                 // Считать данные
  #ifdef BEEP
     beep(100);
  #endif
resetKey();
}

void loop()
{
 wdt_reset();                        // Сбросить сторожевой таймер
// Обработка нажатия кнопки
if (pressKey==true) // Кнопка 
{
 myOS.pauseTask(measurement);                                 // Остановить задачи
 if (flagKey!=false)
 if (time_key > LONG_KEY)                                     // Длительное нажатие кнопки
   printInfo();                                               // Вывод информационного экрана
 else 
   if (time_key > SHORT_KEY)                                  // Короткое нажатие кнопки
     { 
        #ifdef BEEP
          beep(30);                                            // Звук нажатия на клавишу
        #endif
       if (infoScreen==true)  clearInfo();                    // если информационный экран то стереть его
       else {  if (settingRAM.fStart >= NUM_SETTING) settingRAM.fStart=0; //  Кольцевой счетчик настроек
               else settingRAM.fStart++;                      // В противном случае следующая настройка
               Setting(); }  
     }
  resetKey();                                                //  Сброс состояния кнопки
  myOS.restartTask(measurement);                             // Запустить задачи
  }
}

void resetKey(void)  // Сброс состяния кнопки
{
  flagKey=false;                                             
  time_key=millis();                                                
  pressKey=false;
}  
void print_static()  // Печать статической картинки 
{
   cli();
  // Заголовок 
  ucg.setColor(0, 0, 180);  // 
  ucg.drawBox(0, 0, 320-1, 23);
  ucg.setColor(250, 250, 250);
  ucg.setFontMode(UCG_FONT_MODE_TRANSPARENT);
  print_StrXY(2,19,F("ОСУШИТЕЛЬ ID: 0x"));
  ucg.print( hex(packet.id >> 4));
  ucg.print( hex(packet.id&0x0f));
   
  #ifdef DEMO
    ucg.print(F(" demo"));
  #endif
  
  // Таблица для данных
  ucg.setColor(0, 200, 0);
  ucg.drawHLine(0,25,320-1);
  ucg.drawHLine(0,25+23*1,320-1);
  ucg.drawHLine(0,25+23*2,320-1);
  ucg.drawHLine(0,25+23*3,320-1);
  ucg.drawHLine(0,25+23*4,320-1);
  ucg.drawVLine(200-4,25,24+23*3);
  ucg.drawVLine(260,25,24+23*3);

  // Заголовки таблиц
  ucg.setColor(255, 255, 0);
  print_StrXY(180+30,25+0+18,F("Дом"));
  print_StrXY(250+20,25+0+18,F("Улица"));
  print_StrXY(0,25+23*1+18,F("Температура градусы C")); 
  print_StrXY(0,25+23*2+18,F("Относительная влаж. %")); 
  print_StrXY(0,25+23*3+18,F("Абсолют. влаж. г/м*3")); 

  // Графики
  ucg.setColor(200, 200, 200);
  ucg.drawHLine(1,240-1,130);
  ucg.drawVLine(1,135,105);
  ucg.drawHLine(10+150,240-1,130);
  ucg.drawVLine(10+150,135,105);
  print_StrXY(10,135+0,F("Температура")); 
  print_StrXY(20+150,135+0,F("Абс. влажность")); 
  
  // надписи на графиках
  print_StrXY(128,154,F("+20")); 
  print_StrXY(135,194,F("0")); 
  print_StrXY(128,233,F("-20"));
  
  print_StrXY(296,164,F("15"));
  print_StrXY(296,194,F("10"));
  print_StrXY(296,223,F("5"));
   sei();
}

void print_status() // Печать панели статуса Значки на статус панели
{
  if (infoScreen==true) return;        // если отображен информационный экран то ничего не выводим  
   cli();
 // 1. печать ошибки чтения датчиков
   print_error_DHT();
 // 2. Признак включения мотора
  if (packet.motor==true)  ucg.setColor(0, 240, 0); 
  else                     ucg.setColor(0, 40, 0);
  ucg.drawBox(290-32, 5, 14, 14);
  
  #ifdef  RADIO 
  // 3. Признак удачной передачи информации по радиоканалу
      if (send_packet_ok==true)  ucg.setColor(0, 240, 0); 
      else                       ucg.setColor(0, 40, 0);
      ucg.setPrintDir(3);
      ucg.setFontMode(UCG_FONT_MODE_TRANSPARENT);
      print_StrXY(290-40,20,F(">>")); 
      ucg.setPrintDir(0);
      ucg.setFontMode(UCG_FONT_MODE_SOLID);
  #endif
  sei();
}  

void print_error_DHT() // Печать ошибки чтения датчиков выводится при каждом чтении датчика
{
  if (infoScreen==true) return;        // если отображен информационный экран то ничего не выводим  
 // 1. печать ошибки чтения датчиков
  if (packet.error!=last_error)        // если статус ошибки поменялся то надо вывести если нет то не выводим - экономия время и нет мерцания
  {
      cli();
      last_error=packet.error; 
      ucg.setColor(0, 0, 180);         // Сначала стереть
      ucg.drawBox(290, 0, 26, 18);
      ucg.setPrintPos(290,18); 
      ucg.setFontMode(UCG_FONT_MODE_TRANSPARENT);
      if (packet.error>0) 
        { 
        ucg.setColor(255, 100, 100); 
        print_StrXY(280,19,F("0x"));
        ucg.print( hex(packet.error >> 4));
        ucg.print( hex(packet.error & 0x0f)); 
        }
      else  { ucg.setColor(200, 240, 0);   ucg.print(F("ok")); } 
     sei(); 
   }   
} 
//  вывод на экран данных (то что меняется)
void print_data()
{ 
 if (infoScreen==true) return;                  // если отображен информационный экран то ничего не выводим  
  cli();
 // Печать значений для дома
  ucg.setFontMode(UCG_FONT_MODE_SOLID);
  ucg.setColor(250, 0, 100);  // Цвет ДОМА
  print_floatXY(200+0,25+23*1+18,((float)packet.tIn)/100);
  print_floatXY(200+0,25+23*2+18,((float)packet.relHIn)/100);
  print_floatXY(200+0,25+23*3+18,((float)packet.absHIn)/100);
  ucg.setColor(0, 250, 100);  // Цвет УЛИЦЫ
  print_floatXY(260+4,25+23*1+18,((float)packet.tOut)/100);
  print_floatXY(260+6,25+23*2+18,((float)packet.relHOut)/100);
  print_floatXY(260+6,25+23*3+18,((float)packet.absHOut)/100);
  sei();
}  

// Печать графика на экране, добавляется одна точка и график сдвигается 
void printChart() 
{
byte i,x=0;
byte tInLast,absHInLast,tOutLast,absHOutLast;

// Статистика по моточасам, время ведется в тиках графика а потом пересчитывается в часы при выводе.
settingRAM.hour_unit++;
if (packet.motor==true) settingRAM.hour_motor++;

// Работаем через кольцевой буфер
// Добавить новую точку в кольцевой буфер
     // Температура в доме. диапазон -25 . . . +25 растягиваем на 100 точек
     tInLast=tInChart[posChart];                       // Сохранить точку для стирания на графике 
     if (packet.tIn<=-2500) tInChart[posChart]=0;      // Если температура меньше -25 то округляем до -25
     if (packet.tIn>=2500)  tInChart[posChart]=100;    // Если температура больше 25  то округляем до 25
     if ((packet.tIn>-2500)&&(packet.tIn<2500)) tInChart[posChart]=((2*packet.tIn)+5000)/100;  // внутри -25...+25 растягиваем в два раза
     // Температура на улице. диапазон -25 . . . +25 растягиваем на 100 точек
     tOutLast=tOutChart[posChart];                       // Сохранить точку для стирания на графике 
     if (packet.tOut<=-2500) tOutChart[posChart]=0;      // Если температура меньше -25 то округляем до -25
     if (packet.tOut>=2500)  tOutChart[posChart]=100;    // Если температура больше 25  то округляем до 25
     if ((packet.tOut>-2500)&&(packet.tOut<2500)) tOutChart[posChart]=((2*packet.tOut)+5000)/100;  // внутри -25...+25 растягиваем в два раза
     // Абсолютная влажность в доме диапазон от 0 до 20 грамм на кубометр, растягиваем на 100 точек
     absHInLast=absHInChart[posChart];                   // Сохранить точку для стирания на графике 
     if (packet.absHIn>=2000) absHInChart[posChart]=100;
     else absHInChart[posChart]=(5*packet.absHIn)/100;   // внутри 0...20 растягиваем в пять  раз
     // Абсолютная влажность на улицу диапазон от 0 до 20 грамм на кубометр, растягиваем на 100 точек
     absHOutLast=absHOutChart[posChart];                   // Сохранить точку для стирания на графике 
     if (packet.absHOut>=2000) absHOutChart[posChart]=100;
     else absHOutChart[posChart]=(5*packet.absHOut)/100;   // внутри 0...20 растягиваем в пять раз
     
  if (infoScreen==false)                 // если отображен информационный экран то ничего не выводим
   {
   cli();  
   for(i=0;i<120;i++)    // График слева на право
     { 
     // Вычислить координаты текущей точки x в кольцевом буфере. Изменяются от 0 до 120-1
     if (posChart<i) x=120+posChart-i; else x=posChart-i;

     ucg.setColor(0, 0, 0); // Стереть предыдущую точку
     if (x-1<0) ucg.drawPixel(5+120-i,237-tInLast);
     else       ucg.drawPixel(5+120-i,237-tInChart[x-1]);
     if (x-1<0) ucg.drawPixel(5+120-i,237-tOutLast);
     else       ucg.drawPixel(5+120-i,237-tOutChart[x-1]);
     if (x-1<0) ucg.drawPixel(6+120-i+158,237-absHInLast);
     else       ucg.drawPixel(6+120-i+158,237-absHInChart[x-1]); 
     if (x-1<0) ucg.drawPixel(6+120-i+158,237-absHOutLast);
     else       ucg.drawPixel(6+120-i+158,237-absHOutChart[x-1]); 

     // Вывести новую точку
     if ((tInChart[x]==0)||(tInChart[x]==100))   ucg.setColor(255, 255, 255); else ucg.setColor(250, 0, 100); 
     ucg.drawPixel(5+120-i,237-tInChart[x]);
     
     if (absHInChart[x]==100) ucg.setColor(255, 255, 255); else ucg.setColor(250, 0, 100); 
     ucg.drawPixel(6+120-i+158,237-absHInChart[x]);
     
     if ((tOutChart[x]==0) || (tOutChart[x]==100)) ucg.setColor(255, 255, 255); else ucg.setColor(0, 250, 100); 
     ucg.drawPixel(5+120-i,237-tOutChart[x]);
     
     if (absHOutChart[x]==100) ucg.setColor(255, 255, 255); else ucg.setColor(0, 250, 100); 
     ucg.drawPixel(6+120-i+158,237-absHOutChart[x]);
      }
  
     // Пунктирные линии графика
    ucg.setColor(100, 100, 100);  
    for(i=1;i<=120;i=i+5)
    {
       ucg.drawPixel(5+120-i,237-10);
       ucg.drawPixel(5+120-i,237-50);
       ucg.drawPixel(5+120-i,237-90);
       
       ucg.drawPixel(6+120-i+158,237-25);
       ucg.drawPixel(6+120-i+158,237-50);
       ucg.drawPixel(6+120-i+158,237-75);
     }  
    sei(); 
   } 
 if (posChart<120-1) posChart++; else posChart=0;            // Изменили положение в буфере и Замкнули буфер
} 

// ---ПЕРЕДАЧА ДАННЫХ ЧЕРЕЗ РАДИОМОДУЛЬ -----------------------------
#ifdef  RADIO    // Радио модуль NRF42l
void send_packet()
{
        radio.stopListening();     // Остановить приемник
        send_packet_ok = radio.write(&packet,sizeof(packet));
         #ifdef BEEP
           if (send_packet_ok==true) beep(40);           // Пакет передан успешно
         #endif
         #ifdef  DEBUG  
           if (send_packet_ok==true)  Serial.println(F("Packet sending ++++++++++"));
           else                       Serial.println(F("Packet NOT sending -----------"));
         #endif   
        radio.startListening();    // Включить приемник
 }  
#endif 
// Чтение датчика возвращает код ошибки:
// DHTLIB_OK                   0
// DHTLIB_ERROR_CHECKSUM       1
// DHTLIB_ERROR_TIMEOUT        2
// DHTLIB_ERROR_CONNECT        3
// DHTLIB_ERROR_ACK_L          4
// DHTLIB_ERROR_ACK_H          5
byte readDHT(byte pin)
{
delay(5);
cli();
  byte err=-1*DHT.read22(pin); // Чтение датчика
sei();  
return err; 
} 

// Измерение и обработка данных чтение датчиков --------------------------------------------------------------------------------
void measurement()
{ 
 myOS.pauseTask(measurement);        // Обязательно здесь, а то датчики плохо читаются мешает leos
// wdt_reset();                        // Сбросить сторожевой таймер
 
 packet.error=readDHT(PIN_DHT22a);   // ПЕРВЫЙ ДАТЧИК ДОМ  Новый пакет, сбросить все ошибки и прочитать первый датчик
 
 #ifdef  DEMO
   DHT.temperature=packet.tIn/100+random(-20,25)/10.0; 
   if (DHT.temperature>20) DHT.temperature=19;
   if (DHT.temperature<-10) DHT.temperature=-9;
   DHT.humidity=packet.relHIn/100+(float)random(-5,7);
   if (DHT.humidity>96) DHT.humidity=90;
   if (DHT.humidity<1) DHT.humidity=10;
   packet.error=0; // в Демо режиме
//   DHT.temperature=3.0;
//   DHT.humidity=21.0;
 #endif  
     sensors.tIn=(int)(DHT.temperature*100.0)+TIN_ERR;  // Запомнить результаты для суммирования
     sensors.relHIn=(int)(DHT.humidity*100.0)+HOUT_ERR;  
     
    #ifdef  DEBUG  
       Serial.print(F("Sensor read samples:")); Serial.println(sensors.num); 
       Serial.print(F("IN T="));Serial.print(sensors.tIn);Serial.print(F(" H=")); Serial.print(sensors.relHIn); Serial.print(F(" error=")); Serial.println(packet.error);
    #endif   
 
 packet.error=packet.error+16*readDHT(PIN_DHT22b);// ВТОРОЙ ДАТЧИК УЛИЦА  ошибки в старшие четыре бита
 #ifdef  DEMO
   DHT.temperature=packet.tOut/100+random(-40,45)/10.0; 
   if (DHT.temperature>30) DHT.temperature=29;
   if (DHT.temperature<-30) DHT.temperature=-29;
   DHT.humidity=packet.relHOut/100+random(-10,12);
   if (DHT.humidity>96) DHT.humidity=90;
   if (DHT.humidity<1)  DHT.humidity=10;
   packet.error=0;      // в Демо режиме
 //  DHT.temperature=-10.0;
 //  DHT.humidity=40.0;
 #endif  
     sensors.tOut=(int)(DHT.temperature*100.0)+TOUT_ERR;  // Запомнить результаты для суммирования
     sensors.relHOut=(int)(DHT.humidity*100.0)+HOUT_ERR;
 
    #ifdef  DEBUG  
       Serial.print(F("OUT T="));Serial.print(sensors.tOut);Serial.print(F(" H=")); Serial.print(sensors.relHOut); Serial.print(F(" error=")); Serial.println(packet.error);
    #endif   
 
 print_error_DHT();    // Вывод ошибки чтения датчика при каждом чтении контроль за качеством связи с датчиком
 
 if (packet.error==0)// Если чтение без ошибок у ДВУХ датчиков  копим сумму для усреднения
  {
     sensors.sum_tIn=sensors.sum_tIn+sensors.tIn;
     sensors.sum_relHIn=sensors.sum_relHIn+sensors.relHIn;
     sensors.sum_tOut=sensors.sum_tOut+sensors.tOut;
     sensors.sum_relHOut=sensors.sum_relHOut+sensors.relHOut;
     sensors.num++;
   }
 
 // набрали в сумме нужное число отсчетов рассчитываем усреднение и выводим
 if (sensors.num>=NUM_SAMPLES)  // Пора усреднять и выводить значения 
 {
         resetKey();          // сброс кнопки почему то часто выскакивает длительное нажатие
        // вычисление средних значений
         packet.tIn=sensors.sum_tIn/NUM_SAMPLES;
         packet.relHIn=sensors.sum_relHIn/NUM_SAMPLES;
         packet.tOut=sensors.sum_tOut/NUM_SAMPLES;
         packet.relHOut=sensors.sum_relHOut/NUM_SAMPLES;
         reset_sum();       // Сброс счетчиков и сумм
         // вычисление абсолютной влажности
         packet.absHIn=(int)(calculationAbsH((float)(packet.tIn/100.0),(float)(packet.relHIn/100.0))*100.0);
         packet.absHOut=(int)(calculationAbsH((float)(packet.tOut/100.0),(float)(packet.relHOut/100.0))*100.0);
         
     #ifdef  DEBUG  
       Serial.println(F("Average value >>>>>>>>>>"));
       Serial.print(F("IN T="));Serial.print(packet.tIn);Serial.print(F(" H=")); Serial.print(packet.relHIn); Serial.print(F(" abs H=")); Serial.println(packet.absHIn);
       Serial.print(F("OUT T="));Serial.print(packet.tOut);Serial.print(F(" H=")); Serial.print(packet.relHOut); Serial.print(F(" abs H=")); Serial.println(packet.absHOut);
     #endif   
                     
         #ifdef  RADIO     // Радио модуль NRF42l 
            send_packet();   // Послать данные
         #endif
         CheckON();         // Проверка статуса вентилятора
         print_status();    // панель состояния
         print_data();       // вывод усредненных значений 
         if ((long)((long)TimeChart*TIME_SCAN_SENSOR*NUM_SAMPLES)>=(long)TIME_PRINT_CHART) // проврека не пора ли выводить график
            { printChart(); TimeChart=0; // Сдвиг графика и вывод новой точки
              #ifdef  DEBUG  
                 Serial.println(F("Point add chart ++++++++++++++++++++"));
              #endif  
              #ifdef BEEP
 //               beep(50);
              #endif
             } 
         else TimeChart++;
    }
    myOS.restartTask(measurement);     // Пустить задачи
}

// Функция переводит относительную влажность в абсолютную 
// t-температура в градусах Цельсия h-относительная влажность в процентах
float calculationAbsH(float t, float h)
{
 float temp;
 temp=pow(2.718281828,(17.67*t)/(t+243.5));
 return (6.112*temp*h*2.1674)/(273.15+t);
}

// Сканирование клавиш ------------------------------------------
void scanKey()
{  
    byte key;  
    cli(); 
    key=digitalRead(PIN_KEY);                                         // Прочитать кнопку 0 - нажата 
    if ((key==0)&&(flagKey==false))                                   // Если кнопка была нажата запомнить время и поставить флаг нажатия
    {
        flagKey=true;                                                 // Кнопка нажата  ждем обратного фронта
        time_key=millis();                                            // Время нажатия запомнили
     }
      
    if ((key==1)&&(flagKey==true))                                    // Если кнопка была отжата 
    {
         time_key=millis()-time_key;                                  // Рассчитать время нажатия
         pressKey=true;
    }
   sei();
 }

// Проверка статуса вытяжки, не пора ли переключится
void CheckON()
{
cli();
if (packet.motor==false) // Вентилятор выключен 
  {
  if (settingRAM.fStart!=MOTOR_OFF)
  if (settingRAM.fStart==MOTOR_ON) {packet.motor=true; digitalWrite(PIN_RELAY, HIGH);}  // Все время включен  
  else  if (( packet.tIn>=packet.T )&& ((packet.absHIn-packet.dH)> packet.absHOut)) {packet.motor=true; digitalWrite(PIN_RELAY, HIGH);} // ВКЛЮЧЕНИЕ
  }
else // Вентилятор включен  
  {
  if (settingRAM.fStart!=MOTOR_ON) 
  if (settingRAM.fStart==MOTOR_OFF) {packet.motor=false; digitalWrite(PIN_RELAY, LOW);}  // Все время выключен 
  else  if((packet.tIn<=packet.T-dT_OFF)||(packet.absHIn-packet.absHOut<packet.dH-dT_OFF)) {packet.motor=false; digitalWrite(PIN_RELAY, LOW);} // ВЫКЛЮЧЕНИЕ  
  } 
sei(); 
} 

// Вывод информации о настройках и сохрание индекса настроек в eeprom ---------------------------------
void Setting()
{
 // Настройка
  cli();
  ucg.setColor(0, 100, 255);
  ucg.setFontMode(UCG_FONT_MODE_SOLID);
  ucg.setPrintPos(0,25+0+18); 
  switch (settingRAM.fStart)
        {
        case  MOTOR_OFF: ucg.print(F("Выключено              ")); packet.dH=255;packet.T=255; break; 
        case  MOTOR_ON:  ucg.print(F("Режим вытяжки         "));  packet.dH=0;  packet.T=0;   break; 
        case  2:         ucg.print(F("Включение T>+2 dH>0.2"));   packet.dH=20; packet.T=200; break;
        case  3:         ucg.print(F("Включение T>+2 dH>0.4"));   packet.dH=40; packet.T=200; break;
        case  4:         ucg.print(F("Включение T>+3 dH>0.3"));   packet.dH=30; packet.T=300; break;
        case  5:         ucg.print(F("Включение T>+3 dH>0.5"));   packet.dH=50; packet.T=300; break;
        case  6:         ucg.print(F("Включение T>+4 dH>0.6"));   packet.dH=30; packet.T=600; break;
        } 
 resetKey();
 writeEeprom();       // Запись в EEPROM  
 CheckON();           // Возможно надо включить мотор
 print_status();      // панель состояния
 sei();   
}

// Вывод float  с одним десятичным знаком в координаты x y // для экономии места
void print_floatXY(int x,int y, float v)
{
 ucg.setPrintPos(x,y);
 ucg.print(v,2);
 ucg.print(F("  ")); // Стереть хвост от предыдущего числа
} 

// Вывод строки константы в координаты x y // для экономии места
void print_StrXY(int x,int y, const __FlashStringHelper* b)
{
 ucg.setPrintPos(x,y);
 ucg.print(b);
} 

void printInfo() // Окно с информацией о блоке, появляется при длительном нажатии на кнопку
{
  infoScreen=true;
  resetKey();
  cli();
  ucg.setColor(250, 250, 250);  // 
  ucg.drawBox(10, 10, 320-1-20, 240-1-20);
  ucg.setColor(0, 50, 250);
  ucg.drawFrame(10+5, 10+5, 320-1-20-10, 240-1-20-10);
  
  ucg.setFontMode(UCG_FONT_MODE_TRANSPARENT);
  ucg.setColor(0, 150, 10);
  print_StrXY(35,18+15,F("ОСУШИТЕЛЬ на Arduino Pro Mini"));
  
  ucg.setColor(0, 50, 50);
  print_StrXY(10+10,15+17*2,F("1 Напряжение питания В.")); 
  print_floatXY(10+230,15+17*2,readVcc()/1000.0);
 
  print_StrXY(10+10,15+17*3,F("2 Температура блока гр.")); 
  print_floatXY(10+230,15+17*3,GetTemp());
 
  print_StrXY(10+10,15+17*4,F("3 Свободная память байт")); 
  ucg.setPrintPos(10+230,15+17*4); 
  ucg.print(freeRam()); 
 
  print_StrXY(10+10,15+17*5,F("4 Мото часы блока")); 
  ucg.setPrintPos(10+230,15+17*5); 
  ucg.print(settingRAM.hour_unit/(TIME_HOUR/TIME_PRINT_CHART)); 
  
  print_StrXY(10+10,15+17*6,F("5 Мото часы вентилятора")); 
  ucg.setPrintPos(10+230,15+17*6); 
  ucg.print(settingRAM.hour_motor/(TIME_HOUR/TIME_PRINT_CHART)); 
  
  print_StrXY(10+10,15+17*7,F("6 Канал NRF24l01+")); 
  ucg.setPrintPos(10+230,15+17*7); 
  ucg.print(NRF24_CHANEL); 
 
  print_StrXY(10+10,15+17*8,F("7 Гистерезис абс. влажности")); 
  print_floatXY(10+255,15+17*8,(float)dH_OFF/100.0);
  
  print_StrXY(10+10,15+17*9,F("8 Гистерезис температуры")); 
  print_floatXY(10+255,15+17*9,(float)dT_OFF/100.0);
  
  print_StrXY(10+10,15+17*10,F("9 ERR Т/Н in:")); 
  ucg.print(TIN_ERR); 
  ucg.print(F("/")); 
  ucg.print(HIN_ERR); 
  ucg.print(F(" out:"));
  ucg.print(TOUT_ERR); 
  ucg.print(F("/")); 
  ucg.print(HOUT_ERR); 
 
  ucg.setColor(0, 0, 150);
  print_StrXY(10+10,16+17*11,F("СБРОС - Вкл. при нажатой кнопке.")); 
  
  ucg.setColor(250,80,80);
  print_StrXY(10+10,20+21+18*10,F(VERSION)); 
  resetKey();
  sei();
  #ifdef BEEP
   beep(40);
  #endif
} 

void clearInfo()  // Стереть информационный экран
{
      infoScreen=false;
      resetKey();
      last_error=100;         // Признак обновления ошибки
      cli();
      ucg.setColor(0, 0, 0);  // залить черным
      ucg.drawBox(10, 10, 320-1-20, 240-1-20);
      print_static();
      Setting();  
      printChart();
      sei();
} 
// Чтение свободной памяти --------------------------------------------------------------------
int freeRam () {
  extern int __heap_start, *__brkval; 
  int v; 
  return (int) &v - (__brkval == 0 ? (int) &__heap_start : (int) __brkval); 
}
// Чтение внутреннего датчика температуры ---------------------------------------
double GetTemp(void)
{
  unsigned int wADC;
  double t;
  sei();  // Должны быть разрешены прерывания
  ADMUX = (_BV(REFS1) | _BV(REFS0) | _BV(MUX3));
  ADCSRA |= _BV(ADEN);  
  delay(20);           
  ADCSRA |= _BV(ADSC);  
  while (bit_is_set(ADCSRA,ADSC));
  wADC = ADCW;
  t = (wADC - 324.31 ) / 1.22;
  return (t); 
}
// Чтение напряжения питания ----------------------------------------------
long readVcc() {
  long result;
  // Read 1.1V reference against AVcc
  ADMUX = _BV(REFS0) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1);
  delay(2); // Wait for Vref to settle
  ADCSRA |= _BV(ADSC); // Convert
  while (bit_is_set(ADCSRA,ADSC));
  result = ADCL;
  result |= ADCH<<8;
  result = ConstADC / result; // Back-calculate AVcc in mV
  return result;
} 
// Запись счетчиков в Eeprom --------------------------------------------------
void writeEeprom()
{ 
cli(); 
  eeprom_write_block((const void*)&settingRAM, (void*) &settingEEPROM, sizeof(settingRAM)); 
sei();
}
// Чтение счетчиков из Eeprom --------------------------------------------------
void readEeprom()
{
cli(); 
   eeprom_read_block((void*)&settingRAM, (const void*) &settingEEPROM, sizeof(settingRAM)); 
sei();
}

void reset_sum()  // Сброс счетчиков накоплений
{
sensors.num=0;  // Рассчитать величину усреднения
sensors.sum_tOut=0;
sensors.sum_tIn=0;
sensors.sum_relHOut=0;
sensors.sum_relHIn=0;
}

char hex(byte x)  // Функция для вывода в hex
{
   if(x >= 0 && x <= 9 ) return (char)(x + '0');
   else      return (char)('a'+x-10);
}

bool reset_ili9341(void)
{
  pinMode(PIN_RESET, OUTPUT);                    // Сброс дисплея сигнал активным является LOW
  digitalWrite(PIN_RESET, LOW);  
  delay(100);
  digitalWrite(PIN_RESET, HIGH);  
  // Дисплей
  ucg.begin(UCG_FONT_MODE_TRANSPARENT);
  ucg.setFont(my14x10rus);  
 //  ucg.setRotate90();
  ucg.setRotate270();
  ucg.clearScreen();
}

#ifdef BEEP  
  void beep(int x)  // Пищать х мсек
  {
    digitalWrite(PIN_BEEP, HIGH); 
    delay(x);
    digitalWrite(PIN_BEEP, LOW); 
  } 
#endif

Файл фонтов не поменялся.

Также была проведена оптимизация работы с кнопкой по прерыванию. Результат сомнительный.

Сейчас пишу программу для приемника для maple mini в среде arduino (проект https://github.com/rogerclarkmelbourne/Arduino_STM32  форум http://www.stm32duino.com/). Основной функционал работает (дисплей и радиомодуль), проблема с прерываниями радиомодуля.

Портировал библиотеки RF24 и ucglib. Отдельно напишу по этому проекту пост.

miaua
Offline
Зарегистрирован: 20.08.2015

Отличная работа!

Уменя подобная схема осушения работает 2 года. Но собрана на китайских котроллерах температуры и влажности.

В подвале гаража стоит вентилятор 9Вт на приточной трубе, без клапана. Управление по влажности настроено на 72% отн. с гистерезисом -5% (при 67%-выкл.). На верхней (выходной трубе), часть её железная, подвешена воронка в которую зимой стекает конденсат и по трубке отводится в закрытую 30л. ёмкость. Набирается за зиму до 20л. конденсата..

Дополнительно стоит тепловентилятор на 1кВт. Он ключается если температура опускается ниже +3 (редкий случай). За 2 зимы ниразу не включился. Хватает тепла от земли.

Но Ваш метод и воплощение намного правильнее...Тоже думал сравнивать влажность снаружи и внутри, только не хватило терпения.. :)

Хочу повторить Вашу разработку, если Вы не против.

Joiner
Offline
Зарегистрирован: 04.09.2014

Нажал стрелочку вверх. Спасибо за публикацию. Будем изучать, перенимать опыт.

pav2000
Offline
Зарегистрирован: 15.12.2014

miaua пишет:

В подвале гаража стоит вентилятор 9Вт на приточной трубе, без клапана. Управление по влажности настроено на 72% отн. с гистерезисом -5% (при 67%-выкл.). На верхней (выходной трубе), часть её железная, подвешена воронка в которую зимой стекает конденсат и по трубке отводится в закрытую 30л. ёмкость. Набирается за зиму до 20л. конденсата..

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

На счет калорифера тоже думаю, для подвала где хранятся продукты, что бы было +1...+3 градуса. Это можно реализовать на том же устройстве.

По поводу повторения я конечно не против -))

 

miaua
Offline
Зарегистрирован: 20.08.2015

Как ни странно, но до появления моей системы, влажность не опускалась ниже 89%. Теперь по факту за год выше 76% не поднималось. Несколько раз проверял датчик влажности по психрометру (висит на стене, вдали от потоков воздуха ) практически совпадает...

Сегодня утром заехал посмотреть на циферки...в 6:30 утра -72% и 19,4*С .. Этот подвал для хранения продуктов давно не использую...Теперь задача сохранить различное железо (моторы, приборы и т.д.). 

Однозначно буду собирать и уже комплектую вашу схему... Ломаю голову - нужна ли мне удаленка...Вот собирать лог на SD карту было бы вернее ( в т.ч. и количество отключений питания). Опять же вопрос по часам реального времени...

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

Какой размер кода у Вас получился?

pav2000
Offline
Зарегистрирован: 15.12.2014

miaua пишет:

Однозначно буду собирать и уже комплектую вашу схему... Ломаю голову - нужна ли мне удаленка...Вот собирать лог на SD карту было бы вернее ( в т.ч. и количество отключений питания). Опять же вопрос по часам реального времени...

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

Какой размер кода у Вас получился?

Размер кода почти на приделе (в начале исходника есть дифайны на опции), В зависимости от их установок код получается 29-31 кБ. Это с перешитым загрузчиком (+1.5 кб места).

Карту прикрутить не получится - места не хватит.  Можно использовать мегу там памяти с запасом. Количество отключений питания можно реализовать записью во флеш. Надо только автономное питание для блока сделать и детектор сети. Можно выкитуть радиомодуль, место появится но ИХМО для карточки не хватит. Еще Вариант - выкинуть все что касается дисплея тогда на карту места хватит, а наблюдать все с удаленки. Я думаю что в течении месяца я блок удаленного мониторинга выложу. Причем сейчас я пишу сразу для двух вариантов pro mini и maple mini (условная компиляция). Правда может быть конечный вариант будет только на STM32  maple mini.

Малый размер памяти программ это одна из причин почему я ухожу на STM32.  maple mini - 120 кб это гараздо лучше чем 32. Правда код получается более емкий. но выйгрыш есть.

miaua
Offline
Зарегистрирован: 20.08.2015

В какой среде вы пишете и компилируете? Может выкинуть загрузчик и грузить код по ISP ? А вообще мне и мегу 2560 под это дело не жалко, с перспективой на расширение...

Собираю пока библиотеки... Недостающее железо едет..

Еще вопрос по расположению наружного датчика? Его показания, в отличие от внутреннего, сильно зависят от ветра и солнца. 

П.С. 

Я в прошлом, если можно так сказать, писал на АСМЕ под CPM. На СИ так и не смог перейти, после многолетней работы на ПЛ-1 и Фортране..в начале 80х..Теперь вот пытаюсь наверстывать.. STM для меня пока темный лес.. Но чувствую с него и надо было начинать...

pav2000
Offline
Зарегистрирован: 15.12.2014

Я пишу в Arduino IDE 1.6.1. Выкидывание загрузчика даст 512 байт, это погоды не сделает. Если  есть возможность покупать в Китае можно посмотреть типа:

http://www.ebay.com/itm/Mega2560-CORE-mini-2560-Arduino-compatible-3-3V-...

Обратите внимание ucglib не новее 1.02 в более новой библиотеке автор переработал систему шрифтов (со сжатием)  исчезла поддержка UTF-8 и изменился формат шрифтов. Адаптировать эту библиотеку наверное  буду если  получится подключить SPI DMA для STM32.

С датчиком есть проблема. Показания влажности скачут. Пока он висит просто на улице но в тени.

STM32  у меня тоже тяжело идет после avr. Но я использую проект http://www.stm32duino.com  он сильно помогает - фактически можно программировать как avr в той же Arduino IDE.  По этому я и перехожу на STM32. Портировано уже много библиотек. Я сам "портировал" на STM32 две библиотеки RF24 Uucglib.

 

 

 

miaua
Offline
Зарегистрирован: 20.08.2015

я сейчас потихоньку осваиваю АВРы.. Уже пол-года играюсь с Uno, Nano и вот пару дней назад приехала MEGA 2560 за $6,12 отсюда http://www.aliexpress.com/snapshot/6807944392.html?orderId=68405489764729

Вполне рабочий вариант. Зачем платить больше?

У меня лежит давно ucglib_arduino_v1.01.zip

Есть вопрос по скечу: - пин 15 под бипер и пин 14 -реле в строках 49 и 51 соответственно. О каких пинах идет речь?

pav2000
Offline
Зарегистрирован: 15.12.2014

Тоже осваиваю микроконтроллеры меньше года :=))

Так еще нумеруют аналоговые входы (где то видел.) т.е. А0 и А1 соответственно.  Я их перевожу в режим цифровых выходов.

Если в обозначениях портов то это С0 и С1 кажись.

Рекомендую покупать дисплей 2.8 а не как у меня 2.4 программно они одинаковые (spi ili9341) а размер больше. У меня были два дисплея 2.4 я их использовал на два таких устройства. В дальнейшем я буду использовать только 2.8 дюйма. Больше размером на ili9341 со spi я не нашел.

miaua
Offline
Зарегистрирован: 20.08.2015

По пинам ясно, спасибо.

У меня уже есть 2 дисплея на ili9341 И оба 2.4". Один из них с тачпадом...+ в обоих ридеры SD.. Проверены в работе по SPI. Маловаты они...Я бы вообще 3,2" брал...

http://ru.aliexpress.com/premium/3.2%2522-tft-ili9341.html?site=rus&grou...

bwn
Offline
Зарегистрирован: 25.08.2014

pav2000 пишет:

С датчиком есть проблема. Показания влажности скачут. Пока он висит просто на улице но в тени.

У датчика, исключите сквозняки, он на малейшее дуновение реагирует.

miaua
Offline
Зарегистрирован: 20.08.2015

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

Думаю начать с сетки с мелкой ячейкой и в консервной банке..

bwn
Offline
Зарегистрирован: 25.08.2014

miaua пишет:

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

Думаю начать с сетки с мелкой ячейкой и в консервной банке..

Наиболее подходящее, как на метеостанциях (стационарных) - домик типа скворечника в теньке и наклонные жалюзи на всех стенках.

miaua
Offline
Зарегистрирован: 20.08.2015

Да это чтото типа такого-                    

pav2000
Offline
Зарегистрирован: 15.12.2014

miaua пишет:

У меня уже есть 2 дисплея на ili9341 И оба 2.4". Один из них с тачпадом...+ в обоих ридеры SD.. Проверены в работе по SPI. Маловаты они...Я бы вообще 3,2" брал...

http://ru.aliexpress.com/premium/3.2%2522-tft-ili9341.html?site=rus&groupsort=1&SearchText=3.2%22+tft+ili9341&SortType=price_asc&initiative_id=SB_20150825074153&shipCountry=ua&filterCat=202001115,202000349,202000056&isFreeShip=y

Этот дисплей не подойдет (для про мини), Так как он имеет  параллельную шину 16 бит - ножек не хватит. На мегу встанет, но надо конфигурировать. Контроллер ili9341 может быть сконфигурирован на SPI, 8 бит и 16 бит. Конечно 16 бит гораздо быстрее, но ног надо много. А как быстро он работает через SPI DMA на stm32 на частоте 46 мГц -))))))) песня, 16 битный интерфейс отдыхает.

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

На этой неделе допиливал программу (надо завершать проект). Что было сделано:

- оптимизирован код, Функционал стал больше места занимает меньше (около 200 байт)

- реализована "быстрая" работа с GPIO через прямую работу с портами

- в логике работы была ошибка она исправленна, Полностью переписана функция отвечающая за управление вентилятором. Изменены настройки - минимальная температура стала +3 (было +2) при которой включается вентилятор. Боюсь неравномерности температур в подвале а датчик один.

- добавлен еще один режим "охлаждение"   когда внутренняя температура больше +10 градусов а с наружи меньше на 2 градуса (по сравнению с внутренней) включается вытяжка. Это режим актуален летом когда ночи холодные и будет проводится охлаждение подвала.

- доработан функционал графиков, Теперь в точках где включена вытяжка (хотя бы на один инетрвал измерения) фон становится темно желтым (обычно он черный) и сразу видно когда работал вентилятор. В график влезает 10 часов, Одна точка 5 минут.

- изменен алгоритм прорисовки графиков теперь"лишние" точки не возникают на графике.

- добавлен калорифер (пока без аппартной поддержки) теперь если температура в подвале опускается ниже +2 градусов включается калорифер

- оптимизирован код работы с nrf24. Успех не достигнут -((

Основное время убил на  nrf24. Стабильность увеличилась, но есть проблемы. Бывает что после включения данные не "проходят" на приемник (скорее всего из за передатчика).  Начинаюся танцы с бубном и через некоторое время связь восстанавливается, что служило причиной восстановления не выяснял. Все сделано на совесть - отдельный стабилизатор, конденсаторы по питанию, "оптимальный" код. Причем есть несколько вариантов модулей, худшую стабильность показали модули с усилителем и внешней антенной.

Пытался улучшить получение "квитанций" от приемника о приеме (опция setAutoAck). Не получилось. Получение "квитанций" работает на очень маленьком расстоянии.

Вывод: Если рассматривать радио канал как дополнительный бонус, то нормально (типа если не показывает то и ладно, спущусь в подвал если сильно надо), но делать что то серьезное на нем наверное не получится (либо надо все многократно дублировать).

Новая версия кода приведена ниже:

#pragma GCC optimize ("-Os")
//#pragma pack(push, 1)     // выравнивание по одному байту ????????

#include <SPI.h>
#include <EEPROM.h>
#include <avr/wdt.h>
#include <leOS.h>      // Шедуллер задач
#include <dht.h>       // Дачик влажности и температуры
#include "Ucglib.h"    // ВНИМАНИЕ использовать библиотеку не познее 1.01 справка https://code.google.com/p/ucglib/wiki/
#include "rusFont.h"   // Русские шрифты
#include "nRF24L01.h"  // Беcпроводной модуль
#include "RF24.h"      // Беcпроводной модуль

// - ОПЦИИ -------------------------------
//#define DEBUG                                   // Отладочную  информацию в ком порт посылает  
//#define DEMO                                    // Признак демонстрации - данные с датчиков генерятся рандом
#define BEEP                                    // Использовать пищалку
#define RADIO                                   // Признак использования радио модуля
#define VERSION "Version: 0.58 28/08/15"        // Текущая версия
#define ID             0x21                     // уникально Идентификатор устройства - старшие 4 бита, вторые (младшие) 4 бита серийный номер устройства


// Макросы для работы с портами  скорость и место
#define SetOutput(port,bit)       DDR ## port |= _BV(bit)
#define SetInput(port,bit)        DDR ## port &= ~_BV(bit)
#define SetBit(port,bit)          PORT ## port |= _BV(bit)
#define ClearBit(port,bit)        PORT ## port &= ~_BV(bit)
#define WritePort(port,bit,value) PORT ## port = (PORT ## port & ~_BV(bit)) | ((value & 1) << bit)
#define ReadPort(port,bit)        (PIN ## port >> bit) & 1
#define PullUp(port,bit)          { SetInput(port,bit); SetBit(port,bit); }
#define Release(port,bit)         { SetInput(port,bit); ClearBit(port,bit); }
// Мои макросы
#define MOTOR_BIT                 0            // бит мотора в packet.flags
#define HEAT_BIT                  1            // бит калорифера в packet.flags

#define FLAG_MOTOR_ON             packet.flags |= (1<<MOTOR_BIT)   // бит мотора установить в 1
#define FLAG_MOTOR_OFF            packet.flags &= ~(1<<MOTOR_BIT)  // бит мотора установить в 0
#define FLAG_MOTOR_CHECK          packet.flags & (1<<MOTOR_BIT)    // бит мотора проверить на 1
#define MOTOR_ON                  { WritePort(C,0,HIGH); FLAG_MOTOR_ON;  }   // включить мотор
#define MOTOR_OFF                 { WritePort(C,0,LOW) ; FLAG_MOTOR_OFF; }   // выключить мотор

#define FLAG_HEAT_ON              packet.flags |= (1<<HEAT_BIT)   // бит калорифера установить в 1
#define FLAG_HEAT_OFF             packet.flags &= ~(1<<HEAT_BIT)  // бит калорифера установить в 0
#define FLAG_HEAT_CHECK           packet.flags & (1<<HEAT_BIT)    // бит калорифера проверить на 1
#define HEAT_ON                   { WritePort(C,2,HIGH); FLAG_HEAT_ON;  }   // включить калорифер
#define HEAT_OFF                  { WritePort(C,2,LOW); FLAG_HEAT_OFF; }   // выключить калорифер


// - КОНСТАНТЫ --------------------------------------
#define dH_OFF          15                      // Гистерезис абсолютной влажности в сотых грамма на куб
#define dT_OFF          25                      // Гистерезис температуры в сотых градуса
#define TEMP_LOW       200                      // Температура подвала критическая (в сотых градуса) - система выключается и включается калорифер
// СИСТЕМАТИЧЕСКИЕ ОШИБКИ ДАТЧИКОВ для ID 0x21  ОШИБКИ ДОБАВЛЯЮТСЯ!!
#define TOUT_ERR      -40                        // Ошибка уличного датчика температуры в сотых долях градуса
#define TIN_ERR       -40                        // Ошибка домового датчика температуры в сотых долях градуса
#define HOUT_ERR      -30                        // Ошибка уличного датчика влажности в сотых долях %
#define HIN_ERR       +30                        // Ошибка домового датчика влажности в сотых долях %

// - ВРЕМЕНА ---------------------------------------
#ifdef DEMO                                     // Для демо все быстрее и случайным образом
    #define NUM_SAMPLES      2                  // Число усреднений измерений датчика
    #define TIME_SCAN_SENSOR 1000               // Время опроса датчиков мсек, для демки быстрее
    #define TIME_PRINT_CHART 4000               // Время вывода точки графика мсек, для демки быстрее
    #define TIME_HOUR        50000              // Число мсек в часе, для демки быстрее   
#else   
   #define NUM_SAMPLES      10                  // Число усреднений измерений датчика
   #define TIME_SCAN_SENSOR 2000                // Время опроса датчиков мсек
   #define TIME_PRINT_CHART 300000              // Время вывода точки графика мсек
   #define TIME_HOUR        3600000             // Число мсек в часе
#endif

#define LONG_KEY      3000                      // Длительное нажатие кнопки мсек, появляется Экран инфо
#define SHORT_KEY     150                       // Короткое нажатие кнопки более мсек
#define NRF24_CHANEL  100                       // Номер канала nrf24

//  НОГИ к которым прицеплена переферия (SPI используется для TFT и NRF24 - 11,12,13)
#define PIN_HEAT      16                         // Ножка куда повешен калорифер A2 (port C2)
#ifdef BEEP
    #define PIN_BEEP  15                         // Ножка куда повешена пищалка A1 (port C1)
#endif
#define PIN_RELAY     14                         // Ножка на которую повешено реле (SSR) вентилятора - аналоговый вход A0 через резистор 470 ом (port C0)
#define PIN_CS        10                         // TFT дисплей spi
#define PIN_CD        9                          // TFT дисплей spi
#define PIN_RESET     8                          // TFT дисплей spi
#define PIN_CE        7                          // nrf24 ce
#define PIN_CSN       6                          // nrf24 cs
#define PIN_DHT22a    5                          // Первый датчик DHT22   IN  ДОМ
#define PIN_DHT22b    4                          // Второй датчик DHT22   OUT УЛИЦА
#define PIN_KEY       3                          // Кнопка, повешена на прерывание, что бы ресурсов не тратить (port D3)
#define PIN_IRQ_NRF24 2                          // Ножка куда заведено прерывание от NRF24 (пока не используется)

// Настройки
#define NUM_SETTING    7                        // Число вариантов настроек 
#define BLOCK_OFF      0                        // Выключено (вариант настроек)
#define HOOD_ON        1                        // Режим вытяжки (вариант настроек)
#define COOLING        2                        // Режим хлаждение (вариант настроек)

// АЦП ----------------------------------------
const long ConstADC=1126400;                    // Калибровка встроенного АЦП (встроенный ИОН) по умолчанию 1126400 дальше измеряем питание и смотрим на дисплей    

Ucglib_ILI9341_18x240x320_HWSPI ucg(/*cd=*/PIN_CD, /*cs=*/PIN_CS, /*reset=*/PIN_RESET); // Аппаратный SPI на дисплей ILI9341
leOS myOS;                                      // многозадачность
dht DHT;                                        // Датчики
bool infoScreen=false;                          // Признак отображениея иформационного экрана  1 - на экран ничего не выводится кроме информационного экрана
bool flagKey=false;                             // Флаг нажатия клавиши
bool pressKey=false;                            // Флаг необходимости обработки кнопки

unsigned long time_key=0;                       // Время нажатия копки
byte last_error=100;                            // Предыдущая ошибка чтения датчиков

 struct type_setting_eeprom                     // Структура для сохранения данных в eeprom
 {
     byte fStart = 0;                           // Какой набор настроек используется
     unsigned long hour_unit;                   // мото часы блок измеряется в интервалах вывода графика TIME_PRINT_CHART
     unsigned long hour_motor;                  // мото часы мотор измеряется в интервалах вывода графика TIME_PRINT_CHART
 }; 
volatile type_setting_eeprom settingRAM;        // Рабочая копия счетчиков в памяти
type_setting_eeprom settingEEPROM EEMEM;        // Копия счетчиков в eeprom - туда пишем 

// Пакет передаваемый, используется также для хранения результатов. 
 struct type_packet_NRF24   // Версия 2.3!! адаптация для stm32 Структура передаваемого пакета 30 байта - 32 максимум
    {
        byte id=ID;                                 // Идентификатор типа устройства - старшие 4 бита, вторые (младшие) 4 бита серийный номер устройства
        byte error;                                 // Ошибка разряды: 0-1 первый датчик (00-ок) 2-3 второй датчик (00-ок) 4 - радиоканал     
        int16_t   tOut=-500,tIn=-500;               // Текущие температуры в сотых градуса !!! место экономим
        uint16_t  absHOut=456,absHIn=123;           // Абсолютные влажности в сотых грамма на м*3 !!! место экономим
        uint16_t  relHOut=456,relHIn=123;           // Относительные влажности сотых процента !!! место экономим
        uint8_t   flags=0x00;                       // байт флагов  0 бит - мотор включен/выключен 1 бит - калорифер включен/выключен
        uint8_t   dH_min;                           // Порог включения вентилятора по РАЗНИЦЕ абсолютной влажности в сотых грамма на м*3
        int16_t   T_min;                            // Порог выключения вентилятора по температуре в сотых градуса
        char note[12] = "ArduDry :-)";              // Примечание не более 13 байт + 0 байт
    } packet; 
    
struct type_sensors                                 // структура для усреднения
{
       int  num;                                    // сколько отсчетов уже сложили не болле NUM_SAMPLES
       long  sum_tOut=-5000,sum_tIn=-5000;          // Сумма для усреднения Текущие температуры в сотых градуса !!! место экономим
       long  sum_relHOut=55555,sum_relHIn=55555;    // Сумма для усреднения Относительные влажности сотых процента !!! место экономим
       int  tOut=-5000,tIn=-5000;                   // Текущие температуры в сотых градуса !!! место экономим
       int  absHOut=55555,absHIn=55555;             // Абсолютные влажности в сотых грамма на м*3 !!! место экономим
       int  relHOut=55555,relHIn=55555;             // Относительные влажности сотых процента !!! место экономим
} sensors;
    
// Массивы для графиков
uint8_t tOutChart[120];
uint8_t tInChart[120];
uint8_t absHOutChart[120];
uint8_t absHInChart[120];
uint8_t posChart=0;       // Позиция в массиве графиков - начало вывода от 0 до 120-1
uint8_t TimeChart=0;      // Время до вывода очередной точки на график. 
bool ChartMotor=false;    // Признак работы мотора во время интервала графика если мотор был включен на любое время то на графике фон меняется в этом месте


#ifdef  RADIO    // Радио модуль NRF42l  
   RF24 radio(PIN_CE, PIN_CSN);  // определение управляющих ног
   bool send_packet_ok=false;    // признак удачной отправки последнего пакета 
#endif 
///////////////////////////////////////////////////////////////////////////////////////////////////////////
// ПРОГРАММА
///////////////////////////////////////////////////////////////////////////////////////////////////////////
void setup(){
#ifdef  DEBUG  
   Serial.begin(9600); 
   Serial.println(F("DEBUG MODE")); 
   #ifdef  BEEP  
     Serial.println(F("BEEP ON")); 
   #else         
     Serial.println(F("BEEP OFF"));
   #endif
   #ifdef  RADIO 
     Serial.println(F("RADIO ON")); 
   #else         
     Serial.println(F("RADIO OFF"));
   #endif
#endif

reset_sum();

#ifdef  RADIO    // Радио модуль NRF42l  первичная настройка
   radio.begin();
   radio.setDataRate(RF24_250KBPS);         //  выбор скорости RF24_250KBPS RF24_1MBPS RF24_2MBPS
   radio.setPALevel(RF24_PA_MAX);           // выходная мощность передатчика
   radio.setChannel(NRF24_CHANEL);          //тут установка канала
   radio.setCRCLength(RF24_CRC_16);         // использовать контрольную сумму в 16 бит
   radio.setAutoAck(1);                 // выключить аппаратное потверждение
// radio.enableDynamicPayloads();           // разрешить Dynamic Payloads
// radio.enableAckPayload();                // разрешить AckPayload
  radio.setRetries(10,50);                  // Количество повторов и пауза между повторами
  radio.openWritingPipe(0xF0F0F0F0E1LL);    // передатчик
  radio.openReadingPipe(1,0xF0F0F0F0D2LL);  // приемник
  radio.startListening();
#endif
  #ifdef BEEP
     SetOutput(C,1);                      //  Настройка ноги для динамика
     WritePort(C,1,LOW);
  #endif
  SetInput(D,3);                        //  Включена кнопка
  WritePort(D,3,HIGH);
  SetOutput(C,0);                       //  Подключить Реле
  WritePort(C,0,LOW);
  SetOutput(C,2);                       //  Подключить Калорифер
  WritePort(C,2,LOW);
  
  pinMode(PIN_DHT22a, OUTPUT);          //  Датчик DHT22 #1 
  digitalWrite(PIN_DHT22a, HIGH);  
  pinMode(PIN_DHT22b, OUTPUT);          //  Датчик DHT22 #2
  digitalWrite(PIN_DHT22b, HIGH);  
  
  reset_ili9341();                      // сброс дисплея
  readEeprom();                         // Прочитать настройки 
 if (ReadPort(D,3)==0)                  // Если при включении нажата кнопка то стираем Eeprom
  {   int i;
      settingRAM.fStart=0;
      settingRAM.hour_unit=0;
      settingRAM.hour_motor=0;
      ucg.setColor(255, 255, 255);
      print_StrXY(10,50,F("Сброс настроек и счетчиков"));
      for(i=0;i<3;i++)
        {
         delay(1000);
         ucg.print(F(" ."));
        }
      writeEeprom();       // Запись в EEPROM  
      delay(1000);
      ucg.clearScreen();
  } 
   
  wdt_enable(WDTO_8S); // Сторожевой таймер Для тестов не рекомендуется устанавливать значение менее 8 сек.
  // Запуск задач по таймеру
  myOS.begin();
  myOS.addTask(measurement,TIME_SCAN_SENSOR);    // Измерение 
  attachInterrupt(1, scanKey, CHANGE);           // КНОПКА Прерывания по обоим фронтам
  print_static();                                // распечатать таблицу
  Setting();                                     // Применить настройки
  measurement();                                 // Считать данные
  #ifdef BEEP
     beep(100);
  #endif
resetKey();
}

void loop()
{
 wdt_reset();                        // Сбросить сторожевой таймер
// Обработка нажатия кнопки
if (pressKey==true) // Кнопка была нажата
{
 myOS.pauseTask(measurement);                                 // Остановить задачи
 if (flagKey!=false)                                          // Обработчик кнопки
 if (time_key > LONG_KEY)                                     // Длительное нажатие кнопки
   printInfo();                                               // Вывод информационного экрана
 else 
   if (time_key > SHORT_KEY)                                  // Короткое нажатие кнопки
     { 
        #ifdef BEEP
          beep(30);                                            // Звук нажатия на клавишу
        #endif
       if (infoScreen==true)  clearInfo();                    // если информационный экран то стереть его
       else {if (settingRAM.fStart >= NUM_SETTING) settingRAM.fStart=0; //  Кольцевой счетчик настроек
               else settingRAM.fStart++;                      // В противном случае следующая настройка
               Setting(); }  
     }
  resetKey();                                                //  Сброс состояния кнопки
  myOS.restartTask(measurement);                             // Запустить задачи
  }
}

void resetKey(void)  // Сброс состяния кнопки
{
  flagKey=false;                                             
  time_key=millis();                                                
  pressKey=false;
}  
void print_static()  // Печать статической картинки 
{
   cli();
  // Заголовок 
  ucg.setColor(0, 0, 180);  // 
  ucg.drawBox(0, 0, 320-1, 23);
  ucg.setColor(250, 250, 250);
  ucg.setFontMode(UCG_FONT_MODE_TRANSPARENT);
  print_StrXY(2,19,F("ОСУШИТЕЛЬ ID: 0x"));
  ucg.print( hex(packet.id >> 4));
  ucg.print( hex(packet.id&0x0f));
   
  #ifdef DEMO
    ucg.print(F(" demo"));
  #endif
  
  // Таблица для данных
  ucg.setColor(0, 200, 0);
  ucg.drawHLine(0,25,320-1);
  ucg.drawHLine(0,25+23*1,320-1);
  ucg.drawHLine(0,25+23*2,320-1);
  ucg.drawHLine(0,25+23*3,320-1);
  ucg.drawHLine(0,25+23*4,320-1);
  ucg.drawVLine(200-4,25,24+23*3);
  ucg.drawVLine(260,25,24+23*3);

  // Заголовки таблиц
  ucg.setColor(255, 255, 0);
  print_StrXY(180+30,25+0+18,F("Дом"));
  print_StrXY(250+20,25+0+18,F("Улица"));
  print_StrXY(0,25+23*1+18,F("Температура градусы C")); 
  print_StrXY(0,25+23*2+18,F("Относительная влаж. %")); 
  print_StrXY(0,25+23*3+18,F("Абсолют. влаж. г/м*3")); 

  // Графики
  ucg.setColor(220, 220, 200);
  ucg.drawHLine(1,240-1,130);
  ucg.drawVLine(1,135,105);
  ucg.drawHLine(10+150,240-1,130);
  ucg.drawVLine(10+150,135,105);
  print_StrXY(10,135+0,F("Температура")); 
  print_StrXY(20+150,135+0,F("Абс. влажность")); 
  
  // надписи на графиках
  print_StrXY(128,154,F("+20")); 
  print_StrXY(135,194,F("0")); 
  print_StrXY(128,233,F("-20"));
  
  print_StrXY(296,164,F("15"));
  print_StrXY(296,194,F("10"));
  print_StrXY(296,223,F("5"));
   sei();
}

void print_status(void) // Печать панели статуса Значки на статус панели
{
  if (infoScreen==true) return;        // если отображен информационный экран то ничего не выводим  
   cli();
 // 1. печать ошибки чтения датчиков
   print_error_DHT();
 // 2. Признак включения мотора
  if (FLAG_MOTOR_CHECK)    ucg.setColor(0, 240, 0); 
  else                     ucg.setColor(0, 40, 0);
  ucg.drawBox(290-32, 5, 14, 14);
  
  #ifdef  RADIO 
  // 3. Признак удачной передачи информации по радиоканалу
      if (send_packet_ok==true)  ucg.setColor(0, 240, 0); 
      else                       ucg.setColor(0, 40, 0);
      ucg.setPrintDir(3);
      ucg.setFontMode(UCG_FONT_MODE_TRANSPARENT);
      print_StrXY(290-40,20,F(">>")); 
      ucg.setPrintDir(0);
      ucg.setFontMode(UCG_FONT_MODE_SOLID);
  #endif
  sei();
}  

void print_error_DHT(void) // Печать ошибки чтения датчиков выводится при каждом чтении датчика
{
  if (infoScreen==true) return;        // если отображен информационный экран то ничего не выводим  
 // 1. печать ошибки чтения датчиков
  if (packet.error!=last_error)        // если статус ошибки поменялся то надо вывести если нет то не выводим - экономия время и нет мерцания
  {
      cli();
      last_error=packet.error; 
      ucg.setColor(0, 0, 180);         // Сначала стереть
      ucg.drawBox(290, 0, 26, 18);
      ucg.setPrintPos(290,18); 
      ucg.setFontMode(UCG_FONT_MODE_TRANSPARENT);
      if (packet.error>0) 
        { 
        ucg.setColor(255, 100, 100); 
        print_StrXY(280,19,F("0x"));
        ucg.print( hex(packet.error >> 4));
        ucg.print( hex(packet.error & 0x0f)); 
        }
      else  { ucg.setColor(200, 240, 0);   ucg.print(F("ok")); } 
     sei(); 
   }   
} 
//  вывод на экран данных (то что меняется)
void print_data()
{ 
 if (infoScreen==true) return;                  // если отображен информационный экран то ничего не выводим  
  cli();
 // Печать значений для дома
  ucg.setFontMode(UCG_FONT_MODE_SOLID);
  ucg.setColor(250, 0, 100);  // Цвет ДОМА
  print_floatXY(200+0,25+23*1+18,((float)packet.tIn)/100);
  print_floatXY(200+0,25+23*2+18,((float)packet.relHIn)/100);
  print_floatXY(200+0,25+23*3+18,((float)packet.absHIn)/100);
  ucg.setColor(0, 250, 100);  // Цвет УЛИЦЫ
  print_floatXY(260+4,25+23*1+18,((float)packet.tOut)/100);
  print_floatXY(260+6,25+23*2+18,((float)packet.relHOut)/100);
  print_floatXY(260+6,25+23*3+18,((float)packet.absHOut)/100);
  sei();
}  

// Печать графика на экране, добавляется одна точка и график сдвигается 
void printChart() 
{
byte i,x=0;
uint8_t tmp;
byte tInLast,absHInLast,tOutLast,absHOutLast;

// Статистика по моточасам, время ведется в тиках графика а потом пересчитывается в часы при выводе.
settingRAM.hour_unit++;
if (FLAG_MOTOR_CHECK) settingRAM.hour_motor++;

// Работаем через кольцевой буфер
// Добавить новую точку в кольцевой буфер
     // Температура в доме. диапазон -25 . . . +25 растягиваем на 100 точек
     tInLast=tInChart[posChart];                       // Сохранить точку для стирания на графике 
     if (packet.tIn<=-2500) tInChart[posChart]=0;      // Если температура меньше -25 то округляем до -25
     if (packet.tIn>=2500)  tInChart[posChart]=100;    // Если температура больше 25  то округляем до 25
     if ((packet.tIn>-2500)&&(packet.tIn<2500)) tInChart[posChart]=((2*packet.tIn)+5000)/100;  // внутри -25...+25 растягиваем в два раза
     if (ChartMotor==true) tInChart[posChart]=tInChart[posChart]+0x80;                       // Признак включения мотора- старший бит в 1 - цвет фона на графике меняется
     ChartMotor=false;
     // Температура на улице. диапазон -25 . . . +25 растягиваем на 100 точек
     tOutLast=tOutChart[posChart];                       // Сохранить точку для стирания на графике 
     if (packet.tOut<=-2500) tOutChart[posChart]=0;      // Если температура меньше -25 то округляем до -25
     if (packet.tOut>=2500)  tOutChart[posChart]=100;    // Если температура больше 25  то округляем до 25
     if ((packet.tOut>-2500)&&(packet.tOut<2500)) tOutChart[posChart]=((2*packet.tOut)+5000)/100;  // внутри -25...+25 растягиваем в два раза
     // Абсолютная влажность в доме диапазон от 0 до 20 грамм на кубометр, растягиваем на 100 точек
     absHInLast=absHInChart[posChart];                   // Сохранить точку для стирания на графике 
     if (packet.absHIn>=2000) absHInChart[posChart]=100;
     else absHInChart[posChart]=(5*packet.absHIn)/100;   // внутри 0...20 растягиваем в пять  раз
     // Абсолютная влажность на улицу диапазон от 0 до 20 грамм на кубометр, растягиваем на 100 точек
     absHOutLast=absHOutChart[posChart];                   // Сохранить точку для стирания на графике 
     if (packet.absHOut>=2000) absHOutChart[posChart]=100;
     else absHOutChart[posChart]=(5*packet.absHOut)/100;   // внутри 0...20 растягиваем в пять раз
     
  if (infoScreen==false)                 // если отображен информационный экран то ничего не выводим
   {
   cli();  
   for(i=0;i<120;i++)    // График слева на право
     { 
     // Вычислить координаты текущей точки x в кольцевом буфере. Изменяются от 0 до 120-1
     if (posChart<i) x=120+posChart-i; else x=posChart-i;
    
     // нарисовать фон в зависимости от статуса мотора
     if  (tInChart[x]>0x80)  ucg.setColor(80, 80, 0);  // Мотор быв ключен
     else                    ucg.setColor(0, 0, 0);    // Мотор был выключен
     ucg.drawVLine(5+120-i,238-101,101); 
     ucg.drawVLine(5+120-i+158+1,238-101,101);  
     
     ucg.setColor(180, 180, 180);  
     if (x%5==0) // Пунктирные линии графика
     {
       ucg.drawPixel(5+120-i,237-10);
       ucg.drawPixel(5+120-i,237-50);
       ucg.drawPixel(5+120-i,237-90);
       
       ucg.drawPixel(6+120-i+158,237-25);
       ucg.drawPixel(6+120-i+158,237-50);
       ucg.drawPixel(6+120-i+158,237-75);  
     } 
     
     // Вывести новую точку
     tmp=tInChart[x]&0x7f;   // Отбросить старший разряд - признак включения мотора
     if ((tmp==0)||(tmp==100))   ucg.setColor(255, 255, 255); else ucg.setColor(250, 0, 100); 
     ucg.drawPixel(5+120-i,237-tmp);
     
     if (absHInChart[x]==100) ucg.setColor(255, 255, 255); else ucg.setColor(250, 0, 100); 
     ucg.drawPixel(6+120-i+158,237-absHInChart[x]);
     
     if ((tOutChart[x]==0) || (tOutChart[x]==100)) ucg.setColor(255, 255, 255); else ucg.setColor(0, 250, 100); 
     ucg.drawPixel(5+120-i,237-tOutChart[x]);
     
     if (absHOutChart[x]==100) ucg.setColor(255, 255, 255); else ucg.setColor(0, 250, 100); 
     ucg.drawPixel(6+120-i+158,237-absHOutChart[x]);
      }
     sei(); 
   } 
 if (posChart<120-1) posChart++; else posChart=0;            // Изменили положение в буфере и Замкнули буфер
} 

// ---ПЕРЕДАЧА ДАННЫХ ЧЕРЕЗ РАДИОМОДУЛЬ -----------------------------
#ifdef  RADIO    // Радио модуль NRF42l
void send_packet()
{       
        radio.stopListening();     // Остановить приемник
        delay(2);
        radio.openWritingPipe(0xF0F0F0F0E1LL);    // передатчик
        cli();
            send_packet_ok = radio.write(&packet,sizeof(packet));
        sei(); 
         #ifdef BEEP
           if (send_packet_ok==true) beep(40);           // Пакет передан успешно
         #endif
         #ifdef  DEBUG  
           if (send_packet_ok==true)  Serial.println(F("Packet sending ++++++++++"));
           else                       Serial.println(F("Packet NOT sending -----------"));
         #endif   
        radio.startListening();    // Включить приемник
 }  
#endif 
// Чтение датчика возвращает код ошибки:
// DHTLIB_OK                   0
// DHTLIB_ERROR_CHECKSUM       1
// DHTLIB_ERROR_TIMEOUT        2
// DHTLIB_ERROR_CONNECT        3
// DHTLIB_ERROR_ACK_L          4
// DHTLIB_ERROR_ACK_H          5
byte readDHT(byte pin)
{
delay(5);
cli();
  byte err=-1*DHT.read22(pin); // Чтение датчика
sei();  
return err; 
} 

// Измерение и обработка данных чтение датчиков --------------------------------------------------------------------------------
void measurement()
{ 
 myOS.pauseTask(measurement);        // Обязательно здесь, а то датчики плохо читаются мешает leos
 wdt_reset();                        // Сбросить сторожевой таймер
 
 packet.error=readDHT(PIN_DHT22a);   // ПЕРВЫЙ ДАТЧИК ДОМ  Новый пакет, сбросить все ошибки и прочитать первый датчик
 
 #ifdef  DEMO
   DHT.temperature=packet.tIn/100+random(-20,30)/10.0; 
   if (DHT.temperature>20) DHT.temperature=19;
   if (DHT.temperature<-10) DHT.temperature=-9;
   DHT.humidity=packet.relHIn/100+(float)random(-5,8);
   if (DHT.humidity>96) DHT.humidity=90;
   if (DHT.humidity<1) DHT.humidity=10;
   packet.error=0; // в Демо режиме
//   DHT.temperature=3.0;
//   DHT.humidity=21.0;
  #endif  
     sensors.tIn=(int)(DHT.temperature*100.0)+TIN_ERR;  // Запомнить результаты для суммирования с учетом ошибок
     sensors.relHIn=(int)(DHT.humidity*100.0)+HIN_ERR;  
     
    #ifdef  DEBUG  
       Serial.print(F("Sensor read samples:")); Serial.println(sensors.num); 
       Serial.print(F("IN T="));Serial.print(sensors.tIn);Serial.print(F(" H=")); Serial.print(sensors.relHIn); Serial.print(F(" error=")); Serial.println(packet.error);
    #endif
    

 
 packet.error=packet.error+16*readDHT(PIN_DHT22b);// ВТОРОЙ ДАТЧИК УЛИЦА  ошибки в старшие четыре бита
 #ifdef  DEMO
   DHT.temperature=packet.tOut/100+random(-40,45)/10.0; 
   if (DHT.temperature>30) DHT.temperature=29;
   if (DHT.temperature<-30) DHT.temperature=-29;
   DHT.humidity=packet.relHOut/100+random(-10,12);
   if (DHT.humidity>96) DHT.humidity=90;
   if (DHT.humidity<1)  DHT.humidity=10;
   packet.error=0;      // в Демо режиме
// DHT.temperature=-10.0;
// DHT.humidity=40.0;
 #endif  
     sensors.tOut=(int)(DHT.temperature*100.0)+TOUT_ERR;  // Запомнить результаты для суммирования с учетом ошибок
     sensors.relHOut=(int)(DHT.humidity*100.0)+HOUT_ERR;
 
    #ifdef  DEBUG  
       Serial.print(F("OUT T="));Serial.print(sensors.tOut);Serial.print(F(" H=")); Serial.print(sensors.relHOut); Serial.print(F(" error=")); Serial.println(packet.error);
    #endif   
 
 print_error_DHT();    // Вывод ошибки чтения датчика при каждом чтении контроль за качеством связи с датчиком
 
 if (packet.error==0)// Если чтение без ошибок у ДВУХ датчиков  копим сумму для усреднения
  {
     sensors.sum_tIn=sensors.sum_tIn+sensors.tIn;
     sensors.sum_relHIn=sensors.sum_relHIn+sensors.relHIn;
     sensors.sum_tOut=sensors.sum_tOut+sensors.tOut;
     sensors.sum_relHOut=sensors.sum_relHOut+sensors.relHOut;
     sensors.num++;
   }
 
 // набрали в сумме нужное число отсчетов рассчитываем усреднение и выводим
 if (sensors.num>=NUM_SAMPLES)  // Пора усреднять и выводить значения 
 {
         resetKey();          // сброс кнопки почему то часто выскакивает длительное нажатие
        // вычисление средних значений
         packet.tIn=sensors.sum_tIn/NUM_SAMPLES;
         packet.relHIn=sensors.sum_relHIn/NUM_SAMPLES;
         packet.tOut=sensors.sum_tOut/NUM_SAMPLES;
         packet.relHOut=sensors.sum_relHOut/NUM_SAMPLES;
         reset_sum();       // Сброс счетчиков и сумм
         
         // вычисление абсолютной влажности
         packet.absHIn=(int)(calculationAbsH((float)(packet.tIn/100.0),(float)(packet.relHIn/100.0))*100.0);
         packet.absHOut=(int)(calculationAbsH((float)(packet.tOut/100.0),(float)(packet.relHOut/100.0))*100.0);
         
     #ifdef  DEBUG  
       Serial.println(F("Average value >>>>>>>>>>"));
       Serial.print(F("IN T="));Serial.print(packet.tIn);Serial.print(F(" H=")); Serial.print(packet.relHIn); Serial.print(F(" abs H=")); Serial.println(packet.absHIn);
       Serial.print(F("OUT T="));Serial.print(packet.tOut);Serial.print(F(" H=")); Serial.print(packet.relHOut); Serial.print(F(" abs H=")); Serial.println(packet.absHOut);
     #endif   
                     
         #ifdef  RADIO     // Радио модуль NRF42l 
            send_packet();   // Послать данные
         #endif
         CheckON();                                // Проверка статуса вентилятора
         print_data();                             // вывод усредненных значений 
         print_status();                           // панель состояния
         if (FLAG_MOTOR_CHECK) ChartMotor=true;  // Признак того что надо показывать включение мотора на графике
         
         if ((long)((long)TimeChart*TIME_SCAN_SENSOR*NUM_SAMPLES)>=(long)TIME_PRINT_CHART) // проврека не пора ли выводить график
            { printChart(); TimeChart=0; // Сдвиг графика и вывод новой точки
              #ifdef  DEBUG  
                 Serial.println(F("Point add chart ++++++++++++++++++++"));
              #endif  
              #ifdef BEEP
 //               beep(50);
              #endif
             } 
         else TimeChart++;
    }
    myOS.restartTask(measurement);     // Пустить задачи
}

// Функция переводит относительную влажность в абсолютную 
// t-температура в градусах Цельсия h-относительная влажность в процентах
float calculationAbsH(float t, float h)
{
 float temp;
 temp=pow(2.718281828,(17.67*t)/(t+243.5));
 return (6.112*temp*h*2.1674)/(273.15+t);
}

// Сканирование клавиш ------------------------------------------
void scanKey()
{  
    byte key;  
    cli(); 
    key=ReadPort(D,3);                                                // Прочитать кнопку 0 - нажата 
    if ((key==0)&&(flagKey==false))                                   // Если кнопка была нажата запомнить время и поставить флаг нажатия
    {
        flagKey=true;                                                 // Кнопка нажата  ждем обратного фронта
        time_key=millis();                                            // Время нажатия запомнили
     }
      
    if ((key==1)&&(flagKey==true))                                    // Если кнопка была отжата 
    {
         time_key=millis()-time_key;                                  // Рассчитать время нажатия
         pressKey=true;
    }
   sei();
 }

// Проверка статуса вытяжки, не пора ли переключится
void CheckON(void)
{
//cli();
// 0.  Проверить замораживание подвала КАЛОРИФЕР 
if (packet.tIn<=TEMP_LOW) { MOTOR_OFF; HEAT_ON; return;}   // Контроль от промораживания подвала по идеи здесь надо включать калорифер
if ((FLAG_HEAT_CHECK)&&(packet.tIn>TEMP_LOW+dT_OFF)) HEAT_OFF;    // Выключить калорифер

// 1. Режимы не зависящие от влажности и температуры ВЫСШИЙ приоритет
if ((settingRAM.fStart==BLOCK_OFF)&&(~FLAG_MOTOR_CHECK))  return;
if ((settingRAM.fStart==BLOCK_OFF)&&(FLAG_MOTOR_CHECK))  { MOTOR_OFF ; return;}
if ((settingRAM.fStart==HOOD_ON )&&(FLAG_MOTOR_CHECK))   return;
if ((settingRAM.fStart==HOOD_ON )&&(~FLAG_MOTOR_CHECK))  { MOTOR_ON  ; return;}

// 2. Режим охлаждения (второй приоритет) температура внутри больше 10 градусов темература снаружи меньше на 2 градуса чем внутри, на влажность не смотрим
if (settingRAM.fStart==COOLING)          // Режим охлаждение
  {
    if ((~FLAG_MOTOR_CHECK)&&(packet.tIn>packet.T_min)&&((packet.tIn-packet.tOut)>packet.dH_min)) // dH_min используется не штатно для температуры
       {MOTOR_ON; return;}            // мотор выключен, температура выше установленной и снаружи температура ниже на 2 градуса  то ВКЛЮЧЕНИЕ мотора
    if ((FLAG_MOTOR_CHECK)&&(packet.tIn<=packet.tOut))   
       {MOTOR_OFF; return;}        // мотор включен и темература внутри ниже наружней то ВЫКЛЮЧЕННИЕ мотора
   return;                             // изменений нет выходим    
  } 
// 3. В режиме осушения - проверка на достижение минимальной температуры помещения в режиме осушения - СРОЧНО ВЫКЛЮЧИТЬ  третий приоритет
if (packet.tIn<=packet.T_min)
   {
     if (~FLAG_MOTOR_CHECK)   return;      // Мотор уже выключен, выходим
     else  { MOTOR_OFF; return;}         // выключить и выйти
   } 
   
// 4. Режимы зависящие от температуры и влажности низший приоритет (что осталось)
if ((~FLAG_MOTOR_CHECK)&&(packet.tIn>packet.T_min)&&((packet.absHIn-packet.dH_min)>packet.absHOut)) 
        {MOTOR_ON; return;}        // мотор выключен, темература выше критической, абс влажность с наружи меньше  то ВКЛЮЧЕНИЕ мотора
if ((FLAG_MOTOR_CHECK)&&((packet.tIn<=(packet.T_min-dT_OFF))||(packet.absHIn<(packet.absHOut-dH_OFF)))) 
        {MOTOR_OFF; return;}       // мотор включен и темература ниже критической или абс влажность внутри ниже  то ВЫКЛЮЧЕННИЕ мотора
         
//sei(); 
} 
 

// Вывод информации о настройках и сохрание индекса настроек в eeprom ---------------------------------
void Setting()
{
 // Настройка
  cli();
  ucg.setColor(0, 100, 255);
  ucg.setFontMode(UCG_FONT_MODE_SOLID);
  ucg.setPrintPos(0,25+0+18); 
  switch (settingRAM.fStart)
        {
        case  BLOCK_OFF: ucg.print(F("Выключено              ")); packet.dH_min=255;packet.T_min=255; break; 
        case  HOOD_ON:   ucg.print(F("Режим вытяжки         "));  packet.dH_min=0;  packet.T_min=0;   break; 
        case  COOLING:   ucg.print(F("Охлаждение T>10 dT>2"));    packet.dH_min=200;packet.T_min=1000;break;   // dH_min используется не штатно для температуры     
        case  3:         ucg.print(F("Осушение T>+3 dH>0.2 "));   packet.dH_min=20; packet.T_min=300; break;
        case  4:         ucg.print(F("Осушение T>+3 dH>0.4 "));   packet.dH_min=40; packet.T_min=300; break;
        case  5:         ucg.print(F("Осушение T>+4 dH>0.3 "));   packet.dH_min=30; packet.T_min=400; break;
        case  6:         ucg.print(F("Осушение T>+4 dH>0.5 "));   packet.dH_min=50; packet.T_min=400; break;
        case  7:         ucg.print(F("Осушение T>+5 dH>0.6 "));   packet.dH_min=30; packet.T_min=500; break;
        } 
 resetKey();
 writeEeprom();       // Запись в EEPROM  новых настроек
 MOTOR_OFF;           // Поменяли настройки - отключить мотор, пусть заново настройки сработают если потребуется
 CheckON();           // Возможно надо включить мотор
 print_status();      // Показать панель состояния (смена настроек)
 sei();   
}

// Вывод float  с двумя десятичными знаком в координаты x y // для экономии места
void print_floatXY(int x,int y, float v)
{
 ucg.setPrintPos(x,y);
 ucg.print(v,2);
 ucg.print(F("  ")); // Стереть хвост от предыдущего числа
} 

// Вывод строки константы в координаты x y // для экономии места
void print_StrXY(int x,int y, const __FlashStringHelper* b)
{
 ucg.setPrintPos(x,y);
 ucg.print(b);
} 

void printInfo() // Окно с информацией о блоке, появляется при длительном нажатии на кнопку
{
  infoScreen=true;
  resetKey();
  cli();
  ucg.setColor(250, 250, 250);  // 
  ucg.drawBox(10, 10, 320-1-20, 240-1-20);
  ucg.setColor(0, 50, 250);
  ucg.drawFrame(10+5, 10+5, 320-1-20-10, 240-1-20-10);
  
  ucg.setFontMode(UCG_FONT_MODE_TRANSPARENT);
  ucg.setColor(0, 150, 10);
  print_StrXY(35,18+15,F("ОСУШИТЕЛЬ на Arduino Pro Mini"));
  
  ucg.setColor(0, 50, 50);
  print_StrXY(10+10,15+17*2,F("1 Напряжение питания В.")); 
  print_floatXY(10+230,15+17*2,readVcc()/1000.0);
 
  print_StrXY(10+10,15+17*3,F("2 Температура блока гр.")); 
  print_floatXY(10+230,15+17*3,GetTemp());
 
  print_StrXY(10+10,15+17*4,F("3 Свободная память байт")); 
  ucg.setPrintPos(10+230,15+17*4); 
  ucg.print(freeRam()); 
 
  print_StrXY(10+10,15+17*5,F("4 Мото часы блока")); 
  ucg.setPrintPos(10+230,15+17*5); 
  ucg.print(settingRAM.hour_unit/(TIME_HOUR/TIME_PRINT_CHART)); 
  
  print_StrXY(10+10,15+17*6,F("5 Мото часы вентилятора")); 
  ucg.setPrintPos(10+230,15+17*6); 
  ucg.print(settingRAM.hour_motor/(TIME_HOUR/TIME_PRINT_CHART)); 
  
  print_StrXY(10+10,15+17*7,F("6 Канал NRF24l01+")); 
  ucg.setPrintPos(10+230,15+17*7); 
  ucg.print(NRF24_CHANEL); 
 
  print_StrXY(10+10,15+17*8,F("7 Гистерезис абс. влажности")); 
  print_floatXY(10+255,15+17*8,(float)dH_OFF/100.0);
  
  print_StrXY(10+10,15+17*9,F("8 Гистерезис температуры")); 
  print_floatXY(10+255,15+17*9,(float)dT_OFF/100.0);
  
  print_StrXY(10+10,15+17*10,F("9 ERR Т/Н in:")); 
  ucg.print(TIN_ERR); 
  ucg.print(F("/")); 
  ucg.print(HIN_ERR); 
  ucg.print(F(" out:"));
  ucg.print(TOUT_ERR); 
  ucg.print(F("/")); 
  ucg.print(HOUT_ERR); 
 
  ucg.setColor(0, 0, 150);
  print_StrXY(10+10,16+17*11,F("СБРОС - Вкл. при нажатой кнопке.")); 
  
  ucg.setColor(250,80,80);
  print_StrXY(10+10,20+21+18*10,F(VERSION)); 
  resetKey();
  sei();
  #ifdef BEEP
   beep(40);
  #endif
} 

void clearInfo()  // Стереть информационный экран
{
      infoScreen=false;
      resetKey();
      last_error=100;         // Признак обновления ошибки
      cli();
      ucg.setColor(0, 0, 0);  // залить черным
      ucg.drawBox(10, 10, 320-1-20, 240-1-20);
      print_static();
      Setting();  
      printChart();
      sei();
} 
// Чтение свободной памяти --------------------------------------------------------------------
int freeRam () {
  extern int __heap_start, *__brkval; 
  int v; 
  return (int) &v - (__brkval == 0 ? (int) &__heap_start : (int) __brkval); 
}
// Чтение внутреннего датчика температуры ---------------------------------------
double GetTemp(void)
{
  unsigned int wADC;
  double t;
  sei();  // Должны быть разрешены прерывания
  ADMUX = (_BV(REFS1) | _BV(REFS0) | _BV(MUX3));
  ADCSRA |= _BV(ADEN);  
  delay(20);           
  ADCSRA |= _BV(ADSC);  
  while (bit_is_set(ADCSRA,ADSC));
  wADC = ADCW;
  t = (wADC - 324.31 ) / 1.22;
  return (t); 
}
// Чтение напряжения питания ----------------------------------------------
long readVcc() {
  long result;
  // Read 1.1V reference against AVcc
  ADMUX = _BV(REFS0) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1);
  delay(2); // Wait for Vref to settle
  ADCSRA |= _BV(ADSC); // Convert
  while (bit_is_set(ADCSRA,ADSC));
  result = ADCL;
  result |= ADCH<<8;
  result = ConstADC / result; // Back-calculate AVcc in mV
  return result;
} 
// Запись счетчиков в Eeprom --------------------------------------------------
void writeEeprom()
{ 
cli(); 
  eeprom_write_block((const void*)&settingRAM, (void*) &settingEEPROM, sizeof(settingRAM)); 
sei();
}
// Чтение счетчиков из Eeprom --------------------------------------------------
void readEeprom()
{
cli(); 
   eeprom_read_block((void*)&settingRAM, (const void*) &settingEEPROM, sizeof(settingRAM)); 
   if ((settingRAM.fStart>(NUM_SETTING-1))||(settingRAM.fStart<0)) settingRAM.fStart=0;        // гарантированно попадаем в диапазон
sei();
}

void reset_sum()  // Сброс счетчиков накоплений
{
sensors.num=0;  // Рассчитать величину усреднения
sensors.sum_tOut=0;
sensors.sum_tIn=0;
sensors.sum_relHOut=0;
sensors.sum_relHIn=0;
}

char hex(byte x)  // Функция для вывода в hex
{
   if(x >= 0 && x <= 9 ) return (char)(x + '0');
   else      return (char)('a'+x-10);
}

bool reset_ili9341(void)
{
  pinMode(PIN_RESET, OUTPUT);                    // Сброс дисплея сигнал активным является LOW
  digitalWrite(PIN_RESET, LOW);  
  delay(100);
  digitalWrite(PIN_RESET, HIGH);  
  // Дисплей
  ucg.begin(UCG_FONT_MODE_TRANSPARENT);
  ucg.setFont(my14x10rus);  
//   ucg.setRotate90();
  ucg.setRotate270();
  ucg.clearScreen();
}

#ifdef BEEP  
  void beep(int x)  // Пищать х мсек
  {
    WritePort(C,1,HIGH);
    delay(x);
    WritePort(C,1,LOW);
  } 
#endif

 

miaua
Offline
Зарегистрирован: 20.08.2015

Я еще предыдущий листинг не изучил, а у Вас уже новая работа вышла...Плодотворно!   

Жаль железо еще не приехало..

 

miaua
Offline
Зарегистрирован: 20.08.2015

Доброй Ночи!

В Version: 0.58 28/08/15 управление кнопкой PIN_KEY не предусмотрено? 

При каких условиях происходит управление кнопкой ? Начиная с версии 0.51, при отсутствии датчиков и радио, на нажатие кнопки (D3) никак не реагирует...

Еще вопрос:- какая версия LeOs используется?

 

pav2000
Offline
Зарегистрирован: 15.12.2014

miaua пишет:

Доброй Ночи!

В Version: 0.58 28/08/15 управление кнопкой PIN_KEY не предусмотрено? 

Еще вопрос:- какая версия LeOs используется?

Управление кнопкой предусмотрено. Просто сейчас она читается без использования функции digitalWrite. Используется макрос  ReadPort(D,3). Определение PIN_KEY осталось только для того что бы помнить куда кнопку вешать.

LeOS используется первой версии, которая использует таймер.

В субботу тестировал устройство. Нашел одну ошибку (при замене функции digitalWrite не проверил) - не получается обнулить счетчики при нажатой кнопке. Было (строки 210 -  212)

 * * *
  readEeprom();         // Прочитать настройки
 if (ReadPort(D,3)==0)  // Если при включении нажата кнопка то стираем Eeprom
  {   int i;
   settingRAM.fStart=0;
* * *

поправить на

 readEeprom();       // Прочитать настройки 
 byte i=ReadPort(D,3);
  if (i==0)         // Если при включении нажата кнопка то стираем Eeprom
  {   settingRAM.fStart=0;
 

После этого обнуление настроек будет происходить

Дальность получается метров 50 + 2 стены бетонные 40 см по диагонале. Плохо что квитанции не приходят и около 5% пакетов теряются. Фото устройства в подвале (подсветка работы вентилятора):

Информационный экран (долгое нажатие на кнопку)

Прототип приемника на stm32 и 2.8 дисплеем:

miaua
Offline
Зарегистрирован: 20.08.2015

Не хочет работать кнопка... Строки -210-213 заменил... У меня режим "ДЕМО" т.к. не приехали еще датчики... Может это причина?

Пытаюсть разобраться в коде, но пока не все ясно. Например -кнопка должна быть подтянута к +5 ? Я уже пробовал и к +5 через 10к резистор, и земле - результата увы нет. где я накосячил? 

Логика управления кнопкой ? как устанавливать режим? или пока есть только вывод экрана статистики? Эти вопросы возникают, т.к. пока не вижу результата нажатия кнопки..

Может встроить вывод состояния кнопки в Serial port, в режиме "Debug"?

Мне приехал модуль реле с оптопарами - вкл. реле по логическому "0". В результате логика обратная.. Попытка заменить в строках (вер. 0.51) не приводило к изменению логики реле...

[code]
#define MOTOR_OFF      1                        // Мотор выключен
#define MOTOR_ON       0                        // Мотор включен
 
[/code]

На каких модулях "Дальность получается метров 50 " ? Тех, что без наружных антенн?

Заказал maple mini. Уже в пути. 

miaua
Offline
Зарегистрирован: 20.08.2015

Еще один момент - Судя по-всему в датчиках DHT21 и DHT22 не точный датчик температуры. В моем фабричном контроллере влажности установлено 2 датчика -DHT21 и DS12b20 (или аналогичный) в герметичном исполнении. А в инструкции написано, что этот датчик необходим для более точного определения влажности...

На фото ,см выше, рядом с "скворечником" тоже видно 2 отдельных датчика. 

Это мой 

http://ru.aliexpress.com/premium/WH8040.html?ltype=wholesale&SearchText=WH8040&d=y&origin=y&initiative_id=SB_20150831002334&isViewCP=y&catId=0

pav2000
Offline
Зарегистрирован: 15.12.2014

1. Кнопка у меня работает. Через 10к вывод кнопки на +5. Кнопка замыкающая. Активный уровень 0. У меня кнопка повешена на прерывание (пин кнопки менять нельзя). Для кнопки сначала железо проверьте, даю код для проверки (без прерываний)

int ledPin = 13;                 // Светодиод подключенный к вход/выходу 13
int inPin = 3;                   // кнопка на входе 3  - НЕ МЕНЯТЬ
int val = 0;                     // переменная для хранения значения
 
void setup()
{
  pinMode(ledPin, OUTPUT);       // устанавливает режим работы - выход для 13го вход/выхода (pin)
  pinMode(inPin, INPUT);         //  устанавливает режим работы - вход для 7го вход/выхода (pin)
}
 
void loop()
{
  val = digitalRead(inPin);      // считываем значение с входа
  digitalWrite(ledPin, val);     // устанавливаем значение на светодиоде равным значению входа кнопки
}

Логика кнопки следующая:

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

- в работающем блоке короткое нажатие по кругу перебирает заданные режимы (7 шт)  режим отображается на дисплее синим

- в работающем блоке длиное нажатие выводит экран информации (есть ложные рабатывания, пока не понял почему)

debug можно поставить, раскоментировать соответсвующий ключ и добавить нужные строчки.

2. По реле - обычно есть на шильде перемычка приводящая к инверсии сигналов. если нет то (в последней версии) поменяйте макросы

#define MOTOR_ON                  { WritePort(C,0,HIGH); FLAG_MOTOR_ON;  }   // включить мотор
#define MOTOR_OFF                 { WritePort(C,0,LOW) ; FLAG_MOTOR_OFF; }   // выключить мотор

на

#define MOTOR_ON                  { WritePort(C,0,LOW); FLAG_MOTOR_ON;  }   // включить мотор
#define MOTOR_OFF                 { WritePort(C,0,HIGH) ; FLAG_MOTOR_OFF; }   // выключить мотор

и в функции setup вызовите MOTOR_OFF  один раз что бы при включении вентилятор не работал.

3. Дальность 50 метров это с антеннами и усилителем.

4. По датчику темературы - ds1820 поточнее будет, НО!! Систематическую погрешность можну убрать калибровкой датчиков (нелинейность в узком диапазоне не так значительна). У меня уже есть учет систематической погрешности. см код

// СИСТЕМАТИЧЕСКИЕ ОШИБКИ ДАТЧИКОВ для ID 0x21  ОШИБКИ ДОБАВЛЯЮТСЯ!!
#define TOUT_ERR      -40                        // Ошибка уличного датчика температуры в сотых долях градуса
#define TIN_ERR       -40                        // Ошибка домового датчика температуры в сотых долях градуса
#define HOUT_ERR      -30                        // Ошибка уличного датчика влажности в сотых долях %
#define HIN_ERR       +30                        // Ошибка домового датчика влажности в сотых долях %

Вам для начала надо поставить одни "0" везде. Далее берете датчики и термометр (которому Вы верите, я использую ртутный лабораторный с делением 0.1 С) связываете вместе кладете в банку (что бы небыло скозников) и наблюдаете, 10 измерений в течении 2-3 часов и вычисляете среднюю ошибку и ставите в программу. Влажность берете усредняете если нет гигрометра. После этого Вы будете приятно удивлены полученной точностью.

 

miaua
Offline
Зарегистрирован: 20.08.2015

Кнопку кодом проверил - мигает!. Инвертировал мотор и зодно нагреватель. тут все в порядке. НО!!! Проклятая кнопка никакой реакции в проге...  Я сейчас ковыряюсь с УНО, до этого был НАНО, оба 328Р... результат одинаковый. Пищалка срабатывает один раз в конце старта..

Загадка!

Еще заметил, что нагреватель слишком чувствителен...думаю дельту в 1 градус надо.

miaua
Offline
Зарегистрирован: 20.08.2015

Стоп! похоже обрыв на плате uno.. Сачс разберусь..

miaua
Offline
Зарегистрирован: 20.08.2015

Так и есть! Китай рулит! Мало того, что D3 непропай чипа, так еще и новый провод перемычки на бредборд подорван.

Устранил. РАБОТАЕТ!!!

Теперь жду доставки железа... STM тоже заказан. 

Отличный проект!

 

miaua
Offline
Зарегистрирован: 20.08.2015

А такой модуль прицепить? Можно и без кнопок обойтись? Вроде SPI и ili9341

http://ru.aliexpress.com/item/3-2-Inch-TFT-LCD-Screen-240-320-40PIN-QVGA-SPI-For-ILI9341-Drive-IC-3line/32267594735.html?spm=2114.03020208.3.2.BdnW4I&ws_ab_test=201407_3,201444_5,201409_4

pav2000
Offline
Зарегистрирован: 15.12.2014

miaua пишет:

А такой модуль прицепить? Можно и без кнопок обойтись? Вроде SPI и ili9341

http://ru.aliexpress.com/item/3-2-Inch-TFT-LCD-Screen-240-320-40PIN-QVGA-SPI-For-ILI9341-Drive-IC-3line/32267594735.html?spm=2114.03020208.3.2.BdnW4I&ws_ab_test=201407_3,201444_5,201409_4

Скорее всего можно, вроде поддерживает три интерфейса 16 и 8 битный параллельный и spi. Последние три провода в шлейфе определяют выбор интерфейса (как контретно не понятно). Паять сложнее. Для разовых поделок нужно самому городить переходную плату. Я так же такие дисплеи смотрел но решил не заморачиваться, слишком много проблем.

 

miaua
Offline
Зарегистрирован: 20.08.2015

Как продвигается проект? Я правильно понимаю -приемник будет принимать данные от нескольких управляющих систем? с переключением экранов?

Я подумал о дополнительно к вентилятору добавить управление заслонкой. Возможно шаговиком.

 В зимнее время приточная работает сама по себе и может привести к повышению влажности независимо от системы.

pav2000
Offline
Зарегистрирован: 15.12.2014

miaua пишет:

Как продвигается проект? Я правильно понимаю -приемник будет принимать данные от нескольких управляющих систем? с переключением экранов?

Я подумал о дополнительно к вентилятору добавить управление заслонкой. Возможно шаговиком.

 В зимнее время приточная работает сама по себе и может привести к повышению влажности независимо от системы.

1. Проект потихоньку продвигается. Было исправлена масса мелких ошибок. Было добавлено (из то что вспомнил)

- моточасы нагревателя. Теперь  нагреватель по функционалу не уступает вентилятору.

- долго бился с nrf24. Перешел на библиотеку TMRh20 (http://tmrh20.github.io/RF24/). Эта библиотека наиболее свежая (конец 2014) и учитывает вся исправления.  С этой библиотекой немного изменил режим отправки. В результате стабильность отправки возрасла. На расстоянии 50 метров доходит почти все. Квитанции о приеме также почти все  доходят. Портировал эту библиотеку для stm32.

- немного поменял отображение графиков. Сделал подсветку синим - работу вентилятора желтым - работу нагревателя.

- сделал режимы в которых управление происходит не только по разнице абсолютных влажностей задаваемых в граммах на куб но и в % от влажности подвала. Наверное это более правильно.

Последняя версия кода

#pragma GCC optimize ("-Os")
//#pragma pack(push, 1)     // выравнивание по одному байту ????????

#include <SPI.h>
#include <EEPROM.h>
#include <avr/wdt.h>
#include <leOS.h>      // Шедуллер задач
#include <dht.h>       // Дачик влажности и температуры
#include "Ucglib.h"    // ВНИМАНИЕ использовать библиотеку не познее 1.01 справка https://code.google.com/p/ucglib/wiki/
#include "rusFont.h"   // Русские шрифты
#include "nRF24L01.h"  // Беcпроводной модуль надо использовать http://tmrh20.github.io/RF24
#include "RF24.h"      // Беcпроводной модуль используются не стандартные функции https://github.com/TMRh20

// - ОПЦИИ -------------------------------
//#define DEBUG                                   // Отладочную  информацию в ком порт посылает  
//#define DEMO                                    // Признак демонстрации - данные с датчиков генерятся рандом
#define BEEP                                    // Использовать пищалку
#define RADIO                                   // Признак использования радио модуля
#define VERSION "Version: 0.64 04/09/15"        // Текущая версия
#define ID             0x21                     // уникально Идентификатор устройства - старшие 4 бита, вторые (младшие) 4 бита серийный номер устройства
#define LABEL "Подпол"  // "Подпол" "Подвал"    // Место установки блока - отоображается как заголовок колонки в таблице. Не более 6 символов а то не влезает

// Макросы для работы с портами  скорость и место
#define SetOutput(port,bit)       DDR ## port |= _BV(bit)
#define SetInput(port,bit)        DDR ## port &= ~_BV(bit)
#define SetBit(port,bit)          PORT ## port |= _BV(bit)
#define ClearBit(port,bit)        PORT ## port &= ~_BV(bit)
#define WritePort(port,bit,value) PORT ## port = (PORT ## port & ~_BV(bit)) | ((value & 1) << bit)
#define ReadPort(port,bit)        (PIN ## port >> bit) & 1
#define PullUp(port,bit)          { SetInput(port,bit); SetBit(port,bit); }
#define Release(port,bit)         { SetInput(port,bit); ClearBit(port,bit); }
// Мои макросы
#define MOTOR_BIT                 0            // бит мотора в packet.flags
#define HEAT_BIT                  1            // бит калорифера в packet.flags
#define ABS_H_BIT                 2            // бит авсалютной влажности в packet.flags
#define MODE_BIT                  5            // первый бит режима в packet.flags
//#define MASK_MODE_BITS            0xe0         // маска для выделения номера режима в packet.flags
#define SET_MODE_BITS             packet.flags |= (settingRAM.mode<<MODE_BIT)  // запись номера режима в packet.flags
#define FLAG_ABS_H_ON             packet.flags |= (1<<ABS_H_BIT)   // бит ABS_H_BIT установить в 1
#define FLAG_ABS_H_OFF            packet.flags &= ~(1<<ABS_H_BIT)  // бит ABS_H_BIT установить в 0
#define FLAG_ABS_H_CHECK          packet.flags & (1<<ABS_H_BIT)    // бит ABS_H_BIT проверить на 1

#define FLAG_MOTOR_ON             packet.flags |= (1<<MOTOR_BIT)   // бит мотора установить в 1
#define FLAG_MOTOR_OFF            packet.flags &= ~(1<<MOTOR_BIT)  // бит мотора установить в 0
#define FLAG_MOTOR_CHECK          packet.flags & (1<<MOTOR_BIT)    // бит мотора проверить на 1
#define MOTOR_ON                  { WritePort(C,0,HIGH); FLAG_MOTOR_ON;  }   // включить мотор
#define MOTOR_OFF                 { WritePort(C,0,LOW) ; FLAG_MOTOR_OFF; }   // выключить мотор

#define FLAG_HEAT_ON              packet.flags |= (1<<HEAT_BIT)   // бит калорифера установить в 1
#define FLAG_HEAT_OFF             packet.flags &= ~(1<<HEAT_BIT)  // бит калорифера установить в 0
#define FLAG_HEAT_CHECK           packet.flags & (1<<HEAT_BIT)    // бит калорифера проверить на 1
#define HEAT_ON                   { WritePort(C,2,HIGH); FLAG_HEAT_ON;  }   // включить калорифер
#define HEAT_OFF                  { WritePort(C,2,LOW); FLAG_HEAT_OFF; }   // выключить калорифер


// - КОНСТАНТЫ --------------------------------------
#define dH_OFF          15                      // Гистерезис абсолютной влажности в сотых грамма на куб
#define dT_OFF          11                      // Гистерезис температуры в сотых градуса
#define TEMP_LOW       150                      // Температура подвала критическая - может быть замораживание (в сотых градуса) - система выключается и включается нагреватель
// СИСТЕМАТИЧЕСКИЕ ОШИБКИ ДАТЧИКОВ для каждого ID свой. ОШИБКИ ДОБАВЛЯЮТСЯ!!
#if ID == 0x21
    #define TOUT_ERR      -40                  // Ошибка уличного датчика температуры в сотых долях градуса
    #define TIN_ERR       -40                  // Ошибка домового датчика температуры в сотых долях градуса
    #define HOUT_ERR      -30                  // Ошибка уличного датчика влажности в сотых долях %
    #define HIN_ERR       +30                  // Ошибка домового датчика влажности в сотых долях %
#elif ID == 0x22
    #define TOUT_ERR      -12                  // Ошибка уличного датчика температуры в сотых долях градуса
    #define TIN_ERR       -10                  // Ошибка домового датчика температуры в сотых долях градуса
    #define HOUT_ERR      -250                 // Ошибка уличного датчика влажности в сотых долях %
    #define HIN_ERR       +250                 // Ошибка домового датчика влажности в сотых долях %
#else 
    #define TOUT_ERR      0                    // Ошибка уличного датчика температуры в сотых долях градуса
    #define TIN_ERR       0                    // Ошибка домового датчика температуры в сотых долях градуса
    #define HOUT_ERR      0                    // Ошибка уличного датчика влажности в сотых долях %
    #define HIN_ERR       0                    // Ошибка домового датчика влажности в сотых долях %
#endif

// - ВРЕМЕНА ---------------------------------------
#ifdef DEMO                                     // Для демо все быстрее и случайным образом
    #define NUM_SAMPLES      2                  // Число усреднений измерений датчика
    #define TIME_SCAN_SENSOR 1000               // Время опроса датчиков мсек, для демки быстрее
    #define TIME_PRINT_CHART 4000               // Время вывода точки графика мсек, для демки быстрее
    #define TIME_HOUR        50000              // Число мсек в часе, для демки быстрее   
#else   
   #define NUM_SAMPLES      10                  // Число усреднений измерений датчика
   #define TIME_SCAN_SENSOR 2000                // Время опроса датчиков мсек
   #define TIME_PRINT_CHART 300000              // Время вывода точки графика мсек
   #define TIME_HOUR        3600000             // Число мсек в часе
#endif

#define LONG_KEY      3000                      // Длительное нажатие кнопки мсек, появляется Экран инфо
#define SHORT_KEY     150                       // Короткое нажатие кнопки более мсек
#define NRF24_CHANEL  100                       // Номер канала nrf24

//  НОГИ к которым прицеплена переферия (SPI используется для TFT и NRF24 - 11,12,13)
#define PIN_HEAT      16                         // Ножка куда повешен калорифер A2 (port C2)
#ifdef BEEP
    #define PIN_BEEP  15                         // Ножка куда повешена пищалка A1 (port C1)
#endif
#define PIN_RELAY     14                         // Ножка на которую повешено реле (SSR) вентилятора - аналоговый вход A0 через резистор 470 ом (port C0)
#define PIN_CS        10                         // TFT дисплей spi
#define PIN_CD        9                          // TFT дисплей spi
#define PIN_RESET     8                          // TFT дисплей spi
#define PIN_CE        7                          // nrf24 ce
#define PIN_CSN       6                          // nrf24 cs
#define PIN_DHT22a    5                          // Первый датчик DHT22   IN  ДОМ
#define PIN_DHT22b    4                          // Второй датчик DHT22   OUT УЛИЦА
#define PIN_KEY       3                          // Кнопка, повешена на прерывание, что бы ресурсов не тратить (port D3)
#define PIN_IRQ_NRF24 2                          // Ножка куда заведено прерывание от NRF24 (пока не используется)

// Настройки
#define NUM_SETTING    7                        // Число вариантов настроек 
#define BLOCK_OFF      0                        // Выключено (вариант настроек)
#define HOOD_ON        1                        // Режим вытяжки (вариант настроек)
#define COOLING        2                        // Режим охлаждение (вариант настроек)

// АЦП ----------------------------------------
const long ConstADC=1126400;                    // Калибровка встроенного АЦП (встроенный ИОН) по умолчанию 1126400 дальше измеряем питание и смотрим на дисплей    

Ucglib_ILI9341_18x240x320_HWSPI ucg(/*cd=*/PIN_CD, /*cs=*/PIN_CS, /*reset=*/PIN_RESET); // Аппаратный SPI на дисплей ILI9341
leOS myOS;                                      // многозадачность
dht DHT;                                        // Датчики
bool infoScreen=false;                          // Признак отображениея иформационного экрана  1 - на экран ничего не выводится кроме информационного экрана
bool flagKey=false;                             // Флаг нажатия клавиши
bool pressKey=false;                            // Флаг необходимости обработки кнопки

unsigned long time_key=0;                       // Время нажатия копки
byte last_error=100;                            // Предыдущая ошибка чтения датчиков

 struct type_setting_eeprom                     // Структура для сохранения данных в eeprom
 {
     byte mode = 0;                             // Какой режим работы блока
     unsigned long hour_unit;                   // мото часы блок измеряется в интервалах вывода данных = NUM_SAMPLES*TIME_SCAN_SENSOR
     unsigned long hour_motor;                  // мото часы мотора измеряется в интервалах вывода данных = NUM_SAMPLES*TIME_SCAN_SENSOR
     unsigned long hour_heat;                   // мото часы нагревателя измеряется в интервалах вывода данных = NUM_SAMPLES*TIME_SCAN_SENSOR
  }; 
volatile type_setting_eeprom settingRAM;        // Рабочая копия счетчиков в памяти
type_setting_eeprom settingEEPROM EEMEM;        // Копия счетчиков в eeprom - туда пишем 

// Пакет передаваемый, используется также для хранения результатов. 
 struct type_packet_NRF24   // Версия 2.4!! адаптация для stm32 Структура передаваемого пакета 32 байта - 32 максимум
    {
        byte id=ID;                             // Идентификатор типа устройства - старшие 4 бита, вторые (младшие) 4 бита серийный номер устройства
        byte DHT_error;                         // Ошибка разряды: 0-1 первый датчик (00-ок) 2-3 второй датчик (00-ок) 4 - радиоканал     
        int16_t   tOut=-500,tIn=500;            // Текущие температуры в сотых градуса !!! место экономим
        uint16_t  absHOut=123,absHIn=123;       // Абсолютные влажности в сотых грамма на м*3 !!! место экономим
        uint16_t  relHOut=123,relHIn=123;       // Относительные влажности сотых процента !!! место экономим
        uint8_t   flags=0x00;                   // байт флагов  
                                                // 0 бит - мотор включен/выключен 
                                                // 1 бит - нагреватель включен/выключен
                                                // 2 бит -[1 - dH_min задается в сотых грамма на м*3] [0 - dH_min задается в ДЕСЯТЫХ процента от absHIn]
                                                // 3 4 - пока пусто
                                                // 5-7 - номер настройки = settingRAM.mode до 8 настроек, надо передавать, что бы на приемнике восстановить
        uint8_t   dH_min;                       // Порог включения вентилятора по РАЗНИЦЕ абсолютной влажности в сотых грамма на м*3 или в ДЕСЯТЫХ % см flags:2
        uint8_t   T_min;                        // Порог выключения вентилятора по температуре в ДЕСЯТЫХ долях градуса, только положительные значения
        uint8_t   count=0;                      // циклический счетчик отправленных пакетов нужен что бы на приемнике проверять качество связи
        char note[14] = LABEL;                  // Примечание не более 13 байт + 0 байт Русские буквы в два раза меньше т.к. UTF-8
    } packet; 
    
struct type_sensors                             // структура для усреднения измерений
{
       int  num=0;                              // сколько отсчетов уже сложили не болле NUM_SAMPLES
       long  sum_tOut=0,sum_tIn=0;              // Сумма для усреднения Текущие температуры в сотых градуса !!! место экономим
       long  sum_relHOut=0,sum_relHIn=0;        // Сумма для усреднения Относительные влажности сотых процента !!! место экономим
       int  tOut=-5000,tIn=5000;                // Текущие температуры в сотых градуса !!! место экономим
       int  absHOut=55555,absHIn=55555;         // Абсолютные влажности в сотых грамма на м*3 !!! место экономим
       int  relHOut=55555,relHIn=55555;         // Относительные влажности сотых процента !!! место экономим
} sensors;
    
// Массивы для графиков
uint8_t tOutChart[120];
uint8_t tInChart[120];
uint8_t absHOutChart[120];
uint8_t absHInChart[120];
uint8_t posChart=0;       // Позиция в массиве графиков - начало вывода от 0 до 120-1
uint8_t TimeChart=0;      // Время до вывода очередной точки на график. 
bool ChartMotor=false;    // Признак работы мотора во время интервала графика если мотор был включен на любое время (даже одно измерение) то на графике фон меняется в этом месте
bool ChartHeat=false;     // Признак работы нагревателя во время интервала графика если нагреватель был включен на любое время (даже одно измерение) то на графике фон меняется в этом месте

#ifdef  RADIO    // Радио модуль NRF42l  
   RF24 radio(PIN_CE, PIN_CSN);  // определение управляющих ног
   bool send_packet_ok=false;    // признак удачной отправки последнего пакета 
#endif 
///////////////////////////////////////////////////////////////////////////////////////////////////////////
// ПРОГРАММА
///////////////////////////////////////////////////////////////////////////////////////////////////////////
void setup(){
#ifdef  DEBUG  
   Serial.begin(9600); 
   Serial.println(F("DEBUG MODE")); 
   #ifdef  BEEP  
     Serial.println(F("BEEP ON")); 
   #else         
     Serial.println(F("BEEP OFF"));
   #endif
   #ifdef  RADIO 
     Serial.println(F("RADIO ON")); 
   #else         
     Serial.println(F("RADIO OFF"));
   #endif
#endif

reset_sum();

#ifdef  RADIO    // Радио модуль NRF42l  первичная настройка
   radio.begin();
   radio.setDataRate(RF24_250KBPS);         //  выбор скорости RF24_250KBPS RF24_1MBPS RF24_2MBPS
   radio.setPALevel(RF24_PA_MAX);           // выходная мощность передатчика
   radio.setChannel(NRF24_CHANEL);          //тут установка канала
   radio.setCRCLength(RF24_CRC_16);         // использовать контрольную сумму в 16 бит
   radio.setAutoAck(true);                  // выключить аппаратное потверждение
// radio.enableDynamicPayloads();           // разрешить Dynamic Payloads
// radio.enableAckPayload();                // разрешить AckPayload
  radio.setRetries(50,10);                  // Количество пауза и количество повторов 
  radio.openWritingPipe(0xF0F0F0F0E1LL);    // передатчик
  radio.openReadingPipe(1,0xF0F0F0F0D2LL);  // приемник
  radio.startListening();
#endif
  #ifdef BEEP
     SetOutput(C,1);                      //  Настройка ноги для динамика
     WritePort(C,1,LOW);
  #endif
  SetInput(D,3);                        //  Включена кнопка
  WritePort(D,3,HIGH);
  SetOutput(C,0);                       //  Подключить Реле
  WritePort(C,0,LOW);
  SetOutput(C,2);                       //  Подключить Калорифер
  WritePort(C,2,LOW);
  
  pinMode(PIN_DHT22a, OUTPUT);          //  Датчик DHT22 #1 
  digitalWrite(PIN_DHT22a, HIGH);  
  pinMode(PIN_DHT22b, OUTPUT);          //  Датчик DHT22 #2
  digitalWrite(PIN_DHT22b, HIGH);  
  
  reset_ili9341();                      // сброс дисплея
  readEeprom();                         // Прочитать настройки 
  byte i=ReadPort(D,3);
  if (i==0)                  // Если при включении нажата кнопка то стираем Eeprom
  {   settingRAM.mode=0;
      settingRAM.hour_unit=0;
      settingRAM.hour_motor=0;
      settingRAM.hour_heat=0;
      ucg.setColor(255, 255, 255);
      print_StrXY(10,50,F("Сброс настроек и счетчиков"));
      for(i=0;i<3;i++)
        {
         delay(1000);
         ucg.print(F(" ."));
        }
      writeEeprom();       // Запись в EEPROM  
      delay(1000);
      ucg.clearScreen();
  } 
//  settingRAM.mode=4; 
  wdt_enable(WDTO_8S); // Сторожевой таймер Для тестов не рекомендуется устанавливать значение менее 8 сек.
  // Запуск задач по таймеру
  myOS.begin();
  myOS.addTask(measurement,TIME_SCAN_SENSOR);    // Измерение 
  attachInterrupt(1, scanKey, CHANGE);           // КНОПКА Прерывания по обоим фронтам
  print_static();                                // распечатать таблицу
  Setting();                                     // Применить настройки
//  printChart();
  measurement();                                 // Считать данные
  #ifdef BEEP
     beep(50);
     delay(200);
     beep(50);
  #endif
resetKey();

}

void loop()
{
 wdt_reset();                        // Сбросить сторожевой таймер
// Обработка нажатия кнопки
if (pressKey==true) // Кнопка была нажата
{
 myOS.pauseTask(measurement);                                 // Остановить задачи
 if (flagKey!=false)                                          // Обработчик кнопки
 if (time_key > LONG_KEY)                                     // Длительное нажатие кнопки
   printInfo();                                               // Вывод информационного экрана
 else 
   if (time_key > SHORT_KEY)                                  // Короткое нажатие кнопки
     { 
        #ifdef BEEP
          beep(30);                                            // Звук нажатия на клавишу
        #endif
       if (infoScreen==true)  clearInfo();                    // если информационный экран то стереть его
       else {if (settingRAM.mode >= NUM_SETTING) settingRAM.mode=0; //  Кольцевой счетчик настроек
               else settingRAM.mode++;                      // В противном случае следующая настройка
               Setting(); }  
     }
  resetKey();                                                //  Сброс состояния кнопки
  myOS.restartTask(measurement);                             // Запустить задачи
  }
}

void resetKey(void)  // Сброс состяния кнопки
{
  flagKey=false;                                             
  time_key=millis();                                                
  pressKey=false;
}  
void print_static()  // Печать статической картинки 
{  
  int i;
   cli();
  // Заголовок 
  ucg.setColor(0, 0, 180);  // 
  ucg.drawBox(0, 0, 320-1, 23);
  ucg.setColor(250, 250, 250);
  ucg.setFontMode(UCG_FONT_MODE_TRANSPARENT);
  print_StrXY(2,19,F("ОСУШИТЕЛЬ ID: 0x"));
  ucg.print( hex(packet.id >> 4));
  ucg.print( hex(packet.id&0x0f));
   
  #ifdef DEMO
    ucg.print(F(" demo"));
  #endif
  
  // Таблица для данных
  ucg.setColor(0, 200, 0);
  ucg.drawHLine(0,25,320-1);
  ucg.drawHLine(0,25+23*1,320-1);
  ucg.drawHLine(0,25+23*2,320-1);
  ucg.drawHLine(0,25+23*3,320-1);
  ucg.drawHLine(0,25+23*4,320-1);
  ucg.drawVLine(200-4,25,24+23*3);
  ucg.drawVLine(260,25,24+23*3);

  // Заголовки таблиц
  ucg.setColor(255, 255, 0);
  // В зависимости от id разные надписи - привязка местоположения блока к ID
  
//  print_StrXY(180+30-9,25+0+18,F(LABEL));
  ucg.setPrintPos(180+30-9,25+0+18); 
  ucg.print(packet.note); 
  
  print_StrXY(250+20,25+0+18,F("Улица"));
  print_StrXY(0,25+23*1+18,F("Температура градусы C")); 
  print_StrXY(0,25+23*2+18,F("Относительная влаж. %")); 
  print_StrXY(0,25+23*3+18,F("Абсолют. влаж. г/м*3")); 

  // Графики
  ucg.setColor(210, 210, 210);
//  ucg.drawHLine(1,240-2,130);
//  ucg.drawVLine(1,135,105);
//  ucg.drawHLine(10+154,240-2,130);
//  ucg.drawVLine(10+154,135,105);
  print_StrXY(10,135+0,F("Температура")); 
  print_StrXY(20+154,135+0,F("Абс. влажность")); 
   
  // надписи на графиках
  print_StrXY(128,154,F("+20")); 
  print_StrXY(135,194,F("0")); 
  print_StrXY(128,233,F("-20"));
  
  print_StrXY(296,164,F("15"));
  print_StrXY(296,194,F("10"));
  print_StrXY(296,223,F("5"));
  
  // Горизонтальная шкала по часам
//  ucg.setColor(255, 255, 0);
  for(i=0;i<=120;i=i+12)
    {
      ucg.drawPixel(4+i,239);
      ucg.drawPixel(4+i,238);
      ucg.drawPixel(167+i,239);
      ucg.drawPixel(167+i,238);
     } 
     
   sei();
}

void print_status(void) // Печать панели статуса Значки на статус панели
{
  if (infoScreen==true) return;        // если отображен информационный экран то ничего не выводим  
   cli();
 // 1. печать ошибки чтения датчиков
   print_error_DHT();
 // 2. Признак включения мотора или нагревателя
  if (FLAG_MOTOR_CHECK)         ucg.setColor(0, 240, 0); 
  else if (FLAG_HEAT_CHECK)     ucg.setColor(240, 0, 0);
  else                          ucg.setColor(0, 40, 0);
  ucg.drawBox(290-32, 5, 14, 14);
  
  #ifdef  RADIO 
  // 3. Признак удачной передачи информации по радиоканалу
      if (send_packet_ok==true)  ucg.setColor(0, 240, 0); 
      else                       ucg.setColor(0, 40, 0);
      ucg.setPrintDir(3);
      ucg.setFontMode(UCG_FONT_MODE_TRANSPARENT);
      print_StrXY(290-40,20,F(">>")); 
      ucg.setPrintDir(0);
      ucg.setFontMode(UCG_FONT_MODE_SOLID);
  #endif
  sei();
}  

void print_error_DHT(void) // Печать ошибки чтения датчиков выводится при каждом чтении датчика
{
  if (infoScreen==true) return;        // если отображен информационный экран то ничего не выводим  
 // 1. печать ошибки чтения датчиков
  if (packet.DHT_error!=last_error)        // если статус ошибки поменялся то надо вывести если нет то не выводим - экономия время и нет мерцания
  {
      cli();
      last_error=packet.DHT_error; 
      ucg.setColor(0, 0, 180);         // Сначала стереть
      ucg.drawBox(290, 0, 26, 18);
      ucg.setPrintPos(280,18); 
      ucg.setFontMode(UCG_FONT_MODE_TRANSPARENT);
      if (packet.DHT_error>0) 
        { 
        ucg.setColor(255, 100, 100); 
        print_StrXY(280,19,F("0x"));
        ucg.print( hex(packet.DHT_error >> 4));
        ucg.print( hex(packet.DHT_error & 0x0f)); 
        }
      else  { ucg.setColor(200, 240, 0);   ucg.print(F(" ok ")); } 
     sei(); 
   }   
} 
//  вывод на экран данных (то что меняется)
void print_data()
{ 
  // Статистика по моточасам, время ведется в интервалах вывода данных = NUM_SAMPLES*TIME_SCAN_SENSOR а потом пересчитывается в часы при выводе.
settingRAM.hour_unit++;
if (FLAG_MOTOR_CHECK) settingRAM.hour_motor++;  // если мотор включен
if (FLAG_HEAT_CHECK) settingRAM.hour_heat++;    // если нагреватель включен

 if (infoScreen==true) return;                  // если отображен информационный экран то ничего не выводим  
  cli();
 // Печать значений для дома
  ucg.setFontMode(UCG_FONT_MODE_SOLID);
  ucg.setColor(250, 0, 100);  // Цвет ДОМА
  print_floatXY(200+0,25+23*1+18,((float)packet.tIn)/100);
  print_floatXY(200+0,25+23*2+18,((float)packet.relHIn)/100);
  print_floatXY(200+0,25+23*3+18,((float)packet.absHIn)/100);
  ucg.setColor(0, 250, 100);  // Цвет УЛИЦЫ
  print_floatXY(260+4,25+23*1+18,((float)packet.tOut)/100);
  print_floatXY(260+6,25+23*2+18,((float)packet.relHOut)/100);
  print_floatXY(260+6,25+23*3+18,((float)packet.absHOut)/100);
  sei();
}  

// Печать графика на экране, добавляется одна точка и график сдвигается 
void printChart() 
{
byte i,x=0;
uint8_t tmp;

// Работаем через кольцевой буфер
// Добавить новую точку в кольцевой буфер
     // Температура в доме. диапазон -25 . . . +25 растягиваем на 100 точек
     if (packet.tIn<=-2500) tInChart[posChart]=0;           // Если температура меньше -25 то округляем до -25
     else  if (packet.tIn>=2500)  tInChart[posChart]=100-1;   // Если температура больше 25  то округляем до 25
      else tInChart[posChart]=(packet.tIn+2500)/50;    // внутри -25...+25 растягиваем в два раза
   
    if (ChartMotor==true) tInChart[posChart]|=0x80;     // Признак включения мотора- старший бит в 1 - цвет фона на графике меняется
     ChartMotor=false;

     // Температура на улице. диапазон -25 . . . +25 растягиваем на 100 точек
     if (packet.tOut<=-2500) tOutChart[posChart]=0;         // Если температура меньше -25 то округляем до -25
     else  if (packet.tOut>=2500)  tOutChart[posChart]=100-1; // Если температура больше 25  то округляем до 25
      else tOutChart[posChart]=(packet.tOut+2500)/50;  // внутри -25...+25 растягиваем в два раза

    if (ChartHeat==true) tOutChart[posChart]|=0x80;     // Признак включения нагревателя- старший бит в 1 - цвет фона на графике меняется
     ChartHeat=false;

     // Абсолютная влажность в доме диапазон от 0 до 20 грамм на кубометр, растягиваем на 100 точек
     if (packet.absHIn>=2000) absHInChart[posChart]=100-1;
     else absHInChart[posChart]=packet.absHIn/20;   // внутри 0...20 растягиваем в пять  раз в сотых % по этому делем не на 100 а на 20

     // Абсолютная влажность на улицу диапазон от 0 до 20 грамм на кубометр, растягиваем на 100 точек
     if (packet.absHOut>=2000) absHOutChart[posChart]=100-1;
     else absHOutChart[posChart]=packet.absHOut/20;   // внутри 0...20 растягиваем в пять раз,  в сотых % по этому делем не на 100 а на 20
     
  if (infoScreen==false)                 // если отображен информационный экран то ничего не выводим
   {
   cli();  
   for(i=0;i<120;i++)    // График слева на право
     { 
     // Вычислить координаты текущей точки x в кольцевом буфере. Изменяются от 0 до 120-1
     if (posChart<i) x=120+posChart-i; else x=posChart-i;
    
     // нарисовать фон в зависимости от статуса мотора
     if  (tOutChart[x]>=0x80) ucg.setColor(90, 60, 0);     // Нагреватель был ключен - бледно желтый
     else if (tInChart[x]>=0x80) ucg.setColor(0, 60, 90);  // Мотор был ключен - бледно синий
     else                     ucg.setColor(0, 0, 0);       // все выключено
      
     ucg.drawVLine(5+120-i,237-100,100); 
     ucg.drawVLine(5+120-i+162,237-100,100);  
     
     ucg.setColor(180, 180, 180);  
     if (i%5==0) // Пунктирные линии графика
     {
       ucg.drawPixel(5+120-i,236-10-1);
       ucg.drawPixel(5+120-i,236-50-1);
       ucg.drawPixel(5+120-i,236-90-1);
       
       ucg.drawPixel(5+120-i+162,236-25-1);
       ucg.drawPixel(5+120-i+162,236-50-1);
       ucg.drawPixel(5+120-i+162,236-75-1);  
     } 
     
     // Вывести новую точку
     tmp=tInChart[x]&0x7f;   // Отбросить старший разряд - признак включения мотора
     if ((tmp==0)||(tmp==100))   ucg.setColor(255, 255, 255); else ucg.setColor(255, 100, 100); 
     ucg.drawPixel(5+120-i,236-tmp);

     tmp=tOutChart[x]&0x7f;   // Отбросить старший разряд - признак включения калорифера   
     if ((tmp==0) || (tmp==100)) ucg.setColor(255, 255, 255); else ucg.setColor(100, 255, 100); 
     ucg.drawPixel(5+120-i,236-tmp);
     
     if (absHInChart[x]==100) ucg.setColor(255, 255, 255); else ucg.setColor(255, 100, 100); 
     ucg.drawPixel(5+120-i+162,236-absHInChart[x]);
 
     if (absHOutChart[x]==100) ucg.setColor(255, 255, 255); else ucg.setColor(100, 255, 100); 
     ucg.drawPixel(5+120-i+162,236-absHOutChart[x]);
       }
     sei(); 
   } 
 if (posChart<120-1) posChart++; else posChart=0;            // Изменили положение в буфере и Замкнули буфер
} 

// ---ПЕРЕДАЧА ДАННЫХ ЧЕРЕЗ РАДИОМОДУЛЬ -----------------------------
#ifdef  RADIO    // Радио модуль NRF42l
void send_packet()
{       radio.powerUp();
        radio.stopListening();     // Остановить приемник
        delay(2);
    //    radio.openWritingPipe(0xF0F0F0F0E1LL);    // передатчик
        cli();
          //  send_packet_ok = radio.write(&packet,sizeof(packet));
          radio.writeBlocking(&packet,sizeof(packet),200);  // Writes 1 payload to the buffers
  	  send_packet_ok=radio.txStandBy();
        sei(); 
         #ifdef BEEP
           if (send_packet_ok==true) beep(40);           // Пакет передан успешно
         #endif
         #ifdef  DEBUG  
           if (send_packet_ok==true)  Serial.println(F("Packet sending ++++++++++"));
           else                       Serial.println(F("Packet NOT sending -----------"));
         #endif   
        radio.startListening();    // Включить приемник
        packet.count++;            // при переполнении сам сбросится
     //   if (packet.count>255) packet.count=0;
 }  
#endif 
// Чтение датчика возвращает код ошибки:
// DHTLIB_OK                   0
// DHTLIB_ERROR_CHECKSUM       1
// DHTLIB_ERROR_TIMEOUT        2
// DHTLIB_ERROR_CONNECT        3
// DHTLIB_ERROR_ACK_L          4
// DHTLIB_ERROR_ACK_H          5
byte readDHT(byte pin)
{
//delay(5);
cli();
  byte err=-1*DHT.read22(pin); // Чтение датчика
sei();  
return err; 
} 

// Измерение и обработка данных чтение датчиков --------------------------------------------------------------------------------
void measurement()
{ 
 myOS.pauseTask(measurement);        // Обязательно здесь, а то датчики плохо читаются мешает leos
 wdt_reset();                        // Сбросить сторожевой таймер
 
 packet.DHT_error=readDHT(PIN_DHT22a);   // ПЕРВЫЙ ДАТЧИК ДОМ  Новый пакет, сбросить все ошибки и прочитать первый датчик
 
 #ifdef  DEMO
   DHT.temperature=packet.tIn/100+random(-20,30)/10.0; 
   if (DHT.temperature>20) DHT.temperature=19;
   if (DHT.temperature<-10) DHT.temperature=-9;
   DHT.humidity=packet.relHIn/100+(float)random(-5,8);
   if (DHT.humidity>96) DHT.humidity=90;
   if (DHT.humidity<1) DHT.humidity=10;
   packet.DHT_error=0; // в Демо режиме
//   DHT.temperature=3.0;
//   DHT.humidity=21.0;
  #endif  
     sensors.tIn=(int)(DHT.temperature*100.0)+TIN_ERR;  // Запомнить результаты для суммирования с учетом ошибок
     sensors.relHIn=(int)(DHT.humidity*100.0)+HIN_ERR;  
     
    #ifdef  DEBUG  
       Serial.print(F("Sensor read samples:")); Serial.println(sensors.num); 
       Serial.print(F("IN T="));Serial.print(sensors.tIn);Serial.print(F(" H=")); Serial.print(sensors.relHIn); Serial.print(F(" error=")); Serial.println(packet.DHT_error);
    #endif
 
 packet.DHT_error=packet.DHT_error+16*readDHT(PIN_DHT22b);// ВТОРОЙ ДАТЧИК УЛИЦА  ошибки в старшие четыре бита
 #ifdef  DEMO
   DHT.temperature=packet.tOut/100+random(-20,30)/10.0; 
   if (DHT.temperature>30) DHT.temperature=19;
   if (DHT.temperature<-30) DHT.temperature=-9;
   DHT.humidity=packet.relHOut/100+(float)random(-5,8);
   if (DHT.humidity>96) DHT.humidity=90;
   if (DHT.humidity<1)  DHT.humidity=10;
   packet.DHT_error=0;      // в Демо режиме
// DHT.temperature=-10.0;
// DHT.humidity=40.0;
 #endif  
     sensors.tOut=(int)(DHT.temperature*100.0)+TOUT_ERR;  // Запомнить результаты для суммирования с учетом ошибок
     sensors.relHOut=(int)(DHT.humidity*100.0)+HOUT_ERR;
 
    #ifdef  DEBUG  
       Serial.print(F("OUT T="));Serial.print(sensors.tOut);Serial.print(F(" H=")); Serial.print(sensors.relHOut); Serial.print(F(" error=")); Serial.println(packet.DHT_error);
    #endif   
 
 print_error_DHT();    // Вывод ошибки чтения датчика при каждом чтении контроль за качеством связи с датчиком
 
 if (packet.DHT_error==0)// Если чтение без ошибок у ДВУХ датчиков  копим сумму для усреднения
  {
     sensors.sum_tIn=sensors.sum_tIn+sensors.tIn;
     sensors.sum_relHIn=sensors.sum_relHIn+sensors.relHIn;
     sensors.sum_tOut=sensors.sum_tOut+sensors.tOut;
     sensors.sum_relHOut=sensors.sum_relHOut+sensors.relHOut;
     sensors.num++;
   }
 
 // набрали в сумме нужное число отсчетов рассчитываем усреднение и выводим
 if (sensors.num>=NUM_SAMPLES)  // Пора усреднять и выводить значения 
 {
         resetKey();          // сброс кнопки почему то часто выскакивает длительное нажатие
        // вычисление средних значений
         packet.tIn=sensors.sum_tIn/NUM_SAMPLES;
         packet.relHIn=sensors.sum_relHIn/NUM_SAMPLES;
         packet.tOut=sensors.sum_tOut/NUM_SAMPLES;
         packet.relHOut=sensors.sum_relHOut/NUM_SAMPLES;
         reset_sum();       // Сброс счетчиков и сумм
         
         // вычисление абсолютной влажности
         packet.absHIn=(int)(calculationAbsH((float)(packet.tIn/100.0),(float)(packet.relHIn/100.0))*100.0);
         packet.absHOut=(int)(calculationAbsH((float)(packet.tOut/100.0),(float)(packet.relHOut/100.0))*100.0);
         
     #ifdef  DEBUG  
       Serial.println(F("Average value >>>>>>>>>>"));
       Serial.print(F("IN T="));Serial.print(packet.tIn);Serial.print(F(" H=")); Serial.print(packet.relHIn); Serial.print(F(" abs H=")); Serial.println(packet.absHIn);
       Serial.print(F("OUT T="));Serial.print(packet.tOut);Serial.print(F(" H=")); Serial.print(packet.relHOut); Serial.print(F(" abs H=")); Serial.println(packet.absHOut);
     #endif   
                     
         #ifdef  RADIO     // Радио модуль NRF42l 
            send_packet();   // Послать данные
         #endif
         CheckON();                                // Проверка статуса вентилятора
         print_data();                             // вывод усредненных значений 
         print_status();                           // панель состояния
         if (FLAG_MOTOR_CHECK) ChartMotor=true;    // Признак того что надо показывать включение мотора на графике
         if (FLAG_HEAT_CHECK) ChartHeat=true;      // Признак того что надо показывать включение нагревателя на графике 
         
         TimeChart++;
         if ((long)((long)TimeChart*TIME_SCAN_SENSOR*NUM_SAMPLES)>=(long)TIME_PRINT_CHART) // проврека не пора ли выводить график
            { printChart(); TimeChart=0; // Сдвиг графика и вывод новой точки
              #ifdef  DEBUG  
                 Serial.println(F("Point add chart ++++++++++++++++++++"));
              #endif  
              #ifdef BEEP
 //               beep(50);
              #endif
             } 
        }
    myOS.restartTask(measurement);     // Пустить задачи
}

// Функция переводит относительную влажность в абсолютную 
// t-температура в градусах Цельсия h-относительная влажность в процентах
float calculationAbsH(float t, float h)
{
 float temp;
 temp=pow(2.718281828,(17.67*t)/(t+243.5));
 return (6.112*temp*h*2.1674)/(273.15+t);
}

// Сканирование клавиш ------------------------------------------
void scanKey()
{  
    byte key;  
    cli(); 
    key=ReadPort(D,3);                                                // Прочитать кнопку 0 - нажата 
    if ((key==0)&&(flagKey==false))                                   // Если кнопка была нажата запомнить время и поставить флаг нажатия
    {
        flagKey=true;                                                 // Кнопка нажата  ждем обратного фронта
        time_key=millis();                                            // Время нажатия запомнили
     }
      
    if ((key==1)&&(flagKey==true))                                    // Если кнопка была отжата 
    {
         time_key=millis()-time_key;                                  // Рассчитать время нажатия
         pressKey=true;
    }
   sei();
 }

// Проверка статуса вытяжки, не пора ли переключится
void CheckON(void)
{
int tmp=0;
// 0.  Проверить замораживание подвала КАЛОРИФЕР 
if (packet.tIn<=TEMP_LOW) { MOTOR_OFF; HEAT_ON; return;}   // Контроль от промораживания подвала по идеи здесь надо включать калорифер
if ((FLAG_HEAT_CHECK)&&(packet.tIn>TEMP_LOW+dT_OFF)) HEAT_OFF;    // Выключить калорифер

// 1. Режимы не зависящие от влажности и температуры ВЫСШИЙ приоритет
if ((settingRAM.mode==BLOCK_OFF)&&(~FLAG_MOTOR_CHECK))  return;
if ((settingRAM.mode==BLOCK_OFF)&&(FLAG_MOTOR_CHECK))  { MOTOR_OFF ; return;}
if ((settingRAM.mode==HOOD_ON )&&(FLAG_MOTOR_CHECK))   return;
if ((settingRAM.mode==HOOD_ON )&&(~FLAG_MOTOR_CHECK))  { MOTOR_ON  ; return;}

// 2. Режим охлаждения (второй приоритет) температура внутри больше 10 градусов темература снаружи меньше на 2 градуса чем внутри, на влажность не смотрим
if (settingRAM.mode==COOLING)          // Режим охлаждение
  {
    if ((~FLAG_MOTOR_CHECK)&&(packet.tIn>(packet.T_min*10))&&((packet.tIn-packet.tOut)>packet.dH_min)) // dH_min используется не штатно для температуры
       {MOTOR_ON; return;}            // мотор выключен, температура выше установленной и снаружи температура ниже на 2 градуса  то ВКЛЮЧЕНИЕ мотора
    if ((FLAG_MOTOR_CHECK)&&(packet.tIn<=packet.tOut))   
       {MOTOR_OFF; return;}        // мотор включен и темература внутри ниже наружней то ВЫКЛЮЧЕННИЕ мотора
   return;                             // изменений нет выходим    
  } 
// 3. В режиме осушения - проверка на достижение минимальной температуры помещения в режиме осушения - СРОЧНО ВЫКЛЮЧИТЬ  третий приоритет
if (packet.tIn<=(packet.T_min*10))
   {
     if (~FLAG_MOTOR_CHECK)   return;      // Мотор уже выключен, выходим
     else  { MOTOR_OFF; return;}         // выключить и выйти
   } 
   
// 4. Режимы зависящие от температуры и влажности низший приоритет (что осталось)
// Расчитываем разность срабатывания по влажности
if (FLAG_ABS_H_CHECK)   tmp=packet.dH_min;   // Если режимы используют абсолютную разность
else tmp=(int)(packet.absHIn*packet.dH_min)/1000;  // Если режимы используют ДЕСЯТЫЕ % от абсолютной разности внутренней температуры 

 if ((~FLAG_MOTOR_CHECK)&&(packet.tIn>(packet.T_min*10))&&((packet.absHIn-tmp)>packet.absHOut)) 
        {MOTOR_ON; return;}        // мотор выключен, темература выше критической, абс влажность с наружи меньше  то ВКЛЮЧЕНИЕ мотора
 if ((FLAG_MOTOR_CHECK)&&((packet.tIn<=(tmp-dT_OFF))||(packet.absHIn<(packet.absHOut-dH_OFF)))) 
        {MOTOR_OFF; return;}       // мотор включен и темература ниже критической или абс влажность внутри ниже  то ВЫКЛЮЧЕННИЕ мотора
; 
} 
 

// Вывод информации о настройках и сохрание индекса настроек в eeprom ---------------------------------
void Setting()
{
 // Настройка
  cli();
  ucg.setColor(0, 100, 255);
  ucg.setFontMode(UCG_FONT_MODE_SOLID);
  ucg.setPrintPos(0,25+0+18); 
  switch (settingRAM.mode)
        {
        case  BLOCK_OFF: ucg.print(F("Выключено              ")); packet.dH_min=255; packet.T_min=255; FLAG_ABS_H_ON;  break; 
        case  HOOD_ON:   ucg.print(F("Режим вытяжки         "));  packet.dH_min=0;   packet.T_min=0;   FLAG_ABS_H_ON;  break; 
        case  COOLING:   ucg.print(F("Охлаждение T>10 dT>2"));    packet.dH_min=200; packet.T_min=100; FLAG_ABS_H_ON;  break; // dH_min используется не штатно для температуры     
        case  3:         ucg.print(F("Осушение T>+3 dH>0.2 "));   packet.dH_min=20;  packet.T_min=30;  FLAG_ABS_H_ON;  break;
        case  4:         ucg.print(F("Осушение T>+3 dH>5% "));    packet.dH_min=50;  packet.T_min=30;  FLAG_ABS_H_OFF; break;
        case  5:         ucg.print(F("Осушение T>+4 dH>0.3 "));   packet.dH_min=30;  packet.T_min=40;  FLAG_ABS_H_ON;  break;
        case  6:         ucg.print(F("Осушение T>+4 dH>8%  "));   packet.dH_min=80;  packet.T_min=30;  FLAG_ABS_H_OFF; break;
        case  7:         ucg.print(F("Осушение T>+5 dH>0.6 "));   packet.dH_min=30;  packet.T_min=50;  FLAG_ABS_H_ON;  break;
        } 
 writeEeprom();       // Запись в EEPROM  новых настроек
 SET_MODE_BITS;       // в пакет для передачи добавили смену режима
 MOTOR_OFF;           // Поменяли настройки - отключить мотор, пусть заново настройки сработают если потребуется
 CheckON();           // Возможно надо включить мотор
 print_status();      // Показать панель состояния (смена настроек)
 resetKey();
 sei();   
}

// Вывод float  с двумя десятичными знаком в координаты x y // для экономии места
void print_floatXY(int x,int y, float v)
{
 ucg.setPrintPos(x,y);
 ucg.print(v,2);
 ucg.print(F("  ")); // Стереть хвост от предыдущего числа
} 

// Вывод строки константы в координаты x y // для экономии места
void print_StrXY(int x,int y, const __FlashStringHelper* b)
{
 ucg.setPrintPos(x,y);
 ucg.print(b);
} 

void printInfo() // Окно с информацией о блоке, появляется при длительном нажатии на кнопку
{
  infoScreen=true;
  resetKey();
  cli();
  ucg.setColor(250, 250, 250);  // 
  ucg.drawBox(10, 10, 320-1-20, 240-1-20);
  ucg.setColor(0, 50, 250);
  ucg.drawFrame(10+5, 10+5, 320-1-20-10, 240-1-20-10);
  
  ucg.setFontMode(UCG_FONT_MODE_TRANSPARENT);
  ucg.setColor(0, 150, 10);
  print_StrXY(35,18+15,F("ОСУШИТЕЛЬ на Arduino Pro Mini"));
  
  ucg.setColor(0, 50, 50);
  print_StrXY(10+10,15+17*2,F("1 Напряжение питания В.")); 
  print_floatXY(10+230,15+17*2,readVcc()/1000.0);
 
  print_StrXY(10+10,15+17*3,F("2 Температура блока гр.")); 
  print_floatXY(10+230,15+17*3,GetTemp());
 
  print_StrXY(10+10,15+17*4,F("3 Свободная память байт")); 
  ucg.setPrintPos(10+230,15+17*4); 
  ucg.print(freeRam()); 
 
  print_StrXY(10+10,15+17*5,F("4 Мото часы блока")); 
  ucg.setPrintPos(10+230,15+17*5); 
  ucg.print(settingRAM.hour_unit/(TIME_HOUR/(NUM_SAMPLES*TIME_SCAN_SENSOR))); 
  
  print_StrXY(10+10,15+17*6,F("5 Мото часы вентилятора")); 
  ucg.setPrintPos(10+230,15+17*6); 
  ucg.print(settingRAM.hour_motor/(TIME_HOUR/(NUM_SAMPLES*TIME_SCAN_SENSOR))); 
  
  print_StrXY(10+10,15+17*7,F("6 Мото часы нагревателя")); 
  ucg.setPrintPos(10+230,15+17*7); 
  ucg.print(settingRAM.hour_heat/(TIME_HOUR/(NUM_SAMPLES*TIME_SCAN_SENSOR)));  
 
  print_StrXY(10+10,15+17*8,F("7 Канал NRF24l01+")); 
  ucg.setPrintPos(10+230,15+17*8); 
  ucg.print(NRF24_CHANEL); 
  
  print_StrXY(10+10,15+17*9,F("8 Гистерезис T/absH   ")); 
  ucg.print((float)dT_OFF/100.0);
  ucg.print(F("/"));
  ucg.print((float)dH_OFF/100.0);
  
  print_StrXY(10+10,15+17*10,F("9 ERR Т/Н in:")); 
  ucg.print(TIN_ERR); 
  ucg.print(F("/")); 
  ucg.print(HIN_ERR); 
  ucg.print(F(" out:"));
  ucg.print(TOUT_ERR); 
  ucg.print(F("/")); 
  ucg.print(HOUT_ERR); 
 
  ucg.setColor(0, 0, 150);
  print_StrXY(10+10,16+17*11,F("СБРОС - Вкл. при нажатой кнопке.")); 
  
  ucg.setColor(250,80,80);
  print_StrXY(10+10,20+21+18*10,F(VERSION)); 
  resetKey();
  sei();
  #ifdef BEEP
   beep(40);
  #endif
} 

void clearInfo()  // Стереть информационный экран
{
      infoScreen=false;
      resetKey();
      last_error=100;         // Признак обновления ошибки
      cli();
      ucg.setColor(0, 0, 0);  // залить черным
      ucg.drawBox(10, 10, 320-1-20, 240-1-20);
      print_static();
      Setting();  
      printChart();
      sei();
} 
// Чтение свободной памяти --------------------------------------------------------------------
int freeRam () {
  extern int __heap_start, *__brkval; 
  int v; 
  return (int) &v - (__brkval == 0 ? (int) &__heap_start : (int) __brkval); 
}
// Чтение внутреннего датчика температуры ---------------------------------------
double GetTemp(void)
{
  unsigned int wADC;
  double t;
  sei();  // Должны быть разрешены прерывания
  ADMUX = (_BV(REFS1) | _BV(REFS0) | _BV(MUX3));
  ADCSRA |= _BV(ADEN);  
  delay(20);           
  ADCSRA |= _BV(ADSC);  
  while (bit_is_set(ADCSRA,ADSC));
  wADC = ADCW;
  t = (wADC - 324.31 ) / 1.22;
  return (t); 
}
// Чтение напряжения питания ----------------------------------------------
long readVcc() {
  long result;
  // Read 1.1V reference against AVcc
  ADMUX = _BV(REFS0) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1);
  delay(2); // Wait for Vref to settle
  ADCSRA |= _BV(ADSC); // Convert
  while (bit_is_set(ADCSRA,ADSC));
  result = ADCL;
  result |= ADCH<<8;
  result = ConstADC / result; // Back-calculate AVcc in mV
  return result;
} 
// Запись счетчиков в Eeprom --------------------------------------------------
void writeEeprom()
{ 
cli(); 
  eeprom_write_block((const void*)&settingRAM, (void*) &settingEEPROM, sizeof(settingRAM)); 
sei();
}
// Чтение счетчиков из Eeprom --------------------------------------------------
void readEeprom()
{
cli(); 
   eeprom_read_block((void*)&settingRAM, (const void*) &settingEEPROM, sizeof(settingRAM)); 
   if ((settingRAM.mode>(NUM_SETTING-1))||(settingRAM.mode<0)) settingRAM.mode=0;        // гарантированно попадаем в диапазон
sei();
}

void reset_sum()  // Сброс счетчиков накоплений
{
sensors.num=0;  // Рассчитать величину усреднения
sensors.sum_tOut=0;
sensors.sum_tIn=0;
sensors.sum_relHOut=0;
sensors.sum_relHIn=0;
}

char hex(byte x)  // Функция для вывода в hex
{
   if(x >= 0 && x <= 9 ) return (char)(x + '0');
   else      return (char)('a'+x-10);
}

bool reset_ili9341(void)
{
  pinMode(PIN_RESET, OUTPUT);                    // Сброс дисплея сигнал активным является LOW
  digitalWrite(PIN_RESET, LOW);  
  delay(100);
  digitalWrite(PIN_RESET, HIGH);  
  // Дисплей
  ucg.begin(UCG_FONT_MODE_TRANSPARENT);
  ucg.setFont(my14x10rus);  
//   ucg.setRotate90();
  ucg.setRotate270();
  ucg.clearScreen();
}

#ifdef BEEP  
  void beep(int x)  // Пищать х мсек
  {
    WritePort(C,1,HIGH);
    delay(x);
    WritePort(C,1,LOW);
  } 
#endif

Одновременно делаю код приемника. Пока получается делать одну версию под avr и stm32 (условная компиляция). Приемник будет принимать данные от разных (не однотипных, тут пригодится ID) устройств. Планируется как минимум два осушителя (у меня два подвала), контроль за тепловым насосом отопления, и 2-3 удаленных термометра-гигрометра с автономным питанием. Переключение между устройствами будет происходить либо по энкодеру либо по кнопке (пока не решил).

2. По поводу заслонки. Я тоже думал об управляемой заслонке (подключение параллельно вентилятору). Но потом решил что хватит обратного клапана, входного давления (без вытяжного вентилятора) не хватит для открытия клапана. По этому "паразитная" вытяжка будет стремится к 0. Решение уже реализовано.

3. Сейчас погружаюсь в дебри измерения относительной влажности. Датчик DHT22 конечно лучше чем DHТ11 но его погрешность все равно очень большая.  Его надо как минимум калибровать, а лучше  заменить на что то более точное типа SHT75 (это дорого) или SHT21 (минус - фиксированный i2c  адрес т.е. на одной шине один датчик).

 

miaua
Offline
Зарегистрирован: 20.08.2015

Опробовал 0,64 версию на УНО . Пока нет датчиков, в режиме демо и с отключенным радио. Инвертировал, по Вашему совету, работу реле.

Вроде работает как надо. Но есть несоответствие с индикацией ...Реле ВЫКЛ. - Индикация  ВКЛ. Пока не разобрался где логика индикации.

А нет ли возможности прописать индикацию что-бы отображалось 2 квадратика рядом : 1й- Синий вентилятор, 2й -красный калорифер ? Место там вроде есть. Т.к. бывают одновременные включения...

Ну и злая кнопка... режимы переключаются не четко, но более или менее .. А вот экран статистики вызвать удалось только один раз... Держал и 3сек и 10 сек без разницы... Сброс по включению работает четко...

Мжет это проблема дребезга контактов?

Насчет заслонки- я имел ввиду сокращение общего потребления системы за счет природной вентиляции. Т.е. заслонкой можно управлять пошагово и если потока не достаточно -включать вентилятор. Но это еще куча кода.. (на возможную перспективу)...

По поводу  DHT. Гдето читал, что основная погрешность именно из-за температурного , встроенного датчика.

Видимо поэтому в регуляторе влажности wk8140t в паре с  DHT21 стоит DS18B20. А в инструкции так и написано - для повышения точности измерения.

Вот внутри AM2303 стоит DS18B20.. По даташиту..

 

bwn
Offline
Зарегистрирован: 25.08.2014

Опыт по DHT. Использую AM2301, что в целом аналог. Функции контроллера аналогичны, вместо подвала, термоизолированное помещение для хранения продуктов. Забор воздуха уличный снизу, вытяжка принудительная вверху, подогрев - четыре прожектора по 150 ватт с диммированием. Сделано усреднение температуры по высоте - забор воздуха из под потолка и подача через трубы к полу, гистерезис 3градуса.

Датчик поверял по NaCl, для нижних диапазонов ничего не было под рукой. За год эксплуатации, расхождение с контрольным прибором составляло 2-3% по отн.вл. Соответственно, на медленноидущих процессах вещь достаточно точная, не любит сквозняков и принудительного осаждения влаги (типа подышать). Если выбрать подходящее место, вполне можно юзать. ИМХО.

miaua
Offline
Зарегистрирован: 20.08.2015

Я решил, что принудительный наддув,  из нижней трубы, предпочтительнее.

1. Т.к. таким образом достигается получение более сухого воздуха и с меньшим содержанием пыли.

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

2. Вентилятор довольно эффективно перемешивает воздух в помещении, тем самым устраняет необходимость усреднять температуру по высоте.

Недостаток - необходимо искать место для датчика, что бы не попадал в поток воздуха ..Таких мест много.

Из моего опыта- вентилятора VA-9 (9Вт), от болгарского дисковода на 29мб 1983г. , вполне достаточно для помещения 10м2 с высотой 2,5м .

 

pav2000
Offline
Зарегистрирован: 15.12.2014

miaua пишет:

Опробовал 0,64 версию на УНО . Пока нет датчиков, в режиме демо и с отключенным радио. Инвертировал, по Вашему совету, работу реле.

Вроде работает как надо. Но есть несоответствие с индикацией ...Реле ВЫКЛ. - Индикация  ВКЛ. Пока не разобрался где логика индикации.

А нет ли возможности прописать индикацию что-бы отображалось 2 квадратика рядом : 1й- Синий вентилятор, 2й -красный калорифер ? Место там вроде есть. Т.к. бывают одновременные включения...

Ну и злая кнопка... режимы переключаются не четко, но более или менее .. А вот экран статистики вызвать удалось только один раз... Держал и 3сек и 10 сек без разницы... Сброс по включению работает четко...

Мжет это проблема дребезга контактов?

Насчет заслонки- я имел ввиду сокращение общего потребления системы за счет природной вентиляции. Т.е. заслонкой можно управлять пошагово и если потока не достаточно -включать вентилятор. Но это еще куча кода.. (на возможную перспективу)...

По поводу  DHT. Гдето читал, что основная погрешность именно из-за температурного , встроенного датчика.

1. У меня индикация соответствует (значек и вентилятор работают синхронно). Может по факту у Вас реле не с инверсным выходом (поменять обратно как было код). Это единственное что мне приходит в голову.

2. По поводу двух квадратиков - можно но ИХМО не нужно. т.к. одновременое они гореть не будут. Нагреватель включается при температуре ниже +1.5 а вентилятор включается при температуре не ниже +3.0. Поэтому одного индикатора достаточно.

3. Кнопка - основная проблема дребезг контактов. Тут от кнопки много зависит. Можно RC цепь поставить 200 ом и 0.1 мкф между кнопкой и ножкой контроллера, должно помочь (резистор последовательно, конденсатор на ножку и землю). В очередной раз "оптимизировал код", вроде дребезга стало меньше.

#pragma GCC optimize ("-Os")
//#pragma pack(push, 1)     // выравнивание по одному байту ????????

#include <SPI.h>
#include <EEPROM.h>
#include <avr/wdt.h>
#include <leOS.h>      // Шедуллер задач
#include <dht.h>       // Дачик влажности и температуры
#include "Ucglib.h"    // ВНИМАНИЕ использовать библиотеку не познее 1.01 справка https://code.google.com/p/ucglib/wiki/
#include "rusFont.h"   // Русские шрифты
#include "nRF24L01.h"  // Беcпроводной модуль надо использовать библиотеку http://tmrh20.github.io/RF24
#include "RF24.h"      // Беcпроводной модуль используются не стандартные функции https://github.com/TMRh20

// - ОПЦИИ -------------------------------
//#define DEBUG                                   // Отладочную  информацию в ком порт посылает  
//#define DEMO                                    // Признак демонстрации - данные с датчиков генерятся рандом
#define BEEP                                      // Использовать пищалку
#define RADIO                                     // Признак использования радио модуля
#define VERSION "Ver. 0.66 11/09/15"              // Текущая версия
#define ID             0x21                       // уникально Идентификатор устройства - старшие 4 бита, вторые (младшие) 4 бита серийный номер устройства
#define LABEL "Подпол"  // "Подпол" "Подвал"      // Место установки блока - отоображается как заголовок колонки в таблице. Не более 6 символов а то не влезает

// Макросы для работы с портами  скорость и место
#define SetOutput(port,bit)       DDR ## port |= _BV(bit)
#define SetInput(port,bit)        DDR ## port &= ~_BV(bit)
#define SetBit(port,bit)          PORT ## port |= _BV(bit)
#define ClearBit(port,bit)        PORT ## port &= ~_BV(bit)
#define WritePort(port,bit,value) PORT ## port = (PORT ## port & ~_BV(bit)) | ((value & 1) << bit)
#define ReadPort(port,bit)        (PIN ## port >> bit) & 1
#define PullUp(port,bit)          { SetInput(port,bit); SetBit(port,bit); }
#define Release(port,bit)         { SetInput(port,bit); ClearBit(port,bit); }
// Мои макросы
#define MOTOR_BIT                 0            // бит мотора в packet.flags
#define HEAT_BIT                  1            // бит калорифера в packet.flags
#define ABS_H_BIT                 2            // бит калорифера в packet.flags
#define MODE_BIT                  5            // первый бит режима в packet.flags
//#define MASK_MODE_BITS            0xe0         // маска для выделения номера режима в packet.flags
#define SET_MODE_BITS             packet.flags |= (settingRAM.mode<<MODE_BIT)  // запись номера режима в packet.flags
#define FLAG_ABS_H_ON             packet.flags |= (1<<ABS_H_BIT)   // бит ABS_H_BIT установить в 1
#define FLAG_ABS_H_OFF            packet.flags &= ~(1<<ABS_H_BIT)  // бит ABS_H_BIT установить в 0
#define FLAG_ABS_H_CHECK          packet.flags & (1<<ABS_H_BIT)    // бит ABS_H_BIT проверить на 1

#define FLAG_MOTOR_ON             packet.flags |= (1<<MOTOR_BIT)   // бит мотора установить в 1
#define FLAG_MOTOR_OFF            packet.flags &= ~(1<<MOTOR_BIT)  // бит мотора установить в 0
#define FLAG_MOTOR_CHECK          packet.flags & (1<<MOTOR_BIT)    // бит мотора проверить на 1
#define MOTOR_ON                  { WritePort(C,0,HIGH); FLAG_MOTOR_ON;  }   // включить мотор
#define MOTOR_OFF                 { WritePort(C,0,LOW) ; FLAG_MOTOR_OFF; }   // выключить мотор

#define FLAG_HEAT_ON              packet.flags |= (1<<HEAT_BIT)   // бит калорифера установить в 1
#define FLAG_HEAT_OFF             packet.flags &= ~(1<<HEAT_BIT)  // бит калорифера установить в 0
#define FLAG_HEAT_CHECK           packet.flags & (1<<HEAT_BIT)    // бит калорифера проверить на 1
#define HEAT_ON                   { WritePort(C,2,HIGH); FLAG_HEAT_ON;  }   // включить калорифер
#define HEAT_OFF                  { WritePort(C,2,LOW); FLAG_HEAT_OFF; }   // выключить калорифер


// - КОНСТАНТЫ --------------------------------------
#define dH_OFF          10                      // Гистерезис абсолютной влажности в сотых грамма на куб
#define dT_OFF          10                      // Гистерезис температуры в сотых градуса
#define TEMP_LOW       150                      // Температура подвала критическая - может быть замораживание (в сотых градуса) - система выключается и включается нагреватель
// СИСТЕМАТИЧЕСКИЕ ОШИБКИ ДАТЧИКОВ для каждого ID свой. ОШИБКИ ДОБАВЛЯЮТСЯ!!
#if ID == 0x21
    #define TOUT_ERR      -40                  // Ошибка уличного датчика температуры в сотых долях градуса
    #define TIN_ERR       -40                  // Ошибка домового датчика температуры в сотых долях градуса
    #define HOUT_ERR      -30                  // Ошибка уличного датчика влажности в сотых долях %
    #define HIN_ERR       +30                  // Ошибка домового датчика влажности в сотых долях %
#elif ID == 0x22
    #define TOUT_ERR      -12                  // Ошибка уличного датчика температуры в сотых долях градуса
    #define TIN_ERR       -10                  // Ошибка домового датчика температуры в сотых долях градуса
    #define HOUT_ERR      -250                 // Ошибка уличного датчика влажности в сотых долях %
    #define HIN_ERR       +250                 // Ошибка домового датчика влажности в сотых долях %
#else 
    #define TOUT_ERR      0                    // Ошибка уличного датчика температуры в сотых долях градуса
    #define TIN_ERR       0                    // Ошибка домового датчика температуры в сотых долях градуса
    #define HOUT_ERR      0                    // Ошибка уличного датчика влажности в сотых долях %
    #define HIN_ERR       0                    // Ошибка домового датчика влажности в сотых долях %
#endif

// - ВРЕМЕНА ---------------------------------------
#ifdef DEMO                                     // Для демо все быстрее и случайным образом
    #define NUM_SAMPLES      2                  // Число усреднений измерений датчика
    #define TIME_SCAN_SENSOR 1000               // Время опроса датчиков мсек, для демки быстрее
    #define TIME_PRINT_CHART 4000               // Время вывода точки графика мсек, для демки быстрее
    #define TIME_HOUR        50000              // Число мсек в часе, для демки быстрее   
#else   
   #define NUM_SAMPLES      10                  // Число усреднений измерений датчика
   #define TIME_SCAN_SENSOR 3000                // Время опроса датчиков мсек
   #define TIME_PRINT_CHART 300000              // Время вывода точки графика мсек
   #define TIME_HOUR        3600000             // Число мсек в часе
#endif

#define LONG_KEY      2000                      // Длительное нажатие кнопки мсек, появляется Экран инфо
#define SHORT_KEY     100                       // Короткое нажатие кнопки более мсек
#define NRF24_CHANEL  100                       // Номер канала nrf24

//  НОГИ к которым прицеплена переферия (SPI используется для TFT и NRF24 - 11,12,13)
#define PIN_HEAT      16                         // Ножка куда повешен калорифер A2 (port C2)
#ifdef BEEP
    #define PIN_BEEP  15                         // Ножка куда повешена пищалка A1 (port C1)
#endif
#define PIN_RELAY     14                         // Ножка на которую повешено реле (SSR) вентилятора - аналоговый вход A0 через резистор 470 ом (port C0)
#define PIN_CS        10                         // TFT дисплей spi
#define PIN_CD        9                          // TFT дисплей spi
#define PIN_RESET     8                          // TFT дисплей spi
#define PIN_CE        7                          // nrf24 ce
#define PIN_CSN       6                          // nrf24 cs
#define PIN_DHT22a    5                          // Первый датчик DHT22   IN  ДОМ
#define PIN_DHT22b    4                          // Второй датчик DHT22   OUT УЛИЦА
#define PIN_KEY       3                          // Кнопка, повешена на прерывание, что бы ресурсов не тратить (port D3)
#define PIN_IRQ_NRF24 2                          // Ножка куда заведено прерывание от NRF24 (пока не используется)

// Настройки
#define NUM_SETTING    7                        // Число вариантов настроек 
#define BLOCK_OFF      0                        // Выключено (вариант настроек)
#define HOOD_ON        1                        // Режим вытяжки (вариант настроек)
#define COOLING        2                        // Режим охлаждение (вариант настроек)

// АЦП ----------------------------------------
const long ConstADC=1126400;                    // Калибровка встроенного АЦП (встроенный ИОН) по умолчанию 1126400 дальше измеряем питание и смотрим на дисплей    

Ucglib_ILI9341_18x240x320_HWSPI ucg(/*cd=*/PIN_CD, /*cs=*/PIN_CS, /*reset=*/PIN_RESET); // Аппаратный SPI на дисплей ILI9341
leOS myOS;                                      // многозадачность
dht DHT;                                        // Датчики
bool infoScreen=false;                          // Признак отображениея иформационного экрана  1 - на экран ничего не выводится кроме информационного экрана
bool flagKey=false;                             // Флаг нажатия клавиши
bool pressKey=false;                            // Флаг необходимости обработки кнопки
unsigned long time_key=0;                       // Время нажатия копки
byte last_error=100;                            // Предыдущая ошибка чтения датчиков

 struct type_setting_eeprom                     // Структура для сохранения данных в eeprom
 {
     byte mode = 0;                             // Какой режим работы блока
     unsigned long hour_unit=0;                 // мото часы блок измеряется в интервалах вывода данных = NUM_SAMPLES*TIME_SCAN_SENSOR
     unsigned long hour_motor=0;                // мото часы мотора измеряется в интервалах вывода данных = NUM_SAMPLES*TIME_SCAN_SENSOR
     unsigned long hour_heat=0;                 // мото часы нагревателя измеряется в интервалах вывода данных = NUM_SAMPLES*TIME_SCAN_SENSOR
  }; 
volatile type_setting_eeprom settingRAM;        // Рабочая копия счетчиков в памяти
type_setting_eeprom settingEEPROM EEMEM;        // Копия счетчиков в eeprom - туда пишем 

// Пакет передаваемый, используется также для хранения результатов. 
 struct type_packet_NRF24   // Версия 2.4!! адаптация для stm32 Структура передаваемого пакета 32 байта - 32 максимум
    {
        byte id=ID;                             // Идентификатор типа устройства - старшие 4 бита, вторые (младшие) 4 бита серийный номер устройства
        byte DHT_error;                         // Ошибка разряды: 0-1 первый датчик (00-ок) 2-3 второй датчик (00-ок) 4 - радиоканал     
        int16_t   tOut=-500,tIn=500;            // Текущие температуры в сотых градуса !!! место экономим
        uint16_t  absHOut=123,absHIn=123;       // Абсолютные влажности в сотых грамма на м*3 !!! место экономим
        uint16_t  relHOut=123,relHIn=123;       // Относительные влажности сотых процента !!! место экономим
        uint8_t   flags=0x00;                   // байт флагов  
                                                // 0 бит - мотор включен/выключен 
                                                // 1 бит - нагреватель включен/выключен
                                                // 2 бит -[1 - dH_min задается в сотых грамма на м*3] [0 - dH_min задается в ДЕСЯТЫХ процента от absHIn]
                                                // 3 4 - пока пусто
                                                // 5-7 - номер настройки = settingRAM.mode до 8 настроек, надо передавать, что бы на приемнике восстановить
        uint8_t   dH_min;                       // Порог включения вентилятора по РАЗНИЦЕ абсолютной влажности в сотых грамма на м*3 или в ДЕСЯТЫХ % см flags:2
        uint8_t   T_min;                        // Порог выключения вентилятора по температуре в ДЕСЯТЫХ долях градуса, только положительные значения
        uint8_t   count=0;                      // циклический счетчик отправленных пакетов нужен что бы на приемнике проверять качество связи
        char note[14] = LABEL;                  // Примечание не более 13 байт + "0" байт Русские буквы в два раза меньше т.к. UTF-8
    } packet; 
    
struct type_sensors                             // структура для усреднения измерений
{
       int  num=0;                              // сколько отсчетов уже сложили не болле NUM_SAMPLES
       long  sum_tOut=0,sum_tIn=0;              // Сумма для усреднения Текущие температуры в сотых градуса !!! место экономим
       long  sum_relHOut=0,sum_relHIn=0;        // Сумма для усреднения Относительные влажности сотых процента !!! место экономим
       int  tOut=-5000,tIn=5000;                // Текущие температуры в сотых градуса !!! место экономим
       int  absHOut=55555,absHIn=55555;         // Абсолютные влажности в сотых грамма на м*3 !!! место экономим
       int  relHOut=55555,relHIn=55555;         // Относительные влажности сотых процента !!! место экономим
} sensors;
    
// Массивы для графиков
uint8_t tOutChart[120];
uint8_t tInChart[120];
uint8_t absHOutChart[120];
uint8_t absHInChart[120];
uint8_t posChart=0;       // Позиция в массиве графиков - начало вывода от 0 до 120-1
uint8_t TimeChart=0;      // Время до вывода очередной точки на график. 
bool ChartMotor=false;    // Признак работы мотора во время интервала графика если мотор был включен на любое время (даже одно измерение) то на графике фон меняется в этом месте
bool ChartHeat=false;     // Признак работы нагревателя во время интервала графика если нагреватель был включен на любое время (даже одно измерение) то на графике фон меняется в этом месте

#ifdef  RADIO    // Радио модуль NRF42l  
   RF24 radio(PIN_CE, PIN_CSN);  // определение управляющих ног
   bool send_packet_ok=false;    // признак удачной отправки последнего пакета 
   unsigned long err_ask=0;      // число не полученных ответов
#endif 
///////////////////////////////////////////////////////////////////////////////////////////////////////////
// ПРОГРАММА
///////////////////////////////////////////////////////////////////////////////////////////////////////////
void setup(){
#ifdef  DEBUG  
   Serial.begin(9600); 
   Serial.println(F("DEBUG MODE")); 
   #ifdef  BEEP  
     Serial.println(F("BEEP ON")); 
   #else         
     Serial.println(F("BEEP OFF"));
   #endif
   #ifdef  RADIO 
     Serial.println(F("RADIO ON")); 
   #else         
     Serial.println(F("RADIO OFF"));
   #endif
#endif

reset_sum();

#ifdef  RADIO    // Радио модуль NRF42l  первичная настройка
   radio.begin();
   radio.setDataRate(RF24_250KBPS);         //  выбор скорости RF24_250KBPS RF24_1MBPS RF24_2MBPS
   radio.setPALevel(RF24_PA_MAX);           // выходная мощность передатчика
   radio.setChannel(NRF24_CHANEL);          //тут установка канала
   radio.setCRCLength(RF24_CRC_16);         // использовать контрольную сумму в 16 бит
   radio.setAutoAck(true);                  // выключить аппаратное потверждение
// radio.enableDynamicPayloads();           // разрешить Dynamic Payloads
// radio.enableAckPayload();                // разрешить AckPayload
  radio.setRetries(50,10);                  // Количество пауза и количество повторов 
  radio.openWritingPipe(0xF0F0F0F0E1LL);    // передатчик
  radio.openReadingPipe(1,0xF0F0F0F0D2LL);  // приемник
  radio.startListening();
#endif
  #ifdef BEEP
     SetOutput(C,1);                      //  Настройка ноги для динамика
     WritePort(C,1,LOW);
  #endif
  SetInput(D,3);                        //  Включена кнопка
  WritePort(D,3,HIGH);
  SetOutput(C,0);                       //  Подключить Реле
  WritePort(C,0,LOW);
  SetOutput(C,2);                       //  Подключить Калорифер
  WritePort(C,2,LOW);
  
  pinMode(PIN_DHT22a, OUTPUT);          //  Датчик DHT22 #1 
  digitalWrite(PIN_DHT22a, HIGH);  
  pinMode(PIN_DHT22b, OUTPUT);          //  Датчик DHT22 #2
  digitalWrite(PIN_DHT22b, HIGH);  
  
  reset_ili9341();                      // сброс дисплея
  readEeprom();                         // Прочитать настройки 
  byte i=ReadPort(D,3);
  if (i==0)                  // Если при включении нажата кнопка то стираем Eeprom
  {   settingRAM.mode=0;
      settingRAM.hour_unit=0;
      settingRAM.hour_motor=0;
      settingRAM.hour_heat=0;
      ucg.setColor(255, 255, 255);
      print_StrXY(10,50,F("Сброс настроек и счетчиков"));
      for(i=0;i<3;i++)
        {
         delay(1000);
         ucg.print(F(" ."));
        }
      writeEeprom();       // Запись в EEPROM  
      delay(1000);
      ucg.clearScreen();
  } 
//  settingRAM.mode=4; 
  wdt_enable(WDTO_8S); // Сторожевой таймер Для тестов не рекомендуется устанавливать значение менее 8 сек.
  // Запуск задач по таймеру
  myOS.begin();
  myOS.addTask(measurement,TIME_SCAN_SENSOR);    // Измерение 
  attachInterrupt(1, scanKey, CHANGE);           // КНОПКА Прерывания по обоим фронтам
  print_static();                                // распечатать таблицу
  Setting();                                     // Применить настройки
//  printChart();
  measurement();                                 // Считать данные
  #ifdef BEEP
     beep(100);
     delay(300);
     beep(500);
  #endif
resetKey();
}

void loop()
{
 wdt_reset();                        // Сбросить сторожевой таймер
// Обработка нажатия кнопки
if (pressKey==true)                 // Кнопка была нажата и отпущена
{
// myOS.pauseTask(measurement);                                 // Остановить задачи
 if (flagKey!=false)                                          // Обработчик кнопки
 if ((time_key>=LONG_KEY)&&(infoScreen!=true))                // Длительное нажатие кнопки и информационный экран не показан
   printInfo();                                               // Вывод информационного экрана
 else 
   if (time_key >= SHORT_KEY)                                  // Короткое нажатие кнопки все остальное игнорируется
     { 
        #ifdef BEEP
          beep(30);                                            // Звук нажатия на клавишу
        #endif
       if (infoScreen==true)  clearInfo();                    // если информационный экран показан то стереть его
       else {if (settingRAM.mode >= NUM_SETTING) settingRAM.mode=0; //  Кольцевой счетчик настроек
               else settingRAM.mode++;                      // В противном случае следующая настройка
               Setting(); }  
     }
  resetKey();                                                //  Сброс состояния кнопки
//  myOS.restartTask(measurement);                             // Запустить задачи
  }
}

void resetKey(void)  // Сброс состяния кнопки
{
  flagKey=false;                                             
// time_key=millis();                                                
  pressKey=false;
}  
void print_static()  // Печать статической картинки 
{  
  int i;
   cli();
  // Заголовок 
  ucg.setColor(0, 0, 180);  // 
  ucg.drawBox(0, 0, 320-1, 23);
  ucg.setColor(250, 250, 250);
  ucg.setFontMode(UCG_FONT_MODE_TRANSPARENT);
  print_StrXY(2,19,F("ОСУШИТЕЛЬ ID: 0x"));
  ucg.print( hex(packet.id >> 4));
  ucg.print( hex(packet.id&0x0f));
   
  #ifdef DEMO
    ucg.print(F(" demo"));
  #endif
  
  // Таблица для данных
  ucg.setColor(0, 200, 0);
  ucg.drawHLine(0,25,320-1);
  ucg.drawHLine(0,25+23*1,320-1);
  ucg.drawHLine(0,25+23*2,320-1);
  ucg.drawHLine(0,25+23*3,320-1);
  ucg.drawHLine(0,25+23*4,320-1);
  ucg.drawVLine(200-4,25,24+23*3);
  ucg.drawVLine(260,25,24+23*3);

  // Заголовки таблиц
  ucg.setColor(255, 255, 0);
  // В зависимости от id разные надписи - привязка местоположения блока к ID
  
//  print_StrXY(180+30-9,25+0+18,F(LABEL));
  ucg.setPrintPos(180+30-9,25+0+18); 
  ucg.print(packet.note); 
  
  print_StrXY(250+20,25+0+18,F("Улица"));
  print_StrXY(0,25+23*1+18,F("Температура градусы C")); 
  print_StrXY(0,25+23*2+18,F("Относительная влаж. %")); 
  print_StrXY(0,25+23*3+18,F("Абсолют. влаж. г/м*3")); 

  // Графики
  ucg.setColor(210, 210, 210);
//  ucg.drawHLine(1,240-2,130);
//  ucg.drawVLine(1,135,105);
//  ucg.drawHLine(10+154,240-2,130);
//  ucg.drawVLine(10+154,135,105);
  print_StrXY(10,135+0,F("Температура")); 
  print_StrXY(20+154,135+0,F("Абс. влажность")); 
   
  // надписи на графиках
  print_StrXY(128,154,F("+20")); 
  print_StrXY(135,194,F("0")); 
  print_StrXY(128,233,F("-20"));
  
  print_StrXY(296,164,F("15"));
  print_StrXY(296,194,F("10"));
  print_StrXY(296,223,F("5"));
  
  // Горизонтальная шкала по часам
//  ucg.setColor(255, 255, 0);
  for(i=0;i<=120;i=i+12)
    {
      ucg.drawPixel(4+i,239);
      ucg.drawPixel(4+i,238);
      ucg.drawPixel(167+i,239);
      ucg.drawPixel(167+i,238);
     } 
   sei();
}

void print_status(void) // Печать панели статуса Значки на статус панели
{
  if (infoScreen==true) return;        // если отображен информационный экран то ничего не выводим  
   cli();
 // 1. печать ошибки чтения датчиков
   print_error_DHT();
 // 2. Признак включения мотора или нагревателя
  if (FLAG_MOTOR_CHECK)         ucg.setColor(0, 240, 0); 
  else if (FLAG_HEAT_CHECK)     ucg.setColor(240, 0, 0);
  else                          ucg.setColor(0, 40, 0);
  ucg.drawBox(290-32, 5, 14, 14);
  
  #ifdef  RADIO 
  // 3. Признак удачной передачи информации по радиоканалу - получение квитанции
      if (send_packet_ok==true)  ucg.setColor(0, 240, 0); 
      else                       ucg.setColor(0, 40, 0);
      ucg.setPrintDir(3);
      ucg.setFontMode(UCG_FONT_MODE_TRANSPARENT);
      print_StrXY(290-40,20,F(">>")); 
      ucg.setPrintDir(0);
      ucg.setFontMode(UCG_FONT_MODE_SOLID);
  #endif
  sei();
}  

void print_error_DHT(void) // Печать ошибки чтения датчиков выводится при каждом чтении датчика
{
  if (infoScreen==true) return;        // если отображен информационный экран то ничего не выводим  
 // 1. печать ошибки чтения датчиков
  if (packet.DHT_error!=last_error)        // если статус ошибки поменялся то надо вывести если нет то не выводим - экономия время и нет мерцания
  {
      cli();
      last_error=packet.DHT_error; 
      ucg.setColor(0, 0, 180);         // Сначала стереть
      ucg.drawBox(290, 0, 26, 18);
      ucg.setPrintPos(280,18); 
      ucg.setFontMode(UCG_FONT_MODE_TRANSPARENT);
      if (packet.DHT_error>0) 
        { 
        ucg.setColor(255, 100, 100); 
        print_StrXY(280,19,F("0x"));
        ucg.print( hex(packet.DHT_error >> 4));
        ucg.print( hex(packet.DHT_error & 0x0f)); 
        }
      else  { ucg.setColor(200, 240, 0);   ucg.print(F(" ok ")); } 
     sei(); 
   }   
} 
//  вывод на экран данных (то что меняется)
void print_data()
{ 
  // Статистика по моточасам, время ведется в интервалах вывода данных = NUM_SAMPLES*TIME_SCAN_SENSOR а потом пересчитывается в часы при выводе.
settingRAM.hour_unit++;
if (FLAG_MOTOR_CHECK) settingRAM.hour_motor++;  // если мотор включен
if (FLAG_HEAT_CHECK) settingRAM.hour_heat++;    // если нагреватель включен

 if (infoScreen==true) return;                  // если отображен информационный экран то ничего не выводим  
  cli();
 // Печать значений 
  ucg.setFontMode(UCG_FONT_MODE_SOLID);
  ucg.setColor(250, 0, 100);  // Цвет ДОМА
  print_floatXY(200+0,25+23*1+18,((float)packet.tIn)/100);
  print_floatXY(200+0,25+23*2+18,((float)packet.relHIn)/100);
  print_floatXY(200+0,25+23*3+18,((float)packet.absHIn)/100);
  ucg.setColor(0, 250, 100);  // Цвет УЛИЦЫ
  print_floatXY(260+4,25+23*1+18,((float)packet.tOut)/100);
  print_floatXY(260+6,25+23*2+18,((float)packet.relHOut)/100);
  print_floatXY(260+6,25+23*3+18,((float)packet.absHOut)/100);
  sei();
}  

// Печать графика на экране, добавляется одна точка и график сдвигается 
void printChart() 
{
byte i,x=0;
uint8_t tmp;

// Работаем через кольцевой буфер
// Добавить новую точку в кольцевой буфер
     // Температура в доме. диапазон -25 . . . +25 растягиваем на 100 точек
     if (packet.tIn<=-2500) tInChart[posChart]=0;           // Если температура меньше -25 то округляем до -25
     else  if (packet.tIn>=2500)  tInChart[posChart]=100-1;   // Если температура больше 25  то округляем до 25
      else tInChart[posChart]=(packet.tIn+2500)/50;    // внутри -25...+25 растягиваем в два раза
   
    if (ChartMotor==true) tInChart[posChart]|=0x80;     // Признак включения мотора- старший бит в 1 - цвет фона на графике меняется
     ChartMotor=false;

     // Температура на улице. диапазон -25 . . . +25 растягиваем на 100 точек
     if (packet.tOut<=-2500) tOutChart[posChart]=0;         // Если температура меньше -25 то округляем до -25
     else  if (packet.tOut>=2500)  tOutChart[posChart]=100-1; // Если температура больше 25  то округляем до 25
      else tOutChart[posChart]=(packet.tOut+2500)/50;  // внутри -25...+25 растягиваем в два раза

    if (ChartHeat==true) tOutChart[posChart]|=0x80;     // Признак включения нагревателя- старший бит в 1 - цвет фона на графике меняется
     ChartHeat=false;

     // Абсолютная влажность в доме диапазон от 0 до 20 грамм на кубометр, растягиваем на 100 точек
     if (packet.absHIn>=2000) absHInChart[posChart]=100-1;
     else absHInChart[posChart]=packet.absHIn/20;   // внутри 0...20 растягиваем в пять  раз в сотых % по этому делем не на 100 а на 20

     // Абсолютная влажность на улицу диапазон от 0 до 20 грамм на кубометр, растягиваем на 100 точек
     if (packet.absHOut>=2000) absHOutChart[posChart]=100-1;
     else absHOutChart[posChart]=packet.absHOut/20;   // внутри 0...20 растягиваем в пять раз,  в сотых % по этому делем не на 100 а на 20
     
  if (infoScreen==false)                 // если отображен информационный экран то ничего не выводим
   {
   cli();  
   for(i=0;i<120;i++)    // График слева на право
     { 
     // Вычислить координаты текущей точки x в кольцевом буфере. Изменяются от 0 до 120-1
     if (posChart<i) x=120+posChart-i; else x=posChart-i;
    
     // нарисовать фон в зависимости от статуса мотора
     if  (tOutChart[x]>=0x80) ucg.setColor(90, 60, 0);     // Нагреватель был ключен - бледно желтый
     else if (tInChart[x]>=0x80) ucg.setColor(0, 60, 90);  // Мотор был ключен - бледно синий
     else                     ucg.setColor(0, 0, 0);       // все выключено
      
     ucg.drawVLine(5+120-i,237-100,100); 
     ucg.drawVLine(5+120-i+162,237-100,100);  
     
     ucg.setColor(180, 180, 180);  
     if (i%5==0) // Пунктирные линии графика
     {
       ucg.drawPixel(5+120-i,236-10-1);
       ucg.drawPixel(5+120-i,236-50-1);
       ucg.drawPixel(5+120-i,236-90-1);
       
       ucg.drawPixel(5+120-i+162,236-25-1);
       ucg.drawPixel(5+120-i+162,236-50-1);
       ucg.drawPixel(5+120-i+162,236-75-1);  
     } 
     
     // Вывести новую точку
     tmp=tInChart[x]&0x7f;   // Отбросить старший разряд - признак включения мотора
     if ((tmp==0)||(tmp==100))   ucg.setColor(255, 255, 255); else ucg.setColor(255, 100, 100); 
     ucg.drawPixel(5+120-i,236-tmp);

     tmp=tOutChart[x]&0x7f;   // Отбросить старший разряд - признак включения калорифера   
     if ((tmp==0) || (tmp==100)) ucg.setColor(255, 255, 255); else ucg.setColor(100, 255, 100); 
     ucg.drawPixel(5+120-i,236-tmp);
     
     if (absHInChart[x]==100) ucg.setColor(255, 255, 255); else ucg.setColor(255, 100, 100); 
     ucg.drawPixel(5+120-i+162,236-absHInChart[x]);
 
     if (absHOutChart[x]==100) ucg.setColor(255, 255, 255); else ucg.setColor(100, 255, 100); 
     ucg.drawPixel(5+120-i+162,236-absHOutChart[x]);
       }
     sei(); 
   } 
 if (posChart<120-1) posChart++; else posChart=0;            // Изменили положение в буфере и Замкнули буфер
} 

// ---ПЕРЕДАЧА ДАННЫХ ЧЕРЕЗ РАДИОМОДУЛЬ -----------------------------
#ifdef  RADIO    // Радио модуль NRF42l
void send_packet()
{       radio.powerUp();
        radio.stopListening();     // Остановить приемник
        delay(2);
    //    radio.openWritingPipe(0xF0F0F0F0E1LL);    // передатчик
        cli();
          //  send_packet_ok = radio.write(&packet,sizeof(packet));
          radio.writeBlocking(&packet,sizeof(packet),200);  // Writes 1 payload to the buffers
  	  send_packet_ok=radio.txStandBy();
        sei(); 
         if (send_packet_ok!=true) err_ask++;            // квитанция не получена добавить счетчик
         #ifdef BEEP
           if (send_packet_ok==true) beep(30);           // Пакет передан успешно
         #endif
         #ifdef  DEBUG  
           if (send_packet_ok==true)  Serial.println(F("Packet sending ++++++++++"));
           else                       Serial.println(F("Packet NOT sending -----------"));
         #endif   
        radio.startListening();    // Включить приемник
        packet.count++;            // при переполнении сам сбросится
     //   if (packet.count>255) packet.count=0;
 }  
#endif 
// Чтение датчика возвращает код ошибки:
// DHTLIB_OK                   0
// DHTLIB_ERROR_CHECKSUM       1
// DHTLIB_ERROR_TIMEOUT        2
// DHTLIB_ERROR_CONNECT        3
// DHTLIB_ERROR_ACK_L          4
// DHTLIB_ERROR_ACK_H          5
byte readDHT(byte pin)
{
//delay(5);
cli();
  byte err=-1*DHT.read22(pin); // Чтение датчика
sei();  
return err; 
} 

// Измерение и обработка данных чтение датчиков --------------------------------------------------------------------------------
void measurement()
{ 
 myOS.pauseTask(measurement);        // Обязательно здесь, а то датчики плохо читаются мешает leos
 wdt_reset();                        // Сбросить сторожевой таймер
 
 packet.DHT_error=readDHT(PIN_DHT22a);   // ПЕРВЫЙ ДАТЧИК ДОМ  Новый пакет, сбросить все ошибки и прочитать первый датчик
 #ifdef  DEMO
   DHT.temperature=packet.tIn/100+random(-20,30)/10.0; 
   if (DHT.temperature>20) DHT.temperature=19;
   if (DHT.temperature<-10) DHT.temperature=-9;
   DHT.humidity=packet.relHIn/100+(float)random(-5,8);
   if (DHT.humidity>96) DHT.humidity=90;
   if (DHT.humidity<1) DHT.humidity=10;
   packet.DHT_error=0; // в Демо режиме
//   DHT.temperature=3.0;
//   DHT.humidity=21.0;
  #endif  
     sensors.tIn=(int)(DHT.temperature*100.0)+TIN_ERR;  // Запомнить результаты для суммирования с учетом ошибок
     sensors.relHIn=(int)(DHT.humidity*100.0)+HIN_ERR;  
     
    #ifdef  DEBUG  
       Serial.print(F("Sensor read samples:")); Serial.println(sensors.num); 
       Serial.print(F("IN T="));Serial.print(sensors.tIn);Serial.print(F(" H=")); Serial.print(sensors.relHIn); Serial.print(F(" error=")); Serial.println(packet.DHT_error);
    #endif
 
 packet.DHT_error=packet.DHT_error+16*readDHT(PIN_DHT22b);// ВТОРОЙ ДАТЧИК УЛИЦА  ошибки в старшие четыре бита
 #ifdef  DEMO
   DHT.temperature=packet.tOut/100+random(-20,30)/10.0; 
   if (DHT.temperature>30) DHT.temperature=19;
   if (DHT.temperature<-30) DHT.temperature=-9;
   DHT.humidity=packet.relHOut/100+(float)random(-5,8);
   if (DHT.humidity>96) DHT.humidity=90;
   if (DHT.humidity<1)  DHT.humidity=10;
   packet.DHT_error=0;      // в Демо режиме
// DHT.temperature=-10.0;
// DHT.humidity=40.0;
 #endif  
     sensors.tOut=(int)(DHT.temperature*100.0)+TOUT_ERR;  // Запомнить результаты для суммирования с учетом ошибок
     sensors.relHOut=(int)(DHT.humidity*100.0)+HOUT_ERR;
 
    #ifdef  DEBUG  
       Serial.print(F("OUT T="));Serial.print(sensors.tOut);Serial.print(F(" H=")); Serial.print(sensors.relHOut); Serial.print(F(" error=")); Serial.println(packet.DHT_error);
    #endif   
 
 print_error_DHT();    // Вывод ошибки чтения датчика при каждом чтении контроль за качеством связи с датчиками
 
 if (packet.DHT_error==0)// Если чтение без ошибок у ДВУХ датчиков  копим сумму для усреднения
  {
     sensors.sum_tIn=sensors.sum_tIn+sensors.tIn;
     sensors.sum_relHIn=sensors.sum_relHIn+sensors.relHIn;
     sensors.sum_tOut=sensors.sum_tOut+sensors.tOut;
     sensors.sum_relHOut=sensors.sum_relHOut+sensors.relHOut;
     sensors.num++;
   }
 
 // набрали в сумме нужное число отсчетов рассчитываем усреднение и выводим
 if (sensors.num>=NUM_SAMPLES)  // Пора усреднять и выводить значения 
 {
//         resetKey();          // сброс кнопки почему то часто выскакивает длительное нажатие
        // вычисление средних значений
         packet.tIn=sensors.sum_tIn/NUM_SAMPLES;
         packet.relHIn=sensors.sum_relHIn/NUM_SAMPLES;
         packet.tOut=sensors.sum_tOut/NUM_SAMPLES;
         packet.relHOut=sensors.sum_relHOut/NUM_SAMPLES;
         reset_sum();       // Сброс счетчиков и сумм
         
         // вычисление абсолютной влажности
         packet.absHIn=(int)(calculationAbsH((float)(packet.tIn/100.0),(float)(packet.relHIn/100.0))*100.0);
         packet.absHOut=(int)(calculationAbsH((float)(packet.tOut/100.0),(float)(packet.relHOut/100.0))*100.0);
         
     #ifdef  DEBUG  
       Serial.println(F("Average value >>>>>>>>>>"));
       Serial.print(F("IN T="));Serial.print(packet.tIn);Serial.print(F(" H=")); Serial.print(packet.relHIn); Serial.print(F(" abs H=")); Serial.println(packet.absHIn);
       Serial.print(F("OUT T="));Serial.print(packet.tOut);Serial.print(F(" H=")); Serial.print(packet.relHOut); Serial.print(F(" abs H=")); Serial.println(packet.absHOut);
     #endif   
                     
         #ifdef  RADIO     // Радио модуль NRF42l 
            send_packet();   // Послать данные
         #endif
         CheckON();                                // Проверка статуса вентилятора
         print_data();                             // вывод усредненных значений 
         print_status();                           // панель состояния
         if (FLAG_MOTOR_CHECK) ChartMotor=true;    // Признак того что надо показывать включение мотора на графике
         if (FLAG_HEAT_CHECK) ChartHeat=true;      // Признак того что надо показывать включение нагревателя на графике 
         
         TimeChart++;
         if ((long)((long)TimeChart*TIME_SCAN_SENSOR*NUM_SAMPLES)>=(long)TIME_PRINT_CHART) // проврека не пора ли выводить график
            { printChart(); TimeChart=0; // Сдвиг графика и вывод новой точки
              #ifdef  DEBUG  
                 Serial.println(F("Point add chart ++++++++++++++++++++"));
              #endif  
              #ifdef BEEP
 //               beep(50);
              #endif
             } 
        }
    myOS.restartTask(measurement);     // Пустить задачи
}

// Функция переводит относительную влажность в абсолютную 
// t-температура в градусах Цельсия h-относительная влажность в процентах
float calculationAbsH(float t, float h)
{
 float temp;
 temp=pow(2.718281828,(17.67*t)/(t+243.5));
 return (6.112*temp*h*2.1674)/(273.15+t);
}

// Сканирование клавиш ------------------------------------------
void scanKey()
{  
    byte key,key1; 
    cli(); 
    key=ReadPort(D,3);                                // Прочитать кнопку 0 - нажата 
    delay(30);                                        // Задержка для подавления дребезга контактов
    key1=ReadPort(D,3);                               // читаем еще раз кнопку
    if (key!=key1) {resetKey();return;}               // если значения не равны то ложное срабатывание выходим
          
    if ((key==0)&&(flagKey==false))                   // Если кнопка была нажата запомнить время и поставить флаг нажатия
    {
        flagKey=true;                                 // Кнопка нажата  ждем обратного фронта
        time_key=millis();                            // Время нажатия запомнили
     }
    if ((key==1)&&(flagKey==true))                    // Если кнопка была отжата 
    {
         time_key=millis()-time_key;                  // Рассчитать время нажатия
         pressKey=true;                               // Поставить признак обработки кнопки
    }
   sei();
 }

// Проверка статуса вытяжки, не пора ли переключится
void CheckON(void)
{
int tmp=0;
// 0.  Проверить замораживание подвала КАЛОРИФЕР 
if (packet.tIn<=TEMP_LOW) { MOTOR_OFF; HEAT_ON; return;}   // Контроль от промораживания подвала по идеи здесь надо включать калорифер
if ((FLAG_HEAT_CHECK)&&(packet.tIn>TEMP_LOW+dT_OFF)) HEAT_OFF;    // Выключить калорифер

// 1. Режимы не зависящие от влажности и температуры ВЫСШИЙ приоритет
if ((settingRAM.mode==BLOCK_OFF)&&(~FLAG_MOTOR_CHECK))  return;
if ((settingRAM.mode==BLOCK_OFF)&&(FLAG_MOTOR_CHECK))  { MOTOR_OFF ; return;}
if ((settingRAM.mode==HOOD_ON )&&(FLAG_MOTOR_CHECK))   return;
if ((settingRAM.mode==HOOD_ON )&&(~FLAG_MOTOR_CHECK))  { MOTOR_ON  ; return;}

// 2. Режим охлаждения (второй приоритет) температура внутри больше 10 градусов темература снаружи меньше на 2 градуса чем внутри, на влажность не смотрим
if (settingRAM.mode==COOLING)          // Режим охлаждение
  {
    if ((~FLAG_MOTOR_CHECK)&&(packet.tIn>(packet.T_min*10))&&((packet.tIn-packet.tOut)>packet.dH_min)) // dH_min используется не штатно для температуры
       {MOTOR_ON; return;}            // мотор выключен, температура выше установленной и снаружи температура ниже на 2 градуса  то ВКЛЮЧЕНИЕ мотора
    if ((FLAG_MOTOR_CHECK)&&(packet.tIn<=packet.tOut))   
       {MOTOR_OFF; return;}        // мотор включен и темература внутри ниже наружней то ВЫКЛЮЧЕННИЕ мотора
   return;                             // изменений нет выходим    
  } 
// 3. В режиме осушения - проверка на достижение минимальной температуры помещения в режиме осушения - СРОЧНО ВЫКЛЮЧИТЬ  третий приоритет
if (packet.tIn<=(packet.T_min*10))
   {
     if (~FLAG_MOTOR_CHECK)   return;      // Мотор уже выключен, выходим
     else  { MOTOR_OFF; return;}           // выключить и выйти
   } 
   
// 4. Режимы зависящие от температуры и влажности низший приоритет (что осталось)
// Расчитываем разность срабатывания по влажности
if (FLAG_ABS_H_CHECK)   tmp=packet.dH_min;                 // Если режимы используют абсолютную разность в сотых грамма на куб
else tmp=(int)(packet.absHIn*(packet.dH_min/10)/100);      // Если режимы используют ДЕСЯТЫЕ % от абсолютной разности внутренней температуры 

 if ((~FLAG_MOTOR_CHECK)&&(packet.tIn>(packet.T_min*10))&&((packet.absHIn-tmp)>packet.absHOut)) 
        {MOTOR_ON; return;}        // мотор выключен, темература выше критической, абс влажность с наружи меньше  то ВКЛЮЧЕНИЕ мотора
 if ((FLAG_MOTOR_CHECK)&&((packet.tIn<=(tmp+dT_OFF))||(packet.absHIn<(packet.absHOut+dH_OFF)))) 
        {MOTOR_OFF; return;}       // мотор включен и темература ниже критической или абс влажность внутри ниже  то ВЫКЛЮЧЕННИЕ мотора
} 
 

// Вывод информации о настройках и сохрание индекса настроек в eeprom ---------------------------------
void Setting()
{
 // Настройка
  cli();
  ucg.setColor(0, 100, 255);
  ucg.setFontMode(UCG_FONT_MODE_SOLID);
  ucg.setPrintPos(0,25+0+18); 
  switch (settingRAM.mode)
        {
        case  BLOCK_OFF: ucg.print(F("Выключено              ")); packet.dH_min=255; packet.T_min=255; FLAG_ABS_H_ON;  break; 
        case  HOOD_ON:   ucg.print(F("Режим вытяжки         "));  packet.dH_min=0;   packet.T_min=0;   FLAG_ABS_H_ON;  break; 
        case  COOLING:   ucg.print(F("Охлаждение T>10 dT>2"));    packet.dH_min=200; packet.T_min=100; FLAG_ABS_H_ON;  break; // dH_min используется не штатно для температуры     
        case  3:         ucg.print(F("Осушение T>+3 dH>0.2 "));   packet.dH_min=20;  packet.T_min=30;  FLAG_ABS_H_ON;  break;
        case  4:         ucg.print(F("Осушение T>+3 dH>4% "));    packet.dH_min=40;  packet.T_min=30;  FLAG_ABS_H_OFF; break;
        case  5:         ucg.print(F("Осушение T>+4 dH>0.3 "));   packet.dH_min=30;  packet.T_min=40;  FLAG_ABS_H_ON;  break;
        case  6:         ucg.print(F("Осушение T>+4 dH>8%  "));   packet.dH_min=80;  packet.T_min=40;  FLAG_ABS_H_OFF; break;
        case  7:         ucg.print(F("Осушение T>+5 dH>0.7 "));   packet.dH_min=70;  packet.T_min=50;  FLAG_ABS_H_ON;  break;
        } 
 writeEeprom();       // Запись в EEPROM  новых настроек
 SET_MODE_BITS;       // в пакет для передачи добавили смену режима
 MOTOR_OFF;           // Поменяли настройки - отключить мотор, пусть заново настройки сработают если потребуется
 CheckON();           // Возможно надо включить мотор
 print_status();      // Показать панель состояния (смена настроек)
// resetKey();
 sei();   
}

// Вывод float  с двумя десятичными знаком в координаты x y // для экономии места
void print_floatXY(int x,int y, float v)
{
 ucg.setPrintPos(x,y);
 ucg.print(v,2);
 ucg.print(F("  ")); // Стереть хвост от предыдущего числа
} 

// Вывод строки константы в координаты x y // для экономии места
void print_StrXY(int x,int y, const __FlashStringHelper* b)
{
 ucg.setPrintPos(x,y);
 ucg.print(b);
} 

void printInfo() // Окно с информацией о блоке, появляется при длительном нажатии на кнопку
{
  infoScreen=true;
  cli();
  ucg.setColor(250, 250, 250);  // 
  ucg.drawBox(10, 10, 320-1-20, 240-1-20);
  ucg.setColor(0, 50, 250);
  ucg.drawFrame(10+5, 10+5, 320-1-20-10, 240-1-20-10);
  
  ucg.setFontMode(UCG_FONT_MODE_TRANSPARENT);
  ucg.setColor(0, 150, 10);
  print_StrXY(35,18+15,F("ОСУШИТЕЛЬ на Arduino Pro Mini"));
  
  ucg.setColor(0, 50, 50);
  print_StrXY(10+10,15+17*2,F("1 Напряжение питания В.")); 
  print_floatXY(10+230,15+17*2,readVcc()/1000.0);
 
  print_StrXY(10+10,15+17*3,F("2 Температура блока гр.")); 
  print_floatXY(10+230,15+17*3,GetTemp());
 
  print_StrXY(10+10,15+17*4,F("3 Свободная память байт")); 
  ucg.setPrintPos(10+230,15+17*4); 
  ucg.print(freeRam()); 
 
  print_StrXY(10+10,15+17*5,F("4 Мото часы блока")); 
  ucg.setPrintPos(10+230,15+17*5); 
  ucg.print(settingRAM.hour_unit/(TIME_HOUR/(NUM_SAMPLES*TIME_SCAN_SENSOR))); 
  
  print_StrXY(10+10,15+17*6,F("5 Мото часы вентилятора")); 
  ucg.setPrintPos(10+230,15+17*6); 
  ucg.print(settingRAM.hour_motor/(TIME_HOUR/(NUM_SAMPLES*TIME_SCAN_SENSOR))); 
  
  print_StrXY(10+10,15+17*7,F("6 Мото часы нагревателя")); 
  ucg.setPrintPos(10+230,15+17*7); 
  ucg.print(settingRAM.hour_heat/(TIME_HOUR/(NUM_SAMPLES*TIME_SCAN_SENSOR)));  
 
  print_StrXY(10+10,15+17*8,F("7 Канал NRF24l01+")); 
  ucg.setPrintPos(10+230,15+17*8); 
  ucg.print(NRF24_CHANEL); 
  
  print_StrXY(10+10,15+17*9,F("8 Гистерезис T/absH   ")); 
  ucg.print((float)dT_OFF/100.0);
  ucg.print(F("/"));
  ucg.print((float)dH_OFF/100.0);
  
  print_StrXY(10+10,15+17*10,F("9 ERR Т/Н in:")); 
  ucg.print(TIN_ERR); 
  ucg.print(F("/")); 
  ucg.print(HIN_ERR); 
  ucg.print(F(" out:"));
  ucg.print(TOUT_ERR); 
  ucg.print(F("/")); 
  ucg.print(HOUT_ERR); 
 
  ucg.setColor(0, 0, 150);
  print_StrXY(10+10,16+17*11,F("СБРОС - Вкл. при нажатой кнопке.")); 
  
  ucg.setColor(250,80,80);
  print_StrXY(10+10,20+21+18*10,F(VERSION)); 
  #ifdef  RADIO     // Радио модуль NRF42l вывести число ошибок ask
     ucg.setColor(0, 150, 10);
     ucg.print(F(" Ask err:"));
     ucg.print(err_ask);
  #endif
  sei();
  
  #ifdef BEEP
   beep(40);
  #endif
//  resetKey();
} 

void clearInfo()  // Стереть информационный экран
{
      infoScreen=false;
//      resetKey();
      last_error=100;         // Признак обновления ошибки
      cli();
      ucg.setColor(0, 0, 0);  // залить черным
      ucg.drawBox(10, 10, 320-1-20, 240-1-20);
      print_static();
      Setting();  
      print_data();                            
      printChart();
//      resetKey();
      sei();
} 
// Чтение свободной памяти --------------------------------------------------------------------
int freeRam () {
  extern int __heap_start, *__brkval; 
  int v; 
  return (int) &v - (__brkval == 0 ? (int) &__heap_start : (int) __brkval); 
}
// Чтение внутреннего датчика температуры ---------------------------------------
double GetTemp(void)
{
  unsigned int wADC;
  double t;
  sei();  // Должны быть разрешены прерывания
  ADMUX = (_BV(REFS1) | _BV(REFS0) | _BV(MUX3));
  ADCSRA |= _BV(ADEN);  
  delay(20);           
  ADCSRA |= _BV(ADSC);  
  while (bit_is_set(ADCSRA,ADSC));
  wADC = ADCW;
  t = (wADC - 324.31 ) / 1.22;
  return (t); 
}
// Чтение напряжения питания ----------------------------------------------
long readVcc() {
  long result;
  // Read 1.1V reference against AVcc
  ADMUX = _BV(REFS0) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1);
  delay(2); // Wait for Vref to settle
  ADCSRA |= _BV(ADSC); // Convert
  while (bit_is_set(ADCSRA,ADSC));
  result = ADCL;
  result |= ADCH<<8;
  result = ConstADC / result; // Back-calculate AVcc in mV
  return result;
} 
// Запись счетчиков в Eeprom --------------------------------------------------
void writeEeprom()
{ 
cli(); 
  eeprom_write_block((const void*)&settingRAM, (void*) &settingEEPROM, sizeof(settingRAM)); 
sei();
}
// Чтение счетчиков из Eeprom --------------------------------------------------
void readEeprom()
{
cli(); 
   eeprom_read_block((void*)&settingRAM, (const void*) &settingEEPROM, sizeof(settingRAM)); 
   if ((settingRAM.mode>(NUM_SETTING-1))||(settingRAM.mode<0)) settingRAM.mode=0;        // гарантированно попадаем в диапазон
sei();
}

void reset_sum()  // Сброс счетчиков накоплений
{
sensors.num=0;  // Рассчитать величину усреднения
sensors.sum_tOut=0;
sensors.sum_tIn=0;
sensors.sum_relHOut=0;
sensors.sum_relHIn=0;
}

char hex(byte x)  // Функция для вывода в hex
{
   if(x >= 0 && x <= 9 ) return (char)(x + '0');
   else      return (char)('a'+x-10);
}

bool reset_ili9341(void)
{
  pinMode(PIN_RESET, OUTPUT);                    // Сброс дисплея сигнал активным является LOW
  digitalWrite(PIN_RESET, LOW);  
  delay(100);
  digitalWrite(PIN_RESET, HIGH);  
  // Дисплей
  ucg.begin(UCG_FONT_MODE_TRANSPARENT);
  ucg.setFont(my14x10rus);  
//   ucg.setRotate90();
  ucg.setRotate270();
  ucg.clearScreen();
}

#ifdef BEEP  
  void beep(int x)  // Пищать х мсек
  {
    WritePort(C,1,HIGH);
    delay(x);
    WritePort(C,1,LOW);
  } 
#endif

4. Заслонка - наверное можно сделать, только без вентилятора воздух "естественным" путем будет плохо перемешиваться и будет большая неравномерность температур. И процесы будут очень медленные.

5. Датчик вызывывает много вопросов. На неделе он три дня показывал влажность 99.6. Думал кирдык ему. Но похоже влажность действительно была близка к 100%. Сейчас вроде показывает правильно. Надо его откалибровать (возможно по нескольким точкам). Тогда и доверия будет больше ему.

Понравился датчик sht21 он будет поточнее. Единственный недостаток нельзя изменить адрес по i2c  шины и на шину можно вешать только один датчик, обидно.

6. Активно занимался выяснением качества связи датчик-головное устройство. Сделал счетчики на переданные пакеты и полученные квитанции. На небольших расстояниях теряется менее 1% пакетов, бывает что в течении суток потерь нет вообще и квитанции все доходят.  У меня датчик стоит в бетонном подвале (две стены по 40 см), масса железа. Приемник относится на 70-80 метров связь при этом устойчивая. По дому все отлично работает. Но рассчитывать на дальности более 100-150 метров наверное не стоит.

 

miaua
Offline
Зарегистрирован: 20.08.2015

1. модуль реле с оптопарами. на плате 4 шт. срабатывание по лог "0". Проверено, и логично т.к при неподключении входов реле выключено. С этим я разберусь...

2. В режиме "Демо" там включается все подряд..и включение 2х реле одновременно не редкость... Потому и возник вопрос разделения индикации...И вопрос -нет колизии из-за индикации?

3,4,5 тут все ясно..

6. -вполне устраивает такая дальность..

Не пошла компиляция.... Сам пока не разобрался..

Ver._0.66_11-09-15.ino: In function 'void send_packet()':

 
Ver._0.66_11-09-15.ino:534:17: error: 'class RF24' has no member named 'writeBlocking'
 
Ver._0.66_11-09-15.ino:535:27: error: 'class RF24' has no member named 'txStandBy'
 
Ошибка компиляции.
 
  
 
pav2000
Offline
Зарегистрирован: 15.12.2014

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

Ошибка компиляции связана с тем что линкер не находит функций библиотеки.  Надо ставить не классическую библиотеку rf24 аTMRh20 (http://tmrh20.github.io/RF24/) там эти функции есть. Об этом я писал в предыдущем посте, и то сто ИХМО это лучшая библиотека. 

 

 

miaua
Offline
Зарегистрирован: 20.08.2015

pav2000 пишет:

 

2. "Ошибка компиляции связана с тем что линкер не находит функций библиотеки.  Надо ставить не классическую библиотеку rf24 аTMRh20 (http://tmrh20.github.io/RF24/) там эти функции есть. Об этом я писал в предыдущем посте, и то сто ИХМО это лучшая библиотека. "

1. Еще раз проверил - программа работает правильно (и работала).. Проблема в том, что у меня уже смонтирован 4х релейный модуль с гальванической развязкой оптопарами. Срабатывание реле по низкому уровню. Поэтому и хотел инвертировать управление в программе...Но пока неудачно..

2. Перепутал библиотеки...уже их куча. :) вопрос закрыт.

? Появился режим "Охлаждение" ? Какой принцип (Устройство)?

Спасибо за подсказки.

 

Thunderix
Offline
Зарегистрирован: 12.09.2015

Не могу найти все библиотеки , в часности rusFont , дайте ссылочку. :)

И еще 2 датчика DHT22 ,строки 105 и 106 , один на улице , второй в доме , а подвал где?

 

miaua
Offline
Зарегистрирован: 20.08.2015

Thunderix пишет:

Не могу найти все библиотеки , в часности rusFont , дайте ссылочку. :)

И еще 2 датчика DHT22 ,строки 105 и 106 , один на улице , второй в доме , а подвал где?

rusfont весь находится в конце первого скеча. Только поправьте коментарии вначале....

2.В доме и есть -подвал.. :)

Thunderix
Offline
Зарегистрирован: 12.09.2015

Можно поподробней про коментарии вначале...

miaua
Offline
Зарегистрирован: 20.08.2015

файл фонтов

http://arduino.ru/sites/all/libraries/syntaxhighlighter/styles/page_whit...) !important; background-repeat: no-repeat !important;" title="код">код
 

http://arduino.ru/sites/all/libraries/syntaxhighlighter/styles/printer.png) !important; background-repeat: no-repeat !important;" title="расечатать">расечататьhttp://arduino.ru/sites/all/libraries/syntaxhighlighter/styles/help.png) !important; background-repeat: no-repeat !important;" title="?">?

001 / Русский шрифт 10х14 ------------------------------------------------------
002

const ucg_fntpgm_uint8_t my14x10rus[4157] UCG_SECTION(".progmem.my14x10"

еще одну косую в коментарии добавить //Русский шрифт 10х14 ----------

:)

Thunderix
Offline
Зарегистрирован: 12.09.2015

Спасибо не обратил внимания :))

pav2000
Offline
Зарегистрирован: 15.12.2014

Добрался до паяльника. Поставил RC цепь на кнопку. После этого кнопка стала работать хорошо. Ставил резистор 220 ом и конденсатор 0.1 мкф.

Режим охлаждения  это режим для лета. Влажность вообще не учитывается. Если в подвале больше 10 градусов и тепература на улице меньше не 2 градуса включается вентилятор. По задумке холодные ночи должны охлаждать подвал.

 

miaua
Offline
Зарегистрирован: 20.08.2015

Будем пробовать с рекомендованной RC цепочкой...Схема подключения цепочки? Как у меня?

Спасибо.. 

П.С. 

Приехала часть железа: Радиомодули и Stm32f103rcbt6 ARM Cortex-M3 мини модуль для arduino STM32...

А датчики еще в пути..

Теперь понадобятся билиотеки и остальной софт под STM32. Ну и узнать алгоритм обмена между модулем(ями) и базой...:)