ПИ регулятор отопления

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

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

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

Проработал зиму на даче, никаких замечаний к работе регулятора нет, температуру держет точно.

В качестве датчика температуры используется терморезистор NTC 10кОм при 20С, выходным устройством являеться реле (в дальнейшем планирую поставить симистор) (организован медленный ШИМ частотой 2Гц).

//кнопки А0, реле 13,12 подсветка 10
#include <LiquidCrystal.h>
#define key_in analogRead(0)//вход аналоговых кнопок
#define T_Sensor_OFF digitalWrite(16,LOW)//отключить термистор
#define T_Sensor_ON digitalWrite(16,HIGH)//включить термистор  
#define HI_OFF digitalWrite(13,LOW)//отключить реле HI
#define HI_ON digitalWrite(13,HIGH)//включить реле HI
//#define LO_OFF digitalWrite(12,LOW)//отключить реле LO
//#define LO_ON digitalWrite(12,HIGH)//включить реле LO
#define n_f 10.0//температура по умолчанию
#define p_s 50//частота обновления экрана
#define t_pwm 600//период ШИМ
#define t_time 1000//период опроса температуры
//для ПИ регулятора
float p,i;
LiquidCrystal lcd(7,6,5,4,3,2);
long time;//интервал замера температуры
long lcd_time;//интервал отрисовки экрана
long on_time;//для периода ШИМ
int zad=0;
int k_b[]={
  512,683,768};//аналоговые кнопки SET + -
float temp=0.0;//текущее значение температуры
float pre_temp=0.0;//предыдущее значение температуры
int w=0;
float ust=n_f;//termostate
int c=0;//пройденые циклы мощности
float addr[25][2] = {
  {
    0,32.0                                                              
  }
  ,
  {
    5,25.5                                                              
  }
  ,
  {
    10,20.0                                                              
  }
  ,
  {
    15,15.7                                                                
  }
  ,
  {
    20,12.62                                                                
  }
  ,
  {
    25,10.0                                                                
  }
  ,
  {
    30,8.24                                                                
  }
  ,
  {
    35,6.63                                                                
  }
  ,
  {
    40,5.41                                                                
  }
  ,
  {
    45,4.41                                                                
  }
  ,
  {
    50,3.62                                                                
  }
  ,
  {
    55,2.99                                                                
  }
  ,
  {
    60,2.48                                                                
  }
  ,
  {
    65,2.08                                                                
  }
  ,
  {
    70,1.75                                                                
  }
  ,
  {
    75,1.47                                                                
  }
  ,
  {
    80,1.258                                                                
  }
  ,
  {
    85,1.063                                                                
  }
  ,
  {
    90,0.905                                                                
  }
  ,
  {
    95,0.776                                                                
  }
  ,
  {
    100,0.669                                                                
  }
  ,
  {
    105,0.581                                                                
  }
  ,
  {
    110,0.505                                                                
  }
  ,
  {
    115,0.442                                                                
  }
  ,
  {
    120,0.387                                                                
  }
  ,
};

byte cel[] =
{
  0b01000,
  0b10100,
  0b01000,
  0b00111,
  0b01000,
  0b01000,
  0b00111
};
void setup(){
  // Serial.begin(9600);
  pinMode(13,OUTPUT);//реле HI
//  pinMode(12,OUTPUT);//реле LO
  pinMode(16,OUTPUT);//питание термистора
  T_Sensor_OFF;
  HI_OFF;//отключить реле HI
//  LO_OFF;//отключить реле LO
  lcd.begin(16, 2);
  analogWrite(10,250);//подсветка
  time = millis();
  lcd_time = time;
  on_time = time;
  lcd.createChar(1, cel);
}
void loop(){

  key();//опрос кнопок
  if (time>millis()){//защита от переполнения
    time=millis();
    lcd_time=time;
    on_time=time;
  }
 //---------------чтение температуры---------------
 if ((millis()-time)>=t_time){
    time=millis();
    temp=getTemp();
   temp=(pre_temp*0.2)+(temp*0.8);//фильтр
 //   w=abs((ust-temp)*(pre_temp-temp));//время выхода на уставку
    pre_temp=temp;
    PIctl();//расчет мощности
  }
  //-------------------вывод на экран----------------
  if ((millis()-lcd_time)>=p_s){//вывод на экран
    lcd_time=millis();
    Screen_print();
  }

  //---------------------управление мощностью--------------------------
  //выдаем мощность
  if (millis()>3000){
    if((millis()-on_time)>=t_pwm){//период ШИМ
      on_time=millis();

      if(c<zad){
        HI_ON;
      }
      else{
        if (zad != 100){
          HI_OFF;
        }
      }
      c=c+1; 
      if(c==100){
        c=0;
        //PIctl();
      }
    }
  }
}
void PIctl()
{
    if(temp>(ust-0.5)&&temp<(ust+0.5))//зона нечувствительности +-0,5C
  {
    zad=i;
  }
  else
  {
  float e;
  e=(ust-temp);
  // расчет выходной мощности:
  p=130.0*e;//130 коффициент пропорциональности
  if (p<0.0)
  {
    p=0.0;//ограничение P
  }
  i=(i+(e*0.014));//0.7
   if (i>100.0)
  {
    i=100.0;//ограничение I
  } 
     if (i<-100.0)
  {
    i=-100.0;//ограничение I
  } 
  //расчет выходной мощности
  zad=p+i;
  }
  //ограничение управляющего сигнала
  //для исключения частого переключения реле
  //если твердотельное реле, можно не ограничивать
  if (zad<4){
    zad=0;
  }
  if (zad>96){
    zad=100;
  }
//корректировка при отклонении температуры более чем на +-2С
//для быстрого выхода на уставку
  if (temp<(ust-2.0))//если темп < уст на 2С
  {
    zad=100;//то мощность 100%
    i=0;
  }
  if (temp>(ust+2.0))//если темп > уст на 2С
  {
    zad=0;//то мощность 0%
    i=0;
  }
}
//----------------опрос клавиш-------------------------------
void key(){
  int x;
  x = key_in;
  if (x>k_b[0]-5 && x<k_b[0]+5){//МЕНЮ
    UP_butt(); 
    key_m(0);
  }
  if (x>k_b[1]-5 && x<k_b[1]+5){// +
    UP_butt();
    key_m(1);
  } 
  if (x>k_b[2]-5 && x<k_b[2]+5){// -
    UP_butt();
    key_m(2);
  }  
}
//---------------проверка отпускания кнопки-----------------
void UP_butt(){
  while (key_in<1010){//ждем отпускания кнопки
  } 
}
//------------------замер температуры----------------------
double getTemp()
{
    T_Sensor_ON;
    delay(10);//задержка при подаче питания терморезистора
    int t;
    t=0;
    for (int i=0; i <= 15; i++){
    //for (int i=0; i <= 4; i++){
      t = t + analogRead(1);
    }
//    T_Sensor_OFF;
     t=t/4;
    //t=t/5;
  
  
  float R;
  R=getR(t);
  int i=0;
  while (addr[i][1] >R)
  {
    i++;
  }
  double result;
  result=(R-addr[i][1])*(addr[i-1][0]-addr[i][0])/(addr[i-1][1]-addr[i][1])+addr[i][0];
  return result;
}
float getR(float V)
{
  float result;
  result=-10*V/(V-4096);
  //result=-10*V/(V-1024);
      T_Sensor_OFF;
  return result;
}
//-----------------обработка кнопок-------------------------
void key_m(int x){
  //  lcd.clear();
  switch (x){
  case 0://menu
  ust=10;
    break;
  case 1://кнопка +
    ust++;
    if (ust>35){
      ust=35;
    }
    break;
  case 2://кнопка -
    ust--;
    if (ust<5){
      ust=5;
    }
    break;
  }
}
  int oversempling(){
  T_Sensor_ON;
    delay(10);//задержка при подаче питания терморезистора
    int o;
  o=0;
    for (int i=0; i <= 15; i++){//оверсемплинг до 4096
    //for (int i=0; i <= 4; i++){
      o = o + analogRead(1);
    }
    T_Sensor_OFF;
     o=o/4;
    //t=t/5;
    return o;
    }
void Screen_print()
{
  byte a=0;
  byte b=0;

  tempPrint(temp,0,0);
  tempPrint(ust,0,1);

  lcd.setCursor(6, 0);
  lcd.print(zad);
  lcd.print("%  "); 

  varPrint(i,"I ",6,1);
  //  lcd.print("I "); 

  lcd.setCursor(12, 0);
  lcd.print(c);
  lcd.print("m  "); 

  a=digitalRead(13);
  b=digitalRead(12);
  if (a==1 && b==0){
    lcd.setCursor(12, 1);
    lcd.print("HI ");
  }
  if (a==0 && b==1){
    lcd.setCursor(12, 1);
    lcd.print("LO ");
  }
  if (a==0 && b==0){
    lcd.setCursor(12, 1);
    lcd.print("OFF");
  } 
}
//-------------вывод температуры----------------------------
void tempPrint(double t, int x, int y){
  lcd.setCursor(x, y);
  lcd.print(t);
  lcd.setCursor(x+4, y);
  lcd.print(char(1));//вывод градусов
}
//---------------вывод переменной-------------------------
void varPrint (double v, char* t, int x, int y){
  lcd.setCursor(x, y);
  lcd.print(v);
  lcd.print(t);
}

Схема включения терморезистора стандартная, кнопки (аналоговые). Изскетча должно быть понятно что куда цеплять. У меня девайс выполнен на своей плате, использован микроконтроллер ATMega8, тактирование от встроенного генератора 8МГц. Собрано всё в монтажной коробке 100х100х40.

Схема устройства, LCD экран не показан.

vde69
Offline
Зарегистрирован: 10.01.2016

2 герца на реле...

2*60*60*24*365 = 63 ляма в год... реле столько не ходят, обычно от 200 до 500 тыс, я когда печку делал себе посчитал 1 переклюяение в 20 секунд и то вышло всего на 5 лет 8 часовой работы...

Штирлиц
Штирлиц аватар
Offline
Зарегистрирован: 13.06.2015

vde69 пишет:

2 герца на реле...

 

Вместо реле ставим SSR на нужную мощность и все.

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

Штирлиц пишет:

vde69 пишет:

2 герца на реле...

 

Вместо реле ставим SSR на нужную мощность и все.

Согласен, я выше написал что планируется замена на симистор и подача напряжения по алгоритму Брезенхема. Опыт уже есть - видео.

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

Такую же задачу решал недавно. Реализовал алгоритм "мячик на двух резинках" с поиском равновесной мощности под заданную температуру итеративным перебором. Регулирование мощности на моем преобразователе ШИМ в ФИМ

зима показала очень высокую точность поддержания температуры. Разве что помех много от симистора но у меня это никак не проявляется, знаю, что должны быть)

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

axill пишет:

Такую же задачу решал недавно. Реализовал алгоритм "мячик на двух резинках" с поиском равновесной мощности под заданную температуру итеративным перебором.

Ссылочку не дадите для ознакомления

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

Не публиковал, но если что конкретно интересует поделюсь

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

Интересуемся.

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

yul-i-an пишет:

Согласен, я выше написал что планируется замена на симистор и подача напряжения по алгоритму Брезенхема. Опыт уже есть - видео.

спасибо за информацию, не знал, что такая простая вещь как пропуски полупериодов так круто называются)

логика програмная пропусков почти ничем не отличается от ФИМ. 

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

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

yul-i-an пишет:

Интересуемся.

что именно? Схема, алгоритм?

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

vde69 пишет:

2 герца на реле...

2*60*60*24*365 = 63 ляма в год... реле столько не ходят, обычно от 200 до 500 тыс, я когда печку делал себе посчитал 1 переклюяение в 20 секунд и то вышло всего на 5 лет 8 часовой работы...

Я прикидывал 600000-800000 переключений за отопительный сезон 210 дней.

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

axill пишет:

yul-i-an пишет:

Интересуемся.

что именно? Схема, алгоритм?

Алгоритм.

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

Штирлиц пишет:

vde69 пишет:

2 герца на реле...

 

Вместо реле ставим SSR на нужную мощность и все.

Если точно то 1% - 0,6с. (100% за 60с.). Если нужна мощность 15% то реле включаеться на 9с. а остальные 51с. отключено. И так каждые 60с.

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

yul-i-an пишет:

Алгоритм.

хорошо. С компа пришлю примеры кода, а на словах примерно так:

- по таймеру беру равные интервалы времени, это шаг итерации

- на каждой итерации считаю три дельты - 1. Дельта между текущей и целевой температурой 2. Дельта между целевой температурой как прогноз на следующий период 3. Дельта как ошибка прогноза на текущий период на предыдущем цикле

- все три цифры суммирую с коэффициентами и получаю корректировку на этой итеррации для мощности в плюс или минус. Коэффициенты подбираются при отладке

- кроме того есть крайние условия 1. Если разница между текущей тем и целевой больше заданной константы (кажется у меня там 10 градусов) включаю полную мощность 2. Если разница меньше (у меня 0.5 градуса) то в эту итерацию мощность не меняется

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

а целом алгоритм показал очень хороший результат для меня. Его минус в необходимости подбора параметров под конкретную задачу

vde69
Offline
Зарегистрирован: 10.01.2016

может кому интересено управление мощностью, оставлю тут http://arduino.ru/forum/proekty/kontroller-mufelnoi-pechi для меня нормальным было попадание в плюс минус 5 градусов на высоких температурах, алгоритм вполне рабочий... по ссылке реальный график работы, виден колебательный процесс, обусловлен он изменением температуры теплоизоляции (и следовательно изменением характеристик теплообмена), на испытаниях характеристики требуемой мощности менялись примерно на 30% за 1 час, что равнозначно резким калебаниям температуры на улице для плохоутепленого домика, тем не менее алгоритм оказался вполне рабочим.

По моим прикидкам имея датчик с точностью 0.25с (тот-же даллас) вполне можно попадать в диапазон воздуха плюс минус 1.5...2 градуса...

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

vde69 пишет:

может кому интересено управление мощностью, оставлю тут http://arduino.ru/forum/proekty/kontroller-mufelnoi-pechi для меня нормальным было попадание в плюс минус 5 градусов на высоких температурах, алгоритм вполне рабочий... по ссылке реальный график работы, виден колебательный процесс, обусловлен он изменением температуры теплоизоляции (и следовательно изменением характеристик теплообмена), на испытаниях характеристики требуемой мощности менялись примерно на 30% за 1 час, что равнозначно резким калебаниям температуры на улице для плохоутепленого домика, тем не менее алгоритм оказался вполне рабочим.

По моим прикидкам имея датчик с точностью 0.25с (тот-же даллас) вполне можно попадать в диапазон воздуха плюс минус 1.5...2 градуса...

Читал вашу тему, даже комент оставил.

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

Переписал функцию управления реле. Написал библиотеку для терморезистора и преминил её в этом проекте.

Теперь код выглядит так.

#include <TermoRes3950.h>//библиотека терморезистора
//кнопки А0, реле 13,12 подсветка 10
#include <LiquidCrystal.h>
#define key_in analogRead(0)//вход аналоговых кнопок
#define HI_OFF digitalWrite(13,LOW)//отключить реле HI
#define HI_ON digitalWrite(13,HIGH)//включить реле HI
#define n_f 10.0//температура по умолчанию
#define p_s 50//частота обновления экрана
#define t_pwm 60000//период 100% мощности
#define t_time 1000//период опроса температуры
TermoRes3950 termo(1);//терморезистор на АЦП 1
//для ПИ регулятора
float p,i;
LiquidCrystal lcd(7,6,5,4,3,2);
long time;//интервал замера температуры
long lcd_time;//интервал отрисовки экрана
long on_time=0;//для периода ШИМ
int zad=0;
int k_b[]={
  512,683,768};//аналоговые кнопки SET + -
float temp=0.0;//текущее значение температуры
float pre_temp=0.0;//предыдущее значение температуры
int w=0;
float ust=n_f;//termostate

byte cel[] =
{
  0b01000,
  0b10100,
  0b01000,
  0b00111,
  0b01000,
  0b01000,
  0b00111
};
void setup(){
  // Serial.begin(9600);
  pinMode(13,OUTPUT);//реле HI
  HI_OFF;//отключить реле HI
  lcd.begin(16, 2);
  analogWrite(10,250);//подсветка
  time = millis();
  lcd_time = time;
  on_time = time;
  lcd.createChar(1, cel);
}
void loop(){
  byte k;
  k=key();//получить код нажатой кнопки
  key_m(k);//выполнить действие согласно нажатой кнопке
  if (time>millis()){//защита от переполнения
    time=millis();
    lcd_time=time;
    on_time=time;
  }
  //---------------чтение температуры---------------
  if ((millis()-time)>=t_time){
    time=millis();
    temp=termo.getTemp();//получаем температуру
    temp=(pre_temp*0.2)+(temp*0.8);//фильтр
    //   w=abs((ust-temp)*(pre_temp-temp));//время выхода на уставку
    pre_temp=temp;
    PIctl();//расчет мощности
  }
  //-------------------вывод на экран----------------
  if ((millis()-lcd_time)>=p_s){//вывод на экран
    lcd_time=millis();
    Screen_print();
  }

  //---------------------управление мощностью--------------------------
  //выдаем мощность
  if (millis()>3000){
    if(millis()-on_time >= t_pwm){//период ШИМ
      on_time=millis();
    }
    if (millis()<((zad*(t_pwm/100))+on_time)){
      HI_ON;
    }
    else
    {
      HI_OFF; 
    }
  }
}

void PIctl()
{
    if(temp>(ust-0.5)&&temp<(ust+0.5))//зона нечувствительности +-0,5C
  {
    zad=i;
  }
  else
  {
  float e;
  e=(ust-temp);
  // расчет выходной мощности:
  p=65.0*e;//130 коффициент пропорциональности 130.0
  if (p<0.0)
  {
    p=0.0;//ограничение P
  }
  i=(i+(e*0.014));//0.7
   if (i>100.0)
  {
    i=100.0;//ограничение I
  } 
     if (i<-100.0)
  {
    i=-50.0;//ограничение I
  } 
  //расчет выходной мощности
  zad=p+i;
  }
  //ограничение управляющего сигнала
  //для исключения частого переключения реле
  //если твердотельное реле, можно не ограничивать
  if (zad<4){
    zad=0;
  }
  if (zad>96){
    zad=100;
  }
//корректировка при отклонении температуры более чем на +-2С
//для быстрого выхода на уставку
  if (temp<(ust-2.0))//если темп < уст на 2С
  {
    zad=100;//то мощность 100%
    i=0;
  }
  if (temp>(ust+2.0))//если темп > уст на 2С
  {
    zad=0;//то мощность 0%
    i=0;
  }
}

//----------------опрос клавиш-------------------------------
byte key(){
  int x;
  x = key_in;
  if (x>k_b[0]-5 && x<k_b[0]+5){//МЕНЮ
    UP_butt(); 
return 0;
//    key_m(0);
  }
  if (x>k_b[1]-5 && x<k_b[1]+5){// +
    UP_butt();
return 1;
//    key_m(1);
  } 
  if (x>k_b[2]-5 && x<k_b[2]+5){// -
    UP_butt();
return 2;
//    key_m(2);
  }  
}
//---------------проверка отпускания кнопки-----------------
void UP_butt(){
  while (key_in<1010){//ждем отпускания кнопки
  } 
}

//-----------------обработка кнопок-------------------------
void key_m(int x){
  switch (x){
  case 0://menu
  ust=10;
    break;
  case 1://кнопка +
    ust++;
    if (ust>35){
      ust=35;
    }
    break;
  case 2://кнопка -
    ust--;
    if (ust<5){
      ust=5;
    }
    break;
  }
}

void Screen_print()
{
  byte a=0;

  tempPrint(temp,0,0);
  tempPrint(ust,0,1);

  lcd.setCursor(6, 0);
  lcd.print(zad);
  lcd.print("%  "); 

  varPrint(i,"I ",6,1);

  lcd.setCursor(10, 0);
  lcd.print(on_time); 

  a=digitalRead(13);
  if (a==1){
    lcd.setCursor(12, 1);
    lcd.print("HI ");
  }
  if (a==0){
    lcd.setCursor(12, 1);
    lcd.print("OFF");
  } 
}
//-------------вывод температуры----------------------------
void tempPrint(double t, int x, int y){
  lcd.setCursor(x, y);
  lcd.print(t);
  lcd.setCursor(x+4, y);
  lcd.print(char(1));//вывод градусов
}
//---------------вывод переменной-------------------------
void varPrint (double v, char* t, int x, int y){
  lcd.setCursor(x, y);
  lcd.print(v);
  lcd.print(t);
}

 

artemka_patriot
Offline
Зарегистрирован: 10.04.2016

Приветствую можешь помоч у меня датчик температуры дс 18б20 на основе его построить код и lcd икран с кнобками

faeton
faeton аватар
Offline
Зарегистрирован: 21.03.2016

axill пишет:

зима показала очень высокую точность поддержания температуры. Разве что помех много от симистора но у меня это никак не проявляется, знаю, что должны быть)

Озадачили как ФИМ можно мощность регулировать... ФИМ - это же фазо-импульсная?

А для инерционных систем с активной нагрузкой, чтобы не шуметь симистором в сеть, лучше полупериоды вырезать с коммутацией в нуле - этакая АМ синхронизированная с  пропаданием несущей. :)