Схема
В качестве реле используется модуль с реле и оптической развязкой, представленный на рисунке 1. Datasheet реле доступен по ссылке в репозитории.
Реле в данной схеме используется потому, что нагрузка подключается к сети переменного тока 220в.
Важное замечание: в процессе разработки выяснилось, что реле является нормально-замкнутым и управляющий сигнал на контакте IN его размыкает — необходимо проверить реле перед использованием его с имеющейся прошивкой.
В качестве температурного датчика используется DS18B20 в герметичном исполнении, изображенный на рисунке 2. Datasheet датчика доступен по ссылке в репозитории.
Важное замечание: данный датчик требует дополнительный подтягивающий резистор (сопротивление, подключенное к +5В) на сигнальном проводе в 4.7 кОм, но для простоты можно использовать 5 кОм.
Схема подключения изображена на рисунке 3. Для удобства белый провод с датчика заменен на желтый.
Сигнальный провод датчика подключен к 2 пину, а управляющий сигнал реле подается с 5 пина.
Важное напоминание: необходим подтягивающий резистор на 4.7 или 5 кОм.
Для удобства отладки можно подключить к реле зуммер, который при срабатывании будет издавать звук:
- красный провод зуммера подключить к реле в нормально-открытом состоянии (NO);
- черный провод зуммера подключить к GND Arduino/реле;
- провод питания VCC от Arduino/реле подключить к входному контакту нагрузки (COM).
Текст программы
Для работы программы необходимы следующие библиотеки:
- OneWire;
- DallasTemperature;
- MicroDS18B20 — спасибо AlexGyver за простую библиотеку для работы с датчиком температуры.
Исходные тексты программы доступны в репозитории TempRelay. Рассмотрим фрагменты данной программы подробнее.
Для хранения данных о температуре и используемых при ней таймаутах используется структура TIMEOUT_DATA:
// Структура для хранения данных о температуре и таймаутах
struct TIMEOUT_DATA
{
float temp;
uint32_t openTime;
uint32_t closeTime;
} timeouts[MAX_TIMEOUTS];
Данная структура используется для создания массива объектов timeouts максимальной размерностью MAX_TIMEOUTS, которая задается в макроконстанте:
// Лимит таймаутов
#define MAX_TIMEOUTS 10
Макрофункция REGISTER_TIMEOUT, принимает в качестве аргументов значения температуры и времен открытия и закрытия в миллисекундах. Для удобства записи долгого интервала в секундах используется макрофункция SEC_TO_MSEC. Реализация данных макрофункций представлена ниже:
// Макрос для регистрации данных о тем-ре и таймаутах (тем-ра в градусах цельсия, время открытия и закрытия в миллисекундах)
#define REGISTER_TIMEOUT(temp_C, openTime_ms, closeTime_ms) timeouts[timeouts_count++] = {temp_C, openTime_ms, closeTime_ms};
// Макрос для удобства записи таймаута в секундах (переводит в мс)
#define SEC_TO_MSEC(time) (long)time*1000
Как можно заметить в реализации макрофункции REGISTER_TIMEOUT используется переменная timeouts_count, которая хранит в себе количество используемых таймаутов и в дальнейшем используется в расчетах и для проверки выхода за рамки MAX_TIMEOUTS.
Важное замечание: если в процессе проверки происходит превышение значения MAX_TIMEOUTS программа не начинает работу.
Для отладки используются макроконстанты (0 — отладка отключена, 1 — отладка включена):
- DEBUG — отладочный вывод в процессе работы;
- DEBUG_INPUT — отладка температуры по вводу с клавиатуры.
Для удобства работы используемые пины записаны в макроконстанты:
- PIN_SENSOR — сигнал с датчика (2);
- PIN_RELAY — управляющий сигнал реле (5).
Следующий фрагмент кода содержит объявление объекта класса датчика из библиотеки MicroDS18B20 на заданном пине:
#include <microDS18B20.h> // Датчик температуры
// ...
MicroDS18B20<PIN_SENSOR> sensor; // датчик на пине D2
Программа состоит из двух основных функций для Arduino:
- setup — вызывается единожды при запуске программы;
- loop — вызывается в цикле в процессе работы программы.
В функции setup производятся следующие действия:
- настройка отладочной печати, если она включена (Serial.begin);
- регистрация температур и их таймаутов (REGISTER_TIMEOUT);
- проверка выхода за пределы массива данных (timeouts_count > MAX_TIMEOUTS);
- сортировка массива данных по возрастанию температуры (qsort);
- настойка реле — задание сигнала «выключено» (LOW);
- запрос замера температуры с датчика;
- включение watchdog на 4 секунды — для перезагрузки в случае зависания программы (wdt_enable).
Для функции сортировки qsort требуется вспомогательная функция для сравнения двух элементов:
// функция сравнения элементов для qsort по возрастанию
int qsort_compare(const void *cmp1, const void *cmp2)
{
// Указатели для простоты работы
struct TIMEOUT_DATA *a = (struct TIMEOUT_DATA *)cmp1;
struct TIMEOUT_DATA *b = (struct TIMEOUT_DATA *)cmp2;
// если значение отрицательное, то a < b, если положительное, то a > b, иначе равны
return a->temp - b->temp;
}
В функции loop производятся следующие действия:
- определение текущего времени в переменную current_time;
- отладка ввода температуры, если таковая включена;
- программный таймер на 800 секунд для температурного датчика;
- выбор нужного таймаута, если есть подходящая температура, и проверка на выход за верхний предел температуры;
- отладочная печать, если таковая включена;
- программные таймеры на открытие и закрытие реле;
- задание нужного управляющего сигнала реле;
- сброс watchdog во избежание перезагрузки.
Рассмотрим работу программного таймера для температурного датчика:
// конструкция программного таймера на 800 мс
if (current_time - tmr >= 800)
{
// Обновляем переменную для таймера опроса датчика
tmr = current_time;
// читаем прошлое значение
if (sensor.readTemp())
{
#if DEBUG_INPUT == 0
currentTemp = sensor.getTemp();
#endif // DEBUG_INPUT
}
#if DEBUG == 1
else
Serial.println("sensor.readTemp error");
#endif // DEBUG
// запрашиваем новое измерение
sensor.requestTemp();
}
Таймер реализован на разнице текущего времени и последнего срабатывания, если разница больше заданного порога, то нужно выполнить тело таймера и заменить время последнего срабатывания текущим.
Метод sensor.readTemp возвращает True, если ранее запрошенный замер температуры завершен (обычно около 700 мс). В случае, если замер завершен необходимо обновить температуру, хранящуюся в переменной currentTemp вызовом метода sensor.getTemp. В конце производится запрос нового замера.
Рассмотрим алгоритм выбора нужных таймаутов:
// Подбор подходящих таймаутов по текущей температуре
for (i = 0; i < timeouts_count; i++)
{
// Если обнаружили пороговое значение, которое больше текущей температуры - берем предыдущее и прерываем поиск
if (timeouts[i].temp > currentTemp)
{
i--;
break;
}
}
// Если перебрали все пороги и температура выше - берем последний
if (i == timeouts_count)
i--;
В цикле перебираются все доступные таймауты и производится проверка, что выбранная температура больше текущей. Если данное условие соблюдается необходимо взять предыдущий для который меньше или равен текущей и прервать цикл.
Если в процессе значение перебираемого индекса i стало равно значению timeouts_count, то это означает, что текущая температура выше последней заданной и необходимо взять последнюю заданную.
Рассмотрим алгоритм выбора значения сигнала управляющего реле:
relayState = LOW; // Считаем что если не попали во время открытия, то реле надо закрыть
// Если значение индекса параметров таймаута для данной температуры неотрицательное, значит нужно произвести работу с реле на текущей
if (0 <= i)
{
// Если время открытия ещё не истекло, то открываем реле
if (current_time < lastRelayTime + timeouts[i].openTime)
{
relayState = HIGH;
}
else // иначе,
// Если время превышает время закрытия, то необходимо сбросить время управления реле
if (current_time > lastRelayTime + timeouts[i].openTime + timeouts[i].closeTime
|| !lastRelayTime)
{
lastRelayTime = current_time;
}
}
else // иначе, сбросить время
{
lastRelayTime = 0;
}
Изначально предполагается, что управляющий сигнал реле должен быть LOW. Далее производится проверка, что индекс текущего таймаута i больше или равен нулю — температура преодолела нижний порог, иначе необходимо сбросить время последнего срабатывания таймера реле.
Если условие соблюдается — производится проверка таймеров открытия и закрытия реле.
Для открытия реле необходимо, чтобы текущее время было меньше суммы времени последнего срабатывания таймера и таймаута открытия реле. В остальных случаях реле будет закрыто — на взгляд автора более безопасный подход.
Далее производится проверка выхода текущего времени за сумму времени последнего срабатывания таймера и таймаутов открытия и закрытия реле или время последнего срабатывания = 0, тогда пора перезапустить таймер и изменить значение переменной с временем последнего срабатывания.