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

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

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

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

1. Принцип работы

Идея контроля влажности подвала была подсмотрена на http://geektimes.ru (http://geektimes.ru/post/255298/).

Вся идея состоит в том чтобы измерить температуру и относительную влажность в подвале и на улице, на основании температуры и относительной влажности рассчитать абсолютную влажность и принять решение о включении вытяжного вентилятора в подвале. Теория для расчета изложена здесь – https://carnotcycle.wordpress.com/2012/08/04/how-to-convert-relative-humidity-to-absolute-humidity/

2. Выбор элементной базы

В качестве контроллера было выбрано Arduino pro mini.  Это мой второй проект (первый – контроль работы теплового насоса был сделан на uno). Изучив предлагаемые контроллеры Arduino  пришел к выводу что существует  всего три варианта – DUE (отдельная песня и мне кажется тупик), MEGA и все остальное (сильно схожие контроллеры на одном чипе 328).  Задача достаточно простая, по этому выбор пал на pro mini (не на мегу).

В pro mini был прошит загрузчик optiboot 5 версии, это дало возможность использования сторожевого таймер и увеличением максимального размера кода.
Устройство индикации – до этого были испытаны монохромные дисплеи 4х20 символов и графический 128х64 точки на 9372. Появилось желание подключить  дисплей цветной ЖК. В виду малого количества ножек у МК было использовано подключение по SPI .  Ebay  – имеет большое количество предложений на контроллере ILI9341 с размерами диагонали 1.8-2.8 дюйма и разрешением 320х240 точек. В эту конструкцию был куплен дисплей 2.4 дюйма.

Т.к. устройство будет устанавливаться в подвале то для наблюдения я хочу сделать выносной пульт со связью по радиоканалу. Для организации радиоканала были использованы модули на основе nrf24. Планируется система типа “умного дома” каждый блок (а сейчас есть уже два блока в подвалах) посылает пакет о своем состоянии в головное устройство.

Исполнительное устройство. В начале был использован классический одно канальный релейный шилд для ардуино. Корпус проектировался под него (есть даже выступы для крепления). После установки устройства  в подвале был выявлен косяк – зависание контроллера дисплея при выключении вентилятора (25 вт). Сразу были увеличены конденсаторы по питанию, поставлен LC фильтр по 5 вольтам, дисплей был переведен на питание 3.3 вольта от внешнего стабилизатора (он уже был и использовался для питания nrf24). Все эти меры ничего не дали.

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

Решение меня не удовлетворило. Пришлось везти блок в Москву, там было установлено твердотельное реле шарп S202S02 ( с зеро кросс блоком) и помехогасящий конденсатор на 0.1 мкф. После этого устройсво заработало без зависаний.

Используется датчик темперуры и влажности DHT-22, штатное включение, подтягивающий резистор 5 кОм, длина провода до 6 метров (что испробовал).
Для согласования уровней 5 вольт ардуино и дисплея используются делители из резисторов 2к+4к. Согласвоания уровне й для nrf24 не требуется.

3. Программа

Скетч:

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

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

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

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

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

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

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


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

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

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

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

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

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

reset_sum();

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

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

}

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

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

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

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

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

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

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

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

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

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


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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

файл фонтов

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

У меня есть достаточно богатый опыт программирования (с 89 года) но все это касается полноценных компьютеров, последнее время занимаюсь разработкой управляющих систем на arm под самосборной допиленной Linux,  пишу либо на голом с или qt. Опыта программирования МК не имел, но старался перенести свой опыт на них.

Использую шедуллер задач leos ver 1 (первая версия использует таймер вторая использует сторожевой таймер), это “обертка” таймера для организации периодического вызова функций (у меня вызов опроса датчика).

Сторожевой таймер используется для борьбы с зависанием МК (я их не наблюдал но пусть будет).

Для работы с дисплеем используется библиотека ucglib версии не позднее 1.02. D более поздних версиях переработан формат шрифтов и есть проблемы с русификацией в кодировке UTF-8, допиливать было лень.

Для nrf24 используется библиотека от маньяка.

Для чтения датчиков DHT используется самая свежая библиотека с официального сайта. Там кодов ошибок больше.

Реализован подсчет мото часов блока и вентилятора.Кнопка повешена на прерывание и позволяет менять настройки включения вентилятора, смотреть информацию об устройстве (длительное нажатие) и сбрасывать счетчики (нажатие при включении).

Реализован вывод ошибок чтения DHT-22 (верхний правый угол цифры), с чтением были проблемы, после оптимизации кода они ушли. На графике выводится не весь диапазон темперур (вывод -20…+20) для увеличения разрешающей способности графика в области допустимых темератру в подвале (0…+20).

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

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

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

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

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

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

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

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

5. Фото готового устройства

Оригинал статьи - http://arduino.ru/forum/proekty/kontrol-vlazhnosti-podvala-arduino-pro-mini

Ардуино+