Контроль влажности ванной комнаты

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

Цели:

  • Заставить вентилятор ванной включаться автоматически по достижении определенного порога влажности;
  • Заставить включаться оба вентилятора если кто-то пользуется туалетом

Ну, поехали :)

Часть №1: Подготовка почвы.

Цели вроде вполне достижимые и не такие уж трудные, так что будем решать последовательно наши задачи. Для начала прикинем, что должен включать в себя девайс:

  • Экран для контроля значений влажности и температуры;
  • Элементы управления для ввода порогового значения влажности;
  • Датчик влажности;
  • Датчик расстояния;
  • Два реле на управление вентиляторами;
  • Датчик освещенности;
  • Стабилизатор питания девайса.

Если со всеми пунктами более-менее понятно, то немного объясню зачем мне понадобился датчик расстояния для такого проекта: я перебирал разные варианты, как контролировать присутствие человека в туалете:

  1. Микровыключатель на двери – вариант в лоб и слишком ненадежный, мы можем вполне открыть и закрыть дверь несколько раз, а иногда дверь в принципе никогда не закрывается полностью (у нас есть кот :) );
  2. Фотоизлучатель и приемник на противоположных стенах – уже более серьезно, но мне не нравится, что надо вести провода по разным стенам;
  3. Детектор движения – в туалете много движений не происходит :) ;
  4. Ультразвуковой датчик расстояния – провод по одной стене, четкое срабатывание, неплохая точность – то что нужно!

Я люблю отлаживать проекты сразу на готовой плате, поэтому первым делом я приступил к созданию платы девайса:

PDF по клику!

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

Плата с датчиком влажности, а также с реле для включения/выключения вентилятора в ванной:


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

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

  • Отображение на LCD дисплее – тут все уже реализовано;
  • Запрос значения температуры и влажности, с проверкой целостности принимаемой посылки;
  • Запрос расстояния до ближайшего объекта;
  • Отображение дежурной инфы на экране;
  • Меню для ввода данных о пороговой влажности;
  • Сохранение данных в EEPROM;

Контроль влажности и температуры с проверкой целостности принимаемых байт.

Основные функции с датчиком влажности недавно были реализованы. Осталось добавить проверку целостности принимаемой посылки. Моя логика довольно проста:

  1. Делаем запрос на значение влажности и температуры;
  2. Если сумма первого и третьего байта совпали со значением 5, то выходим, если нет, то ждем 1 с и повторяем запрос;
  3. Повторяем пункт 2 n-ое количество раз.

Получилась вот такая функция:


void GetRHandTempRoutine()
{

RHStatus = 0;                          //Статусная переменная, устанавливается в 1 при успешной отработке функции GetRHandTemp()
lcd_clear();
lcd_goto(0x00);
lcd_putch('*');
GetRHandTemp();                        //Попытка 1

if (!RHStatus)                         //Попытка 2
{
 lcd_clear();
 lcd_goto(0x01);
 lcd_putch('*');
 __delay_ms(200);
 __delay_ms(200);
 __delay_ms(200);
 __delay_ms(200);
 __delay_ms(200);

 GetRHandTemp();

 if (!RHStatus)                       //Попытка 3
 {
   lcd_clear();
   lcd_goto(0x00);
   lcd_putch('*');
   __delay_ms(200);
   __delay_ms(200);
   __delay_ms(200);
   __delay_ms(200);
   __delay_ms(200);

   GetRHandTemp();
   if (!RHStatus)                    //Попытка 4
   {
     lcd_clear();
     lcd_goto(0x01);
     lcd_putch('*');
     __delay_ms(200);
     __delay_ms(200);
     __delay_ms(200);
     __delay_ms(200);
     __delay_ms(200);

     GetRHandTemp();

     if (!RHStatus)                  //Попытка 5
     {
       lcd_clear();
       lcd_goto(0x00);
       lcd_putch('*');
       __delay_ms(200);
       __delay_ms(200);
       __delay_ms(200);
       __delay_ms(200);
       __delay_ms(200);
       GetRHandTemp();
       if (!RHStatus)               //Если ничего не получилось
       {
         DHTbyte[0] = 0;
         lcd_clear();
         lcd_goto(0x00);
         lcd_puts("Error, call");   //опускаем руки
         lcd_goto(0x40);
         lcd_puts("+375297251214"); //и звоним мне
        }
     }
   }
 }
}
}

Звездочки ввел для анимации действий, а то непонятно работает там что или просто висяк поймался. Тестдрайв:

Запрос расстояния до ближайшего объекта

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


if (!RA3)
 {
 __delay_ms(100);
  if (!RA3)
  {
    GetRange();
  }
 }

… и получил, то что должен был получить: Вот бы всегда так просто все можно было внедрять :)

Отображение дежурной инфы на экране

Что должен отображать экран?

  1. Влажность;
  2. Температуру;
  3. Статус вентиляторов;
  4. Присутствие человека (полезность сомнительная, но на этапе разработки может быть важным).

Пишем функцию:


void DisplayAll()
{
  lcd_clear();
  lcd_goto(0x00);
  lcd_puts("RH");
  lcd_goto(0x06);
  lcd_puts("Temp");
  display_digit(DHTbyte[0],0x40);              //Отображаем влажность
  display_digit(DHTbyte[1],0x46);              //Отображаем температуру
  lcd_goto(0x48);
  lcd_putch(0b11011111); //градус
  lcd_putch(0b01000011); //С
  lcd_goto(0x0D);
  lcd_putch('V');
  lcd_goto(0x0F);
  lcd_putch('T');
  lcd_goto(0x4D);
  if (!BathCulStatus)
      lcd_putch('_');                              //Статус вентилятора в ванной
      else
        lcd_putch('*');

if (Range<=RangeTrig)
     lcd_putch('|');

 lcd_goto(0x4F);
 if (!ToiCulStatus)
    lcd_putch('_');
  else
     lcd_putch('*');

}

И сразу небольшой тест с нулевыми статусами и расстоянием меньше 50 см:

Сохранение настроек в EEPROM

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

Для начала введем две переменные и по старту программы будет проверять их значение:


RHTrig = eeprom_read(0x04);
 RangeTrig = eeprom_read(0x05);

Затем добавим функцию сохранения:


void SaveState()
{
eeprom_write(0x04, RHTrig);
eeprom_write(0x05, RangeTrig);
}

Меню для ввода пороговых значений

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


void Menu()
{
 static bit flag = 0;                    //статусная переменная для выхода из меню
 unsigned char RHntStatus = 0;           //флаг текущего пункта меню
 RHntStatus = 1;
 flag = 0;
 lcd_clear();
 lcd_goto(0x00);
 lcd_putch('*');                         //по умолчанию первый пункт
 lcd_puts("RHTrig = ");
 display_digit(RHTrig,0x0A);             //Тригерное значение влажности
 lcd_goto(0x41);
 lcd_puts("Range Trig = ");
 display_digit(RangeTrig,0x4E);          //Тригерное значение расстояния
 while(!flag)
 {
   if (!RA0) //Выбор пункта меню
   {
     __delay_ms(200);
     if (!RA0)
     {
     switch (RHntStatus)
      {
       case 0 : lcd_goto(0x40);          //Влажность
                lcd_putch(' ');
                lcd_goto(0x00);
                lcd_putch('*');
                RHntStatus = 1;
                break;
       case 1 : lcd_goto(0x00);          //Расстояние
                lcd_putch(' ');
                lcd_goto(0x40);
                lcd_putch('*');
                RHntStatus = 2;
                break;
       case 2 : lcd_goto(0x00); //Выход
                lcd_putch('X');
                lcd_goto(0x40);
                lcd_putch('X');
                RHntStatus = 0;
                break;
      }

    }
  }//конец выбора пункта меню

 if (!RA3) //Выход из меню
  {
    __delay_ms(200);
   if (!RA3)
    {
    switch (RHntStatus)
     {
       case 0 : lcd_clear();
                lcd_puts("EXIT");
                flag = 1;
                break;
       case 1 : RHTrig++;
                if (RHTrig>95)
                  RHTrig = 35;
                display_digit(RHTrig, 0x0A);
                break;
       case 2 : RangeTrig++;
                if (RangeTrig > 90)
                  RangeTrig = 5;
                display_digit(RangeTrig,0x4E);
                break;
       }

  }
 }

}//while(!flag)
SaveState();             //На выходе из меню сохраняемя

GetRange();              //Делаем запрос на расстояние

GetRHandTempRoutine();   //Проверяем влажность и температуру

if (DHTbyte[0])          //И отображаем дежурную информацию
    DisplayAll();
}

Вот что примерно получилось:

Ну вот, базис, можно сказать есть, осталось совсем немного :)

Часть №2: Собираем все в кучу.

Проанализируем все возможные ситуации и спланируем наши действия в них:

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

Как я уже сказал, нужно настроить обновление, а из таймеров у нас свободен только TMR2. С частотой кварца в 12 МГц и максимальными значениями делителей частоты мы можем получить максимум 21.71 мс – слишком частое обновление получается :) Так что надо завести еще переменную счетчик и с учетом расчетного времени она должна быть больше 256, значит тип char уже не покатит.

А вот и наш обработчик событий:


if (TickCount > 300)              //Для тестов, 300 тиков ~ 6.5 c
{
 TickCount = 0;
 TMR2ON = 0;
 TMR2IE = 0;
 GetRange();                      //Запрос расстояния
 GetRHandTempRoutine();           //Влажность и температура

if ((Range <= RangeTrig)&&(!ManInToi)) //Есть кто в туалете, начало
 {
     ManInToi = 1;
     ManInToiCount = 1;                //Запускаем счетчик времени присутствия человека
 }
 else
   {
      if ((Range <= RangeTrig)&&(ManInToi))  //В туалете кто-то уже давно
      {
         ManInToiCount++;
         if (ManInToiCount>=ManInToiCountTrig) //Человек находится больше заданного времени
         {
           ToiCulStatus = 1;                   //Включаем вентилятор в туалете
           ManInToiCount = ManInToiCountTrig;  //И не даем счетчику превысить тригер, для обратного счета
         }
       }
    }//else

if ((Range > RangeTrig)&&(ManInToiCount >= 1)) //Человек только что ушел
 {
    ManInToiCount-- ;                          //Обратный отсчет
    if (ManInToiCount <= 1)                    //Не даем зайти ниже 0
      ManInToiCount = 0;
    ToiCulStatus = 1;                          //И вентилятор пока работает
 }

if ((Range > RangeTrig)&&(DHTbyte[0]<RHTrig)&&(!ManInToiCount))   //если никого нет давно и влажность ниже заданной вырубаем в туалете
   ToiCulStatus = 0;

if (DHTbyte[0]>=RHTrig)                        //Если влажность больше заданной
  {
    BathCulStatus = 1;
    ToiCulStatus = 1;
  }
  else
     BathCulStatus = 0;

BathSw = BathCulStatus;                         //Непосредственное управление вентиляторами
 ToiSw = ToiCulStatus;
 if (DHTbyte[0])                                 //Обновляем инфу
    DisplayAll();

 InitTimer2();                                   //Запускаем таймер заново

 }

Теперь, мы можем регулировать время обновления и ключевое время нахождения человека в туалете переменной ManInToiCountTrig. 

Для тестов я установил эту переменную равной 4, то есть 26 секунд. Можно собирать тестовую установку! :)

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

Апдейт: все-таки решил перестраховаться и добавить внешний вотчдог, так правильнее. Для работы вотчдога надо как то определять, что девайс исправно работает и не висит, в нашем случае есть апдейт инфы и значений датчиков:

Таким образом, один из вариантов, следить за линией Trig – если определенное время по этой линии ничего не изменилось, то выполняем процедуру сброса чипа.

Вот какая какашка получилась:

И сразу добавляем сюда небольшое тестовое видео:

Часть №3. Полевые испытания

Покажем испытания на корявом видео:

Архив с исходниками 19 ноября 2013

Бета версия протестирована и в принципе работает удовлетворительно, теперь о том, что нужно допилить/изменить:

  • Все-таки dht11 ненадежный датчик и часто дает сбои, поэтому думаю заказать dht22 и плюс ко всему сам датчик влажности поместить прямо на решетку в ванной.
  • После где-то месяца юзания пользователь пришел к выводу, что ему не надо настраивать расстояние срабатывания, но было бы неплохо сделать настраиваемым время срабатывания после появления человека и время работы после его ухода, т.к. на данный момент этого времени явно недостаточно.

Данные изменения по мере внедрения добавлю в этой же статье.

Внешний вид пока не шикарен, но вроде как скоро должна появиться декоративная накладка вместе с заменой громкого вентилятора на нормальный, как только это произойдет дополню фотками статью.

Ну вот собственно и все, исходники лежат здесь.

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Можно использовать следующие HTML-теги и атрибуты: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code class="" title="" data-url=""> <del datetime=""> <em> <i> <q cite=""> <strike> <strong> <pre class="" title="" data-url=""> <span class="" title="" data-url="">