Pull to refresh

Arduino и аквариум

Reading time 12 min
Views 102K
Чего общего у севера Италии и тропических рыбок? Да мало ли чего?! – скажет читатель. И будет прав. Поэтому уточню: речь пойдет об Arduino, точнее про автоматизацию обслуживания аквариума.
Все началось с того, что неумолимо приближался отпуск. В отпуске хочется, как правило, сменить обстановку. Но аквариумные рыбки могут этого не понять. Мало того – могут не простить. И я заказал новый фильтр, автокормушку и таймер для бытовых приборов. Но таймера не оказалось в наличии. Поэтому решил побродить по сайту чуть ли не единственного магазина (и это в городе, считающимся миллионником!) радиокомпонентов и устройств и натолкнулся на Arduino Uno за 154 грн. (~20$). Уже не раз читал про устройство, но в руках, как говорится, не держал. Купил. И с этого момента начинается история проб, ошибок и побед.

Для тех, кто не сталкивался с Arduino – поясню.
Это аппаратно-программная платформа. Железо базируется на микроконтроллере Atmel ATmega и оборудовано множеством портов ввода-вывода. Программная часть – среда разработки и набор библиотек. Железо, понятно, стоит каких-то денег (на самом деле, цены, даже на оригиналы из Италии, доступные), а ПО бесплатное. Скачивается с официального сайта. С помощью ПО можно управлять портами ввода/вывода. Присутствуют порты с ШИМ.
Работа с Arduino довольно таки проста благодаря наличию множества библиотек. Например, есть библиотеки для работы с температурными датчиками, цифровыми и графическими(да!) дисплеями, различными протоколами и т.д. В общем случае необходимо подсоединить к одному или нескольким портам Arduino электронную схему которой хотим управлять (или с которой необходимо получать данные), скачать соответствующую библиотеку (если ее нет в стандартной поставке), написать программу при помощи языка Arduino (основан на языке Wiring) и среды разработки Arduino (основана на среде Processing). Заливаем скомпилированную программу через USB в железную память Arduino, и, вуаля! — получаем желаемый результат (если все сделали правильно). И, да, программа остается в памяти железки даже после выключения питания.


Самая главная задача которую я хотел поручить Arduino – управление освещением по расписанию. Да, из пушки по воробьям, но на гостя из Италии (на самом деле из Китая, но это не суть важно) у меня несколько большие планы.
До сего времени освещение аквариума производилось с помощью энергосберегающей лампочки, включать и выключать необходимо было вручную. Понятно, что строгий временной режим не выдерживался, особенно по выходным. Теперь, дабы упростить задачу и сделать более долговечным освещение, было решено перейти на светодиоды напряжением 12V. Прикупил метр влагозащищенной светодиодной ленты. Собрал схемку:

image

Полевой транзистор выпаял из нерабочего компьютерного БП. Вообще, старался использовать максимально то, что доступно под рукой. Но можно взять чуть ли не любой, например IRFBC30. Резистор нужен для обеспечения надежности схемы: сверхмалые токи «уходят в землю» и полевой транзистор открывается и закрывается только при высоком и низком состоянии на выводе Arduino. Также рекомендуют между затвором и выводом порта ставить ограничивающий резистор на сотню-вторую Ом, но в данной схеме им можно пренебречь.
UPDATE! В комментариях все же очень настаивают на на ограничивающем резисторе. Что же, прислушаемся. Ставим резистор на 100 Ом между выводом Arduino и затвором.

Затем нашел на просторах Интернет программку (данный пример вставил только для того, чтобы «на пальцах» объяснить структуру программы):

Программа для начинающих
int ledPin = 5; // светодиод присоединён к порту 5

void setup() {
   pinMode(ledPin, OUTPUT); // режим порта устанавливаем на вывод (запись)
}

void loop() {
   digitalWrite(ledPin, HIGH); // включаем светодиод
   delay(1000); // ждём 1 секунду
   digitalWrite(ledPin, LOW); // выключаем светодиод
   delay(5000); // ждём 5 секунд
}


Заработало! Светодиоды периодически мигают.

Небольшие пояснения к программе.
void setup()
– эта функция запускается при каждом старте железа.
void loop()
– отрабатывает в цикле.
Остальное понятно из комментариев.
Затем встал вопрос о синхронизации времени. Ведь при отключении питания и повторном включении программа запустится «с нуля». Рассматривал три варианта: использовать самодельный UPS (что реализовать не так дешево и просто); синхронизировать с Internet (соответствующая плата стоит дороже моего Arduino); синхронизировать с часами. Довольно скоро нашел на просторах всемирной паутины схему RTC (часы реального времени, да, по-русски звучит странновато) совместимую с Arduino.

image

Очень простая схема. Резисторы можно ставить от 4,7 до 10 кОм. Также понадобится микросхема-часы DS1307 и часовой кварц на 32768 Гц, батарейка. Держатель для элемента питания (CR 2032) взял со тарой компьютерной материнской платы. Поскольку исторически на плате у меня уже был припаян транзистор для цепи управления светодиодами – то разместил часы рядом.
Если вдруг после сборки часов не получается выставить время на часах или они «стоят», то проверьте правильность соединений и их качество. Стоит отойти одному проводку от микросхемы – и часы ведут себя скверно. И, да, разместите DS1307 на DIP панельке.
Часы необходимо инициализировать, т.е. установить корректную дату и время. Это можно сделать следующим образом.

Программа инициализации часов
#include <Wire.h>
#include <DS1307.h>

void setup()
{
  Serial. begin(9600);
  RTC.stop(); //Останавливаем часы
  RTC.set(DS1307_SEC, 0); //Выставляем секунды 
  RTC.set(DS1307_MIN, 36); //Выставляем минуты 
  RTC.set(DS1307_HR, 15); //Выставляем часы 
  RTC.set(DS1307_DOW, 2); //Выставляем  день недели
  RTC.set(DS1307_DATE, 4); /Выставляем  день месяца 
  RTC.set(DS1307_MTH, 1); //Выставляем месяц
  RTC.set(DS1307_YR, 14); //Выставляем год
  RTC.start(); //Запускаем часы
}

void loop()
{
  Serial.print(RTC.get(DS1307_HR, true)); // Считываем часы с обновлением всех значений 
  Serial.print(":");
  Serial.print(RTC.get(DS1307_MIN, false)); // Считываем минуты без обновления 
  Serial.print(":");
  Serial.print(RTC.get(DS1307_SEC, false)); // Считываем секунды без обновления 
  Serial.println(" "); 
  delay(1000);
}


Библиотеку DS1307 можно скачать здесь.
После того как программа загрузится в контроллер, можно в консоли (Serial Monitor, Ctrl+Shift+M) наблюдать как «бежит» время.

Но вернемся к нашим схемам и деталькам.
Потом я увидел схему подключения цифрового термометра DS18B20 передающего данные по интерфейсу 1-Wire. Для этого термометра даже есть специальная библиотека под Arduino позволяющая с легкостью канатоходца и быстротой форварда Баварии получать данные о температуре в удобочитаемом виде. Подключил, игрался-игрался – показывает -127 ⁰С. Оказалось, схема подключения неверная (а я выбрал схему с активным питание). Помогла документация на термометр. Мораль: схема на сайте – это хорошо, а официальная документация – лучше.

Ниже правильная схема. Можно подключать несколько термометров к одной линии 1-Wire. Желательно использовать коаксиальный кабель.
image

Небольшое лирическое отступление.

Я все в этом проекте делал не правильно: паял как попало временные схемы по мере поступления идей, потом под это дело подгонял соединения, корпус, брал попавшиеся под руку детали и практически не оставил места для добавления функционала – когда-то все придется делать заново (и таки пришлось!). Поэтому призываю делать все наоборот:
1) определиться с функционалом;
2) определиться с деталями;
3) подобрать корпус исходя из первых двух пунктов;
4) развести плату исходя из первых трех пунктов.
И так необходимо поступать в практически любом радиоэлектронным проекте.
Если есть необходимость проверить ту или иную идею – используйте специальные макетные платы и набор соединений. Да, знаю, цены на них кусаются. Но, поверьте, сэкономите кучу времени и нервов.

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

Подстроечный резистор необходим для установки контрастности дисплея. Кнопка К1 подает питание на подсветку.
4-й контакт дисплея — это линия адресного сигнала. Известная как A0 или RS. В зависимости от того, 0 она или 1, дисплей понимает имеем ли мы на линии данных команду вроде «передвинуть курсор» или код символа для отображения.
6-й контакт дисплея — это линия разрешения доступа к данным. Известная, как E или Enable. Когда эта линия становится единицей, дисплей исполняет команду или выводит символ с линии данных.
11-й, 12-й, 13-й, 14-й контакт дисплея — это линии данных. Известные как DB4, DB5, DB6, DB7.

Часы и дисплей «в железе».
Arduino display

Теперь настала очередь БП, так как до этого я пользовался двумя разными блоками питания: 5V для дисплея и часов, и 9V для Arduino и светодиодов. Опять же, под рукой оказался БП на 24V, 1,7 А. Сразу скажу, что для светодиодов+Arduino этого маловато. Лучше брать БП на 3А, а яркостью управлять с помощью ШИМ. Чтобы получить нужные мне 5 и 10V собрал схемку на LM317T. Почему 10V? Согласно документации Arduino лучше не питать напряжением больше 12V, светодиоды при меньшем напряжении тоже должны прослужить дольше. Поэтому решил получить 10V. На деле вышло 10,4V. Опять же — не стал мудрить и собрал на минимуме деталей. Схема.
image

Но если есть время, желание и возможность — все же лучше собрать полноценный импульсный БП который бы выдавал на 10-11V до 2-2,5А.
Поскольку LM317 ощутимо греются, то прикрепил их к радиатору от PII (помните, были такие слотовые ЦП от Интел?), резисторы — навесным монтажом. Залил все это дело эпоксидкой. Внимание: корпус LM317 соединен с ножкой выхода, поэтому если крепить на одном радиаторе LM317 для разных напряжений, то хотя бы N-1 микросхем необходимо изолировать от радиатора. А лучше — все. Со стороны радиодеталей я закрыл радиатор пластмассовой крышкой, а ребра оставил «голыми». Попавшийся на глаза 9-ти пиновый RS232 послужил разъемом питания с выхода стабилизатора.

Пришло время собрать все вместе. И тут наступили трудности. Хотя, нет. Трудности наступили когда я начал менять временный соединительные провода на постоянные. Таковые, многожильные, я взял из попавшихся под руку телефонных четырехпроводных кабелей.
Просторный корпус с запасом по размерам стоит, как по мне, неприлично дорого – 10$ и более. Если бы я развел плату под корпус — проблем бы не было, а так пришлось импровизировать. Взял корпус Maszczyk KM51. Монтажную плату пришлось разделить на две части, Arduino прикрепить к крышке. А все из-за того, что было лень переделывать.
Получилось вот что.
image

Да, выглядит, мягко говоря, не ахти. А всего лишь надо было подогнать под размеры и отверстия корпуса плату, впаять в нее штырьки и надевать на них Arduino. Было бы красиво и надежно. В общем, — не делайте так как на фото выше.

В таком исполнении все это проработало не долго — недельного отпуска поделка не пережила. Поэтому пришлось переделывать.
На этот раз я взял подходящую по размерам плату и разместил на ней штырьки для подключения Arduino. Вернее две платы, так как на плате контроллера по два разъема с каждой стороны и расстояние 2,54 мм не выдерживается для всего ряда штырьков. Вышло не очень эстетично, зато работает.

image

Разместил элементы.

image
image

В сборе.
image
image
image

На плате разместилось три разъема: термодатчики, светодиоды, питание (на всякий случай спаял разъем побольше, вдруг в будущем необходимо будет больше одного напряжения подавать). На корпусе: USB, питание Arduino напрямую (не используется), питание всей схемы (+12V).
На этот раз я отказался от двух напряжений, а подал одно — ведь Arduino выдает на один из разъемов 5V. Правда — ток там слабоватый, но для работы часов и дисплея вполне достаточно. Через микрокнопку (видна на фото выше) подал питание на подсветку. Теперь можно кратковременно подсвечивать экран.
Купил еще один термометр и изменил программу с учетом отображения двух температур: в аквариуме и в комнате. Для подключения DS18B20 лучше использовать кабель с экранирующим «минусом», но такового под рукой не оказалось — взял все тот же телефонный четырех жильный провод.

Итоговая программа.
/*Подключаем библиотеки*/

#include <Wire.h>                    //необходимо для работы с часами
#include <OneWire.h>            // подключаем библиотеку для работы с 1-Wire
#include <DS1307.h>             // подключаем библиотеку для работы с часами DS1307
#include <LiquidCrystal.h>    // подключаем библиотеку для работы с LCD текстовыми экранами
#include <DallasTemperature.h>  // подключаем библиотеку для работы с термометром 18B20

#define TempPin 2   // pin для датчика температуры
int ledPin = 3;   // управление светодиодами, присоединён к pin 3
LiquidCrystal lcd(6, 7, 10, 11, 12, 13);    //инициализируем дисплей
OneWire oneWire(TempPin);    // указываем, что через этот пин будем мониторить температуру
DallasTemperature tempSensor(&oneWire);    // создаем объект для работы с термометром
long tmIntv = 1000;    //интервал для обновления времени на экране каждую секунду
long tempIntv = 30000;    //интервал для обновления температуры, хватит и пол минуты
long prvMlsTm = 0;    //предыдущее показание миллисекунд для обновления показания часов
long prvMlsTemp = 0;   //предыдущее показание миллисекунд для обновления показания температуы
long prvMlsLght = 0;    //предыдущее показание миллисекунд для проверки временного интервала
long lghtIntv = 60000;    //интервал для проверки включения/выключения освещения аквариума, 1 минута
int lghtStat = 0;   //если подсветка включена, то в 1
int upLghtTime = 8;      //включаем в 8 утра
int downLghtTime = 17;     //выключаем в 17 вевера
int isNight = 0;    //если включаем на ночь, т.е. начальное время больше конечного
int prevSec = 0;  //предыдущее показание секунд для перезапуска если остановились

//отрабатывает при включении Arduino
void setup(){
  
  pinMode(ledPin, OUTPUT); // режим порта устанавливаем на вывод (запись)
  tempSensor.begin();  //инициализируем термометр
  lcd.begin(16, 2);    //указываем, что индикатор 16 символов в две строки
  lcd.print("Time:");    //выводим "Time"

  if (upLghtTime > downLghtTime) {          //если ночное время
    isNight = 1;
  }
}

void printTemp () {     //отображаем температуру на экране
  float aTemp;   //температура аквариума
  float rTemp;    //температура комнаты
  
  tempSensor.requestTemperatures(); // отправляем запрос на получение температуры
  rTemp = tempSensor.getTempCByIndex(0);  //у нас термометр в комнате с индексом 0
  aTemp = tempSensor.getTempCByIndex(1);  //у нас термометр в аквариуме с индексом 1
  lcd.setCursor(0, 1);    //перемещаем позицию вывода
  lcd.print("R: ");
  lcd.print(rTemp);    //выводим температуру комнаты
  lcd.setCursor(7, 1);
  lcd.print(" A: ");
  lcd.print(aTemp);    //выводим температуру аквариума
  lcd.setCursor(15, 1);
  lcd.print(" ");
  lcd.home();
  
    //если экранированный кабель, то можно подключать до 32 термометров
}

void light () {    //включаем/выключаем освещение аквариума
  int hr;
  int isLght = 0;
  
  hr = RTC.get(DS1307_HR, true);    //получаем значение часов

  if (isNight == 0) {  //если день
    
    if (hr >= upLghtTime && hr < downLghtTime) {   //проверяем интервал
      isLght = 1;     //если необходимо включить свет
    } else {
      isLght = 0;
    }
    
  } else {    //если ночь
     
    if(hr - upLghtTime >= 0) {    //если больше или равно верхнему значению, то необходимо включить свет
      isLght = 1;   //если необходимо включить свет
    } else {
      if(hr < downLghtTime) {     //если меньше нижнего значения, то необходимо включить свет
        isLght = 1;    //если необходимо включить свет
      } else {
        isLght = 0;
      }
    }
  }  //закончился цикл выбора: день или ночь?

  if((isLght == 1) && (lghtStat == 0)) {   //если свет еще не включен и выставлен флаг необходимости включить
       lghtStat = 1;
       digitalWrite(ledPin, HIGH);   //собственно, включаем подавая высокий уровень на вывод
   } else {
     if(isLght == 0 && lghtStat == 1) {
         lghtStat = 0;
        digitalWrite(ledPin, LOW);     //собственно, выключаем снимая высокий уровень на выводе
     }
   }
    
}

void printTime () {  //ф-ция вывода времения на экран
  int hr;
  int minut;
  int sec;
  
  lcd.setCursor(7, 0);
  hr = RTC.get(DS1307_HR, true);   //получаем часы

  if( hr < 10) { 
    lcd.print('0');   //выводим недостающий 0 если знаков меньше 10
  } 

  lcd.print(hr);
  lcd.print(":");
  minut = RTC.get(DS1307_MIN, false);            //получаем минуты

  if( minut < 10) { 
    lcd.print('0');   //выводим недостающий 0 если знаков меньше 10
  }
  lcd.print(minut);
  lcd.print(":");
  
  sec = RTC.get(DS1307_SEC, false);     //получаем секунды
  if( sec < 10) { 
    lcd.print('0');   //выводим недостающий 0 если знаков меньше 10
  }

//дело в том, что иногода часы останавливаются, поэтому если предыдущая и текущая секунды совпадают - перезапускаем часы
  if(prevSec == sec) {
      RTC.start();
  }

  prevSec = sec;
 
  lcd.print(sec);
}

void loop()     //постоянно отрабатывает в цикле
{
  unsigned long currentMillis = millis();   //миллисекунд прошло со старта
  //дело в том, что если делать через delay(), то система в это время простаивает, 
  //а нам постоянно необходимо чего-нить обрабатывать
  //поэтому просто считаем прошедшее время и сверяем его с интервалами
  
  //проверяем не прошел ли нужный интервал для обновления часов, если прошел то
  if(currentMillis - prvMlsTm > tmIntv) {
      prvMlsTm = currentMillis;
      printTime();   //вызываем ф-цию вывода времени на экран если 
  }
  //проверяем не прошел ли нужный интервал для обновления температуры, если прошел то
  if(currentMillis - prvMlsTemp > tempIntv) {
      prvMlsTemp = currentMillis;
      printTemp();  //
  }
  //проверяем не прошел ли нужный интервал для включения/выключения освещения, если прошел то
  if(currentMillis - prvMlsLght > lghtIntv) {
      prvMlsLght = currentMillis;
      light();    //вызываем ф-цию проверки необходимости включения/выключения освещения
  }
}


Скачать библиотеки можно тут: DallasTemperature, OneWire.
Необходимость включения/отключения освещения аквариума проверяется каждую минуту, температура — каждые 30 секунд, а время обновляется каждую секунду.

Переделанное устройство отработало уже неделю без нареканий.

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

Вариант реализации
image

Когда придет лето, а у нас оно бывает жарким, планирую реализовать еще и управление вентилятором для охлаждения аквариума на основе показания термометра. Рассматриваю два варианта: вентиляторы на 12V с включением по такой же схеме, что и подсветка, или вентилятор 230V переменного тока. В данном случае вместо светодиодов в схему включается реле с напряжением управления +12V и коммутируемым 230V.
image
По такой же схеме можно управлять и зимой подогревом аквариума, включив в цепь реле подогреватель.

Что еще можно улучшить из того, что уже есть? К примеру, включать и выключать подсветку постепенно, используя выход ШИМ. В этом случае с определенным интервалом необходимо выполнять:

analogWrite(pin, value);

где pin — номер порта Arduino, а value — меняется от 0 до 255 в случае включения и с 255 до 0 в случае выключения подсветки аквариума.
Еще может понадобиться включать освещение вручную. У меня для этого есть отдельная лампа, но если таковой нет — можно реализовать с помощью Arduino. Идея в данном случае следующая. На один из входов Arduino через не фиксирующуюся кнопку подать напряжение с выхода +5V. К этому же входу подключить «землю» через резистор 10 кОм. Затем мониторить нажатие кнопки:

int isLight = 0;

buttonState = digitalRead(buttonPin);
if (buttonState == HIGH) {   
   if (isLight == 0) {
        // включаем светодиоды  
        isLight == 1;
  }  else {
      // вЫключаем светодиоды  
       isLight == 0;
  }
 }


где buttonPin — номер порта к которому подключена кнопка; isLight — переменная которая запоминает текущее состояние и при нажатии на кнопку меняет на обратное. В данном коде необходимо включить соответствующие строки включения/выключения светодиодов после комментария. При первом нажатии на кнопку освещение будет включаться, при повторном — выключаться.
Еще можно, и, наверно, нужно автоматизировать кормежку с помощью Arduino. Ведь продающиеся кормушки либо дороги, либо не обладают нужным функционалом, либо и то и другое.
И, конечно же, про-хорошему, надо бы реализовать установку интервала работы освещения без перепрошивки Arduino. Например, назначить на это две или даже одну кнопку.

И это далеко не все, что можно реализовать на Arduino. Даже с Arduino Uno есть потенциал как по управлению, так и по получению данных при обслуживании аквариума. И это без существенных денежных и временных затрат.
Tags:
Hubs:
+24
Comments 31
Comments Comments 31

Articles