Релейное управление – самый простой алгоритм из возможных, ведь у нас есть только два состояния – вкл и выкл. В этом уроке рассмотрим алгоритмы, которые сделают релейное управление более правильным, позволят сохранить "здоровье" реле и повысят точность регулирования. Начнём с самого простого и очевидного:
if (temp < 50.0) digitalWrite(relayPin, 1);
else digitalWrite(relayPin, 0);
Данный код не нуждается в комментариях, он просто включает реле, когда условная температура ниже 50 градусов, и выключает, когда она выше. Если вызывать данный код без задержки или таймера - мы получим жуткий дребезг в момент включения и выключения реле, так как шумы измерений будут постоянно менять результат условия:
Зелёный график - как раз состояние реле. Ужас! Из этого графика также следует неутешительный вывод: значения надо фильтровать, это сильно увеличит стабильность системы. Фильтры мы подробно разбирали вот в этом уроке.
Период #
Первым шагом к созданию нормального релейного регулятора является период работы регулятора, его можно реализовать как задержкой, так и "таймером на миллис":
if (temp < 50.0) digitalWrite(relayPin, 1);
else digitalWrite(relayPin, 0);
delay(1000);
Период 1 секунда:
static uint32_t tmr;
if (millis() - tmr >= 1000) {
tmr = millis();
if (temp < 50.0) digitalWrite(relayPin, 1);
else digitalWrite(relayPin, 0);
}
Ситуация в корне изменится, ведь даже при всём желании реле не сможет переключаться чаще, чем раз в секунду!
Гистерезис #
Второй способ – гистерезис, позволяет ещё сильнее уменьшить количество переключений реле и даже избавиться от опроса по таймеру, что повышает реакцию системы на изменения, сохранив при этом хорошую устойчивость к помехам. Гистерезис разделяет установку на две, чуть меньше и чуть больше, на размер окна гистерезиса:
Логика работы такова, что мы включаем реле на нагрев ниже нижней линии и выключаем только выше верхней. То есть образуется область, внутри которой система грубо говоря движется по инерции от последнего переключения и переходит в новое состояние только при выходе из этой области. Понятное дело, что добавление гистерезиса сильно уменьшает не только количество переключений реле, но и точность, потому что мы своими собственными руками задаём область, точность внутри которой нам фактически безразлична, как и шумы измерения. В коде гистерезис можно реализовать так:
#define RELAY_PIN 2
float setpoint = 50.0; // установка
float hyster = 2; // ширина окна гистерезиса
// ...............
static bool relayState = false; // обязательно глобальная или статическая!
if (temp < (setpoint - hyster )) relayState = true;
else if (temp > (setpoint + hyster )) relayState = false;
digitalWrite(RELAY_PIN, relayState);
Скетч
#define THERM_PIN 0
#define RELAY_PIN 2
#define SETPOINT 50.0
#define HYSTER 2
#include "thermistorMinim.h"
// GND --- термистор --- A0 --- 10к --- 5V
thermistor therm(THERM_PIN, 10000, 3950); // пин, сопротивление, бета-коэффициент
void setup() {
Serial.begin(9600);
pinMode(RELAY_PIN, OUTPUT);
}
bool relayState = false;
void regul(float temp) {
// таймер уже не так сильно нужен
if (temp < (SETPOINT - HYSTER)) relayState = true;
else if (temp > (SETPOINT + HYSTER)) relayState = false;
digitalWrite(RELAY_PIN, relayState);
}
void debug(float temp) {
static uint32_t tmr;
if (millis() - tmr > 50) {
tmr = millis();
Serial.print(temp); // фактическая
Serial.print(',');
Serial.print(SETPOINT); // установка
Serial.print(',');
Serial.print(SETPOINT + HYSTER); // гистерезис
Serial.print(',');
Serial.print(SETPOINT - HYSTER); // гистерезис
Serial.print(',');
Serial.println(relayState * 2 + 40); // сост. реле
}
}
void loop() {
float temp = therm.getTempAverage();
regul(temp);
debug(temp);
}
Работает оно следующим образом:
Отлично! Теперь нам не страшны шумы и износ реле, но мы фактически "раскачали" систему, заставляя её включаться чуть ниже заданной температуры, а выключаться - чуть выше. Колебания температуры стали сильнее и это не очень приятно. Есть ли способ их избежать?
Хитрый алгоритм с опережением #
Рассмотренный далее алгоритм позволяет выключать и включать реле заранее, анализируя скорость изменения температуры. Если система чувствует, что температура растёт и может подняться выше установки - она выключает реле и наоборот. Такой способ называется управлением с обратной связью по скорости изменения величины. Сама скорость изменения вводится в алгоритм как производная - изменение величины, делённое на время, за которое произошло изменение. Далее это изменение умножается на некий коэффициент, который играет роль коэффициента усиления и уникален для каждой системы, подбирается вручную в диапазоне от 0.001 до 1000, зависит от инертности системы и выбранного периода работы регулятора. Сам алгоритм можно представить в виде функции:
bool relayGet() {
float signal;
if (k > 0) {
float rate = (input - prevInput) / _dt_s; // производная от величины (величина/секунду)
prevInput = input;
signal = input + rate * k;
} else {
signal = input;
}
int8_t F = (sign(signal - setpoint - hysteresis / 2) + sign(signal - setpoint + hysteresis / 2)) / 2;
if (F == 1) output = _direction;
else if (F == -1) output = !_direction;
return output;
}
Данный алгоритм реализован у меня в библиотеке GyverRelay. Рассмотрим простой пример:
#define THERM_PIN 0
#define RELAY_PIN 2
#define SETPOINT 50.0
#define HYSTER 2
#include "thermistorMinim.h"
// GND --- термистор --- A0 --- 10к --- 5V
thermistor therm(THERM_PIN, 10000, 3950); // пин, сопротивление, бета-коэффициент
#include "GyverRelay.h"
// установка, гистерезис, направление регулирования
GyverRelay regulator;
// либо GyverRelay regulator(); без указания направления (будет REVERSE)
void setup() {
Serial.begin(9600);
pinMode(RELAY_PIN, OUTPUT); // пин реле
regulator.k = 8.5; // коэффициент обратной связи (подбирается по факту)
regulator.setpoint = SETPOINT; // установка (ставим на SETPOINT градусов)
regulator.hysteresis = HYSTER; // ширина гистерезиса
}
void loop() {
regul();
debug();
}
void regul() {
static uint32_t tmr;
if (millis() - tmr > 500) {
tmr = millis();
regulator.input = therm.getTempAverage(); // сообщаем регулятору текущую температуру
digitalWrite(RELAY_PIN, regulator.getResult()); // отправляем на реле (ОС работает по своему таймеру)
}
}
void debug() {
static uint32_t tmr;
if (millis() - tmr > 50) {
tmr = millis();
Serial.print(regulator.input); // фактическая
Serial.print(',');
Serial.print(SETPOINT); // гистерезис
Serial.print(',');
Serial.print(SETPOINT + HYSTER); // гистерезис
Serial.print(',');
Serial.print(SETPOINT - HYSTER); // гистерезис
Serial.print(',');
Serial.println(regulator.output * 2 + 30); // сост. реле
}
}
Как можно видеть, библиотека очень простая: настраиваем установку и гистерезис - система будет стараться удержать установку внутри него, то есть он играет больше роль окна точности. Далее передаём в регулятор значение с датчика, а он нам выдаёт 1 или 0 - включать или выключать реле. И всё! График на той же системе выглядит вот так, регулятор работает просто потрясающе! Такая точность даже и не снилась классическим схемам с гистерезисом:
Как настроить: для быстрой системы, как у меня (обмотанный нихромом термистор), нужно выбирать время опроса датчика поменьше, то есть опрашивать датчик почаще. У меня хороший результат получился на 2 опросах в секунду. Для больших инерционных систем можно брать период в несколько секунд или даже минут. Алгоритм измеряет скорость изменения температуры за это время и умножает его на коэффициент. Если во время работы система перелетает через гистерезис, нужно увеличить коэффициент, чтобы реле выключалось и включалось раньше.