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

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

Немного не правильно. Ножка контроллера присоединяется к точке соединения резистора 220 ом и конденсатора 0.1 Мкф.

В Вашем случае цепочка не работает.

 

Diminator
Offline
Зарегистрирован: 15.09.2015

pav2000 можно как-то с вами связаться? Хотел бы проконсультироваться по вопросу устройства подполья в вашем случае и системы вентиляции для него. Извините что не в тему.

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

Diminator пишет:

Хотел бы проконсультироваться по вопросу устройства подполья.

А вот это уже, экстремисткая, наказуемая статья))))

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

Diminator пишет:

pav2000 можно как-то с вами связаться? Хотел бы проконсультироваться по вопросу устройства подполья в вашем случае и системы вентиляции для него. Извините что не в тему.

Почта pav2000 собака nm точка ру

Подробное описание подвала здесь -  https://www.forumhouse.ru/threads/151893/page-19#post-9928669

 

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

Приехали датчики.. Естественно нужна калибровка.. Появиля вопрос :- как изменить графики(масштаб), что бы увидеть реальную температуру? Сейчас на улице 27,3*С внутри 21*С. Соответственно отн. вл. 58,6% и 50,77%  (13,93г/м3 и 13,4г/м3) ?

2 Для чего нужна калибровка АЦП ?

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

Графики тут не причем, они только показывают что намерили датчики. В программе реализована калибровка датчиков по одной точке. Если потребуется можно реализовать по двум точкам.

Калибровочные константы задаются кодом

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

Нужны образцовые гигрометр и термометр.  Алгоритм следующий

1. Все калибровочные константы в 0 компилируем и загружаем скейч в плату.

2. Запихиваем образцовый прибор и оба датчика в банку, сверху прекрываем. Что бы небыло конвекции воздуха.

3. Берем листочек бумаги и считываем три значения в течении 2- 3 часов раз 10-20.

4. Обрабатываем полученные результаты в меру своих математических способностей. Простейший случай усредняем по 10-20 значениям. В результате получается три значения. Эталон и два датчика.

5. Вычисляем ошибки. Если образцовый больше то ошибка положительная если меньше отрицательная.

6. Переводим в машинные еденицы типа сотых градуса х100. И заносим эти константы в программу.

7. Компилим и загружаем. Наблюдаем уменьшение ошибки. 

Все, профит =)

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

Датчики цифровые. АЦП для их работы не используется. Я использую ацп для измерения питания коонтроллера. По хорошому его надо откалибровать (точнее ион). На показания датчиков ацп не влияет

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

Спасибо. С этим все ясно..Инвертировал (попытался) работу реле вентилятора и нагревателя. С логикой вентилятора все ок, а нагреватель постоянно включен. Даже в режиме "выключено". Я конечно могу добавить пару инверторов для работы реле, но мне кажется это был бы не правильный подход :)

Увы мои скудные познания в СИ не дают мне (пока) разобраться в работе программы...

Менял только эти две группы:


#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
//pav2000#define MOTOR_ON                  { WritePort(C,0,HIGH); FLAG_MOTOR_ON;  }   // включить мотор
//pav2000#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; }   // выключить мотор
#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
//pav2000#define HEAT_ON                   { WritePort(C,2,HIGH); FLAG_HEAT_ON;  }   // включить калорифер
//pav2000#define HEAT_OFF                  { WritePort(C,2,LOW); FLAG_HEAT_OFF; }   // выключить калорифер
#define HEAT_ON                   { WritePort(C,2,LOW); FLAG_HEAT_ON;  }   // включить калорифер
#define HEAT_OFF                  { WritePort(C,2,HIGH); FLAG_HEAT_OFF; }   // выключить калорифер

 

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

У меня нагреватель пока не подключен. Все твердотельное реле не куплю.

По этому программировал без отладки. Если инверсная логика то в сетапе надо поставить heat_off. Для начала. И смотреть. Просто я в начале программирую ноги и эта нога устанавливается 0.

SetOutput(C,2);                       //  Подключить Калорифер
  WritePort(C,2,LOW);

Heat_off ставить ниже этих строк или вместо low high поставить в нижнюю строчку.

Калорифер выключится. При включенных датчиках он включится ТОЛЬКО при температуре 1.5 градуса в подвале. То есть сейчас он не будет включатся. Нет условий.

Только сейчас подумал. В режиме выключено, выключение не распространяется на нагреватель. Гарантированная защита от замораживания подвала. Так сейчас сделано. Можно изменить на отключение. Но ихмо сейчас надежнее.

 

Ps

Вчера дрюкался с блоком, испытывал новые изменения и дополнения. Кнопка с rc цепочкой отлично работает, ни одного ложного срабатывания.

 

 

 

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

Разобрался с реле. Теперь все как надо работает. Спасибо...

Теперь надо радио подключать.... Как оно у Вас подключено? через отдельные делители или параллельно дисплею? У меня преобразователи уровня собраны на 4х транзисторах. И конечно вопрос:- " что с ним (радио)  делать дальше?". :)

По поводу калибровки датчиков... А зачем нам приведение показаний к образцовому прибору? В этой схеме вполне достаточно одинаковых показаний обоих датчиков (в попугаях) для принятия решения. Более менее точный нужен датчик температуры, для управления нагревом и охлаждением.. 

Еще один вопрос: - Нужно ли опускать влажность ниже... скажем 50-55% ? Если на улице влажность 35%, то нужно ли опускать в подвале до такой влажности? Я думаю, это напрасный расход энергии (вентилятор ). Не претендую на истину. :)

 

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

Nrf24l01 можно цеплять к 5 вольтам без делителей. Толеранты к 5 вольтам из даташита.

Т.е. цепляются к ногам ардуино на прямую.

"Делать дальше" постараюсь выложить пост по приемнику, сейчас допиливаю код. Есть проблемы со множеством передатчиков. Думаю над идеологией. Напечатал корпус, приделал энкодер. Сейчас допиливаю интерфейс. Приемник будет только stm32 онли. Три прерывания использую. Есть рроблема ио spi шиной дисплея и nrf24l01. Пока это не решил. В очередной раз опитимизирую код по радиоканалу.

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

 

 

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

Отлично! У меня уже есть в наличии stm32 и пара енкодеров. Но STM32 для меня нечто новое :) . И нужно устанавливать какое то ИДЕ для него. Если у Вас есть немного времени, посоветуйте как организовать среду...

Для STM существует куча всяких ИДЕ...В том числе и ардуино... Но где брать библиотеки? Тем более библиотеки которые Вы сами портировали под этот проект..

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

По stm32 изложу в отдельной ветке про либы и иде в среде ардуино. Сейчас в деревне между рюмками пишу посты. У нас теплый вечер. +20. Отмечаем мой отпуск -)) 

 

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

Приятного Вечера! И вдохновения!

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

Проект близится к завершению. Исправлений все меньше и меньше. В новой версии :

1. В очередной раз оптимизирован код радиоканала.

2. В процессе работы забыл записывать заначения моточасов в eeprom. Сейчас это поправлено запись производится раз в час.

3. Добавил сохранение максимальных и минимальных температур подвала и улицы, с их выводом на информационный экран. Сброс проводится одновременно с моточасами. (наверное это не верно, надо отдельной процедурой)

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проводной модуль надо использовать библиотеку 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.69 19/09/15"              // Текущая версия
#define ID             0x22                       // уникально Идентификатор устройства - старшие 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          5                      // Гистерезис абсолютной влажности в сотых грамма на куб
#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 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
     int16_t  tOutMin=5555,tInMin=5555;         // Минимальные температуры за период температуры в сотых градуса !!! место экономим
     int16_t  tOutMax,tInMax=-5555;             // Максимальные температуры за период температуры в сотых градуса. (Наблюдаю глюк при tOutMax=-5000)
     int16_t tick_eeprom=0;                     // Переменная для хранения времени записи статистики раз в один час хранится в NUM_SAMPLES*TIME_SCAN_SENSOR сравнение с TIME_HOUR
     
  }; 
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);                  // Количество пауза и количество повторов 
  // Рекомендуют первые 2-4 байта адреса устанавливать в E7 или 18 он проще детектируется чипом
  radio.openWritingPipe(0xE7E7E7E7E1LL);    // передатчик 
  radio.openReadingPipe(1,0xE7E7E7E7D2LL);  // приемник
  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;
      settingRAM.tOutMin=5000;
      settingRAM.tInMin=5000; 
      settingRAM.tOutMax=-5000;
      settingRAM.tInMax=-5000;   
      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();                                 // Считать данные
//  printInfo();
  #ifdef BEEP
     beep(200);
     delay(300);
     beep(400);
  #endif
resetKey();
}

void loop()
{
 wdt_reset();                        // Сбросить сторожевой таймер
// Обработка нажатия кнопки
if (pressKey==true)                 // Кнопка была нажата и отпущена
{
 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();                                                //  Сброс состояния кнопки
 }
}

void resetKey(void)  // Сброс состяния кнопки
{
  flagKey=false;                                             
  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)  // Пора усреднять и выводить значения 
 {
        // вычисление средних значений
         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();                           // панель состояния
         
         // Обновляем максимум и минимум температур в EEPROM
         // Cделано не честно - обновляем раз в 30 секунд (NUM_SAMPLES*TIME_SCAN_SENSOR) а пишем раз в час, могут пропасть данные при сбросе
         if (settingRAM.tInMax<packet.tIn) settingRAM.tInMax=packet.tIn;
         else if (settingRAM.tInMin>packet.tIn) settingRAM.tInMin=packet.tIn;
         
         if (settingRAM.tOutMax<packet.tOut) settingRAM.tOutMax=packet.tOut;
         else if (settingRAM.tOutMin>packet.tOut) settingRAM.tOutMin=packet.tOut;
 
         // Пишем в EEPROM не каждый раз!! экономим ресурс записей 100000 мин.
         settingRAM.tick_eeprom++;
         if (((long)settingRAM.tick_eeprom*NUM_SAMPLES*TIME_SCAN_SENSOR)>=(long)TIME_HOUR) // пора писать eeprom
            { writeEeprom();  settingRAM.tick_eeprom=0;  } 

             // Запись статистики в EEPROM
         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(25);                                        // Задержка для подавления дребезга контактов
    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>3% "));    packet.dH_min=30;  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>6%  "));   packet.dH_min=60;  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();      // Показать панель состояния (смена настроек)
 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(5, 5, 320-1-10, 240-1-10);
  ucg.setColor(0, 50, 250);
  ucg.drawFrame(5+5, 5+5, 320-1-20, 240-1-20);
  
  ucg.setFontMode(UCG_FONT_MODE_TRANSPARENT);
  ucg.setColor(0, 200, 10);
  print_StrXY(35,12+16,F("ОСУШИТЕЛЬ на Arduino Pro Mini"));
  
  ucg.setColor(0, 50, 50);
  print_StrXY(5+10,15+16*2,F("1 Напряжение питания В.")); 
  print_floatXY(5+230,15+16*2,readVcc()/1000.0);
 
  print_StrXY(5+10,15+16*3,F("2 Температура блока гр.")); 
  print_floatXY(5+230,15+16*3,GetTemp());
 
  print_StrXY(5+10,15+16*4,F("3 Мото часы блока")); 
  ucg.setPrintPos(5+230,15+16*4); 
  ucg.print(settingRAM.hour_unit/(TIME_HOUR/(NUM_SAMPLES*TIME_SCAN_SENSOR))); 
  
  print_StrXY(5+10,15+16*5,F("4 Мото часы вентилятора")); 
  ucg.setPrintPos(5+230,15+16*5); 
  ucg.print(settingRAM.hour_motor/(TIME_HOUR/(NUM_SAMPLES*TIME_SCAN_SENSOR))); 
  
  print_StrXY(5+10,15+16*6,F("5 Мото часы нагревателя")); 
  ucg.setPrintPos(5+230,15+16*6); 
  ucg.print(settingRAM.hour_heat/(TIME_HOUR/(NUM_SAMPLES*TIME_SCAN_SENSOR)));  
 
  print_StrXY(5+10,15+16*7,F("6 Мах температуры in/out")); 
  ucg.setPrintPos(5+230,15+16*7);
  ucg.print(settingRAM.tInMax/100); 
  ucg.print(F("/"));
  ucg.print(settingRAM.tOutMax/100); 
  
  print_StrXY(5+10,15+16*8,F("7 Мin температуры in/out")); 
  ucg.setPrintPos(5+230,15+16*8);
  ucg.print(settingRAM.tInMin/100); 
  ucg.print(F("/"));
  ucg.print(settingRAM.tOutMin/100); 
  
  print_StrXY(5+10,15+16*9,F("8 Канал NRF24l01+")); 
  ucg.setPrintPos(5+230,15+16*9); 
  ucg.print(NRF24_CHANEL); 
  
  print_StrXY(5+10,15+16*10,F("9 Гистерезис T/absH   ")); 
  ucg.print((float)dT_OFF/100.0);
  ucg.print(F("/"));
  ucg.print((float)dH_OFF/100.0);
  
  print_StrXY(5+10,15+16*11,F("10 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.drawHLine(40,18+16*11,320-1-80);
 
  ucg.setColor(0, 0, 150);
  print_StrXY(5+10,19+16*12,F("СБРОС - Вкл. при нажатой кнопке.")); 
 
  ucg.setColor(250,80,80);
  print_StrXY(5+10,19+16*13,F(VERSION)); 
  #ifdef  RADIO     // Радио модуль NRF42l вывести число ошибок ask
     ucg.setColor(0, 150, 10);
     ucg.print(F(" AskErr:"));
     ucg.print(err_ask);
  #endif
  sei();
  
  #ifdef BEEP
   beep(40);
  #endif
} 

void clearInfo()  // Стереть информационный экран
{
      infoScreen=false;
      last_error=100;         // Признак обновления ошибки
      cli();
      ucg.setColor(0, 0, 0);  // залить черным
      ucg.drawBox(5, 5, 320-1-10, 240-1-10);
      print_static();
      Setting();  
      print_data();                            
      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

 

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

Проверил на уно. Вроде бы все  хорошо...

Вопрос: проводились ли тесты системы при отрицательных температурах?

Думаю мой холодильник, удобный стенд. Морозилка от -32 до -16*С, основная камера от+2 до +8*С .Как раз порог +4* можно проверить.

Есть смысл заняться проверкой?

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

miaua пишет:

Вопрос: проводились ли тесты системы при отрицательных температурах?

Думаю мой холодильник, удобный стенд. Морозилка от -32 до -16*С, основная камера от+2 до +8*С .Как раз порог +4* можно проверить.

Есть смысл заняться проверкой?

Отрицательных температур быть не должно. Отрицательные температуры отрицательно влияют на жки и конденсаторы (импульсный источник может не завестись) и положительно на полупроводниковые элементы (обратные токи и токи утечки меньше). Смысла тестирования не вижу. Первым откажит индикатор в районе -5÷-10 градусов.

Добавил тему про приемник на stm32   http://arduino.ru/forum/proekty/udalennyi-monitoring-za-datchikami-na-maple-mini

 

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

Я имел ввиду расположение датчиков в холодильнике..Насчет климатических условий для различных компонентов я в курсе уже лет 35...

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

2Начинаю изучать ветку по приемнику STM.

Спасибо.

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

miaua пишет:

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

Так по влажности, пользуйтесь солями. Для дома самое простое NaCl. Во всем положительном диапазоне температур от 74 до 76%.

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

bwn пишет:

Так по влажности, пользуйтесь солями. Для дома самое простое NaCl. Во всем положительном диапазоне температур от 74 до 76%.

По этой методике ?

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

Именно так, правда не морочился с температурой, для NaCl сами видите она не актуальна (других солей под рукой не было). Соль правда брал от водоочистки (там таблетки), и поближе к х.ч. чем из магазина. Майонезная банка, таблетка, на нее 2-4 капель воды из пипетки (должна быть влажная, но без лужи внизу). Датчики примерно в середину банки, сверху пленкой-стрейчем заклеил.
Режим в банке устанавливается достаточно долго (часы), далее держал сутки, посматривал, на am2301 колебался примерно 1%, посчитал для целей подвала, более чем достаточно. Сейчас висит он и неподалеку выносной модуль от погодной станции, разница около 2%, но постоянная.
 

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

Откалибровал датчики AM2302 по соли - врали почти на 5 % повлажности. А по температуре на 5соток... Более чем достаточно! 

Не знаю в какой теме писать?

Пробовал компилировать скечи для STM - ошибки:

Arduino: 1.6.2 (Windows 7), Плата"Maple Mini, Original (17k RAM,108k Flash)"
 
Изменена опция сборки, пересобираем все
 
Version_0.361_alpha_19-09-2015.ino: In function 'void loop()':
 
Version_0.361_alpha_19-09-2015.ino:124:20: error: 'dry_static' was not declared in this scope
 
Version_0.361_alpha_19-09-2015.ino:125:18: error: 'dry_data' was not declared in this scope
 
Version_0.361_alpha_19-09-2015.ino:145:21: error: 'dry_update' was not declared in this scope
 
Version_0.361_alpha_19-09-2015.ino: In function 'void check_radio()':
 
Version_0.361_alpha_19-09-2015.ino:192:46: error: 'dry_get_data' was not declared in this scope
 
Ошибка компиляции.
 
 
 
pav2000
Offline
Зарегистрирован: 15.12.2014

Калибровка это хорошо. Неплохо поверить в нескольких точках.

По stm32 предлагаю писать в соответвующей ветке. 

Плата выбрана правильно.

По ошибкам  комилятор не находит функций. В проекте (в директории) должно лежать три файла которые я выкладывал.

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

Новая версия прошивки. Добавлен новый функционал:

1. В информационном экране длительное нажатие, без отпускания (5 сек.) кнопки сбрасывает минимальные и максимальные сохраненные температуры.

2. В основном экране длительное нажатие без отпускания кнопки (10 сек.) приводит к сбросу контроллера дисплея. Сделано для того что бы сбрасывать дисплей ili9341 если он зависнет. У меня этого нет, но пусть будет. Изображение и графики перечерчиваются заново.

 

#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.70 25/09/15"              // Текущая версия
#define ID             0x22                       // уникально Идентификатор устройства - старшие 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          5                      // Гистерезис абсолютной влажности в сотых грамма на куб
#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 2000               // Время опроса датчиков мсек, для демки быстрее
    #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 RESET_TFT_KEY   9999                    // Очень длительное нажатие на клавишу мсек, сброс контроллера дисплея и появляется основной экран данные сохраняются
#define LONG_KEY        2000                    // Длительное нажатие кнопки мсек, появляется Экран инфо
#define SHORT_KEY       100                     // Короткое нажатие кнопки более мсек
#define RESET_TEMP_KEY  4000                    // Время нажатия кнопки более мсек для сброса температур
#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 CS
#define PIN_CD        9                          // TFT дисплей spi CD
#define PIN_RESET     8                          // TFT дисплей spi Reset  дисплей
#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;                       // Время нажатия копки
long time_key_res_temp=-1;                      // время нажатия кнопки для сброса температур  -1 кнопка не нажата
long time_key_res_tft=-1;                       // время нажатия кнопки для сброса дисплея  -1 кнопка не нажата
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
     int16_t  tOutMin=5555,tInMin=5555;         // Минимальные температуры за период температуры в сотых градуса !!! место экономим
     int16_t  tOutMax,tInMax=-5555;             // Максимальные температуры за период температуры в сотых градуса. (Наблюдаю глюк при tOutMax=-5000)
     int16_t tick_eeprom=0;                     // Переменная для хранения времени записи статистики раз в один час хранится в NUM_SAMPLES*TIME_SCAN_SENSOR сравнение с TIME_HOUR
     
  }; 
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);                  // Количество пауза и количество повторов 
  // Рекомендуют первые 2-4 байта адреса устанавливать в E7 или 18 он проще детектируется чипом
  radio.openWritingPipe(0xE7E7E7E7E1LL);    // передатчик 
  radio.openReadingPipe(1,0xE7E7E7E7D2LL);  // приемник
  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;
      settingRAM.tOutMin=5000;
      settingRAM.tInMin=5000; 
      settingRAM.tOutMax=-5000;
      settingRAM.tInMax=-5000;   
      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(300);
     delay(300);
     beep(600);
  #endif
resetKey();
}

void loop()
{
 wdt_reset();                        // Сбросить сторожевой таймер
// Обработка нажатия кнопки
// 1. Высший приоритет - сброс контроллера дисплея если он зависнeт - держать долго сброс пройдет отпустить кнопку
if ((flagKey==true)&&(pressKey!=true))
{
if (time_key_res_tft==-1) time_key_res_tft=millis();     // первый вход - нажата кнопка запомнить время нажатия
   else if (millis()>time_key_res_tft+RESET_TFT_KEY)     // кнопка нажата более RESET_TFT_KEY сек сбрасываем дисплей
    {
    reset_ili9341();               // сброс дисплея 
    ucg.setColor(255, 255, 255);
    print_StrXY(10,100,F("Сброс контроллера дисплея ili9341 . . ."));
    print_StrXY(10,129,F("Кнопку нужно отпустить"));
    delay(1000);
    reset_ili9341();               // сброс дисплея   
    print_static();                // распечатать таблицу
    Setting();                     // Применить настройки
    print_data();                  // вывод усредненных значений 
    print_status();                // панель состояния
    printChart();                  // показать график
    infoScreen=false;              // нет информационного экрана
    #ifdef BEEP
       beep(400);                  // Звук нажатия на клавишу
    #endif
    resetKey();
   }  
}
// 2. Второй приоритет - сброс памяти температур работает только в информационном экране
if ((flagKey==true)&&(pressKey!=true)&&(infoScreen==true)) // Во время информационнного экрана нажата кнопка - подготовка и стирание температур min max
{  
   if (time_key_res_temp==-1) time_key_res_temp=millis(); // первый вход - нажата кнопка для стирания температур 
   else if (millis()>time_key_res_temp+RESET_TEMP_KEY)    // кнопка нажата более RESET_TEMP_KEY сек сбрасываем температуры
          {
          settingRAM.tOutMin= 5000;
          settingRAM.tInMin=  5000; 
          settingRAM.tOutMax=-5000;
          settingRAM.tInMax= -5000;   
          writeEeprom();                                 // Сбросили температуры сохраняем в EEPROM  
          #ifdef BEEP
               beep(500);                                // Звук нажатия на клавишу
          #endif
          printInfo();                                   // Обновить экран с новыми температурами
          resetKey();
          }
} 
else  // 3. Остальной обработчик нажатия на кнопок - Кнопку надо нажать и отпустить
if ((flagKey!=false)&&(pressKey==true))   // Кнопка была нажата и отпущена
  {
//  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();                                                //  Сброс состояния кнопки
 }
}

void resetKey(void)  // Сброс состояния кнопки
{
  flagKey=false;                                             
  pressKey=false;
  time_key_res_temp=-1;   // сброс состояния сброса температур
  time_key_res_tft=-1;    // сброс состояния сброса дисплея
}  
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)  // Пора усреднять и выводить значения 
 {
        // вычисление средних значений
         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();                           // панель состояния
         
         // Обновляем максимум и минимум температур в EEPROM
         // Cделано не честно - обновляем раз в 30 секунд (NUM_SAMPLES*TIME_SCAN_SENSOR) а пишем раз в час, могут пропасть данные при сбросе
         if (settingRAM.tInMax<packet.tIn) settingRAM.tInMax=packet.tIn;
         else if (settingRAM.tInMin>packet.tIn) settingRAM.tInMin=packet.tIn;
         
         if (settingRAM.tOutMax<packet.tOut) settingRAM.tOutMax=packet.tOut;
         else if (settingRAM.tOutMin>packet.tOut) settingRAM.tOutMin=packet.tOut;
 
         // Пишем в EEPROM не каждый раз!! экономим ресурс записей 100000 мин.
         settingRAM.tick_eeprom++;
         if (((long)settingRAM.tick_eeprom*NUM_SAMPLES*TIME_SCAN_SENSOR)>=(long)TIME_HOUR) // пора писать eeprom
            { writeEeprom();  settingRAM.tick_eeprom=0;  } 

             // Запись статистики в EEPROM
         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(25);                                        // Задержка для подавления дребезга контактов
    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+50)) HEAT_OFF;    // Выключить калорифер когда температура поднимится на 0.5 градуса

// 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>3% "));    packet.dH_min=30;  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>6%  "));   packet.dH_min=60;  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();      // Показать панель состояния (смена настроек)
 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(5, 5, 320-1-10, 240-1-10);
  ucg.setColor(0, 50, 250);
  ucg.drawFrame(5+5, 5+5, 320-1-20, 240-1-20);
  
  ucg.setFontMode(UCG_FONT_MODE_TRANSPARENT);
  ucg.setColor(0, 200, 10);
  print_StrXY(35,12+16,F("ОСУШИТЕЛЬ на Arduino Pro Mini"));
  
  ucg.setColor(0, 50, 50);
  print_StrXY(5+10,15+16*2,F("1 Напряжение питания В.")); 
  print_floatXY(5+230,15+16*2,readVcc()/1000.0);
 
  print_StrXY(5+10,15+16*3,F("2 Температура блока гр.")); 
  print_floatXY(5+230,15+16*3,GetTemp());
 
  print_StrXY(5+10,15+16*4,F("3 Мото часы блока")); 
  ucg.setPrintPos(5+230,15+16*4); 
  ucg.print(settingRAM.hour_unit/(TIME_HOUR/(NUM_SAMPLES*TIME_SCAN_SENSOR))); 
  
  print_StrXY(5+10,15+16*5,F("4 Мото часы вентилятора")); 
  ucg.setPrintPos(5+230,15+16*5); 
  ucg.print(settingRAM.hour_motor/(TIME_HOUR/(NUM_SAMPLES*TIME_SCAN_SENSOR))); 
  
  print_StrXY(5+10,15+16*6,F("5 Мото часы нагревателя")); 
  ucg.setPrintPos(5+230,15+16*6); 
  ucg.print(settingRAM.hour_heat/(TIME_HOUR/(NUM_SAMPLES*TIME_SCAN_SENSOR)));  
 
  print_StrXY(5+10,15+16*7,F("6 Мах температуры in/out")); 
  ucg.setPrintPos(5+230,15+16*7);
  ucg.print(settingRAM.tInMax/100); 
  ucg.print(F("/"));
  ucg.print(settingRAM.tOutMax/100); 
  
  print_StrXY(5+10,15+16*8,F("7 Мin температуры in/out")); 
  ucg.setPrintPos(5+230,15+16*8);
  ucg.print(settingRAM.tInMin/100); 
  ucg.print(F("/"));
  ucg.print(settingRAM.tOutMin/100); 
  
  print_StrXY(5+10,15+16*9,F("8 Канал NRF24l01+")); 
  ucg.setPrintPos(5+230,15+16*9); 
  ucg.print(NRF24_CHANEL); 
  
  print_StrXY(5+10,15+16*10,F("9 Гистерезис T/absH   ")); 
  ucg.print((float)dT_OFF/100.0);
  ucg.print(F("/"));
  ucg.print((float)dH_OFF/100.0);
  
  print_StrXY(5+10,15+16*11,F("10 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.drawHLine(40,18+16*11,320-1-80);
 
  ucg.setColor(0, 0, 150);
  print_StrXY(5+10,19+16*12,F("СБРОС - Вкл. при нажатой кнопке.")); 
 
  ucg.setColor(250,80,80);
  print_StrXY(5+10,19+16*13,F(VERSION)); 
  #ifdef  RADIO     // Радио модуль NRF42l вывести число ошибок ask
     ucg.setColor(0, 150, 10);
     ucg.print(F(" AskErr:"));
     ucg.print(err_ask);
  #endif
  sei();
  
  #ifdef BEEP
   beep(40);
  #endif
} 

void clearInfo()  // Стереть информационный экран
{
      infoScreen=false;
      last_error=100;         // Признак обновления ошибки
      cli();
      ucg.setColor(0, 0, 0);  // залить черным
      ucg.drawBox(5, 5, 320-1-10, 240-1-10);
      print_static();
      Setting();  
      print_data();                            
      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

 

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

Отлично. Проверим... У меня ниразу не зависал дисплей...

Потратил немного времени, попытался изобразить мамку, покритикуйте...

Размер 83х60

flyfisher
Offline
Зарегистрирован: 28.09.2015

Отличный проект, собираю подобное утроство, почерпнул много нового.

Спасибо!

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

miaua пишет:

Потратил немного времени, попытался изобразить мамку, покритикуйте...

Размер 83х60

Критика -))

1.  Что то я стабилизатора на 3.3 вольта не вижу для nrf24, Встроенный  на про мини не потянет.

2. nrf24 ногу прерывания лучше завести на 2 или 3 ногу про мини (котрая свободна от кнопки), Плата получится более универсальной.

3. Конденсаторы керамические пару тройку по питанию раскидать.

4. Честно говоря если заморачиваться платой то наверное  имеет смысл на нее вкрячить твердртельные реле и отказаться от реле шилда ввобще.

 

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

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

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

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

1. Действительно пропустил 3,3В -исправил, поставил LD1117s3.3

2.Было предусмотрено кидать проводок на D2, между переходнымим отверстиями. сейчас кинул перемычку.

3.Керамика разбрасывается прямо на шины питания, потому я их так близко развел.

4. Дело в том, что у меня уже все установлено в подвале гаража.. Шилд на 4 реле с опторазвязкой в гермобоксе и подведено силовое питание 220В. Тепловентилятор и вентилятор приточной трубы установлены штатно уже 5 лет. Модуль управления будет находиться на верху в гараже, дистанция около 6м...Для соединения с датчиками и реле будет использован экранированный кабель 8х0,22. Радио канал в гараже использоваться не будет.

А второй комплект для дачи с подвалом, будет делаться в полном объеме в т.ч. и радио.

П.С.

Что-то не смог найти tft2.8" или 3,2" с SPI Ili 9341 без тачьпада по нормальной цене. 2,4 полно..

 

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

Ethernet бы еще прикрутить

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

Thunderix пишет:

Ethernet бы еще прикрутить

Места не хватит, если только вместо nrf24 но все равно места мало.

На короткие расстояния (до 50 метров ) nrf24 с усилком и внешенй антенной работает достаточно стабильно. Я сеть планирую прикручивать  к приемнику (который на stm32). Он будет собирать со всех устройств данные и потом можно будет смотреть через инет их. Лог на карту или на USB флеш. Но это пока планы.

miaua пишет:

4. Дело в том, что у меня уже все установлено в подвале гаража.. Шилд на 4 реле с опторазвязкой в гермобоксе и подведено силовое питание 220В. Тепловентилятор и вентилятор приточной трубы установлены штатно уже 5 лет. Модуль управления будет находиться на верху в гараже, дистанция около 6м...Для соединения с датчиками и реле будет использован экранированный кабель 8х0,22. Радио канал в гараже использоваться не будет.

А второй комплект для дачи с подвалом, будет делаться в полном объеме в т.ч. и радио.

П.С.

Что-то не смог найти tft2.8" или 3,2" с SPI Ili 9341 без тачьпада по нормальной цене. 2,4 полно..

Там можно развести сразу оба варианта на одной плате (с выводами на реле и с силовой частью ) . И первый экземпляр распаять частично, а второй полностью. ИХМО трудозатрат будет меньше. И сделать сразу две три одинаковые платы.

Дисплей 2.8 брал здесь http://www.ebay.com/itm/251885164786?_trksid=p2057872.m2749.l2649&ssPageName=STRK%3AMEBIDX%3AIT

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

Убрать nrf24 и экранчик впринципе можно тоже, тогда место будет. А смотреть удаленно. утпшку раскидать проще и дешевле.

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

Я почитал даташит по "Твердотельные реле серий SSR и TSR"... А там такое написано:- "применяется только с резистивной, емкостной нагрузкой и нельзя с высокоиндуктивной."

А какого типа Вы хотите применить реле?

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

Любое твердотельное реле не любит индуктивную нагрузку. Самоиндукция может пробить его. Но вентилятор 30-50 ватт наверно является не сильно индуктивной нагрузкой.

Я использую шарп s2002s02. И паралельно реле установлен конденсатор 0.1 мкф 400 вольт.

Напомню, использование обычного реле у меня приводило к сбросу контроллера дисплея. Имено из за индуктивной нагрузки.

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

miaua, для вентиляторов у меня разведены связки MOC3061+BT134. (схема в даташите на MOC). Снабберные цепочки не ставил, с Домовентами второй год работает без проблем, радиаторы не требуются.

Твердотелы есть смысл ставить на мощные нагрузки, где требуется большой теплоотвод или кто не разводит плату, иначе - выброшенные деньги. ИМХО.

pav2000, 400В конденсатор очень на пределе, лучше 600В.

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

Очередная версия кода. Код разросся и в режиме демо уже не влезает - пришлось отключть часть кода в этом режиме (инфо экран).

Основные изменения - встроил часы. Время передается в ответе головного блока (в stm32 нашел встроенные rtc). Далее блок сам считает секунды (по таймеру измерения). При новом ответе головного блока производится корректировка времени.  Получилось изящно и без аппартных затрат.

Немного скорректировал настройки блока с учетом его эксплуатации - сделал более "грубое" включение.

Наверное проект пришел к своему логическому завершению - я реализовал все свои мыслимые задумки.

/*
Описание
1. Часы реального времени. Данные приходят от головного блока. Потом обновлются по таймеру (). Пришедшие данные имеют более высокий приоритет
1.1 Установить библиотеку  Time library - https://github.com/PaulStoffregen/Time  
У нее не удачное имя (винды регистронезависимы и имя совпадает с системным -- среда глючит)
переименовать в stmTime  директорию, файлы и ссылки в них !!!
*/

#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.72 07/10/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          5                      // Гистерезис абсолютной влажности в сотых грамма на куб
#define dT_OFF          11                      // Гистерезис температуры в сотых градуса
#define TEMP_LOW       150                     // Температура подвала критическая - может быть замораживание (в сотых градуса) - система выключается и включается нагреватель
// СИСТЕМАТИЧЕСКИЕ ОШИБКИ ДАТЧИКОВ для каждого ID свой. ОШИБКИ ДОБАВЛЯЮТСЯ!!
#if ID == 0x21      // ОТКАЛИБРОВАНО не менять
    #define TOUT_ERR      +10                  // Ошибка уличного датчика температуры в сотых долях градуса
    #define TIN_ERR       +50                  // Ошибка домового датчика температуры в сотых долях градуса
    #define HOUT_ERR      -0                   // Ошибка уличного датчика влажности в сотых долях %
    #define HIN_ERR       +50                  // Ошибка домового датчика влажности в сотых долях %
#elif ID == 0x22     // ОТКАЛИБРОВАНО не менять
    #define TOUT_ERR      +50                  // Ошибка уличного датчика температуры в сотых долях градуса +50
    #define TIN_ERR       +50                  // Ошибка домового датчика температуры в сотых долях градуса +50
    #define HOUT_ERR      -230+60                 // Ошибка уличного датчика влажности в сотых долях %         -260
    #define HIN_ERR       +490                 // Ошибка домового датчика влажности в сотых долях %         +480
#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 2000               // Время опроса датчиков мсек, для демки быстрее
    #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 RESET_TFT_KEY   9999                    // Очень длительное нажатие на клавишу мсек, сброс контроллера дисплея и появляется основной экран данные сохраняются
#define LONG_KEY        2000                    // Длительное нажатие кнопки мсек, появляется Экран инфо
#define SHORT_KEY       100                     // Короткое нажатие кнопки более мсек
#define RESET_TEMP_KEY  4000                    // Время нажатия кнопки более мсек для сброса температур
#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 CS
#define PIN_CD        9                          // TFT дисплей spi CD
#define PIN_RESET     8                          // TFT дисплей spi Reset  дисплей
#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                        // Режим охлаждение (вариант настроек)

#include   "stmTime.h"     // Time library - https://github.com/PaulStoffregen/Time
//#define TZ "UTC+3"       // Часовой пояс
unsigned long tt=0;        // Время пришедшее от головного блока если 0 то вроемя не приходило ничего не выводим
// АЦП ----------------------------------------
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;                       // Время нажатия копки
long time_key_res_temp=-1;                      // время нажатия кнопки для сброса температур  -1 кнопка не нажата
long time_key_res_tft=-1;                       // время нажатия кнопки для сброса дисплея  -1 кнопка не нажата
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
     int16_t  tOutMin=5555,tInMin=5555;         // Минимальные температуры за период температуры в сотых градуса !!! место экономим
     int16_t  tOutMax,tInMax=-5555;             // Максимальные температуры за период температуры в сотых градуса. (Наблюдаю глюк при tOutMax=-5000)
     int16_t tick_eeprom=0;                     // Переменная для хранения времени записи статистики раз в один час хранится в NUM_SAMPLES*TIME_SCAN_SENSOR сравнение с TIME_HOUR
     
  }; 
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);                  // Количество пауза и количество повторов 
  // Рекомендуют первые 2-4 байта адреса устанавливать в E7 или 18 он проще детектируется чипом
  radio.openWritingPipe(0xE7E7E7E7E1LL);    // передатчик 
  radio.openReadingPipe(1,0xE7E7E7E7D2LL);  // приемник
  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;
      settingRAM.tOutMin=5000;
      settingRAM.tInMin=5000; 
      settingRAM.tOutMax=-5000;
      settingRAM.tInMax=-5000;   
      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(300);
     delay(300);
     beep(600);
  #endif
resetKey();
}

void loop()
{
 wdt_reset();                        // Сбросить сторожевой таймер
// Обработка нажатия кнопки
// 1. Высший приоритет - сброс контроллера дисплея если он зависнeт - держать долго сброс пройдет отпустить кнопку
if ((flagKey==true)&&(pressKey!=true))
{
if (time_key_res_tft==-1) time_key_res_tft=millis();     // первый вход - нажата кнопка запомнить время нажатия
   else if (millis()>time_key_res_tft+RESET_TFT_KEY)     // кнопка нажата более RESET_TFT_KEY сек сбрасываем дисплей
    {
    reset_ili9341();               // сброс дисплея 
    ucg.setColor(255, 255, 255);
    print_StrXY(10,100,F("Сброс контроллера дисплея ILI9341 . . ."));
    print_StrXY(10,129,F("Кнопку нужно отпустить"));
    delay(1000);
    reset_ili9341();               // сброс дисплея   
    print_static();                // распечатать таблицу
    Setting();                     // Применить настройки
    print_data();                  // вывод усредненных значений 
    print_status();                // панель состояния
    printChart();                  // показать график
    infoScreen=false;              // нет информационного экрана
    #ifdef BEEP
       beep(400);                  // Звук нажатия на клавишу
    #endif
    resetKey();
   }  
}
// 2. Второй приоритет - сброс памяти температур работает только в информационном экране
if ((flagKey==true)&&(pressKey!=true)&&(infoScreen==true)) // Во время информационнного экрана нажата кнопка - подготовка и стирание температур min max
{  
   if (time_key_res_temp==-1) time_key_res_temp=millis(); // первый вход - нажата кнопка для стирания температур 
   else if (millis()>time_key_res_temp+RESET_TEMP_KEY)    // кнопка нажата более RESET_TEMP_KEY сек сбрасываем температуры
          {
          settingRAM.tOutMin= 5000;
          settingRAM.tInMin=  5000; 
          settingRAM.tOutMax=-5000;
          settingRAM.tInMax= -5000;   
          writeEeprom();                                 // Сбросили температуры сохраняем в EEPROM  
          #ifdef BEEP
               beep(500);                                // Звук нажатия на клавишу
          #endif
          printInfo();                                   // Обновить экран с новыми температурами
          resetKey();
          }
} 
else  // 3. Остальной обработчик нажатия на кнопок - Кнопку надо нажать и отпустить
if ((flagKey!=false)&&(pressKey==true))   // Кнопка была нажата и отпущена
  {
//  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();                                                //  Сброс состояния кнопки
 }
}

void resetKey(void)  // Сброс состояния кнопки
{
  flagKey=false;                                             
  pressKey=false;
  time_key_res_temp=-1;   // сброс состояния сброса температур
  time_key_res_tft=-1;    // сброс состояния сброса дисплея
}  

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);
  for (i=0;i<5;i++) ucg.drawHLine(0,25+23*i,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) // Печать панели статуса Значки на статус панели
{
  byte temp;
  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);
  // 4.  Вывод времени на экран
  if (tt>0) // выводим если было первоначальная установка от головного блока 
     {
      ucg.setColor(0, 0, 180);  // 
      ucg.drawBox(180, 0, 45, 23);
      ucg.setColor(250, 250, 0);
      ucg.setPrintPos(180,19);
      temp=hour(tt);
      if (temp<10) ucg.print("0");
      ucg.print(temp);
      ucg.print(":");
      temp=minute(tt);
      if (temp<10) ucg.print("0");
      ucg.print(temp);
     }
  #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(280, 0, 36, 18);
      ucg.setPrintPos(280,18); 
      ucg.setFontMode(UCG_FONT_MODE_TRANSPARENT);
      if (packet.DHT_error>0) 
        { 
        ucg.setColor(255, 100, 100); 
        print_StrXY(280,18,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);
        cli();
          radio.writeBlocking(&packet,sizeof(packet),200);  // Writes 1 payload to the buffers
          send_packet_ok=radio.txStandBy();
          if ( radio.isAckPayloadAvailable() )   // Ждем получения -- основной блок передает текущее время
          radio.read(&tt,sizeof(tt));            //... и имеем переменную tt с временем от приемника.
        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++;            // при переполнении сам сбросится
 }  
#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)  // Пора усреднять и выводить значения 
 {
        // вычисление средних значений
         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();                           // панель состояния
         
         // Обновляем максимум и минимум температур в EEPROM
         // Cделано не честно - обновляем раз в 30 секунд (NUM_SAMPLES*TIME_SCAN_SENSOR) а пишем раз в час, могут пропасть данные при сбросе
         if (settingRAM.tInMax<packet.tIn) settingRAM.tInMax=packet.tIn;
         else if (settingRAM.tInMin>packet.tIn) settingRAM.tInMin=packet.tIn;
         
         if (settingRAM.tOutMax<packet.tOut) settingRAM.tOutMax=packet.tOut;
         else if (settingRAM.tOutMin>packet.tOut) settingRAM.tOutMin=packet.tOut;
 
         // Пишем в EEPROM не каждый раз!! экономим ресурс записей 100000 мин.
         settingRAM.tick_eeprom++;
         if (((long)settingRAM.tick_eeprom*NUM_SAMPLES*TIME_SCAN_SENSOR)>=(long)TIME_HOUR) // пора писать eeprom
            { writeEeprom();  settingRAM.tick_eeprom=0;  } 

             // Запись статистики в EEPROM
         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
             } 
        }
    #ifdef  RADIO       // Радио модуль NRF42l 
        // Часы работают если только был получено время от головного блока (хотя бы один раз) в противном случае они даже не показываются на экране
        if (tt>0) tt=tt+TIME_SCAN_SENSOR/1000;       // Обновить текущее время - что бы часы шли и без связи с базовым блоком  
    #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(25);                                        // Задержка для подавления дребезга контактов
    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+50)) HEAT_OFF;    // Выключить калорифер когда температура поднимится на 0.5 градуса

// 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+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.3 "));   packet.dH_min=30;  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.6 "));   packet.dH_min=60;  packet.T_min=40;  FLAG_ABS_H_ON;  break;
        case  6:         ucg.print(F("Осушение T>+4 dH>10% "));  packet.dH_min=100; packet.T_min=40;  FLAG_ABS_H_OFF; break;
        case  7:         ucg.print(F("Осушение T>+5 dH>0.8 "));   packet.dH_min=80;  packet.T_min=50;  FLAG_ABS_H_ON;  break;
        } 
 writeEeprom();       // Запись в EEPROM  новых настроек
 SET_MODE_BITS;       // В пакет для передачи добавили смену режима
 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;
  cli();
  ucg.setColor(250, 250, 250);  // 
  ucg.drawBox(5, 5, 320-1-10, 240-1-10);
  ucg.setColor(0, 50, 250);
  ucg.drawFrame(5+5, 5+5, 320-1-20, 240-1-20);
  
  ucg.setFontMode(UCG_FONT_MODE_TRANSPARENT);
  ucg.setColor(0, 200, 10);
  print_StrXY(35,12+16,F("ОСУШИТЕЛЬ на Arduino Pro Mini"));
  
  ucg.setColor(0, 50, 50);
  print_StrXY(5+10,15+16*2,F("1 Напряжение питания В.")); 
  print_floatXY(5+222,15+16*2,readVcc()/1000.0);
 
  print_StrXY(5+10,15+16*3,F("2 Температура блока гр.")); 
  print_floatXY(5+222,15+16*3,GetTemp());
 
  print_StrXY(5+10,15+16*4,F("3 Мото часы блока")); 
  ucg.setPrintPos(5+222,15+16*4); 
  ucg.print(settingRAM.hour_unit/(TIME_HOUR/(NUM_SAMPLES*TIME_SCAN_SENSOR))); 
  
  print_StrXY(5+10,15+16*5,F("4 Мото часы вентилятора")); 
  ucg.setPrintPos(5+222,15+16*5); 
  ucg.print(settingRAM.hour_motor/(TIME_HOUR/(NUM_SAMPLES*TIME_SCAN_SENSOR))); 
  
  print_StrXY(5+10,15+16*6,F("5 Мото часы нагревателя")); 
  ucg.setPrintPos(5+222,15+16*6); 
  ucg.print(settingRAM.hour_heat/(TIME_HOUR/(NUM_SAMPLES*TIME_SCAN_SENSOR)));  
 
  print_StrXY(5+10,15+16*7,F("6 Мах температуры in/out")); 
  ucg.setPrintPos(5+222,15+16*7);
  ucg.print((float)settingRAM.tInMax/100,1); 
  ucg.print(F("/"));
  ucg.print((float)settingRAM.tOutMax/100,1); 
  
  print_StrXY(5+10,15+16*8,F("7 Мin температуры in/out")); 
  ucg.setPrintPos(5+222,15+16*8);
  ucg.print((float)settingRAM.tInMin/100,1); 
  ucg.print(F("/"));
  ucg.print((float)settingRAM.tOutMin/100,1); 
  
  print_StrXY(5+10,15+16*9,F("8 Канал NRF24l01+")); 
  ucg.setPrintPos(5+222,15+16*9); 
  ucg.print(NRF24_CHANEL); 
  
  print_StrXY(5+10,15+16*10,F("9 Гистерезис T/absH   ")); 
  ucg.print((float)dT_OFF/100.0);
  ucg.print(F("/"));
  ucg.print((float)dH_OFF/100.0);
  
#ifndef DEMO     // Уже места не хватает в DEMO. В DEMO кусок ниже не показывается и код сокращается
  print_StrXY(5+10,15+16*11,F("10 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.drawHLine(40,18+16*11,320-1-80);
 
  ucg.setColor(0, 0, 150);
  print_StrXY(5+10,19+16*12,F("СБРОС - Вкл. при нажатой кнопке.")); 
 
  ucg.setColor(250,80,80);
  print_StrXY(5+10,19+16*13,F(VERSION)); 
  
  #ifdef  RADIO     // Радио модуль NRF42l вывести число ошибок ask
     ucg.setColor(0, 150, 10);
     ucg.print(F(" AskErr:"));
     ucg.print(err_ask);
  #endif
  sei();
 #endif   // Сокращение кода
  #ifdef BEEP
   beep(40);
  #endif
 
} 

void clearInfo()  // Стереть информационный экран
{
      infoScreen=false;
      last_error=100;         // Признак обновления ошибки
      cli();
      ucg.setColor(0, 0, 0);  // залить черным
      ucg.drawBox(5, 5, 320-1-10, 240-1-10);
      print_static();
      Setting();  
      print_data();                            
      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

У нас уже был минус на улице. Блок показал свою эффективность. У меня в   подавле влажность за неделю упала с 7 грамм на куб до 3.8 грамма на куб. И еще у меня картошка в этом подвале лежит.

Установил второй блок во втором подвале. Откалибровал датчики. Один выбросил - врал по влажности на +15% решил что это не примелемо.

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

За месяц эксплуатации AM2302, оба датчика начали завышать показания. И что удивительно, тот что был на улице и попадал под дождь завышает на 1,45%, а второй, в комнате- на 2,54%. Это данные по NaCl -75%. А по влажности над поверхностью воды, уличный выдал 97,65%, а комнатный 100,65%. Данные по выдержке более 24 часов.

И немного фото.

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

miaua, это вы уже придираетесь, у них по даташиту, вроде 2% погрешность. А еще скидка на желтых братьев.

Причем, выше 90 и ниже 15 уже до 5% погрешность.

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

Меня больше волнует, что будет с датчиками через 5-6 месяцев. Моя система это время работает автономно, т.е. до весны. +- 5% меня не волнует. Главное чтобы не поменялась функция прибора на обратную.. :)

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

Скорее всего все будет нормально. Дрейф у вас даже за заявленную погрешность не вышел. Наблюдается он  у любых датчиков, а наиболее сильно в первые часы работы. Поверку приборов не зря придумали)))).
Мой АМ2301 если и уполз, то на единицы %, поэтому по серьезному не проверял, иногда выносной модуль OREGON к нему заношу на сутки.
Ну а оставлять бесконтрольную систему на полгода, даже для отработанных решений не рекомендуется. Светик мигающий наружу выведите и соседа за вкусный напиток посматривать попросите, либо что то дублирующее (типа нагревателей с термостатом.) ИМХО.

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

Отличная работа miaua!  Получилось железо отлично.  Теперь откорпусить и в бой.

По датчикам (по моим наблюдениям).

Дейстиветльно используемые со временем "уходят". По этому я второе устройство калибровал после недели работы.  Заоодно проверил и первое - показания не ушли.  У меня два устройства, и я могу сравниватьих показания. т.к. датчики установлены в разных местах на улице, то показания разные.  У меня разница по температуре бывает до 2 градусов. Я проверял термометром и он показал правильность показаний обоих датчиков. Действительно в разных местах разная температура (у меня один датчик не очень правильно весит). Также разнятся и влажности до 3-4 %.  Самое интерсное что абсолютные влажности отличаются на сотые грамма.

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

Как я понял измерение влажности проблемная вещь везде. у датчика есть открытая чуствительная поверхность на которую осаждается из воздуха разные вещества (и датчик уходит).  По этому я теперь все датчики монтирую сеткой вниз и в банках (дно вверху, банка перевернута и в боковых стенках есть небольшие отверстия)  Попадание воды в датчик приводит к фатальным последсвиям (у меня он стал завышать показания на 15%).

Понаблюдав  за показаниями (дневной разброс авболютной влажности достигает 3-4 грамма на куб на улице) я пришел к выводу что выставленные границы для включения и выключения  у меня малы. Я их увеличил в последней выложенной версии кода. Возможно их надо делать еще больше.

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

Но проверить (у меня хотя бы сравнивать ) раз в год надо.

Если есть сосед ему можно вручить дистанционный пульт пусть использует как метеостанцию - смотит влажность и температура улицы. Заодно и за вашим устройством приглядит.

ToteMeiSter
Offline
Зарегистрирован: 01.04.2015

Не пробовали в качестве датчика BME280? 

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

Товарищи выложите библиотеку time переделанную, не могу сообразить в самих файлах что менять и в каких.:(

А ни кто не пробывал питатся от 3.3 на всё, вроде где то читал что от 3.3 работает на 16 мгц про мини.

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

Лови библиотеку (три файла)

stmTime.h

/*
  time.h - low level time and date functions
*/

/*
  July 3 2011 - fixed elapsedSecsThisWeek macro (thanks Vincent Valdy for this)
              - fixed  daysToTime_t macro (thanks maniacbug)
*/     

#ifndef _Time_h
#ifdef __cplusplus
#define _Time_h

#include <inttypes.h>
#ifndef __AVR__
#include <sys/types.h> // for __time_t_defined, but avr libc lacks sys/types.h
#endif


#if !defined(__time_t_defined) // avoid conflict with newlib or other posix libc
typedef unsigned long time_t;
#endif


// This ugly hack allows us to define C++ overloaded functions, when included
// from within an extern "C", as newlib's sys/stat.h does.  Actually it is
// intended to include "time.h" from the C library (on ARM, but AVR does not
// have that file at all).  On Mac and Windows, the compiler will find this
// "Time.h" instead of the C library "time.h", so we may cause other weird
// and unpredictable effects by conflicting with the C library header "time.h",
// but at least this hack lets us define C++ functions as intended.  Hopefully
// nothing too terrible will result from overriding the C library header?!
extern "C++" {
typedef enum {timeNotSet, timeNeedsSync, timeSet
}  timeStatus_t ;

typedef enum {
    dowInvalid, dowSunday, dowMonday, dowTuesday, dowWednesday, dowThursday, dowFriday, dowSaturday
} timeDayOfWeek_t;

typedef enum {
    tmSecond, tmMinute, tmHour, tmWday, tmDay,tmMonth, tmYear, tmNbrFields
} tmByteFields;	   

typedef struct  { 
  uint8_t Second; 
  uint8_t Minute; 
  uint8_t Hour; 
  uint8_t Wday;   // day of week, sunday is day 1
  uint8_t Day;
  uint8_t Month; 
  uint8_t Year;   // offset from 1970; 
} 	tmElements_t, TimeElements, *tmElementsPtr_t;

//convenience macros to convert to and from tm years 
#define  tmYearToCalendar(Y) ((Y) + 1970)  // full four digit year 
#define  CalendarYrToTm(Y)   ((Y) - 1970)
#define  tmYearToY2k(Y)      ((Y) - 30)    // offset is from 2000
#define  y2kYearToTm(Y)      ((Y) + 30)   

typedef time_t(*getExternalTime)();
//typedef void  (*setExternalTime)(const time_t); // not used in this version


/*==============================================================================*/
/* Useful Constants */
#define SECS_PER_MIN  (60UL)
#define SECS_PER_HOUR (3600UL)
#define SECS_PER_DAY  (SECS_PER_HOUR * 24UL)
#define DAYS_PER_WEEK (7UL)
#define SECS_PER_WEEK (SECS_PER_DAY * DAYS_PER_WEEK)
#define SECS_PER_YEAR (SECS_PER_WEEK * 52UL)
#define SECS_YR_2000  (946684800UL) // the time at the start of y2k
 
/* Useful Macros for getting elapsed time */
#define numberOfSeconds(_time_) (_time_ % SECS_PER_MIN)  
#define numberOfMinutes(_time_) ((_time_ / SECS_PER_MIN) % SECS_PER_MIN) 
#define numberOfHours(_time_) (( _time_% SECS_PER_DAY) / SECS_PER_HOUR)
#define dayOfWeek(_time_)  ((( _time_ / SECS_PER_DAY + 4)  % DAYS_PER_WEEK)+1) // 1 = Sunday
#define elapsedDays(_time_) ( _time_ / SECS_PER_DAY)  // this is number of days since Jan 1 1970
#define elapsedSecsToday(_time_)  (_time_ % SECS_PER_DAY)   // the number of seconds since last midnight 
// The following macros are used in calculating alarms and assume the clock is set to a date later than Jan 1 1971
// Always set the correct time before settting alarms
#define previousMidnight(_time_) (( _time_ / SECS_PER_DAY) * SECS_PER_DAY)  // time at the start of the given day
#define nextMidnight(_time_) ( previousMidnight(_time_)  + SECS_PER_DAY )   // time at the end of the given day 
#define elapsedSecsThisWeek(_time_)  (elapsedSecsToday(_time_) +  ((dayOfWeek(_time_)-1) * SECS_PER_DAY) )   // note that week starts on day 1
#define previousSunday(_time_)  (_time_ - elapsedSecsThisWeek(_time_))      // time at the start of the week for the given time
#define nextSunday(_time_) ( previousSunday(_time_)+SECS_PER_WEEK)          // time at the end of the week for the given time


/* Useful Macros for converting elapsed time to a time_t */
#define minutesToTime_t ((M)) ( (M) * SECS_PER_MIN)  
#define hoursToTime_t   ((H)) ( (H) * SECS_PER_HOUR)  
#define daysToTime_t    ((D)) ( (D) * SECS_PER_DAY) // fixed on Jul 22 2011
#define weeksToTime_t   ((W)) ( (W) * SECS_PER_WEEK)   

/*============================================================================*/
/*  time and date functions   */
int     hour();            // the hour now 
int     hour(time_t t);    // the hour for the given time
int     hourFormat12();    // the hour now in 12 hour format
int     hourFormat12(time_t t); // the hour for the given time in 12 hour format
uint8_t isAM();            // returns true if time now is AM
uint8_t isAM(time_t t);    // returns true the given time is AM
uint8_t isPM();            // returns true if time now is PM
uint8_t isPM(time_t t);    // returns true the given time is PM
int     minute();          // the minute now 
int     minute(time_t t);  // the minute for the given time
int     second();          // the second now 
int     second(time_t t);  // the second for the given time
int     day();             // the day now 
int     day(time_t t);     // the day for the given time
int     weekday();         // the weekday now (Sunday is day 1) 
int     weekday(time_t t); // the weekday for the given time 
int     month();           // the month now  (Jan is month 1)
int     month(time_t t);   // the month for the given time
int     year();            // the full four digit year: (2009, 2010 etc) 
int     year(time_t t);    // the year for the given time

time_t now();              // return the current time as seconds since Jan 1 1970 
void    setTime(time_t t);
void    setTime(int hr,int min,int sec,int day, int month, int yr);
void    adjustTime(long adjustment);

/* date strings */ 
#define dt_MAX_STRING_LEN 9 // length of longest date string (excluding terminating null)
char* monthStr(uint8_t month);
char* dayStr(uint8_t day);
char* monthShortStr(uint8_t month);
char* dayShortStr(uint8_t day);
	
/* time sync functions	*/
timeStatus_t timeStatus(); // indicates if time has been set and recently synchronized
void    setSyncProvider( getExternalTime getTimeFunction); // identify the external time provider
void    setSyncInterval(time_t interval); // set the number of seconds between re-sync

/* low level functions to convert to and from system time                     */
void breakTime(time_t time, tmElements_t &tm);  // break time_t into elements
time_t makeTime(tmElements_t &tm);  // convert time elements into time_t

} // extern "C++"
#endif // __cplusplus
#endif /* _Time_h */

stmTime.cpp

/*
  time.c - low level time and date functions
  Copyright (c) Michael Margolis 2009-2014

  This library is free software; you can redistribute it and/or
  modify it under the terms of the GNU Lesser General Public
  License as published by the Free Software Foundation; either
  version 2.1 of the License, or (at your option) any later version.

  This library is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  Lesser General Public License for more details.

  You should have received a copy of the GNU Lesser General Public
  License along with this library; if not, write to the Free Software
  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
  
  1.0  6  Jan 2010 - initial release
  1.1  12 Feb 2010 - fixed leap year calculation error
  1.2  1  Nov 2010 - fixed setTime bug (thanks to Korman for this)
  1.3  24 Mar 2012 - many edits by Paul Stoffregen: fixed timeStatus() to update
                     status, updated examples for Arduino 1.0, fixed ARM
                     compatibility issues, added TimeArduinoDue and TimeTeensy3
                     examples, add error checking and messages to RTC examples,
                     add examples to DS1307RTC library.
  1.4  5  Sep 2014 - compatibility with Arduino 1.5.7
*/

#if ARDUINO >= 100
#include <Arduino.h> 
#else
#include <WProgram.h> 
#endif

#include "stmTime.h"

static tmElements_t tm;          // a cache of time elements
static time_t cacheTime;   // the time the cache was updated
static uint32_t syncInterval = 300;  // time sync will be attempted after this many seconds

void refreshCache(time_t t) {
  if (t != cacheTime) {
    breakTime(t, tm); 
    cacheTime = t; 
  }
}

int hour() { // the hour now 
  return hour(now()); 
}

int hour(time_t t) { // the hour for the given time
  refreshCache(t);
  return tm.Hour;  
}

int hourFormat12() { // the hour now in 12 hour format
  return hourFormat12(now()); 
}

int hourFormat12(time_t t) { // the hour for the given time in 12 hour format
  refreshCache(t);
  if( tm.Hour == 0 )
    return 12; // 12 midnight
  else if( tm.Hour  > 12)
    return tm.Hour - 12 ;
  else
    return tm.Hour ;
}

uint8_t isAM() { // returns true if time now is AM
  return !isPM(now()); 
}

uint8_t isAM(time_t t) { // returns true if given time is AM
  return !isPM(t);  
}

uint8_t isPM() { // returns true if PM
  return isPM(now()); 
}

uint8_t isPM(time_t t) { // returns true if PM
  return (hour(t) >= 12); 
}

int minute() {
  return minute(now()); 
}

int minute(time_t t) { // the minute for the given time
  refreshCache(t);
  return tm.Minute;  
}

int second() {
  return second(now()); 
}

int second(time_t t) {  // the second for the given time
  refreshCache(t);
  return tm.Second;
}

int day(){
  return(day(now())); 
}

int day(time_t t) { // the day for the given time (0-6)
  refreshCache(t);
  return tm.Day;
}

int weekday() {   // Sunday is day 1
  return  weekday(now()); 
}

int weekday(time_t t) {
  refreshCache(t);
  return tm.Wday;
}
   
int month(){
  return month(now()); 
}

int month(time_t t) {  // the month for the given time
  refreshCache(t);
  return tm.Month;
}

int year() {  // as in Processing, the full four digit year: (2009, 2010 etc) 
  return year(now()); 
}

int year(time_t t) { // the year for the given time
  refreshCache(t);
  return tmYearToCalendar(tm.Year);
}

/*============================================================================*/	
/* functions to convert to and from system time */
/* These are for interfacing with time serivces and are not normally needed in a sketch */

// leap year calulator expects year argument as years offset from 1970
#define LEAP_YEAR(Y)     ( ((1970+Y)>0) && !((1970+Y)%4) && ( ((1970+Y)%100) || !((1970+Y)%400) ) )

static  const uint8_t monthDays[]={31,28,31,30,31,30,31,31,30,31,30,31}; // API starts months from 1, this array starts from 0
 
void breakTime(time_t timeInput, tmElements_t &tm){
// break the given time_t into time components
// this is a more compact version of the C library localtime function
// note that year is offset from 1970 !!!

  uint8_t year;
  uint8_t month, monthLength;
  uint32_t time;
  unsigned long days;

  time = (uint32_t)timeInput;
  tm.Second = time % 60;
  time /= 60; // now it is minutes
  tm.Minute = time % 60;
  time /= 60; // now it is hours
  tm.Hour = time % 24;
  time /= 24; // now it is days
  tm.Wday = ((time + 4) % 7) + 1;  // Sunday is day 1 
  
  year = 0;  
  days = 0;
  while((unsigned)(days += (LEAP_YEAR(year) ? 366 : 365)) <= time) {
    year++;
  }
  tm.Year = year; // year is offset from 1970 
  
  days -= LEAP_YEAR(year) ? 366 : 365;
  time  -= days; // now it is days in this year, starting at 0
  
  days=0;
  month=0;
  monthLength=0;
  for (month=0; month<12; month++) {
    if (month==1) { // february
      if (LEAP_YEAR(year)) {
        monthLength=29;
      } else {
        monthLength=28;
      }
    } else {
      monthLength = monthDays[month];
    }
    
    if (time >= monthLength) {
      time -= monthLength;
    } else {
        break;
    }
  }
  tm.Month = month + 1;  // jan is month 1  
  tm.Day = time + 1;     // day of month
}

time_t makeTime(tmElements_t &tm){   
// assemble time elements into time_t 
// note year argument is offset from 1970 (see macros in time.h to convert to other formats)
// previous version used full four digit year (or digits since 2000),i.e. 2009 was 2009 or 9
  
  int i;
  uint32_t seconds;

  // seconds from 1970 till 1 jan 00:00:00 of the given year
  seconds= tm.Year*(SECS_PER_DAY * 365);
  for (i = 0; i < tm.Year; i++) {
    if (LEAP_YEAR(i)) {
      seconds +=  SECS_PER_DAY;   // add extra days for leap years
    }
  }
  
  // add days for this year, months start from 1
  for (i = 1; i < tm.Month; i++) {
    if ( (i == 2) && LEAP_YEAR(tm.Year)) { 
      seconds += SECS_PER_DAY * 29;
    } else {
      seconds += SECS_PER_DAY * monthDays[i-1];  //monthDay array starts from 0
    }
  }
  seconds+= (tm.Day-1) * SECS_PER_DAY;
  seconds+= tm.Hour * SECS_PER_HOUR;
  seconds+= tm.Minute * SECS_PER_MIN;
  seconds+= tm.Second;
  return (time_t)seconds; 
}
/*=====================================================*/	
/* Low level system time functions  */

static uint32_t sysTime = 0;
static uint32_t prevMillis = 0;
static uint32_t nextSyncTime = 0;
static timeStatus_t Status = timeNotSet;

getExternalTime getTimePtr;  // pointer to external sync function
//setExternalTime setTimePtr; // not used in this version

#ifdef TIME_DRIFT_INFO   // define this to get drift data
time_t sysUnsyncedTime = 0; // the time sysTime unadjusted by sync  
#endif


time_t now() {
	// calculate number of seconds passed since last call to now()
  while (millis() - prevMillis >= 1000) {
		// millis() and prevMillis are both unsigned ints thus the subtraction will always be the absolute value of the difference
    sysTime++;
    prevMillis += 1000;	
#ifdef TIME_DRIFT_INFO
    sysUnsyncedTime++; // this can be compared to the synced time to measure long term drift     
#endif
  }
  if (nextSyncTime <= sysTime) {
    if (getTimePtr != 0) {
      time_t t = getTimePtr();
      if (t != 0) {
        setTime(t);
      } else {
        nextSyncTime = sysTime + syncInterval;
        Status = (Status == timeNotSet) ?  timeNotSet : timeNeedsSync;
      }
    }
  }  
  return (time_t)sysTime;
}

void setTime(time_t t) { 
#ifdef TIME_DRIFT_INFO
 if(sysUnsyncedTime == 0) 
   sysUnsyncedTime = t;   // store the time of the first call to set a valid Time   
#endif

  sysTime = (uint32_t)t;  
  nextSyncTime = (uint32_t)t + syncInterval;
  Status = timeSet;
  prevMillis = millis();  // restart counting from now (thanks to Korman for this fix)
} 

void setTime(int hr,int min,int sec,int dy, int mnth, int yr){
 // year can be given as full four digit year or two digts (2010 or 10 for 2010);  
 //it is converted to years since 1970
  if( yr > 99)
      yr = yr - 1970;
  else
      yr += 30;  
  tm.Year = yr;
  tm.Month = mnth;
  tm.Day = dy;
  tm.Hour = hr;
  tm.Minute = min;
  tm.Second = sec;
  setTime(makeTime(tm));
}

void adjustTime(long adjustment) {
  sysTime += adjustment;
}

// indicates if time has been set and recently synchronized
timeStatus_t timeStatus() {
  now(); // required to actually update the status
  return Status;
}

void setSyncProvider( getExternalTime getTimeFunction){
  getTimePtr = getTimeFunction;  
  nextSyncTime = sysTime;
  now(); // this will sync the clock
}

void setSyncInterval(time_t interval){ // set the number of seconds between re-sync
  syncInterval = (uint32_t)interval;
  nextSyncTime = sysTime + syncInterval;
}

DateStrings.cpp

/*
  time.c - low level time and date functions
  Copyright (c) Michael Margolis 2009-2014

  This library is free software; you can redistribute it and/or
  modify it under the terms of the GNU Lesser General Public
  License as published by the Free Software Foundation; either
  version 2.1 of the License, or (at your option) any later version.

  This library is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  Lesser General Public License for more details.

  You should have received a copy of the GNU Lesser General Public
  License along with this library; if not, write to the Free Software
  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
  
  1.0  6  Jan 2010 - initial release
  1.1  12 Feb 2010 - fixed leap year calculation error
  1.2  1  Nov 2010 - fixed setTime bug (thanks to Korman for this)
  1.3  24 Mar 2012 - many edits by Paul Stoffregen: fixed timeStatus() to update
                     status, updated examples for Arduino 1.0, fixed ARM
                     compatibility issues, added TimeArduinoDue and TimeTeensy3
                     examples, add error checking and messages to RTC examples,
                     add examples to DS1307RTC library.
  1.4  5  Sep 2014 - compatibility with Arduino 1.5.7
*/

#if ARDUINO >= 100
#include <Arduino.h> 
#else
#include <WProgram.h> 
#endif

#include "stmTime.h"

static tmElements_t tm;          // a cache of time elements
static time_t cacheTime;   // the time the cache was updated
static uint32_t syncInterval = 300;  // time sync will be attempted after this many seconds

void refreshCache(time_t t) {
  if (t != cacheTime) {
    breakTime(t, tm); 
    cacheTime = t; 
  }
}

int hour() { // the hour now 
  return hour(now()); 
}

int hour(time_t t) { // the hour for the given time
  refreshCache(t);
  return tm.Hour;  
}

int hourFormat12() { // the hour now in 12 hour format
  return hourFormat12(now()); 
}

int hourFormat12(time_t t) { // the hour for the given time in 12 hour format
  refreshCache(t);
  if( tm.Hour == 0 )
    return 12; // 12 midnight
  else if( tm.Hour  > 12)
    return tm.Hour - 12 ;
  else
    return tm.Hour ;
}

uint8_t isAM() { // returns true if time now is AM
  return !isPM(now()); 
}

uint8_t isAM(time_t t) { // returns true if given time is AM
  return !isPM(t);  
}

uint8_t isPM() { // returns true if PM
  return isPM(now()); 
}

uint8_t isPM(time_t t) { // returns true if PM
  return (hour(t) >= 12); 
}

int minute() {
  return minute(now()); 
}

int minute(time_t t) { // the minute for the given time
  refreshCache(t);
  return tm.Minute;  
}

int second() {
  return second(now()); 
}

int second(time_t t) {  // the second for the given time
  refreshCache(t);
  return tm.Second;
}

int day(){
  return(day(now())); 
}

int day(time_t t) { // the day for the given time (0-6)
  refreshCache(t);
  return tm.Day;
}

int weekday() {   // Sunday is day 1
  return  weekday(now()); 
}

int weekday(time_t t) {
  refreshCache(t);
  return tm.Wday;
}
   
int month(){
  return month(now()); 
}

int month(time_t t) {  // the month for the given time
  refreshCache(t);
  return tm.Month;
}

int year() {  // as in Processing, the full four digit year: (2009, 2010 etc) 
  return year(now()); 
}

int year(time_t t) { // the year for the given time
  refreshCache(t);
  return tmYearToCalendar(tm.Year);
}

/*============================================================================*/	
/* functions to convert to and from system time */
/* These are for interfacing with time serivces and are not normally needed in a sketch */

// leap year calulator expects year argument as years offset from 1970
#define LEAP_YEAR(Y)     ( ((1970+Y)>0) && !((1970+Y)%4) && ( ((1970+Y)%100) || !((1970+Y)%400) ) )

static  const uint8_t monthDays[]={31,28,31,30,31,30,31,31,30,31,30,31}; // API starts months from 1, this array starts from 0
 
void breakTime(time_t timeInput, tmElements_t &tm){
// break the given time_t into time components
// this is a more compact version of the C library localtime function
// note that year is offset from 1970 !!!

  uint8_t year;
  uint8_t month, monthLength;
  uint32_t time;
  unsigned long days;

  time = (uint32_t)timeInput;
  tm.Second = time % 60;
  time /= 60; // now it is minutes
  tm.Minute = time % 60;
  time /= 60; // now it is hours
  tm.Hour = time % 24;
  time /= 24; // now it is days
  tm.Wday = ((time + 4) % 7) + 1;  // Sunday is day 1 
  
  year = 0;  
  days = 0;
  while((unsigned)(days += (LEAP_YEAR(year) ? 366 : 365)) <= time) {
    year++;
  }
  tm.Year = year; // year is offset from 1970 
  
  days -= LEAP_YEAR(year) ? 366 : 365;
  time  -= days; // now it is days in this year, starting at 0
  
  days=0;
  month=0;
  monthLength=0;
  for (month=0; month<12; month++) {
    if (month==1) { // february
      if (LEAP_YEAR(year)) {
        monthLength=29;
      } else {
        monthLength=28;
      }
    } else {
      monthLength = monthDays[month];
    }
    
    if (time >= monthLength) {
      time -= monthLength;
    } else {
        break;
    }
  }
  tm.Month = month + 1;  // jan is month 1  
  tm.Day = time + 1;     // day of month
}

time_t makeTime(tmElements_t &tm){   
// assemble time elements into time_t 
// note year argument is offset from 1970 (see macros in time.h to convert to other formats)
// previous version used full four digit year (or digits since 2000),i.e. 2009 was 2009 or 9
  
  int i;
  uint32_t seconds;

  // seconds from 1970 till 1 jan 00:00:00 of the given year
  seconds= tm.Year*(SECS_PER_DAY * 365);
  for (i = 0; i < tm.Year; i++) {
    if (LEAP_YEAR(i)) {
      seconds +=  SECS_PER_DAY;   // add extra days for leap years
    }
  }
  
  // add days for this year, months start from 1
  for (i = 1; i < tm.Month; i++) {
    if ( (i == 2) && LEAP_YEAR(tm.Year)) { 
      seconds += SECS_PER_DAY * 29;
    } else {
      seconds += SECS_PER_DAY * monthDays[i-1];  //monthDay array starts from 0
    }
  }
  seconds+= (tm.Day-1) * SECS_PER_DAY;
  seconds+= tm.Hour * SECS_PER_HOUR;
  seconds+= tm.Minute * SECS_PER_MIN;
  seconds+= tm.Second;
  return (time_t)seconds; 
}
/*=====================================================*/	
/* Low level system time functions  */

static uint32_t sysTime = 0;
static uint32_t prevMillis = 0;
static uint32_t nextSyncTime = 0;
static timeStatus_t Status = timeNotSet;

getExternalTime getTimePtr;  // pointer to external sync function
//setExternalTime setTimePtr; // not used in this version

#ifdef TIME_DRIFT_INFO   // define this to get drift data
time_t sysUnsyncedTime = 0; // the time sysTime unadjusted by sync  
#endif


time_t now() {
	// calculate number of seconds passed since last call to now()
  while (millis() - prevMillis >= 1000) {
		// millis() and prevMillis are both unsigned ints thus the subtraction will always be the absolute value of the difference
    sysTime++;
    prevMillis += 1000;	
#ifdef TIME_DRIFT_INFO
    sysUnsyncedTime++; // this can be compared to the synced time to measure long term drift     
#endif
  }
  if (nextSyncTime <= sysTime) {
    if (getTimePtr != 0) {
      time_t t = getTimePtr();
      if (t != 0) {
        setTime(t);
      } else {
        nextSyncTime = sysTime + syncInterval;
        Status = (Status == timeNotSet) ?  timeNotSet : timeNeedsSync;
      }
    }
  }  
  return (time_t)sysTime;
}

void setTime(time_t t) { 
#ifdef TIME_DRIFT_INFO
 if(sysUnsyncedTime == 0) 
   sysUnsyncedTime = t;   // store the time of the first call to set a valid Time   
#endif

  sysTime = (uint32_t)t;  
  nextSyncTime = (uint32_t)t + syncInterval;
  Status = timeSet;
  prevMillis = millis();  // restart counting from now (thanks to Korman for this fix)
} 

void setTime(int hr,int min,int sec,int dy, int mnth, int yr){
 // year can be given as full four digit year or two digts (2010 or 10 for 2010);  
 //it is converted to years since 1970
  if( yr > 99)
      yr = yr - 1970;
  else
      yr += 30;  
  tm.Year = yr;
  tm.Month = mnth;
  tm.Day = dy;
  tm.Hour = hr;
  tm.Minute = min;
  tm.Second = sec;
  setTime(makeTime(tm));
}

void adjustTime(long adjustment) {
  sysTime += adjustment;
}

// indicates if time has been set and recently synchronized
timeStatus_t timeStatus() {
  now(); // required to actually update the status
  return Status;
}

void setSyncProvider( getExternalTime getTimeFunction){
  getTimePtr = getTimeFunction;  
  nextSyncTime = sysTime;
  now(); // this will sync the clock
}

void setSyncInterval(time_t interval){ // set the number of seconds between re-sync
  syncInterval = (uint32_t)interval;
  nextSyncTime = sysTime + syncInterval;
}

По поводу 3.3 вольта.

А смысл переводить на 3.3 ? от батареек все равно не реализуешь, и реле (если механическое) на 3.3 работать плохо будет.

16 мгц на 3.3 работает (но не документировано). Правда дисплей можно без резисторов (делители) использовать.

У меня головной блок от 3.3 вольта питается. Особой разницы пока не почуствовал, есть заморочки с питанием.

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

Не компилица чтото.

 

stmTime\stmTime.cpp.o: In function `breakTime(unsigned long, tmElements_t&)': C:\Program Files\Arduino\libraries\stmTime/stmTime.cpp:151: multiple definition of `breakTime(unsigned long, tmElements_t&)' stmTime\DateStrings.cpp.o:C:\Program Files\Arduino\libraries\stmTime/DateStrings.cpp:151: first defined here stmTime\stmTime.cpp.o: In function `breakTime(unsigned long, tmElements_t&)': C:\Program Files\Arduino\libraries\stmTime/stmTime.cpp:151: multiple definition of `refreshCache(unsigned long)' stmTime\DateStrings.cpp.o:C:\Program Files\Arduino\libraries\stmTime/DateStrings.cpp:151: first defined here stmTime\stmTime.cpp.o: In function `breakTime(unsigned long, tmElements_t&)': C:\Program Files\Arduino\libraries\stmTime/stmTime.cpp:151: multiple definition of `hour(unsigned long)' stmTime\DateStrings.cpp.o:C:\Program Files\Arduino\libraries\stmTime/DateStrings.cpp:151: first defined here

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

Ошибка повторное определение функции

У меня библитоека stmTime лежит в домашней папке/ардуино/либрарис.

 

 

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

ну и у меня

C:\Program Files\Arduino\libraries\stmTime

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

Перешил загрузчик на прохе, пробовал на 3.3 вольта, работает :)

Ваяю платку.

Aleksahek
Offline
Зарегистрирован: 22.12.2015

pav2000 пишет:

Есть в загородном доме два подвала которые гидроизолированы от земли (нет поступления воды из грунта). Зимой на стенах появляется конденсат, предметы покрываются влагой. Температура в подвалах не опускается ниже 0...+2 градусов, т.е. вода не замерзает. Это достигнуто утеплением стен подвала снаружи ЭППС и нагревом пола подвала (он тепло не изолирован) землей.
Задача убрать выпадение конденсата в подвалах.
.....

можно добавить  на вытяжную трубу подвала инжектор (конус с флюгером) и заслонку на сервоприводе,  и скорость ветра замерять. И вообще посади туда грибы...

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

Thunderix пишет:

Ethernet бы еще прикрутить

По просьбам трудящихся -))

Сейчас допиливаю код головного блока будет web сервер (по витой паре). Функционал будет аналогичный, т.е. показ таблицы данных и графиков изменения за последние 10 часов, На одной странице информация об всех удаленных блоках.  Пока есть верстия на TFT экране (опубликована на этом форуме) и есть версия чисто web сервера (пока ничего не выкладывал). Возможно получится объеденить в одном коде и экран и сервер.  Реализация stm32, для сети используется плата w5100 (можно w5500).

Нашел способ прикрутит POE (пассивный) к плате w5100, думаю об актуальности.

Thunderix пишет:

Перешил загрузчик на прохе, пробовал на 3.3 вольта, работает :)

Ваяю платку.

Тоже отличная плата и компактная. А что Вы используете для коммутации нагрузки?

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

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

Aleksahek пишет:

можно добавить  на вытяжную трубу подвала инжектор (конус с флюгером) и заслонку на сервоприводе,  и скорость ветра замерять. И вообще посади туда грибы...

А смысл измерения скороси ветра в трубе ???

Посадка грибов для мнея не актуальна -))

1. Грибы это влажность близкая к 100% а у меня деревянные перекрытия.

2. Выращивать на "продажу" - это не мой бизнес и объем мал. А выращивать "для себя"  не нужно - у меня лес в 100 метрах и в грибное время  мы набираем грибов сколько захотим. Объем заготовк ограничивается фразой - "мы столько не съедим и не раздарим".

 

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

Человек первый раз на форуме, спросить больше не о чём (Это я про грибы) .:)

Плату рисовал в свободное на работе время, Еще не делал пока. Силовую часть буду делать на moc3023 и bta208-600.

Жду с нетерпением головную часть с етхернетом.