Система автоматического управления аквариумом на Arduino

в 10:02, , рубрики: arduino, arduino mega2560, arduino uno, c++, diy или сделай сам, аквариум, микроконтроллеры, программирование микроконтроллеров

Хотелось бы поделиться своим первым опытом создания такой штуки, как Arduino аквариум. Ранее я вообще не работал с электроникой, и, тем более, не знал как программируются микроконтроллеры. Но все-же решил попробовать свои силы и хотел бы поделиться результатами.

Система автоматического управления аквариумом на Arduino - 1

Возникновение идеи создания аквариума

Так уж получилось, что я в основном занимался .NET программированием и изучил его в обход C++. Наверное, поэтому я так и не встретился с микросхемотехникой и микроконтроллерами, хотя желание познакомится с ними росло практически каждый год. Особенно, последние годы, когда я узнал про Arduino. Но надо было придумать ему практическое применение. И этот вопрос быстро решился.

В нашей комнате стоит аквариум, и каждый день нужно было лезть под стол и выключать рыбкам свет, а потом утром включать. Дополнительно рыбкам надо было включать обогреватель, когда им холодно, а выключать, когда им тепло. Иногда моя забывчивость приводила к гибели рыбок в аквариуме и приходилось покупать новых. Еще рыбкам нужно было периодически менять 2/3 воды. И для нашего аквариума эта процедура была очень долгой и неприятной.

Первым делом я посмотрел готовые решения по аквариумам. Их достаточно много. В основном это видеоролики на youtube. Также есть достаточно интересных статей на geektimes. Но для моей цели — изучение и знакомство с миром микросхемотехники, — это было слишком сложно, а подробного руководства «с нуля» в интернете не нашлось. Идею разработки аквариумного контроллера пришлось отложить до тех пор пока не будут изучены азы самой микроэлектроники.

Знакомство с микроэлектроникой

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

Система автоматического управления аквариумом на Arduino - 2

Обычная лампочка (светодиод), резистор на 220 Ом. Arduino управляет лампочкой по алгоритму на C++. Сразу оговорюсь, что купив любой готовый набор Arduino или его аналога нельзя собрать более-менее полезную вещь. Ну кроме пищалки или, скажем, домашнего термометра. Изучить саму платформу посредством уроков можно, но не более. Для полезных вещей придется мне пришлось освоить пайку, печатные платы, проектирование печатных плат и прочие прелести электроники.

Постройка своего первого прототипа аквариума

Итак, первое с чего я начал свой прототип аквариума — сформировал на бумаге требования к этому устройству.

Аквариум должен:

  1. Светиться утром, днем, вечером и ночью разными цветами;
  2. Включать рыбкам утром белый свет, днем яркость белого света увеличивать, вечером уменьшать (имитация дневного света) и ночью его выключать;
  3. Пузырьки воздуха(аквариумный компрессор) для рыбок должны появляться только вечером и выключаться ночью;
  4. Если рыбкам холодно, аквариум должен гореть синим цветом, если жарко то красным;
  5. Диапазоны температуры при выходе из которых должна срабатывать «световая сигнализация» должны быть настраиваемыми
  6. Аквариум должен всегда отображать дату и время;
  7. Время начала и конца промежутков дня должны быть настраиваемыми. К примеру, утро не всегда начинается в 9:00 AM;
  8. Аквариум должен отображать сведения о влажности воздуха и его температуре вне аквариума, а также выводить температуру воды внутри аквариума;
  9. Аквариум должен управляться с пульта.
  10. Экран с датой при нажатии на кнопку пульта должен подсвечиваться. Если в течении 5 секунд ничего не нажато, то гаснуть.

Я решил начать с изучения работы LCD и Arduino.

Создание главного меню. Работа с LCD

Для LCD я решил использовать библиотеку LiquidCrystal. Так совпало, что у меня в наборе помимо Arduino присутствовал LCD экран. Он мог выводить текст, цифры. Этого было достаточно и я приступил к изучению подключения данного экрана к Arduino. Основную информацию по подключению я брал отсюда. Там же есть примеры кода для вывода «Hello World».

Немного разобравшись с экраном я решил создать главное меню контроллера. Меню состояло из следующих пунктов:

  1. Основная информация;
  2. Настройка времени;
  3. Настройка даты;
  4. Температура;
  5. Климат;
  6. Подсветка;
  7. Устройства;

Каждый пункт это определенный режим вывода информации на текстовый экран LCD. Я хотел допустить возможность создания многоуровневого меню, где в каждом подуровне будут свои реализации вывода на экран.

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

 class qQuariumMode
{
protected:
	LiquidCrystal* LcdLink;
public:

	// Чтобы экран не мерцал, была предусмотрена bool переменная isLcdUpdated.
	bool isLcdUpdated = false;
    
	// Выход из подменю или меню.
	void exit();
	
	// Метод loop в каждом варианте подменю будет свой. Собственно, он и отвечает за вывод 
	// текста на экран. Он будет вызываться из главного цикла программы контроллера.
	virtual void loop();

	// Методы, которые помечены как virtual, будут переопределяться индивидуально в каждом 
	// меню. 
	virtual void OkClick();
	virtual void CancelClick();
	virtual void LeftClick();
	virtual void RightClick();
};

К примеру, для меню «Устройства» реализация базового класса qQuariumMode будет выглядеть так:

#include "qQuariumMode.h"
class qQuariumDevicesMode :
	public qQuariumMode
{
private:

	int deviceCategoryLastIndex = 4;
	
	//Варианты подменю в меню Устройства
	enum DeviceCategory
	{
		MainLight, // управление основным светом
		Aeration, // управление аэратором
		Compressor, // управление компрессором
		Vulcanius, // Управление вулканом
		Pump // Управление помпой
	};
	
	DeviceCategory CurrentDeviceCategory = MainLight;

	char* headerDeviceCategoryText = NULL;

	// Ссылка на "драйвер", с помощью которого осуществляется управление устройством
	BaseOnOfDeviceHelper* GetDeviceHelper();

public:
	void loop();
	void OkClick();
	void CancelClick();
	void LeftClick();
	void RightClick();
};

Вот что получилось в результате реализации первого уровня меню:

Аппаратная часть. Нюансы подключения компонентов

Несколько слов хочется сказать про аппаратную часть аквариумного контроллера. Для нормальной работы контроллера мне пришлось приобрести:

  1. 1 x Arduino Uno/Mega. В последствии решил работать с Mego'ой;
  2. 1 x Часы реального времени, к примеру DS1307;
  3. 2 x Реле типа RTD14005, нужны для управления компрессором и аэрацией, т.к. оба работают от 220В переменного тока;
  4. 1 x Пьезопищалка;
  5. 1 x ИК приемник;
  6. 5 x Транзисторов IRF-530 MOSFET с N каналом. (3 для RGB ленты, 1 для белой ленты, 1 для водяной помпы);
  7. 1 x RGB светодиодная лента. Если планируется погружать светодиодную ленту в воду, то нужно ее изолировать от воды. У меня лента находится внутри силиконовой трубки и залита прозрачным герметиком;
  8. 1 x White светодиодная лента;
  9. 1 x LCD экран;
  10. 1 x Датчик температуры герметичный для измерения температуры воды. Я использовал DS18B20;
  11. 1 x Датчик температуры и влажности. Я использовал DHT11;

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

Порча компонентов

Будьте внимательны. Старайтесь сначала почитать про подключаемый компонент. Он должен эксплуатироваться именно в том диапазоне напряжения, для которого он был создан. Обычно это указано на сайте производителя. Пока я разрабатывал аквариумный контроллер, я уничтожил 2 герметичных датчика температуры и часы реального времени. Датчики вышли из строя из-за того, что я их подключил к 12В, а нужно было к 5В. Часы реального времени погибли из-за «случайного» короткого замыкания в цепи по моей вине.

Светодиодная лента RGB

Особые затруднения возникли со светодиодной лентов. Я попытался реализовать следующую схему:

Система автоматического управления аквариумом на Arduino - 3

При подключении к Arduino я использовал пины, которые поддерживают ШИМ (широтно-импульсную модуляцию). При одновременном включении на максимум напряжения всех 3 пинов у меня сильно грелась лента. В итоге, если оставить ее на час-другой, некоторые светодиоды переставали светиться. Я полагаю, что это происходило из-за выхода из строя некоторых резисторов. Еще один минус данной схемы — разная яркость светодиодной ленты для каждого из цветов. К примеру, если я ставлю максимальное напряжение на красном компоненте ленты, то я получаю условную яркость красной ленты в 255 единиц. Если я включаю одновременно красный и синий компоненты на максимум напряжения, то яркость будет равна 255+255 = 510 единиц, а цвет будет фиолетовым. В общем, такой вариант решения меня не устроил.

Было решено реализовать следующий алгоритм:

void LedRgbHelper::Show(RGBColorHelper colorToShow)
{	
	// RGBColorHelper класс содержит сведения о доли каждого компонента в цвете. 
	// Кроме того, содержит информацию о яркости цвета
	int sumColorParts = colorToShow.RedPart + colorToShow.GreenPart + colorToShow.BluePart;

	// доля каждого компонента в общем цвете
	float rK = 0;
	float gK = 0;
	float bK = 0;

	if (sumColorParts != 0)
	{
		float redPartAsFloat = (float)colorToShow.RedPart;
		float greenPartAsFloat = (float)colorToShow.GreenPart;
		float bluePartAsFloat = (float)colorToShow.BluePart;

		float sumColorPartsAsFloat = (float)sumColorParts;

		int brightness = colorToShow.Brightness;
		

		// определяем относительную яркость каждого компонента в цвете.
		rK = redPartAsFloat / sumColorPartsAsFloat;
		gK = greenPartAsFloat / sumColorPartsAsFloat;
		bK = bluePartAsFloat / sumColorPartsAsFloat;
		
		// определяем абсолютное значение компонента в цвете
		rK = rK*brightness;
		gK = gK*brightness;
		bK = bK*brightness;
	}
		
	uint8_t totalCParts = (uint8_t)rK + (uint8_t)gK + (uint8_t)bK;
	
	if (totalCParts <= 255){
		// подаем напряжение на каждый компонент цвета. в сумме мы должны получить не более 255 единиц.
		analogWrite(RedPinNum, (uint8_t)rK);
		analogWrite(GreenPinNum, (uint8_t)gK);
		analogWrite(BluePinNum, (uint8_t)bK);
	}	
}

В таком варианте исполнения красный цвет и фиолетовый цвет имели одинаковую яркость. Т.е. красные светодиоды в первом случае светили с яркостью 255 единиц, а при фиолетовом цвете красный был с яркостью 127 единиц и синий с яркостью 127 единиц, что в итоге было приблизительно равно 255 единиц:

Светодиодная лента белая

Со светодиодной лентой наверное было проще всего. Единственный сложный момент — это обеспечение плавной смены яркости при смене времени суток.

Для реализации данной задумки я применил линейный алгоритм изменения яркости белой светодиодной ленты.

void MainLightHelper::HandleState()
{
	if (!IsFadeWasComplete)
	{
		unsigned long currentMillis = millis();
		if (currentMillis - previousMillis > 50) {
			previousMillis = currentMillis;

			switch (CurrentLevel)
			{
			case MainLightHelper::Off:
			{
				// Если заявлено выключенное состояние, то снижаем яркость белого света на одну единицу за цикл.
				if (currentBright != 0)
				{
					if (currentBright > 0)
					{
						currentBright--;
					}
					else
					{
						currentBright++;
					}
				}
				else
				{
					// В случае полного выключения, останавливаем анимацию затухания белого цвета.
					currentBright = 0;
					IsFadeWasComplete = true;
				}
				break;
			}
			case MainLightHelper::Low:
			case MainLightHelper::Medium:
			case MainLightHelper::High:
			{
				// В случае установки уровня белого света, постепенно увеличиваем или уменьшаем яркость за один шаг цикла
				if (currentBright != CurrentLevel)
				{
					if (currentBright > CurrentLevel)
					{
						currentBright--;
					}
					else
					{
						currentBright++;
					}
				}
				else
				{
					currentBright = CurrentLevel;
					IsFadeWasComplete = true;
				}
			}
			break;
			}
			
			// подаем напряжение нужной величины для установки яркости белого цвета.
			analogWrite(PinNum, currentBright);
		}
	}
}

Пульсация «вулкана»

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

Система автоматического управления аквариумом на Arduino - 4

Он поставлялся с адаптером, на выходе которого 12В постоянного тока, а на входе — 220 В переменного. Адаптер мне оказался не нужен, так как управление питанием и яркостью вулкана я реализовал через Arduino.

Сама пульсация вулкана была реализована следующим образом:

long time = 0;
int periode = 10000;

void VulcanusHelper::HandleState()
{
	if (IsActive){
		// time - аргумент cos в связке с указанным периодом. 
		// остальные коэффициенты - деформация функции и смещение по оси ординат
		time = millis();
		int value = 160 + 95 * cos(2 * PI / periode*time);

		analogWrite(PinNum, value);
	}
	else
	{
		analogWrite(PinNum, 0);
	}
}

Вулкан отлично подсвечивает аквариум в вечернее время, а сама пульсация смотрится очень красиво:

Система автоматического управления аквариумом на Arduino - 5

Помпа. Замена воды в аквариуме

Водяная помпа помагает быстро поменять воду в аквариуме. Я приобрел помпу, которая работает от постоянного тока 12В. Управление помпой осуществляется через полевой транзистор. Сам драйвер для устройства умеет две вещи: включить помпу, выключить помпу. При реализации драйвера я просто унаследовался от базового класса BaseOnOfDeviceHelper и ничего дополнительно в драйвере не определял. Весь алгоритм работы устройства вполне может реализовать базовый класс.

Помпу протестировал на стенде:

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

Инфракрасный порт и желание его заменить

Управление аквариумом через инфракрасный порт я осуществил по примеру предварительного обучения. Суть примера в следующем: при включении контроллера в сеть я опрашиваю поочередно действия left, right, up, down, ok. Пользователь сам выбирает, какие кнопки пульта он привязывает к каждому из действий. Плюс данной реализации — возможность привязать любой ненужный пульт дистанционного управления.
Обучается аквариум через метод Learn, суть которого отображена ниже:

void ButtonHandler::Learn(IRrecv* irrecvLink, LiquidCrystal* lcdLink)
{
	// Инициализируем прием инфракрасного сигнала с датчика
	irrecvLink->enableIRIn();
	
	// В эту переменную помещаются результаты декодирования сигнала
	decode_results irDecodeResults;
	...
	...
		while (true) {
		// Если пришли результаты и их можно декодировать
		if (irrecvLink->decode(&irDecodeResults)) {
						
			// продолжаем принимать сигналы
			irrecvLink->resume();

			// Пробуем декодировать сигнал с пульта.
			if (irDecodeResults.bits >= 16 && 
				irDecodeResults.value != 0xC53A9966// fix for Pioneer DVD
				) {
			
				lcdLink->setCursor(0, 1);
				// Выводим на экран декодированное значение в формате HEX
				lcdLink->print(irDecodeResults.value, HEX);
				
				// Запоминаем в оперативной памяти Arduino полученный сигнал
				irRemoteButtonId = irDecodeResults.value;
				
				...
				...

В дальнейшем я пришел к выводу, что пульт дистанционного управления это неудобно. Просто потому что его надо искать и это лишнее устройство в доме. Лучше управление реализовать посредством мобильного телефона или планшета. У меня зародилась идея использовать микрокомпьютер Raspberry PI, поднять на ней ASP.NET MVC 5 веб-приложение через Mono и NancyFX. Далее использовать фреймворк jquery mobile для кроссплатформенности веб-приложения. Через Raspberry общаться с Arduino посредством WiFi, или LAN. В этом случае можно даже отказаться от LCD экрана, ведь всю нужную информацию можно посмотреть на смартфоне или планшете. Но этот проект пока только в голове.

Печатная плата и ее изготовление

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

Сборка на монтажных платах(используется Arduino Uno):

Система автоматического управления аквариумом на Arduino - 6

Я разработал однослойную печатную плату в программе Fritzing. Получилось следующее(используется Arduino Mega):

Система автоматического управления аквариумом на Arduino - 7

Самое противное при изготовлении печатной платы это было сверление. Особенно когда я старался создать печатную плату типа Shield, т.е. она одевалась на Arduino. Просверлить тонким сверлом больше 50 отверстий — это очень нудное занятие. А самое сложное — это забрать у жены ее новый утюг и уговорить купить лазерный принтер.

Кстати, если кто боится лазерно-утюжной технологии, то сразу скажу — это очень просто. У меня получилось с первого раза:

Система автоматического управления аквариумом на Arduino - 8

Сама сборка тоже оказалось простой — достаточно было припаять основные компоненты на плату:

Система автоматического управления аквариумом на Arduino - 9

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

Заключение

Проект прошивки аквариума выложен на GitHub. Адаптирован он для Arduino Mega. При использовании Uno приходится избавляться от части функционала. Банально не хватает памяти, производительности и свободных пинов для подключения всех модулей.

Автор: chesdenis

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js