Исходные данные: есть ванная комната и туалет, оборудованные вентиляторами, которые включается вручную.
Цели:
- Заставить вентилятор ванной включаться автоматически по достижении определенного порога влажности;
- Заставить включаться оба вентилятора если кто-то пользуется туалетом
Ну, поехали 🙂
Часть №1: Подготовка почвы.
Цели вроде вполне достижимые и не такие уж трудные, так что будем решать последовательно наши задачи. Для начала прикинем, что должен включать в себя девайс:
- Экран для контроля значений влажности и температуры;
- Элементы управления для ввода порогового значения влажности;
- Датчик влажности;
- Датчик расстояния;
- Два реле на управление вентиляторами;
- Датчик освещенности;
- Стабилизатор питания девайса.
Если со всеми пунктами более-менее понятно, то немного объясню зачем мне понадобился датчик расстояния для такого проекта: я перебирал разные варианты, как контролировать присутствие человека в туалете:
- Микровыключатель на двери – вариант в лоб и слишком ненадежный, мы можем вполне открыть и закрыть дверь несколько раз, а иногда дверь в принципе никогда не закрывается полностью (у нас есть кот 🙂 );
- Фотоизлучатель и приемник на противоположных стенах – уже более серьезно, но мне не нравится, что надо вести провода по разным стенам;
- Детектор движения – в туалете много движений не происходит 🙂 ;
- Ультразвуковой датчик расстояния – провод по одной стене, четкое срабатывание, неплохая точность – то что нужно!
Я люблю отлаживать проекты сразу на готовой плате, поэтому первым делом я приступил к созданию платы девайса:
Здесь все довольно обычно, кроме датчика освещенности, он собран на чистом аналоге и его принцип работы описан в одной из прошлых статей. Еще раз напомню: не стоит размещать датчик яркости справа от такого дисплея, там светодиод подсветки дисплея и фоторезистор надо неплохо прятать, чтобы эта подсветка нам не мешала.
Плата с датчиком влажности, а также с реле для включения/выключения вентилятора в ванной:
Плата включения/выключения вентилятора в туалете:
Ультразвуковой датчик я в отдельную плату не совал, он и так собран, позже его в корпус поместили.
Ну вот, подготовительные работы с железом можно считать завершенными, приступаем к подготовке почвы в коде. Здесь для комфортной работы нам понадобятся следующие блоки:
- Отображение на LCD дисплее – тут все уже реализовано;
- Запрос значения температуры и влажности, с проверкой целостности принимаемой посылки;
- Запрос расстояния до ближайшего объекта;
- Отображение дежурной инфы на экране;
- Меню для ввода данных о пороговой влажности;
- Сохранение данных в EEPROM;
Контроль влажности и температуры с проверкой целостности принимаемых байт.
Основные функции с датчиком влажности недавно были реализованы. Осталось добавить проверку целостности принимаемой посылки. Моя логика довольно проста:
- Делаем запрос на значение влажности и температуры;
- Если сумма первого и третьего байта совпали со значением 5, то выходим, если нет, то ждем 1 с и повторяем запрос;
- Повторяем пункт 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(); } }
… и получил, то что должен был получить: Вот бы всегда так просто все можно было внедрять 🙂
Отображение дежурной инфы на экране
Что должен отображать экран?
- Влажность;
- Температуру;
- Статус вентиляторов;
- Присутствие человека (полезность сомнительная, но на этапе разработки может быть важным).
Пишем функцию:
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 и плюс ко всему сам датчик влажности поместить прямо на решетку в ванной.
- После где-то месяца юзания пользователь пришел к выводу, что ему не надо настраивать расстояние срабатывания, но было бы неплохо сделать настраиваемым время срабатывания после появления человека и время работы после его ухода, т.к. на данный момент этого времени явно недостаточно.
Данные изменения по мере внедрения добавлю в этой же статье.
Внешний вид пока не шикарен, но вроде как скоро должна появиться декоративная накладка вместе с заменой громкого вентилятора на нормальный, как только это произойдет дополню фотками статью.
Ну вот собственно и все, исходники лежат здесь.