Рубрики
HADRWARE Программирование

Умное термореле с таймаутами на Arduino

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

Схема

В качестве реле используется модуль с реле и оптической развязкой, представленный на рисунке 1. Datasheet реле доступен по ссылке в репозитории.

Реле в данной схеме используется потому, что нагрузка подключается к сети переменного тока 220в.

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

Рисунок 1 — Модуль реле с оптической развязкой

В качестве температурного датчика используется DS18B20 в герметичном исполнении, изображенный на рисунке 2. Datasheet датчика доступен по ссылке в репозитории.

Рисунок 2 — Герметичный температурный датчик DS18B20

Важное замечание: данный датчик требует дополнительный подтягивающий резистор (сопротивление, подключенное к +5В) на сигнальном проводе в 4.7 кОм, но для простоты можно использовать 5 кОм.

Схема подключения изображена на рисунке 3. Для удобства белый провод с датчика заменен на желтый.

Рисунок 3 — Схема подключения датчика и реле к Arduino

Сигнальный провод датчика подключен к 2 пину, а управляющий сигнал реле подается с 5 пина.

Важное напоминание: необходим подтягивающий резистор на 4.7 или 5 кОм.

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

  • красный провод зуммера подключить к реле в нормально-открытом состоянии (NO);
  • черный провод зуммера подключить к GND Arduino/реле;
  • провод питания VCC от Arduino/реле подключить к входному контакту нагрузки (COM).

Текст программы

Для работы программы необходимы следующие библиотеки:

Исходные тексты программы доступны в репозитории 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 производятся следующие действия:

  1. настройка отладочной печати, если она включена (Serial.begin);
  2. регистрация температур и их таймаутов (REGISTER_TIMEOUT);
  3. проверка выхода за пределы массива данных (timeouts_count > MAX_TIMEOUTS);
  4. сортировка массива данных по возрастанию температуры (qsort);
  5. настойка реле — задание сигнала «выключено» (LOW);
  6. запрос замера температуры с датчика;
  7. включение 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 производятся следующие действия:

  1. определение текущего времени в переменную current_time;
  2. отладка ввода температуры, если таковая включена;
  3. программный таймер на 800 секунд для температурного датчика;
  4. выбор нужного таймаута, если есть подходящая температура, и проверка на выход за верхний предел температуры;
  5. отладочная печать, если таковая включена;
  6. программные таймеры на открытие и закрытие реле;
  7. задание нужного управляющего сигнала реле;
  8. сброс 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, тогда пора перезапустить таймер и изменить значение переменной с временем последнего срабатывания.

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

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

Этот сайт использует Akismet для борьбы со спамом. Узнайте, как обрабатываются ваши данные комментариев.