Релейное управление


Релейное управление – самый простой алгоритм из возможных, ведь у насесть только два состояния – вкл и выкл. В этом уроке рассмотрим алгоритмы, которые сделают релейное управление более правильным, позволят сохранить “здоровье” реле и повысят точность регулирования. Начнём с самого простого и очевидного:

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);

Для примера ниже используется библиотека thermistorMinim.h, скачать можно здесь.

#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, зависит от инертности системы и выбранного периода работы регулятора. Сам алгоритм можно представить в виде функции:

boolean 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 опросах в секунду. Для больших инерционных систем можно брать период в несколько секунд или даже минут. Алгоритм измеряет скорость изменения температуры за это время и умножает его на коэффициент. Если во время работы система перелетает через гистерезис, нужно увеличить коэффициент, чтобы реле выключалось и включалось раньше.

Важные страницы