ПИД регулятор нужна критика и конструктивные предложения по алгоритму (Библиотеку ПИД не предлагать)

yul-i-an
yul-i-an аватар
Offline
Зарегистрирован: 10.12.2012

Делаю ПИД регулятор для водогрейного электрического котла.

void PID(){
   err=ust-temp;
   I=I+err;
   zad=Kp*err+(Ki*I*0.25)+(Kd*(err-pre_err)/0.25);
   pre_err=err; 
  if (zad<0){ zad=0;}
  if (zad>100){zad=100;}
 }

Что в него можно есче добавить, а что изменить?

По переменным temp-измеренная температура, ust-уставка, zad-управляющее воздействие (0-100%) буду управлять полупериодами по алгоритму Брезенхема, err-ошибка (рассогласование),pre_err-прошлая ошибка, I-интегральная составляющая (остальные составляющие расчитываються прям в формуле), Kp,Ki,Kd-коэффициенты ПИД, 0.25-частота расчета алгоритма ПИД (Функцию void PID() вызываю каждые 0.25с.

faraddin
Offline
Зарегистрирован: 11.08.2013

yul-i-an пишет:

 

Что в него можно есче добавить, а что изменить?

 

Да что угодно. Можно чтоб он тостер утром включал, можно чтоб гимн пиликал через динамик, вариантов куча

yul-i-an
yul-i-an аватар
Offline
Зарегистрирован: 10.12.2012

Мене больше интересны предложения относительно самого алгоритма ПИД.

В приборе никаких излишеств, чистый функционал.

step962
Offline
Зарегистрирован: 23.05.2011

yul-i-an пишет:

Что в него можно есче добавить, а что изменить?

Ну, как минимум, вспомнить, что 0.25 - это 1/4.

И избавиться от умножения/деления на число с плавающей точкой.

Какой-никакой прирост производительности (впрочем, в данном случае это некритично) и уменьшение размеров прошивки.

yul-i-an пишет:

Функцию void PID() вызываю каждые 0.25с.

И что, температура в котле за это время успевает измениться? Одному мне кажется, что при такой частоте опроса датчика дифференциальная часть этой формулы будет реагировать скорее на погрешности в измерении температуры, чем на собственно изменение???
Puhlyaviy
Puhlyaviy аватар
Offline
Зарегистрирован: 22.05.2013

step962 пишет:

И что, температура в котле за это время успевает измениться? Одному мне кажется, что при такой частоте опроса датчика дифференциальная часть этой формулы будет реагировать скорее на погрешности в измерении температуры, чем на собственно изменение???


Вот блин не поверишь. Но есть люди и их очень много. Которые верят что если на период когда дома никого нет понижать/повышать в доме температуру то это принесет экономию и что можно в доме изменить температуру в течении 20 минут на 10-15 градусов. Они не понимают что либо дом должен быть с коробку от холодильника, либо в этот доме нужно подорвать ядреную бомбу что бы резко образовалось нужное количество энергии.
И именно такие люди и измеряют температуру 4 раза в секунду, ибо читай выше, температура у них меняется резко и непредсказуемо!

vvadim
Offline
Зарегистрирован: 23.05.2012

А зачем для котла вообще нужен пид регулятор с огромной инертностью системы .

Хотя может автор проводит сверхночные опыты по физике или химии.

yul-i-an
yul-i-an аватар
Offline
Зарегистрирован: 10.12.2012

Если бы Вы внимательно посмотрели код, то заметили бы что в нем нет опроса датчика температуры, он опрашиваеться другой функцией и реже, а эта функция только расчитывает управляющее воздействие.

yul-i-an
yul-i-an аватар
Offline
Зарегистрирован: 10.12.2012

step962 пишет:

yul-i-an пишет:

Что в него можно есче добавить, а что изменить?

Ну, как минимум, вспомнить, что 0.25 - это 1/4.

И избавиться от умножения/деления на число с плавающей точкой.

Какой-никакой прирост производительности (впрочем, в данном случае это некритично) и уменьшение размеров прошивки.

Производительность никогда не помешает.

yul-i-an
yul-i-an аватар
Offline
Зарегистрирован: 10.12.2012

В котле стоял би металический термостат, у которого переодически залипали контакты и котел перегревался (знаю про пускатели и тврдотельные реле, сам электрик). После переезда на новую квартиру, дочь (4 года) включила  пустой котел и как следствие тен згорел, решено было отремонтировать котел и заодно произвести модернизацию. Планирую еще если всё нормально будет работать поставить такойже регулятор в банный комплекс на приточку с нагревателем.

Umka
Umka аватар
Offline
Зарегистрирован: 19.10.2012

Тогда и датчик наличия жидкости поставьте обязательно. А действительно, зачем котлу ПИД, да еще и такой быстрый? ИМХО по гистерезису в 2-4 градуса (стандарт для котлов 2 или 10 градусов) будет не хуже работать. Я не спец по котлам, но в инкубаторах кое-что понимаю. Так даже в них 90% птицеводов используют релейный принцып поддержания температуры. Правда гистерезис поменьше. А у многих вообще аналоговые регуляторы используются и ничего.

yul-i-an
yul-i-an аватар
Offline
Зарегистрирован: 10.12.2012

Хочеться чтото универсальное и недорогое.

Писал выше (Планирую еще если всё нормально будет работать поставить такойже регулятор в банный комплекс на приточку с нагревателем.), возможно на управление электрокаменкой в сауне. На котле отладка  будет.

Из всех ответивших пока только step962 по делу ответил, остальные...

 

Umka
Umka аватар
Offline
Зарегистрирован: 19.10.2012

Ну а что еще о 8 строчках кода можно сказать? Вы уж весь опубликуйте пожалуйста.

Что вообще делает void PID() если она ничего не возвращает и ничем не управляет? Просто значения присваивает?

yul-i-an
yul-i-an аватар
Offline
Зарегистрирован: 10.12.2012

Весь так весь

#include <LiquidCrystal.h>
LiquidCrystal lcd(12, 11, 5, 4, 3, 7);
//Флаги
boolean PIDAT=0,ALARM=0,FB=0;
//кнопки
int UP=17,DN=18,SET=19;
int reg,er,zad;
boolean out=0;//управление симистором
double I,Kp=1,Ki=1,Kd=1,pre_err,err;
double temp=0;//текущая температура
double ust=22;//уставка
int ten=13;//выход на семистр (нагреватель)
unsigned long PIDMillis;//сброс таймера
unsigned long tempMillis;//сброс таймера для замера темп
unsigned long menuMillis;//сброс таймера для возврата меню
int m=0;//переменная меню

void setup(){
  pinMode(UP,INPUT);//кнопка вверх
  pinMode(DN,INPUT);//кнопка вниз
  pinMode(SET,INPUT);//кнопка SET
  pinMode(ten,OUTPUT);
  Serial.begin(9600);
  attachInterrupt(0, zero_crosss_int, RISING);//внешнее прерывание ZC
 lcd.begin(16, 2);
 lcd.setCursor(1, 0);
 lcd.print("Termostat v2.0");
 delay(2000);
 lcd.clear();
//AutoPIDSet();автонастройка при включении
}
void loop(){
  unsigned long currentMillis = millis();
  //каждую секунду мерим температуру
  if (currentMillis-tempMillis>=1000){

    temp_lm35();
 
    tempMillis=currentMillis;
  }
  //каждые 250мс
  if (currentMillis-PIDMillis>=250){
    PID();//расчитываем ПИД
    PIDMillis=currentMillis;
    key();//проверяем кнопки
    if (currentMillis-menuMillis>=10000){m=0;}
    menu();//выводим меню
  }

  
}
//регулятор ПИД
 void PID(){
   err=ust-temp;
   I=I+err;
   zad=Kp*err+(Ki*I*0.25)+(Kd*(err-pre_err)/0.25);
   pre_err=err; 
    
  if (zad<0){ zad=0;I=0;}
  if (zad>100){zad=100;}
 }
 
 //Замер температуры
 void temp_lm35(){
  temp = analogRead(0);
  temp = temp * 5/1024*100;
 }
 
 //Внешнее прерывание детектора нуля
void zero_crosss_int()
{
      ResOut();//Вызов функции управления симистором по алгоритму Брезенхема
}

//управление симистором
void ResOut(){
  delayMicroseconds(1230);//для попадания в ноль
  reg = zad + er;
  if (reg < 50){
    out=0;
    er = reg ; 
  }
  else {
    out=1;
    er=reg-100;
    }
 digitalWrite(ten,out);
 }
 //Авто настройка коэффициентов ПИД
void AutoPIDSet(){
  //тут будет автонастройка
}

void menu(){
  lcd.clear();
  switch (m){
 case 0:
  lcd.setCursor(0, 0);
 lcd.print(" Temp  Ust  Pwr%");
 lcd.setCursor(0, 2);
 lcd.print(temp);
 lcd.setCursor(6, 2);
 lcd.print(ust);
 lcd.setCursor(13, 2);
 lcd.print(zad);
 break;
  case 10:
 lcd.setCursor(0, 0);
 lcd.print("SET Temperature");
 lcd.setCursor(6, 2);
 lcd.print(ust); 
 break; 
 case 20:
 lcd.setCursor(0, 0);
 lcd.print("PID auto tune");
 break;
 case 30://коды ошибок
 lcd.setCursor(5, 0);
 lcd.print("ALARM");
 lcd.setCursor(7, 2);
 lcd.print(ALARM);
 break;
}
 }
void key(){
  if (digitalRead(SET)==LOW&&FB==1){FB=0;menuMillis=millis();}//сброс защелки кнопок и сброс таймера автовозврата на основной экран
  if (digitalRead(UP)==LOW&&FB==1){FB=0;menuMillis=millis();}
  if (digitalRead(DN)==LOW&&FB==1){FB=0;menuMillis=millis();}
  
  if (digitalRead(SET)==HIGH&&FB==0){key_SET();} 
  if (digitalRead(UP)==HIGH&&FB==0) {key_UP();}

  if (digitalRead(DN)==HIGH&&FB==0){key_DN();}
}

void key_SET(){
  m=m+10;
FB=1;
  if (m>30){m=0;}
 }

void key_UP(){
  if (m==10){ust=ust+0.1;
  FB=1;
  if (ust>80){ust=80;}
  }
}

void key_DN(){
  if (m==10){ust=ust-0.1;
  FB=1;
  if (ust<6){ust=5;}
  }
  if (m==20){PIDAT=1; AutoPIDSet();}
}

Код еще сыроват и не оптимизирован, но рабочий.

yul-i-an
yul-i-an аватар
Offline
Зарегистрирован: 10.12.2012

void PID() расчитывает управляющее воздействие на нагреватель в % (глобальная переменная zad :) ), далее эта переменная берется в функции ResOUT() которая вызываеться от внешнего прерывания детектора нуля для расчета по алгоритму Брезенхема пропустить полуволну синусойды питающей тен сети или нет.

Umka
Umka аватар
Offline
Зарегистрирован: 19.10.2012

Del 

Boing70
Offline
Зарегистрирован: 19.11.2014

yul-i-an пишет:

которая вызываеться от внешнего прерывания детектора нуля

подскажите как  сделать - внешнее прерывание детектора нуля

Boing70
Offline
Зарегистрирован: 19.11.2014

нашел интерессный алгоритм - На основе 3-х температур: прогноза, реальной температуры и целевой, происходит расчет времени включения подогрева. Т.е. программа делает прогноз температуры, учитывает инерцию нагрева, и отключает нагрев заранее, что позволяет удерживать температуру точнее, при максимальной скорости нагрева.

вся статья по ссылке http://we.easyelectronics.ru/Tools/pechka-za-1580-rubley-chast-2-upravlenie-i-primer-raboty.html

может пригодится

-Kostik-
Offline
Зарегистрирован: 20.06.2013

Всем доброго времени суток!

yul-i-an, по самому алгоритму ПИД-регулятора (расчету управляющего воздействия) добавить что-либо тяжело. Единственное, что я бы изменил в расчете, это убрал жесткую зависимость от времени вызова процедуры регулятора, например, передавая в процедуру время между вызовами. Воздух в доме - объект весьма и весьма инерционный, особенно если учесть то, что перемещение нагретых воздушных масс происходит только засчет конвекции, а не перемешивается, к примеру, вентилятором. Отсюда вызывать процедуру регулирования каждые 250 мс, на мой взгляд, очень часто. Хотя бы от одной секунды, не чаще. А освободившееся время уделите обработке сигнала с датчика: делайте выборку, примените фильтрацию. Иначе от скачкообразных погрешностей измерения Ваша Д-составляющая будет только мешать, и Кд будет очень мал. Не исключено, что при настройке Вы придете к более простым законам регулирования - ПИ- или П-регуляторам. Подумайте над тем, где Вы установите датчик, чтобы от отражал именно температуру в комнате, а не у батареи или у окна. Еще рекомендую добавить переключение режима работы: ручной/автоматический. Пригодится, если регулятор уйдет в автоколебания при каких-нибудь настройках, и его понадобится отключить, выставив управляющее воздействие вручную.

Прислушатесь к тому, что Вам уже здесь посоветовали, многое озвучивалось ранее.

trembo
trembo аватар
Offline
Зарегистрирован: 08.04.2011
axill
Offline
Зарегистрирован: 05.09.2011

vvadim пишет:

А зачем для котла вообще нужен пид регулятор с огромной инертностью системы .

Хотя может автор проводит сверхночные опыты по физике или химии.

Чем больше инертность тем как раз таки полезнее ПИД. При нулевой инеции вы получите отличное регулирование в релейном режиме, а вот с высокой инерцией реальная температура без ПИД будет скакать в стороны тем сильнее чем больше инерция. Представьте вы включиле нагрев и ждете когда датчик вам скажет, что вы достигли температуры. При высокой инерции это произойдет тогда когда энергии будет произведено намного больше чем нужно, и она будет продолжать нагревать уже после того когда вы выключите нагрев. В итоге перегрев будет ощутимым.

trembo
trembo аватар
Offline
Зарегистрирован: 08.04.2011

Вряд-ли, даже совсем не так! .....

Масса той воды, что мы греем, намного больше массы самого нагревателя. И "накопленная" в нём энергия быстро утилизуется этой водой. К тому-же в бойлерах датчик стоит не на самом дне, где его никогда не прогреть, а посередине, и при отключении нагревателя  вода прогреется " ниже" датчика. Всего-лишь..... Не критично.

yul-i-an
yul-i-an аватар
Offline
Зарегистрирован: 10.12.2012

схема детектора нуля и управления симистором.

Тут про прерывания.

Схема рабочая, сам ей пользуюсь.

Видео работы.

axill
Offline
Зарегистрирован: 05.09.2011

а как решили с детектором нуля ту проблему, что он срабатывает только на одну полуволну? Я вынуждел был на входе диодный мост ставить, чтобы ловить обе полуволны

yul-i-an
yul-i-an аватар
Offline
Зарегистрирован: 10.12.2012

Так там по схеме и стоит диодный мостик.

НиколаКорень
Offline
Зарегистрирован: 04.06.2016

тоже регулятор нужно в алгоритм, только для сервопривода. Что такое детектор нуля и зачем он нужен?

yul-i-an
yul-i-an аватар
Offline
Зарегистрирован: 10.12.2012

НиколаКорень пишет:

тоже регулятор нужно в алгоритм, только для сервопривода. Что такое детектор нуля и зачем он нужен?

Сетевое напряжение 220В переменное 50Гц, тоесть 50 раз в секунду меняеться полярность - напряжение переходит из положительного в отрицательное. Детектор нуля необходим чтобы уловить момент перехода через ноль.

Чтобы использовать закон регулирования Вам нужна будет обратная связь (знать текущее положение сервы)

Потомучто регуляторы выполняют свои расчеты исходя из величены рассогласования регулирования и его знака.

Вот например вполне рабочий ПИ регулятор

//функция расчета мощности по ПИ закону регулирования
byte PIctl(float temp, float ust)
{
  byte zad=0;
  static float i=0;
  if(temp>(ust-0.5)&&temp<(ust+0.5))//зона нечувствительности +-0,5C
  {
    zad=i;
  }
  else//иначе
  {
    float e, p;
    e=(ust-temp);//вычисление ошибки регулирования
    // расчет выходной мощности
    p=(1.5*e);//расчет пропорциональной состовляющей
    if (p<0.0)//мощность не может быть отрицательной
    {
      p=0.0;//ограничение P
    }
    i=(i+(e*0.014));//расчет интегральной составляющей = тепловым потерям
    if (i>100.0)//мощность не может быть больше 100%
    {
      i=100.0;//ограничение I
    } 
    if (i<-100.0)
    {
      i=-50.0;//ограничение I
    } 
    //расчет выходной мощности
    zad=byte(p+i);
    if(zad>100){zad = 100;}
    if(zad<0){zad = 0;}

  //для быстрого выхода на уставку
  if (temp<(ust-3))//если темп < уст на 3С
  {
    zad=100;//то мощность 100%
    i=0;
  }
  if (temp>(ust+8.5))//если темп > уст на 8,5С
  {
    zad=0;//то мощность 0%
    i=0;
  }
}
  return zad;
}

Он применялся на терморегуляторе.

oleg_kazakof
Offline
Зарегистрирован: 24.04.2015

.

dorian
Offline
Зарегистрирован: 25.02.2018

yul-i-an пишет:

Цитата:

 

 

Вот например вполне рабочий ПИ регулятор

//функция расчета мощности по ПИ закону регулирования
byte PIctl(float temp, float ust)
{
  byte zad=0;
  static float i=0;
  if(temp>(ust-0.5)&&temp<(ust+0.5))//зона нечувствительности +-0,5C
  {
    zad=i;
  }
  else//иначе
  {
    float e, p;
    e=(ust-temp);//вычисление ошибки регулирования
    // расчет выходной мощности
    p=(1.5*e);//расчет пропорциональной состовляющей
    if (p<0.0)//мощность не может быть отрицательной
    {
      p=0.0;//ограничение P
    }
    i=(i+(e*0.014));//расчет интегральной составляющей = тепловым потерям
    if (i>100.0)//мощность не может быть больше 100%
    {
      i=100.0;//ограничение I
    } 
    if (i<-100.0)
    {
      i=-50.0;//ограничение I
    } 
    //расчет выходной мощности
    zad=byte(p+i);
    if(zad>100){zad = 100;}
    if(zad<0){zad = 0;}

  //для быстрого выхода на уставку
  if (temp<(ust-3))//если темп < уст на 3С
  {
    zad=100;//то мощность 100%
    i=0;
  }
  if (temp>(ust+8.5))//если темп > уст на 8,5С
  {
    zad=0;//то мощность 0%
    i=0;
  }
}
  return zad;
}

Он применялся на терморегуляторе.

Здравствуйте.

Если Вас не затруднит, добавьте комментарии ко всем строчкам кода.Я с трудом пока понимаю C/C++, по этой причине хотелось бы самому разобраться как это работает, а не просто копировать Ваш код.
Спасибо.

yul-i-an
yul-i-an аватар
Offline
Зарегистрирован: 10.12.2012

Так там вроде все откопентированно.
Чтобы код ПИД регулятора лучше понять нужно его изучить и понять как он функционирует.
Последняя реализация http://arduino.ru/forum/proekty/narodnyi-inkubator?page=2#comment-328166
Может она понятнее будет.

dorian
Offline
Зарегистрирован: 25.02.2018

...

andriano
andriano аватар
Offline
Зарегистрирован: 20.06.2015

dorian, разбирать работу ПИД по коду на С - занятие крайне неблагодарное вообще. А если Вы еще и не умеете читать (на С) - дело вообще бесперспективное.

Следуйте общему правилу: если Вы не знаете, как решить некоторую задачу, разбейте ее на задачи попроще. Для начала освойте С/С++, сделайте пару-тройку проектов попроще, а когда будете себя чувствовать увереннее, разберитесь с ПИД по литературе, попытайтесь написать ПИД либо какую-либо из его частей сами, и только потом решайте, что для Вас проще - разбираться с имеющимся кодом или написать свой.

А то, что Вы просите "откомментировать" готовый код - вряд ли кто согласится делать это иначе как за деньги, потому что занятие абсолютно глупое и, более того, - неправильное: комментарии служат не затем, чтобы пояснить, что делает код (а Вы просите именно об этом), т.к. это должно быть видно из самого кода, а то, зачем он (код) это делает. Т.е. в комментариях должно содержать только то, чего нет в самом коде. Соответственно, по имеющемуся коду такие комментарии составить просто невозможно.

dorian
Offline
Зарегистрирован: 25.02.2018

Я понял, что мой вопрос не вписался в общую концепцию темы...Модератор, прошу удалить мои сообщения или я сам их затру.

Математика регулирования мне понятна и из кода в том числе (кроме нескольких строк).

Правильно ли я понял: 

byte PIctl(float temp, float ust) - если не затруднит, поясните как это работает. 

byte zad=0 - объявление переменной zad битового типа и запись в нее значение ноль.

float e, p - объявление переменной e и p типа флоут.

zad=byte(p+i) - сумма переменных p и i преобразуются в битовый тип и записывается в переменную zad.

ven-til
Offline
Зарегистрирован: 13.02.2018
dorian
Offline
Зарегистрирован: 25.02.2018

Прошу прощения!Байтового типа.

Umka
Umka аватар
Offline
Зарегистрирован: 19.10.2012

Это функция. Возвращает байт. В нее передаются 2 значения типа float. Дальше в самой функции идет какое-то вычисление. Функцию надо вызывать в основном цикле программы или из другой функции. То есть вызываем функцию и передаемс в нее 2 float числа, на выходе получаем другой число типа байт. Что-то с ним дальше делаем. Ну это же азы Си. Как такое можно комментировать не зная степени незнания читающего? Может он и float что значит не знает.