Контроллер пелетного котла

Нет ответов
Smarodina
Offline
Зарегистрирован: 03.02.2018

Всем привет, меня зовут Дима, и я сварщик:) живу я в Украине, в городе Харьков. Когда то газ был дешевый и некого не парило отопление дома, но в 2008 он подорожал почти на 100% и я утеплил дом пенопластом, стало легче:) жил дальше... но пришел 2014 год и газ подорожал в 7 раз, что стало очень накладно с расходом 300-500 кубометров в месяц и я сварил себе котел на дровах шахтного типа. но через две зимы пришел к тому что дрова топливо хоть и дешевое но очень не стабильное... заинтересовался пеллетами и сделал "гравитационную" горелку, стало лучше. качество топлива стабильное, засыпал в бункер и нагрел теплоаккамулятор но напрягало что гравитационная горелка вредная к тяге в дымоходе и получалось что нужно 2-4 часа в день что бы нагреть ТА, и без присмотра её не оставить... и сделал свою первую факельную горелку, управление сделал на модулях из китая, выставил время подачи и паузы подкрутил вентилятор и работает:) но хотелось сделать "автоматику" которая была бы автоматической и знала что делать с нештатными ситуациями но знания мои в програмировании не позволяли... потом я сделал котел в мастерскую и тоже контроллер на модулях, но в мастерской ТА нет, и котел греет радиаторы на прямую. а хотелось чтоб он мог поддерживать заданную темературу и не палил лишнего, и конце 2019 года понеслось... напряг своих друзей кто понимал больше меня начать контроллер, что то получалось, как я думал, но к весне 2020 года я таки понял как надо сделать и теперь только "допиливаю" свое детище:) в общем вот:

#define PID_INTEGER
#define PID_INTEGRAL_WINDOW 50
#define BTN1 4
#define BTN2 5
#define BTN3 2
#define BTN4 3
#define BTN5 A0

#include <GyverPID.h>
#include <GyverButton.h>
#include <GyverTimer.h>
#include <GyverTM1637.h>
#include <OneWire.h>
#include <DallasTemperature.h>

#define CLK A4
#define DIO A5
#define ONE_WIRE_BUS 11

GyverTM1637 disp(CLK, DIO);
GyverPID pid(1, 0.05, 0.8, 10000);
OneWire oneWire(ONE_WIRE_BUS);
DallasTemperature sensors(&oneWire);
DeviceAddress insideThermometer {0x28, 0xB8, 0x86, 0xD5, 0x33, 0x20, 0x01, 0xCA  };  //УКАЗАТЬ имя  датчика
////////////////////кнопки////////////////////////////////////////////
GButton startButtonPin(BTN1);            //пин кнопки Старт
GButton stopButtonPin(BTN2);             //пин кнопки Стоп
GButton plusButtonPin(BTN3);             //пин кнопки "+"
GButton minusButtonPin(BTN4);            //пин кнопки "-"
GButton disp_ButtonPin(BTN5);            //пин кнопки дисплея
////////////////////датчики///////////////////////////////////////////
const int photoresistorPin = 12;         //пин фоторезистора
const int infraredSensorPin = A3;        //пин ИК датчика
////////////////////иполнители////////////////////////////////////////
const int feederAugerPin = 6;            //пин шнека питателя
const int pumpPin = 7;                   //пин цикуляционного насоса
const int heatingElementPin = 8;         //пин зажигалки
const int stokerAugerPin = 9;            //пин шнека стокера
const int pwmPin = 10;                   //пин для ШИМ
const int firebarPin = 13;               //пин подвижного колосника 
////////////////////переменные///////////////////////////////////////
int desiredTemperature;
int currentTemp;
byte working;
byte start_count;
byte photoresistor;
int p_pause;
int w_pause;
byte start1;
int val_f;
int val[3];
int index;
byte starting;                                    
byte plusButton;
byte minusButton;
byte dispButton;
int dose;
byte displ;
signed char fan_value;
int PWM_val;
byte fanspeed_pr;
byte pid_out_last;
int switch_start;
int switch_stop;
int stop_FPB;
int startpwm;
int disp_on;
uint8_t start_1;
uint8_t stop_1;
int fire_off;  
int reg1;
  
GTimer start_timer0(MS);
GTimer start_timer1(MS);
GTimer start_timer2(MS);
GTimer start_timer3(MS);
GTimer stop_timer1(MS);
GTimer stop_timer2(MS);

int middle_of_3(int a, int b, int c) {
 int x[2];
if (a > b) {x[0] = b; x[1] = a;} 
else {x[0] = a; x[1] = b;} 
if (x[1] > c) {return (x[0]< c) ? c : x[0];} 
else {return x[1];} 
}

void setup() {
  start_1=0;
  stop_1=0;
  disp_on=1;
  switch_stop=9;
  switch_start=9;
  pid_out_last=100;
  PWM_val=0;
  displ=0;
  working=0;
  start1=0;
  start_count=0;
  desiredTemperature=500;
  stop_FPB=1;
  startpwm=0;
  fire_off=0;
  
  disp.clear();
  disp.brightness(0);  // яркость, 0 - 7 (минимум - максимум)
  
    startButtonPin.setTickMode(AUTO);
    stopButtonPin.setTickMode(AUTO);
    plusButtonPin.setTickMode(AUTO);
    minusButtonPin.setTickMode(AUTO);
    disp_ButtonPin.setTickMode(AUTO);
	
	  pinMode(photoresistorPin, INPUT);
    pinMode(infraredSensorPin, INPUT);
    pinMode(pumpPin, OUTPUT);
    pinMode(feederAugerPin, OUTPUT);
    pinMode(stokerAugerPin, OUTPUT);
    pinMode(pwmPin, OUTPUT);
    pinMode(heatingElementPin, OUTPUT);
    pinMode(firebarPin, OUTPUT);
    
    digitalWrite(feederAugerPin, HIGH);
    digitalWrite(stokerAugerPin, HIGH);
    digitalWrite(heatingElementPin, HIGH);
    digitalWrite(pumpPin, HIGH);
    digitalWrite(firebarPin, HIGH);
    
  pid.setDirection(NORMAL); 
  pid.setLimits(1,9);
  pid.setpoint = desiredTemperature; 
  sensors.begin();
  start_timer0.setTimeout(10000);//время продувки перед запуском горелки
  start_timer1.setTimeout(20000);//время загрузки пеллет на розжиг
  start_timer2.setTimeout(180000);//время работы зажигалки
  start_timer3.setTimeout(25000);//время стабилизации пламени
  stop_timer1.setTimeout(100000);//время дожига пеллет перед останокой горелки
  stop_timer2.setTimeout(300000);//время продувки перед останокой горелки
  //Serial.begin(9600);
}

void loop() {
  /*static uint32_t timer_ser;
  if(millis() - timer_ser >= 2000){
    timer_ser = millis();
    Serial.println(pid.output);
    Serial.println(reg1);
   } 
 */ 
  button();
  fillPillets();
  pid.input = currentTemp;
  pid.setpoint = desiredTemperature; 
  pid.getResultTimer();
  reg_limits();
  average_fire();
  temp();
  work();
  startWork();
  finishWork();
  auto_start();
  //fire_bar();
  startPWM();
  screen();
}

void reg_limits(){
  reg1=desiredTemperature-currentTemp;
  if(reg1>=40){
    pid.setLimits(1,9);
  }else if(reg1>=30){
    pid.setLimits(1,7);
  }else if(reg1>=20){
    pid.setLimits(1,5);
  }else if(reg1>=10){
    pid.setLimits(1,3);
  }
}

void fire_bar(){                                                                             //Функция таймера работы подвижного колосника
    static uint32_t oldMillis = millis();
    static uint32_t onOffTime=500000;
    uint32_t newMillis = millis();
    boolean firebar_On = digitalRead(firebarPin);
    
    if(newMillis-oldMillis>=onOffTime){
        oldMillis=millis();
        firebar_On=!firebar_On;
        onOffTime=5000+(300000*firebar_On);//!firebar_On для мосфета, firebar_On для реле управление по LOW.
        digitalWrite(firebarPin,firebar_On);
    }    
}

void temp(){                                                                         //Функция обработки сигнала датчика температуры
   static uint32_t temp_timer;
   sensors.requestTemperatures();
   if(millis()-temp_timer>1000){
   temp_timer=millis(); 
   currentTemp = sensors.getTempC(insideThermometer)*10;}
    }
 
void average_fire(){                                                                         //Функция обработки сигнала датчика огня   
 if (++index > 2) index = 0; 
  val[index] = analogRead(A3); 
  val_f = middle_of_3(val[0], val[1], val[2]);
 
 }

void startPWM() {                                                                            //функция управления ШИМ вентилятора
  static int fanspeed;
  if(startpwm==1){
  fan_value=constrain(fan_value,-30,30);
  fanspeed_pr = PWM_val+fan_value;
  fanspeed_pr = constrain(fanspeed_pr, 0, 100);
  fanspeed = map(fanspeed_pr, 0, 100, 0, 253);
  analogWrite(pwmPin, fanspeed);
  }
}

void button(){                                                                               //Функция обработки кнопок
  desiredTemperature = constrain(desiredTemperature, 300, 800);
  
  if(disp_ButtonPin.isClick()){
    displ+=1;
    if(displ >= 4)displ=0;
  }
  if(startButtonPin.isClick() and photoresistor==1){
    startpwm=1;
    switch_start=0;
    }
  if(stopButtonPin.isClick()){
    startpwm=1;
    stop_FPB=0;
	  working=0;
    start1=0;
    switch_stop=0;
    }
	if(currentTemp>=800 and working==1){
    startpwm=1;
    stop_FPB=0;
    working=0;
    start1=0;
	  switch_stop=0;
	  }
	if(currentTemp>=350){
	digitalWrite(pumpPin, LOW);}
	else{digitalWrite(pumpPin, HIGH);
	}
	if(currentTemp<50 and start_count>=3){
		digitalWrite(pumpPin, LOW);
	}
}

void auto_start(){                                                                           //Функция автостарта горелки при потере огня
 static uint32_t timer_fire;
  if(millis() - timer_fire >= 100){
    timer_fire = millis();
  if(val_f>1000 and start1==1){
    fire_off+=1;
    }
    else{fire_off=0;
    }
  }
  if(fire_off==600 and start1==1){
    start1=0;
      working=0;
      pid_out_last=100;
      switch_start=5;
  }
}

void screen(){                                                                               //функция вывода информации на дисплей
 static int for_disp;
 if(disp_on==1){
 switch (displ){
  //вывод температур, изменение заданной температуры
  case 0 :
    for_disp = desiredTemperature * 10;
      for_disp = for_disp + currentTemp/10;
      disp.displayInt(for_disp);                                                      
      disp.point(true);
    if(plusButtonPin.isClick()){
      desiredTemperature+=50;
      plusButtonPin.resetStates();
      }
    if(minusButtonPin.isClick()){
      desiredTemperature-=50;
      minusButtonPin.resetStates();
      }
    break;
  //вывод расхода грамм\час
  case 1 :
      disp.displayInt(dose);
      disp.point(false);
    break;
  //вывод скорости вентилятора в процентах с возможностью корректировки
  case 2 :
    disp.displayInt(fanspeed_pr);
    disp.displayByte(0, 0x71);
    disp.point(false);
    if(plusButtonPin.isClick() and fanspeed_pr<100){
      fan_value+=5;
      plusButtonPin.resetStates();
      }
    if(minusButtonPin.isClick()){
      fan_value-=5;
      minusButtonPin.resetStates();
      }
    break;
  //вывод уровня пламени 0-1000 или таймер если огня нет
  case 3 :
      if(val_f<1000){
      disp.displayInt(val_f);
      disp.point(false);
      }else{
        disp.displayInt(fire_off/10);
        disp.point(false);}
    break;
  }
 }
}
 
void fillPillets() {                                                                         //Функция подачи пеллет из бункера в стокер
    static uint32_t timer_photo;
  if(millis() - timer_photo >= 500){
    timer_photo = millis();
    photoresistor = digitalRead(photoresistorPin);
  }
  if(stop_FPB==1){
  if (photoresistor == 0) {
     digitalWrite(feederAugerPin, LOW);}
  else {
     digitalWrite(feederAugerPin, HIGH);}
  }
}

void startWork(){                                                                            //функция розжига
  switch(switch_start){
	   case 0:
      PWM_val=100;
      if(val_f<900){
    switch_start=4;}
		break;
	   
	   case 1:
	    PWM_val=20;
		  digitalWrite(stokerAugerPin, LOW);
      if(val_f<600){
    switch_start=2;}
		break;
	   
	   case 2:
	     digitalWrite(stokerAugerPin, HIGH);
	     switch_start=3;
		break;
	   
	   case 3:
	     PWM_val=90;
	     digitalWrite(heatingElementPin, LOW);
    break;
	   
	   case 4:
	     digitalWrite(heatingElementPin, HIGH);
	     p_pause=3750;
	     w_pause=200;
	     PWM_val=60;
       work_timer();
	  break;
		
	   case 5:
        digitalWrite(heatingElementPin, HIGH);
        start_count+=1;
		    	if(start_count<=3){
			    switch_start=0;}
				   else{
				    switch_start=9;
				    start1=0;
				    working=0;
				   }
		break;

	   case 6:
		    start1=1;
		    working=1;
		    switch_start=8;
		break;
    }

if(switch_start==0){
  if(start_timer0.isReady())switch_start=1;}else{start_timer0.start();}

if(switch_start==1){
  if(start_timer1.isReady())switch_start=2;}else{start_timer1.start();}

if(switch_start==3){
  if(val_f<750)switch_start=4;}

if(switch_start==3){
  if(start_timer2.isReady())switch_start=5;}else{start_timer2.start();}

if(switch_start==4){
  if(start_timer3.isReady())switch_start=6;}else{start_timer3.start();}
}

void work() {                                                                                //Функция выбора режима мощности работы горелки
  if (working == 1) { 
    start_count = 0;
    if (pid_out_last != pid.output){
  digitalWrite(stokerAugerPin,HIGH);
    switch (pid.output) {
    //p_pause=пауза подачи. w_pause=подача. PWM_val=вентилятор в процентах. dose=расход грамм\час.
	  case 0 :p_pause=10000;w_pause=100;PWM_val=35;dose = 180;break;
    case 1 :p_pause=8520;w_pause=100;PWM_val=40;dose = 210;break;
    case 2 :p_pause=7500;w_pause=100;PWM_val=45;dose = 240;break;
    case 3 :p_pause=6000;w_pause=100;PWM_val=50;dose = 300;break;
    case 4 :p_pause=5000;w_pause=100;PWM_val=60;dose = 360;break;
    case 5 :p_pause=4500;w_pause=100;PWM_val=65;dose = 400;break;
    case 6 :p_pause=3000;w_pause=155;PWM_val=75;dose = 850;break;
    case 7 :p_pause=3750;w_pause=200;PWM_val=80;dose = 1000;break; 
    case 8 :p_pause=3300;w_pause=200;PWM_val=90;dose = 1630;break;
    case 9 :p_pause=3000;w_pause=300;PWM_val=100;dose = 2000;break;            
    }
  }
  pid_out_last = pid.output;
  work_timer();
  }
}

void work_timer(){                                                                           //Функция таймер работы горелки
    static uint32_t oldMillis = millis();
    static uint16_t onOffTime;
    uint32_t newMillis = millis();
    boolean soker_On = digitalRead(stokerAugerPin);
    
    if(newMillis-oldMillis>=onOffTime){
        oldMillis=millis();
        soker_On=!soker_On;
        onOffTime=w_pause+((p_pause - w_pause)*soker_On);//!soker_On для мосфета, soker_On для реле управление по LOW.
        digitalWrite(stokerAugerPin,soker_On);
    }    
}

void finishWork(){                                                                           //Функция остановки работы горелки
  switch(switch_stop){
	 case 0:
	    disp_on=0;
	    stop_FPB=0;
		  p_pause=3000;
		  w_pause=250;
		  PWM_val=100;
		  disp.displayByte(0x6d, 0x78, 0x3f, 0x73);
		  disp.point(false);
		  work_timer();
   break;
	 
	 case 1:
      digitalWrite(stokerAugerPin, HIGH);
      stop_FPB=0;
	    PWM_val=100;
   break;
   
   case 2:
      disp_on=1;
      PWM_val=0;
      startpwm=0;
      stop_FPB=1;
      switch_stop=3;
   break;
	}
if(switch_stop==0){
    if(stop_timer1.isReady())
    switch_stop=1;}else{stop_timer1.start();}
if(switch_stop==1){
    if(stop_timer2.isReady()){
    PWM_val=0;
    switch_stop=2;}
    }else{stop_timer2.start();}
}

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

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

 

у меня есть канал на ютубе где я делаю это все https://www.youtube.com/channel/UCW6PoUoch5vHGwUPKVOn-cA