ARDUINO Помогите с кодом PID регулятор (Решено)

n_vlad

✩✩✩✩✩✩✩
27 Дек 2023
11
2
Мне дали задание собрать регулятор для электрокотла на 380v 9kW. По ТЗ есть 3 датчика два NTC на 10кОм и один ds18b20.
NTC:
Один датчик будет на улице измерять температуру.
Второй температуру тепло носителя в котле.
ds18b20:
Будет измерять температуру в помещении.
Алгоритм такой.

Улица От +8 до 0 тогда температура котел 35
Но если в помещении выше +22 котел стоп

Улица от 0 до - 8 температура котла 40
Но если в помещении +22 котел стоп

Улица от -9 до -18тогда температура котла 55
Но если в помещении выше 20 котел стоп

Улица от -18 до -25 тогда температура котла - 65
Но если в помещении выше 20 котел стоп

Улица от-25 до-30и выше тогда температура котла 68
Но если в помещении выше 20 котел стоп.

Ну как я понимаю поддерживать температуру котла можно при помощи дифференциального управления в ПИД. По типу набрал он свою температуру выключился, температура упала на 2 градуса включился. Я не понимаю как мне связать все это вместе. Библиотека GyverPID управляет при помощи ШИМ. А у меня тены включаются через пускатель, а пускателем управляет твердотелка подключенная к ардуино помогите мне пожалуйста объясните
 

bort707

★★★★★★✩
21 Сен 2020
3,066
914
Сделай медленный ШИМ - скажем включил тен на 1 минуту, потом выключил на 3 минуты - это шим 25%

А если хотите чтоб вам весь код написали - придется деньги платить
 

n_vlad

✩✩✩✩✩✩✩
27 Дек 2023
11
2
@bort707,за работу надо платить это естественно. Как подружить GyverPID и PWMrelay
 
  • Лойс +1
Реакции: te238s

Bruzzer

★★★✩✩✩✩
23 Май 2020
476
137
@n_vlad,
Если задача не учебная, то на мой взгляд сначала без ПИД методом проб. Т.к. если тен один и запускается пускателем, то ПИД мало применим (очень очень ИМХО). Если тенов несколько то ПИД более реален.
 
  • Лойс +1
Реакции: n_vlad

Bruzzer

★★★✩✩✩✩
23 Май 2020
476
137
@n_vlad,
Тогда сначала просто посмотрите на сколько перегреется бак, если отключить при заданной температуре.
Но это просто мои мысли без всякого опыта.
Уверен есть более профильные форумы где приводятся проверенные на практике способы.
 

n_vlad

✩✩✩✩✩✩✩
27 Дек 2023
11
2
@viktor1703, Ну все знать не возможно. Я имею представление как оно должно работать. Просто у меня не много опыта в работе с ардуино, вот я и путаюсь узнать, как возможно реализовать данный алгоритм. И это не какой-то важнейший заказ где у меня горят сроки и я получи за него кучу денег. Я беру такое, чтобы обучиться работать с такими системами и да документацию я читал на все библиотеки.
 

viktor1703

★★★✩✩✩✩
9 Дек 2021
619
148
документацию я читал на все библиотеки
Представляю кашу в вашей голове. Тут две библиотеки нужно, одна для ds'ки другая для NTC, хотя терморезистор можно обрабатывать без библиотеки, информации море в интернете. А алгоритм работы у вас уже описан в первом сообщении, просто нужно его перевести на язык программирования. Всё довольно просто, опрос датчиков, сравнение результатов и выполнение соответствующего действия. Ну и задержка на несколько секунд, измерять температыры несколько тысяч раз в секунду совсем не нужно, ведь температуры будут изменятся довольно медленно.
 

rkit

★★★✩✩✩✩
5 Фев 2021
508
127
Ну как я понимаю поддерживать температуру котла можно при помощи дифференциального управления в ПИД
Нет, дифференциальный компонент нужен для подавления перерегуляции. Температуру точно регулирует И-компонент, остальные вспомогательные. Прочитай любое введение в ПИД.
 

Sana956

★✩✩✩✩✩✩
5 Мар 2022
48
17
PID очень сложно будет помогать настраивать удаленно. Проще будет сделать управление по гистерезису. Ну или PI в самом крайнем случае. Делать это должен человек, который понимает как оно работает, и на месте, а не удаленно, на форуме.
 
  • Лойс +1
Реакции: n_vlad

n_vlad

✩✩✩✩✩✩✩
27 Дек 2023
11
2
Ну я попробовал решить вот таким способом эту задачу. Я потыкал через порт отладки, в теории все работает.

boiler_regul:
#include <microDS18B20.h>
#include <GyverNTC.h>
#include <iarduino_RTC.h>

#define magnetic 2

MicroDS18B20<A3> ds;
iarduino_RTC watch(RTC_DS1307);
GyverNTC ntc_boiler(A0, 9920, 3950, 25, 10000);
GyverNTC ntc_street(A1, 9920, 3950, 25, 10000);

int temp_boiler;
int temp_room;
int temp_street;
int temp_set;

uint8_t time_h ;

bool mag_on;
bool mag_on_night;
bool night_mode;

void setup() {
  watch.begin();
  watch.period(1);
  pinMode(magnetic, OUTPUT);
  Serial.begin(9600);
}

void loop() {

  if (time_h >= 19 or time_h <= 6 and temp_room < 15) {
    //Serial.println("night_mode");
    temp_set = 35;
    night_mode = true;
    mag_on_night = true;
    magnetic_on();
  }

  if (time_h >= 7) {
    //Serial.println("day_mode");
    night_mode = false;

    if (temp_street == 8 or temp_street > 0 and night_mode == false) {
      if (temp_room < 22) {
        temp_set = 35;
        mag_on = true;
        magnetic_on();
      }
    }
    if (temp_street == 0 or temp_street > -8 and night_mode == false) {
      if (temp_room < 22) {
        temp_set = 40;
        mag_on = true;
        magnetic_on();
      }
    }
    if (temp_street == -8 or temp_street > -18 and night_mode == false) {
      if (temp_room < 22) {
        temp_set = 55;
        mag_on = true;
        magnetic_on();
      }
    }
    if (temp_street == -18 or temp_street > -25 and night_mode == false) {
      if (temp_room < 22) {
        temp_set = 60;
        mag_on = true;
        magnetic_on();
      }
    }
    if (temp_street == -25 or temp_street < -30 and night_mode == false) {
      if (temp_room < 22) {
        temp_set = 68;
        mag_on = true;
        magnetic_on();
      }
    }
  }
}

void magnetic_on() {
  while(mag_on) {
    if (temp_boiler < temp_set) {
      //Serial.println(temp_joi);
      digitalWrite(magnetic, LOW);
  }
    if (temp_boiler > temp_set) {
      digitalWrite(magnetic, HIGH);
      mag_on = false;
    }
  }
  while(mag_on_night) {
    if (temp_boiler < temp_set and night_mode == true) {
      //Serial.println(temp_joi);
      digitalWrite(magnetic, LOW);
    }
    if (temp_boiler > temp_set and night_mode == true) {
      digitalWrite(magnetic, HIGH);
      mag_on_night = false;
    }
  }
}

void yield() {
  static uint32_t timer_ds = millis();
  if (millis() - timer_ds >= 1000) {
    timer_ds = millis();
    if (ds.readTemp()) {
      temp_room = ds.getTemp();
      //Serial.println(temp_room);
    }
    ds.requestTemp();
  }

  static uint32_t timer_ntc_boiler = millis();
  if (millis() - timer_ntc_boiler >= 1000) {
    timer_ntc_boiler = millis();
    temp_boiler = ntc_boiler.getTempAverage();
    //Serial.println(temp_kotel);
  }

  static uint32_t timer_ntc_street = millis();
  if (millis() - timer_ntc_street >= 1000) {
    timer_ntc_street = millis();
    temp_street = ntc_street.getTempAverage();
    //Serial.println(temp_street);
  }
  static uint32_t timer_watch = millis();
  if (millis() - timer_watch >= 60000) {
    timer_watch = millis();
    watch.gettime(); 
      time_h = watch.Hours;
  }
}
 

Bruzzer

★★★✩✩✩✩
23 Май 2020
476
137
@n_vlad,
Ваше первое сообщение я понял так - программировать в целом умею, но не могу разобраться в особенностях ПИД.
Но в приведенном скетче и сама базовая логика ошибочная, и перевод ее на язык программирования с ошибками.
Пишу базовая логика - потому, что есть еще особенности которые надо учитывать при управлении мощным оборудованием - например повышенный риск зависаний и перезагрузок.
В таком случае помочь трудно, т.к. "помощь" фактически сводится к написанию кода за спрашивающего.
Приведу несколько замечаний. Но когда такие ляпы, то ко всему коду нет доверия.
  • в строке 32 скорее всего не хватает скобок в логическом условии
  • строка 40 не логична, т.к. не все что > 7 это день.
  • в строке 83 желателен комментарий - за счет чего буде выход из бесконечного цикла. И в целом комментарии были бы не лишними и для вас самого.
  • нет гистерезиса на температуру.

В общем надо учиться, учиться ......
 

viktor1703

★★★✩✩✩✩
9 Дек 2021
619
148
Я тут пробежался по коду, а в каком месте идет опрос температур?
 

n_vlad

✩✩✩✩✩✩✩
27 Дек 2023
11
2
Я тут пробежался по коду, а в каком месте идет опрос температур?
в отдельной функции

C++:
void yield() {
  static uint32_t timer_ds = millis();
  if (millis() - timer_ds >= 1000) {
    timer_ds = millis();
    if (ds.readTemp()) {
      temp_room = ds.getTemp();
      //Serial.println(temp_room);
    }
    ds.requestTemp();
  }
  static uint32_t timer_ntc_boiler = millis();
  if (millis() - timer_ntc_boiler >= 1000) {
    timer_ntc_boiler = millis();
    temp_boiler = ntc_boiler.getTempAverage();
    //Serial.println(temp_kotel);
  }
  static uint32_t timer_ntc_street = millis();
  if (millis() - timer_ntc_street >= 1000) {
    timer_ntc_street = millis();
    temp_street = ntc_street.getTempAverage();
    //Serial.println(temp_street);
  }
  static uint32_t timer_watch = millis();
  if (millis() - timer_watch >= 60000) {
    timer_watch = millis();
    watch.gettime();   
      time_h = watch.Hours;
  }
}
 

n_vlad

✩✩✩✩✩✩✩
27 Дек 2023
11
2
нет гистерезиса на температуру.
Ну как я понял и нашел примеры реализаций, то по сути должно быть так

Котёл набрал заданную температуру на два градуса больше, потом температура упала на два градуса он заново включил его. Вроде верно понял работу


C++:
// Подключаем все нужные библиотеки для работы с датчиком и дисплеем

#include <microDS18B20.h>
#include <GyverNTC.h>
#include <iarduino_RTC.h>

#define magnetic 2

MicroDS18B20<A3> ds;
iarduino_RTC watch(RTC_DS1307);
GyverNTC ntc_boiler(A0, 9920, 3950, 25, 10000);
GyverNTC ntc_street(A1, 9920, 3950, 25, 10000);

int temp_boiler;
int temp_room;
int temp_street;
int temp_set;

uint8_t time_h;
uint8_t HYSTERESIS = 2;

bool mag_on;
bool mag_on_night;
bool night_mode;

void setup() {
  watch.begin();
  watch.period(1);
  pinMode(magnetic, OUTPUT);
  Serial.begin(9600);
}

void loop() {

  if (time_h >= 19 or time_h <= 6 and temp_room < 15) {
    //Serial.println("night_mode");
    temp_set = 35;
    night_mode = true;
    mag_on_night = true;
    magnetic_on();
  }

  if (time_h >= 7) {
    //Serial.println("day_mode");
    night_mode = false;

    if (temp_street == 8 or temp_street > 0 and night_mode == false) {
      if (temp_room < 22) {
        temp_set = 35;
        mag_on = true;
        magnetic_on();
      }
    }
    if (temp_street == 0 or temp_street > -8 and night_mode == false) {
      if (temp_room < 22) {
        temp_set = 40;
        mag_on = true;
        magnetic_on();
      }
    }
    if (temp_street == -8 or temp_street > -18 and night_mode == false) {
      if (temp_room < 22) {
        temp_set = 55;
        mag_on = true;
        magnetic_on();
      }
    }
    if (temp_street == -18 or temp_street > -25 and night_mode == false) {
      if (temp_room < 22) {
        temp_set = 60;
        mag_on = true;
        magnetic_on();
      }
    }
    if (temp_street == -25 or temp_street < -30 and night_mode == false) {
      if (temp_room < 22) {
        temp_set = 68;
        mag_on = true;
        magnetic_on();
      }
    }
  }
}

void magnetic_on() {
  while(mag_on) {
    if (temp_boiler < temp_set - HYSTERESIS) {
      //Serial.println(temp_joi);
      digitalWrite(magnetic, LOW);
  }
    if (temp_boiler > temp_set + HYSTERESIS) {
      digitalWrite(magnetic, HIGH);
      mag_on = false;
    }
  }
  while(mag_on_night) {
    if (temp_boiler < temp_set - HYSTERESIS and night_mode == true) {
      //Serial.println(temp_joi);
      digitalWrite(magnetic, LOW);
    }
    if (temp_boiler > temp_set + HYSTERESIS and night_mode == true) {
      digitalWrite(magnetic, HIGH);
      mag_on_night = false;
    }
  }
}

void yield() {
  static uint32_t timer_ds = millis();
  if (millis() - timer_ds >= 1000) {
    timer_ds = millis();
    if (ds.readTemp()) {
      temp_room = ds.getTemp();
      //Serial.println(temp_room);
    }
    ds.requestTemp();
  }
  static uint32_t timer_ntc_boiler = millis();
  if (millis() - timer_ntc_boiler >= 1000) {
    timer_ntc_boiler = millis();
    temp_boiler = ntc_boiler.getTempAverage();
    //Serial.println(temp_kotel);
  }
  static uint32_t timer_ntc_street = millis();
  if (millis() - timer_ntc_street >= 1000) {
    timer_ntc_street = millis();
    temp_street = ntc_street.getTempAverage();
    //Serial.println(temp_street);
  }
  static uint32_t timer_watch = millis();
  if (millis() - timer_watch >= 60000) {
    timer_watch = millis();
    watch.gettime();  
      time_h = watch.Hours;
  }
}
 

viktor1703

★★★✩✩✩✩
9 Дек 2021
619
148
По моему это лишнее отсчитывать по три одинаковых времени. Можно эти три опроса засунуть вместе.
C++:
void yield() {
  static uint32_t timer_ds = millis();
  if (millis() - timer_ds >= 1000) {
    timer_ds = millis();
 
      temp_room = ds.getTemp();
      temp_street = ntc_street.getTempAverage();
      temp_boiler = ntc_boiler.getTempAverage();
    }

}
 

Bruzzer

★★★✩✩✩✩
23 Май 2020
476
137
@n_vlad,
Я отключусь от обсуждения.
Только повторюсь.
if (time_h >= 19 or time_h <= 6 and temp_room < 15)
что вы хотите (наверно первое) и что получаете (второе)
if ((time_h >= 19 or time_h <= 6) and temp_room < 15)
if (time_h >= 19 or (time_h <= 6 and temp_room < 15))

Если дойдет до железа. Предусмотрите индикацию перезагрузки контроллера и счетчик перезагрузок (в модуле часов может быть память для хранения). И устойчивость к зависаниям. Т.к. при коммутации нагрузок зависания и перезагрузки не редки.
 
  • Лойс +1
Реакции: n_vlad

viktor1703

★★★✩✩✩✩
9 Дек 2021
619
148
yield не опрашивает датчики вне зависимости от задержек и циклов
Ну она же должна в какой-то момент вызываться, иначе получается, что только yield и должен работать. Но она работает немного по другому.
 

Вложения

Изменено:

n_vlad

✩✩✩✩✩✩✩
27 Дек 2023
11
2
Ну я исправил все свои ошибки добавил WDT и изменил чуток алгоритм работы, все протестировал на железе, работает отлично)
Всем спасибо, кто подсказал и указал на ошибки.

Вот код который у меня получился:


Boiler:
/
// Подключаем все нужные библиотеки для работы с датчиком и дисплеем
#include <EEPROM.h>                // Библиотека энергонезависимой памяти
#include <GyverWDT.h>              // Библиотека для отслеживания зависаний МК
#include <microDS18B20.h>          // Библиотека для цифрового датчика ds18b20
#include <GyverNTC.h>              // Библиотека для аналового NTC термистора
#include <iarduino_RTC.h>          // Библиотека для модуля реального времени
#define magnetic 2                 // Пин подключения реле управления пускателем
#define ds_temp 3                  // Пин подключения ds18b20
#define led_error 12               // Пин подключения сигнализирующего светодиода об аварии
#define ntc_boiler_temp A0         // Пин подключенияn NTC термистора
#define ntc_street_temp A1         // Пин подключенияn NTC термистора
#define R1 12950                   // Сопратевление делительного резитора 
#define R2 12950                   // Сопратевление делительного резитора 
#define K 3950                     // B-постоянная, K
#define K2 3380                    // B-постоянная, K
#define normal_temp 25             // Заданная температура 
#define normal_R 10000             // Сопративление термистора при заданной температуре 
#define HYSTERESIS 2               // Задаём Гистерезис 
MicroDS18B20<ds_temp> ds;                                                       // Прописываем датчик ds18b20
iarduino_RTC watch(RTC_DS1307);                                                 // Прописываем модуль RTC
GyverNTC ntc_boiler(ntc_boiler_temp, R1, K, normal_temp, normal_R);             // Прописываем NTC датчик
GyverNTC ntc_street(ntc_street_temp, R2, K2, normal_temp, normal_R);            // Прописываем NTC датчик

int temp_boiler;          // Переменная температуры в котле
int temp_room;            // Переменная температуры в помещении
int temp_room_set;        // Переменная для установки комнатной температуры  
int temp_street;          // Переменная температуры на улице
int temp_set;             // Переменная для установки температуры котла
uint8_t time_h;           // Переменная хранящая в себе данные о текущем часе
uint8_t reload;           // Переменная хранящая в себе количество перезагрузок МК при зависании
bool mag_on;              // Флаг включения дневного цикла нагрева
bool mag_on_night;        // Флаг включения ночного цикла нагрева
bool night_mode;          // Флаг включения ночного режима
bool temp_room_test;      // Флаг тестирования комнатной температуры
void setup() {
  Watchdog.enable(INTERRUPT_RESET_MODE, WDT_PRESCALER_128);          // Задаём режим и время для WDT 
  pinMode(magnetic, OUTPUT);            // Прописываем пин magnetic как выход
  pinMode(led_error, OUTPUT);           // Прописываем пин led_error как выход
  watch.begin();                        // Инициализируем модуль RTC 
  watch.period(1);                      // Задаём опрос данных с RTC модуля раз в минуту
  Serial.begin(9600);                   // Открываем порт отладки 
}
void loop() {

  if (EEPROM.put(0, reload) >= 5) digitalWrite(led_error, HIGH);          // Если перезагрузок будет больше 5, то включаем сигнализирующий светодиод ошибки 

  yield();          // Опрашиваем все датчики и модуль RTC
  if (temp_room < temp_room_set - HYSTERESIS) temp_room_test = false;           // Если температура в комнате ниже заданной, то опускаем флаг тестирования комнатной температуры
  if ((time_h >= 19 or time_h <= 6) and temp_room_test == false) {              // Если время с 19 вечера по 6 утра и флаг тестирования температуры опущен, то задаём параметры для ночного режима
    //Serial.println("night_mode");
    temp_room_set = 15;          // Температура в помещении
    temp_set = 35;               // Температура котла
    night_mode = true;           // Поднимаем флаг ночного режима
    mag_on_night = true;         // Поднимаем флаг для ночного цикла нагрева
    magnetic_on();               // Переходим в функцию нагрева
  }
  if ((time_h >= 7 or time_h <= 18) and temp_room_test == false) {              // Если время с 7 утра по 18 вечера и флаг тестирования температуры опущен, то выключаем ночной режим и задаём параметры
    //Serial.println("day_mode");
    night_mode = false;
    if ((temp_street == 8 or temp_street > 0) and night_mode == false) {
      temp_room_set = 22;
      temp_set = 35;
      mag_on = true;
      magnetic_on();
    }
    if ((temp_street == 0 or temp_street > -8) and night_mode == false) {
      temp_room_set = 22;
      temp_set = 40;
      mag_on = true;
      magnetic_on();
    }
    if ((temp_street == -8 or temp_street > -18) and night_mode == false) {
      temp_room_set = 20;
      temp_set = 55;
      mag_on = true;
      magnetic_on();
    }
    if ((temp_street == -18 or temp_street > -25) and night_mode == false) {
      temp_room_set = 20;
      temp_set = 60;
      mag_on = true;
      magnetic_on();
    }
    if ((temp_street == -25 or temp_street < -30) and night_mode == false) {
      temp_room_set = 22;
      temp_set = 68;
      mag_on = true;
      magnetic_on();
    }
  }
  Watchdog.reset(); // Переодический сброс watchdog, означающий, что устройство не зависло
}
void magnetic_on() {
  while(mag_on) {
    yield();                                                   // Опрашиваем датчики 
    if (temp_boiler <= temp_set - HYSTERESIS) {                // Если температура котла меньше заданной 
      if (temp_room <= temp_room_set - HYSTERESIS) {           // И если температура в помещении меньше заданной
        digitalWrite(magnetic, LOW);                           // Включаем тен
        Watchdog.reset();                                      // Переодический сброс watchdog, означающий, что устройство не зависло
      }
    }
    if (temp_boiler >= temp_set + HYSTERESIS) {                // Если температура котла набралась до заданной
      digitalWrite(magnetic, HIGH);                            // Выключаем тен
      Watchdog.reset();                                                               // Переодический сброс watchdog, означающий, что устройство не зависло
    }
    if (temp_room >= temp_room_set + HYSTERESIS) {             // Если температура в помещении набралась до заданной
      digitalWrite(magnetic, HIGH);                            // Выключаем тен 
      mag_on = false;                                          // Опускаем флаг цикла нагрева
      temp_room_test = true;                                   // Поднимаем флаг тестирования температуры в комнате
    }
  }
  while(mag_on_night) {
    yield();                                                                            // Опрашиваем датчики 
    if ((temp_boiler <= temp_set - HYSTERESIS) and night_mode == true) {                // Если температура котла меньше заданной 
      if (temp_room <= temp_room_set - HYSTERESIS) {                                    // И если температура в помещении меньше заданной
        digitalWrite(magnetic, LOW);                                                    // Включаем тен
        Watchdog.reset();                                                               // Переодический сброс watchdog, означающий, что устройство не зависло
      }
    }
    if ((temp_boiler >= temp_set + HYSTERESIS) and night_mode == true) {                // Если температура котла набралась до заданной
      digitalWrite(magnetic, HIGH);                                                     // Выключаем тен
      Watchdog.reset();                                                                 // Переодический сброс watchdog, означающий, что устройство не зависло
    }
    if ((temp_room >= temp_room_set + HYSTERESIS) and night_mode == true) {             // Если температура в помещении набралась до заданной
      digitalWrite(magnetic, HIGH);                                                     // Выключаем тен 
      mag_on = false;                                                                   // Опускаем флаг цикла нагрева
      temp_room_test = true;                                                            // Поднимаем флаг тестирования температуры в комнате
    }
  }
}
void yield() {
  static uint32_t timer_ds = millis();                       // Переменная для таймера на millis() на 1 секунду
  if (millis() - timer_ds >= 1000) {
    timer_ds = millis();
    if (ds.readTemp()) {
      temp_room = ds.getTemp();                              // Записываем данные с датчика в переменную
      temp_street = ntc_street.getTempAverage();             // Записываем данные с датчика в переменную
      temp_boiler = ntc_boiler.getTempAverage();             // Записываем данные с датчика в переменную
      //Serial.println(temp_room);
      //Serial.println(temp_boiler);
      //Serial.println(temp_street);
    }
    ds.requestTemp();
  }
  static uint32_t timer_watch = millis();                    // Переменная для таймера на millis() на 1 минуту
  if (millis() - timer_watch >= 60000) {
    timer_watch = millis();
    watch.gettime();                                         // Опрашиваем модуль RTC 
      time_h = watch.Hours;                                  // Записываем данные с датчика в переменную
  }
}
ISR(WATCHDOG) {
  reload++;
  EEPROM.get(0, reload);           // Дабовляем +1 к перезагрузке
}
}
 
Изменено:
  • Лойс +1
Реакции: viktor1703