Працюємо з 09:00 до 19:00 без вихідних
Самовивіз - Київ біля ТЦ Квадрат бул.Перова

Предмет проекта домашней автоматики

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

В нем предусмотрено управление как аналоговой заслонкой, так и дискретной заслонкой системы отопления; предусмотрена защита от пропадания питающего напряжения сети 220 В.

Технологическая схема

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

На картинке схематически показаны твердотопливный котел (на дровах) со своей трубой отвода продуктов горения, и заслонкой подачи воздуха на горение.

Здесь нам интересны три датчика температуры:

Т1 - температура уходящих газов;

Т2 - температура воды в котле;

Т3 - температура воды в бойлере гарячего водоснабжения.

Также на схеме изображены исполнительные механизмы:

К1 - аналоговая заслонка регулирующая расход воздуха, поступающий в топку котла;

KZ1 - дискретная заслонка, перекрывающая поступление воды в бойлер.

Алгоритм работы автоматики

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

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

При поднятии значения температуры Т2 выше заданного значения (такая температура должна продержаться дольше заданного отрезка времени), переходим в режим регулирования температуры уходящих газов. Для этого активируется регулятор температуры Т1.

В этом режиме при повышении температуры воды Т2 на 10 градусов от заданной температуры "Т2 max" активируем пищалку. При повышении Т2 выше Т2 max закрываем заслонку воздуха и возвращаемся в режим распал при опускании этой температуры на заданную величину dT2 max ниже максимальной.

Если температура уходящих газов Т1 падает ниже заданного значения (Т1 потухло) и держится так в течении заданного времени, переходим в режим перегоревших дров, закрываем аналоговую заслонку, дискретную заслонку, активируем пищалку на 60 минут и переводим автоматику в режим стоп.

Заслонка KZ1 открывается при температуре Т3 ниже заданной. Так же она открывается в режиме пуск при пропадании сигнала от датчика T3.

При ошибках датчиков Т1 и Т2 режим пуск отключается и возможен только режим стоп.

При пропадании питающего напряжения сети, автоматика продолжает работать заданное время, после чего переходит в режим стоп.

Выбор элементной базы

По количеству входов выходов, миниатюрности и удобству монтажа равных контроллеру Arduino Nano даже не пытаемся найти. Если кто-то возразит и предложит STM32F103C8T6, я его обломаю двумя фразами: у него нет постоянной памяти для хранения настроек и монтаж на порядок сложнее - к нему нет монтажных шилдов.

Попробуем все запитать от одного аккумулятора TR 18650 - его должно хватать приблизительно на пол часа работы блока управления.

Блок питания выберу внешний с вилкой на 12 В 2 А. В идеале было бы на 9 В для контроллера Nano, но тогда блок питания за такую же цену будет значительно меньшей мощности.

Понижающие преобразователи LM2596 понизят напряжение с 12 В до 5 В и 9 В для питания наших состаляющих управления котлом. Они не дорогие и достаточно мощные до 2 А.

Применим повышающий преобразователь 5V DC-DC Booster для питания контроллера от аккумулятора, а так же повышающий преобразователь напряжения MT3608 для питания серво-двигателя 6 В.

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

Серво двигатель выберем по критерию цена-стойкость - MG996R. Это возможно самый дешовый серво с металлическими шестеренками.

Датчики температуры воды выберу с уже готовыми герметичными корпусами, с цифровым сигналом и откалиброванные на заводе производителе - DS18B20. Их заказчик собирается прицеплять к внешним сторонам труб без погружения в жидкость.

Для измерения температуры уходящих газов беру термопару ТХА с гильзой длиной 100 мм и длиной кабеля 3 м. Для новичков скажу, что термопару необходимо подключать только через специальный кабель, иначе погрешность измерений может достигать 100 °C. К контроллеру этот датчик подключается через специальный модуль термопары MAX6675.

Для получения сигнала о наличии внешнего питания, я использую модуль гальванической развязки с оптопарой и подключу его к входному напряжению 12 В, а выходную сторону оптопары можно подключать прямо к дискретному входу контроллера Arduino Nano.

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

Для управления менюшкой и редактирования настроек устройства стандартно использую энкодер KY-040 с кнопкой.

Для управления электро-клапаном воды как всегда использую самый надёжный и долгоиграющий вариант - модуль MOSFET-транзистора IRF520

Для удобства пользования устройством поставим кнопки ПУСК и СТОП с подсветкой, чтобы не мучаться каждый раз с енкодером для каждого запуска и останова котла.

Индикатор выбран двухстрочный символьный LCD1602 с модулем коммуникации I2C (позднее стало ясно, что лучше брать четырехстрочный LCD2004, но места в выбранном корпусе уже было мало для большого индикатора).


Схема соединений




Программа для контроллера Arduino Nano

#include <Wire.h> 
#include <LiquidCrystal_I2C.h>
#include <EEPROM.h>
#include <OneWire.h>
#include <max6675.h>
#include <Servo.h>

//datchiki DS18B20
OneWire  temp1(0);
OneWire  temp2(1);
byte addr1[8];
byte addr2[8];
float celsius[3];
int dsreset[2] = {0, 0};
int servodelay;
unsigned long prevMillis_ds[3] = {0, 0, 0};
//datchiki DS18B20

byte degree[8] = {B00110, B01001, B01001, B00110, B00000, B00000, B00000};//символ градуса для записи в память индикатора

Servo myservo;
unsigned int K1 = 0.0;
float PIDOUT = 0;
float error, errSum, output, derror;

float Kp, Ki, T1sp, T3sp, dT3sp, T2rozpaleno, T1potyhlo, T2max, dT2max;
unsigned int t2rozpaleno, t1potyhlo, powerout;
unsigned long rozpalenoMillis = 0;
unsigned long potyhloMillis = 0;
unsigned long poweroutMillis = 0;
int OUTMIN, OUTMAX, MANOUT;
bool KZ1MAN, KZ1AUTO;

LiquidCrystal_I2C lcd(0x27, 16, 2);

int thermoDO = 10;
int thermoCS = 12;
int thermoCLK = 11;
MAX6675 thermocouple(thermoCLK, thermoCS, thermoDO);

const int ButRUN = 6;
const int ButSTOP = 7;
const int LampRUN = 8;
const int LampSTOP = 9;
int powerPin = A7;
bool power;

const int KZ1pin = 2;
bool KZ1;

boolean L1, L2, L3;
int L1_count = 0;
int L2_count = 0;
int L3_count = 0;

const int button = 14;
int button_old = 1;
int p = 4; int piskpower = 3; //номер пина, к которому подключен пьезоэлемент
unsigned long piskMillis = 0;
unsigned long piskwaitMillis = 0;
unsigned long piskwait, piskset;
bool pisk_once = 0;
bool pisk1 = 0;
bool highT2old = 0;
int piskstep;

int astep;
int menu = 0;
int menu_ = 0;

int avto = 0; //rezhim avto
boolean Heat[4] = {0, 0, 0, 0};

int pinA = 16;  // номер вывода, подключенный к CLK енкодера
int pinB = 15;  // номер вывода контроллера, подключенный к DT енкодера
 int encoderPosCount = 0; 
 int pinALast;  
 int aVal;
 boolean bCW;
unsigned long prev_Servo_delay = 0;
unsigned long previousMillis = 0;
unsigned long currentMillis;
const long interval = 500;

unsigned long prevMS[4];
unsigned long curMS[4];

void setup(){
  EEPROM.get(50, Kp);
  EEPROM.get(55, T1sp);
  EEPROM.get(70, T3sp);
  EEPROM.get(75, dT3sp);
  EEPROM.get(40, T2rozpaleno);
  EEPROM.get(45, T1potyhlo);
  EEPROM.get(21, Ki);
  EEPROM.get(31, OUTMIN);
  EEPROM.get(25, OUTMAX);
  EEPROM.get(27, t2rozpaleno);
  EEPROM.get(29, t1potyhlo);
  EEPROM.get(33, powerout);
  EEPROM.get(60, T2max);
  EEPROM.get(65, dT2max);
  EEPROM.get(70, derror);

  myservo.attach(5);
  MANOUT = OUTMIN;
  servodelay = OUTMIN;
  myservo.write(MANOUT);
  pinMode(button, INPUT_PULLUP);
  pinMode (pinA,INPUT);
  pinMode (pinB,INPUT);
  pinMode(ButRUN, INPUT_PULLUP);
  pinMode(ButSTOP, INPUT_PULLUP);

  pinMode(piskpower, OUTPUT);
  pinMode(LampRUN, OUTPUT);
  pinMode(LampSTOP, OUTPUT);
  pinMode(KZ1pin, OUTPUT);
  pinMode(p, OUTPUT); //пищалка
	
  lcd.begin();
  lcd.createChar(1, degree);
  
  lcd.backlight();
  lcd.setCursor(0, 0);
  lcd.print("Privit,");
  lcd.setCursor(7, 1);
  lcd.print("gospodar!");

  temp1.search(addr1);
  temp2.search(addr2);
  delay(2000);
  pinALast = digitalRead(pinA);
  currentMillis = millis();
  previousMillis = currentMillis;
  piskMillis = currentMillis;
  piskwaitMillis = currentMillis;
}

void loop()
{
  power = analogRead(powerPin)>500;
  currentMillis = millis();
  if (!digitalRead(ButRUN)) {avto=1; astep = 100; rozpalenoMillis = currentMillis; potyhloMillis = currentMillis; pisk_once = 0;}
  if ((!digitalRead(ButSTOP))||(celsius[0]>999.0)||(String(celsius[2],0)=="NAN")) {avto=0; astep = 0; MANOUT = OUTMIN; menu=0; encoderPosCount=0; pisk_once = 0;}
  
  currentMillis = millis();
  if (avto==0) {errSum = 0.0; PIDOUT = OUTMIN;}
  //datchiki DS18B20
  func_dallas(temp1, addr1, 0);
  func_dallas(temp2, addr2, 1);
  //datchiki DS18B20
  currentMillis = millis();
  if (currentMillis - prevMillis_ds[2] >= 1000) {
    prevMillis_ds[2] = currentMillis;
    celsius[2] = thermocouple.readCelsius();
    if (avto==1) func_auto();
    func_outs();
  }
  
  func_encoder();
  
  func_menu();

  func_button();
  
  currentMillis = millis();
  if (currentMillis - prev_Servo_delay >= 150) {
    prev_Servo_delay = currentMillis;
    if (avto==1) K1 = PIDOUT; else K1 = MANOUT;
    if (servodelay < K1) servodelay=servodelay+1;
    if (servodelay > K1) servodelay=servodelay-1;
    myservo.write(servodelay);
  }

  func_pisk();
  func_ac();
}
void func_ac()
{
  if ((avto==1)&&(power)){
    currentMillis = millis();
    if (currentMillis - poweroutMillis > powerout*1000) {
      avto=0; 
      MANOUT = OUTMIN;
      KZ1MAN = 0;
      astep=0;
    }
  }
  else poweroutMillis = currentMillis;
}
void func_dallas(OneWire  ds, byte addr[8], int num)
{  
  byte present = 0;
  int temp;
  unsigned long curMillis_ds;

  if (dsreset[num]==0){ 
    ds.reset();
    ds.select(addr);
    ds.write(0x44);
    dsreset[num] = 1;
  }
  
  curMillis_ds = millis();
  if (curMillis_ds - prevMillis_ds[num] >= 1000) {
    prevMillis_ds[num] = curMillis_ds;
    dsreset[num] = 0;
    present = ds.reset();
    ds.select(addr);    
    ds.write(0xBE);

    temp = (ds.read() | ds.read()<<8); //Принимаем два байта температуры
    if (present==1) celsius[num] = (float)temp / 16.0; else celsius[num] = 1000.00;
  }
}  
void func_outs()
{
  //button_old = digitalRead(button); 
  digitalWrite(LampRUN, avto);
  digitalWrite(LampSTOP, !avto);
  if (avto==1) {KZ1 = KZ1AUTO; KZ1MAN = KZ1AUTO;} else KZ1 = KZ1MAN;
  digitalWrite(KZ1pin, KZ1);
  
  
}
void func_pid()
{  
  if (Kp>=0.0) error = T1sp - celsius[2]; else error = celsius[2] - T1sp;
  errSum += error;
  //if (Ki *errSum/100.0>OUTMAX) errSum=OUTMAX*100.0/Ki;
  //if (Ki *errSum/100.0<-OUTMAX) errSum=-OUTMAX*100.0/Ki;
  output = abs(Kp) * error + Ki * errSum/100.0;
  if ((output > OUTMAX)||(output < OUTMIN)) errSum = ((K1 - (abs(Kp) * error))/Ki) * 100.0;;
  
  PIDOUT = output;
  if (PIDOUT>OUTMAX) PIDOUT=OUTMAX;
  if (PIDOUT<OUTMIN) PIDOUT=OUTMIN;
}
void func_auto()
{
 if (celsius[1]<1000.0){
   if (celsius[1] > T3sp) KZ1AUTO = 0;
   if (celsius[1] < T3sp - dT3sp) KZ1AUTO = 1;
 }else{
   KZ1AUTO = 1;
 }

 if (celsius[0] > T2max+10.0) {
     pisk_once = 1; piskset = 3000;
 }else pisk_once = 0;  
 
 if (astep==100){
   
   //bezydarnost
   if (Kp>=0.0) error = T1sp - celsius[2]; else error = celsius[2] - T1sp;
   errSum = ((K1 - (abs(Kp) * error))/Ki) * 100.0;
   //bezydarnost
   PIDOUT = OUTMIN;
   if (celsius[0] > T2rozpaleno){
     currentMillis = millis();
     if (currentMillis - rozpalenoMillis > t2rozpaleno*1000) {
       astep = 200;
       potyhloMillis = currentMillis;
     }  
   }else rozpalenoMillis = currentMillis;
   
 }

 if (astep==200){
  
   if ((abs(T1sp - celsius[2]))>derror) func_pid();
   
   if (celsius[2] < T1potyhlo){
     currentMillis = millis();
     if (currentMillis - potyhloMillis > t1potyhlo*1000) {
       astep = 300; 
       piskwait = 1000;
       piskset = 15000;
       piskwaitMillis = currentMillis;
     }  
   }else potyhloMillis = currentMillis;
   if (celsius[0] > T2max) astep=400;
 }

 if (astep==300){
   avto=0; 
   MANOUT = OUTMIN;
   KZ1MAN = 0;
 }

 if (astep==400){ 
   PIDOUT = OUTMIN;
   if (celsius[0] < T2max - dT2max) {
     astep = 100; 
     rozpalenoMillis = currentMillis;
   }
 }
  
}
void func_button()
{
  if ((digitalRead(button)==LOW)&&(button_old==1))
    { 
      if ((menu == 1)and(button_old==1)) {
        switch (menu_) {
          case 0: menu = 100; encoderPosCount=MANOUT;
          break;
          case 1: menu = 200; encoderPosCount=0;
          break;
          case 2: menu = 300; encoderPosCount=0;
          break;
          case 3: menu = 400; encoderPosCount=0;
          break;
          case 4: menu = 0; encoderPosCount=0;
          break;
        }
        button_old=0;
      }

      if ((menu == 100)and(button_old==1)) {
        menu = 1;
        
        encoderPosCount = 0;
        button_old=0;
      }

      if ((menu == 200)and(button_old==1)) {
        switch (menu_) {
          case 0: KZ1MAN = !KZ1MAN;
          break;
          case 1: menu = 1; encoderPosCount=1;
          break;
        }
        button_old=0;
      }

      if ((menu == 300)and(button_old==1)) {
        switch (menu_) {
          case 0: menu = 301; encoderPosCount = T1sp;
          break;
          case 1: menu = 302; encoderPosCount = T2rozpaleno;
          break;
          case 2: menu = 303; encoderPosCount = T1potyhlo;
          break;
          case 3: menu = 304; encoderPosCount = t2rozpaleno;
          break;
          case 4: menu = 305; encoderPosCount = t1potyhlo;
          break;
          case 5: menu = 306; encoderPosCount = T3sp;
          break;
          case 6: menu = 307; encoderPosCount = dT3sp;
          break;
          case 7: menu = 308; encoderPosCount = powerout;
          break;
          case 8: menu = 309; encoderPosCount = T2max;
          break;
          case 9: menu = 310; encoderPosCount = dT2max;
          break;
          case 10: menu = 1; encoderPosCount=2;
          break;
        }
        button_old=0;
      }

      if ((menu == 301)and(button_old==1)) {
        menu = 300;
        T1sp = encoderPosCount;
        EEPROM.put(55, T1sp);
        encoderPosCount = 0;
        button_old=0;
      }

      if ((menu == 302)and(button_old==1)) {
        menu = 300;
        T2rozpaleno = encoderPosCount;
        EEPROM.put(40, T2rozpaleno);
        encoderPosCount = 1;
        button_old=0;
      }

      if ((menu == 303)and(button_old==1)) {
        menu = 300;
        T1potyhlo = encoderPosCount;
        EEPROM.put(45, T1potyhlo);
        encoderPosCount = 2;
        button_old=0;
      }

      if ((menu == 304)and(button_old==1)) {
        menu = 300;
        t2rozpaleno = encoderPosCount;
        EEPROM.put(27, t2rozpaleno);
        encoderPosCount = 3;
        button_old=0;
      }      

      if ((menu == 305)and(button_old==1)) {
        menu = 300;
        t1potyhlo = encoderPosCount;
        EEPROM.put(29, t1potyhlo);
        encoderPosCount = 4;
        button_old=0;
      }   

      if ((menu == 306)and(button_old==1)) {
        menu = 300;
        T3sp = encoderPosCount;
        EEPROM.put(70, T3sp);
        encoderPosCount = 5;
        button_old=0;
      }  

      if ((menu == 307)and(button_old==1)) {
        menu = 300;
        dT3sp = encoderPosCount;
        EEPROM.put(75, dT3sp);
        encoderPosCount = 6;
        button_old=0;
      }  

      if ((menu == 308)and(button_old==1)) {
        menu = 300;
        powerout = encoderPosCount;
        EEPROM.put(33, powerout);
        encoderPosCount = 7;
        button_old=0;
      }  

      if ((menu == 309)and(button_old==1)) {
        menu = 300;
        T2max = encoderPosCount;
        EEPROM.put(60, T2max);
        encoderPosCount = 8;
        button_old=0;
      }  

      if ((menu == 310)and(button_old==1)) {
        menu = 300;
        dT2max = encoderPosCount;
        EEPROM.put(65, dT2max);
        encoderPosCount = 9;
        button_old=0;
      }  

      if ((menu == 400)and(button_old==1)) {
        switch (menu_) {
          case 0: menu = 401; encoderPosCount = Kp*10;
          break;
          case 1: menu = 402; encoderPosCount = Ki;
          break;
          case 2: menu = 403; encoderPosCount = OUTMIN;
          break;
          case 3: menu = 404; encoderPosCount = OUTMAX;
          break;
          case 4: menu = 405; encoderPosCount = derror;
          break;
          case 5: menu = 1; encoderPosCount=3;
          break;
        }
        button_old=0;
      }

      if ((menu == 401)and(button_old==1)) {
        menu = 400;
        Kp = encoderPosCount*0.1;
        EEPROM.put(50, Kp);
        encoderPosCount = 0;
        button_old=0;
      }

      if ((menu == 402)and(button_old==1)) {
        menu = 400;
        Ki = encoderPosCount;
        EEPROM.put(21, Ki);
        encoderPosCount = 1;
        button_old=0;
      }

      if ((menu == 403)and(button_old==1)) {
        menu = 400;
        OUTMIN = encoderPosCount;
        EEPROM.put(31, OUTMIN);
        encoderPosCount = 2;
        button_old=0;
      }

      if ((menu == 404)and(button_old==1)) {
        menu = 400;
        OUTMAX = encoderPosCount;
        EEPROM.put(25, OUTMAX);
        encoderPosCount = 3;
        button_old=0;
      }

      if ((menu == 405)and(button_old==1)) {
        menu = 400;
        derror = encoderPosCount;
        EEPROM.put(70, derror);
        encoderPosCount = 4;
        button_old=0;
      }      
      
      if ((menu == 0)and(button_old==1)) {
        menu = 1;
        menu_ = 0;
        button_old=0;
        encoderPosCount=0;
      }
      
      
      menu_ = 0;
    }
    button_old = digitalRead(button); 
}
void func_encoder()
{
  //encoder
   aVal = digitalRead(pinA);
   if (aVal != pinALast){ // проверка на изменение значения на выводе А по сравнению с предыдущим запомненным, что означает, что вал повернулся
     // а чтобы определить направление вращения, нам понадобится вывод В.
     
     if (digitalRead(pinB) != aVal) {  // Если вывод A изменился первым - вращение по часовой стрелке
       encoderPosCount ++;
       bCW = true;
     } else {// иначе B изменил свое состояние первым - вращение против часовой стрелки
       bCW = false;
       encoderPosCount--;
     }              
   }
   pinALast = aVal;
   //encoder
}
void func_menu()
{
  int celsius2;
  if (currentMillis - previousMillis >= interval) {
      previousMillis = currentMillis;
  if (menu == 1){
    
    if (encoderPosCount>4) encoderPosCount = 0;
    if (encoderPosCount<0) encoderPosCount = 4;
    menu_ = encoderPosCount;
       lcd.setCursor(0, 0);
        lcd.print(">");
        switch (menu_) {
          case 0: lcd.print("K1              ");
          break;
          case 1: lcd.print("KZ1             ");
          break;
          case 2: lcd.print("Parametri       ");
          break;
          case 3: lcd.print("PID reguliator  ");
          break;
          case 4: lcd.print("EXIT            ");
          break;
        }
        
        lcd.setCursor(0, 1);
        switch (menu_) {
          
          case 4: lcd.print("K1              ");
          break;
          case 0: lcd.print("KZ1             ");
          break;
          case 1: lcd.print("Parametri       ");
          break;
          case 2: lcd.print("PID reguliator  ");
          break;
          case 3: lcd.print("EXIT            ");
          break;
        }
  }

  if (menu == 0){
      lcd.setCursor(0, 0);
      celsius2 = celsius[2];
      
      if (String(celsius[2],0)=="NAN"){lcd.print(" ERR ");} else {lcd.print(celsius2);}
      
      lcd.print("\1C  ");
      lcd.setCursor(6, 0);
      if (!power) lcd.print("AV "); else lcd.print("av ");
      if (KZ1) lcd.print("KZ"); else lcd.print("kz");
      
      lcd.print(" ");
      lcd.print(K1); lcd.print("\1   ");
      lcd.setCursor(0, 1);

      //lcd.print("er"); lcd.print(error); lcd.print("ers"); lcd.print(errSum);lcd.print(" ");
      
      
      celsius2 = celsius[0];
      if (celsius[0]<1000.0) {lcd.print(celsius2);lcd.print("\1C  ");} else {lcd.print("ERROR  ");}
      switch (astep) {
          case 100: lcd.print("#");
          break;
          case 200: lcd.write(byte(178));
          break;
          case 400: lcd.write(byte(64));
          break;
          default: lcd.print("_");
          break;
        }
      lcd.print("  ");  
      celsius2 = celsius[1];
      if (celsius[1]<1000.0) {lcd.print(celsius2);lcd.print("\1C");} else lcd.print("ERROR");
      lcd.print("   ");
  }

  if (menu == 100){
        if (encoderPosCount>180) encoderPosCount = 180;
        if (encoderPosCount<0) encoderPosCount = 0;
        MANOUT = encoderPosCount;
        lcd.setCursor(0, 0);
        lcd.print("Otkritie K1     ");
        lcd.setCursor(0, 1);
        lcd.print(encoderPosCount); lcd.print(" grad          ");
   }

  if (menu == 200){    
        if (encoderPosCount>1) encoderPosCount = 0;
        if (encoderPosCount<0) encoderPosCount = 1;
        menu_ = encoderPosCount; 
        lcd.setCursor(0, 0);
        lcd.print(">");
        switch (menu_) {
          case 0: if (KZ1) lcd.print("KZ1 OTKRITO     "); else lcd.print("KZ1 ZAKRITO     ");
          break;
          case 1: lcd.print("EXIT            ");
          break;
        }  
        lcd.setCursor(0, 1);    
        switch (menu_) {
          case 1: if (KZ1) lcd.print("KZ1 OTKRITO     "); else lcd.print("KZ1 ZAKRITO     ");
          break;
          case 0: lcd.print("EXIT            ");
          break;
        }    
   } 

  if (menu == 300){
    if (encoderPosCount>10) encoderPosCount = 0;
    if (encoderPosCount<0) encoderPosCount = 10;
    menu_ = encoderPosCount;
       lcd.setCursor(0, 0);
        lcd.print(">");
        switch (menu_) {
          case 0: lcd.print("T1sp        DG  ");
          break;
          case 1: lcd.print("T2rozpaleno SO  ");
          break;
          case 2: lcd.print("T1potyhlo   DG  ");
          break;
          case 3: lcd.print("t2rozpaleno     ");
          break;
          case 4: lcd.print("t1potyhlo       ");
          break;
          case 5: lcd.print("T3sp        GVS ");
          break;
          case 6: lcd.print("dT3sp       GVS ");
          break;
          case 7: lcd.print("t3 nema 220V    ");
          break;
          case 8: lcd.print("T2max       SO  ");
          break;
          case 9: lcd.print("dT2max      SO  ");
          break;
          case 10:lcd.print("EXIT            ");
          break;
        }
        
        lcd.setCursor(0, 1);
        switch (menu_) {
          case 10:lcd.print("T1sp        DG  ");
          break;
          case 0: lcd.print("T2rozpaleno SO  ");
          break;
          case 1: lcd.print("T1potyhlo   DG  ");
          break;
          case 2: lcd.print("t2rozpaleno     ");
          break;
          case 3: lcd.print("t1potyhlo       ");
          break;
          case 4: lcd.print("T3sp        GVS ");
          break;
          case 5: lcd.print("dT3sp       GVS ");
          break;
          case 6: lcd.print("t3 nema 220V    ");
          break;
          case 7: lcd.print("T2max       SO  ");
          break;
          case 8: lcd.print("dT2max      SO  ");
          break;
          case 9: lcd.print("EXIT            ");
          break;
        }
  }

   if (menu == 301){
        if (encoderPosCount>600) encoderPosCount = 600;
        if (encoderPosCount<0) encoderPosCount = 0;
        lcd.setCursor(0, 0);
        lcd.print("Zadana T1sp DG  ");
        lcd.setCursor(0, 1);
        lcd.print(encoderPosCount); lcd.print(" \1C          ");
   }

   if (menu == 302){
        if (encoderPosCount>100) encoderPosCount = 100;
        if (encoderPosCount<0) encoderPosCount = 0;
        lcd.setCursor(0, 0);
        lcd.print("T2 SO rozpalen  ");
        lcd.setCursor(0, 1);
        lcd.print(encoderPosCount); lcd.print(" \1C          ");
   }

   if (menu == 303){
        if (encoderPosCount>600) encoderPosCount = 600;
        if (encoderPosCount<0) encoderPosCount = 0;
        lcd.setCursor(0, 0);
        lcd.print("T1 DG potyhlo   ");
        lcd.setCursor(0, 1);
        lcd.print(encoderPosCount); lcd.print(" \1C          ");
   }

   if (menu == 304){
        if (encoderPosCount>60000) encoderPosCount = 60000;
        if (encoderPosCount<1) encoderPosCount = 1;
        lcd.setCursor(0, 0);
        lcd.print("Chas rozpaleno  ");
        lcd.setCursor(0, 1);
        lcd.print(encoderPosCount); lcd.print(" cek           ");
   }

   if (menu == 305){
        if (encoderPosCount>60000) encoderPosCount = 60000;
        if (encoderPosCount<1) encoderPosCount = 1;
        lcd.setCursor(0, 0);
        lcd.print("Chas potyhlo    ");
        lcd.setCursor(0, 1);
        lcd.print(encoderPosCount); lcd.print(" cek           ");
   }

   if (menu == 306){
        if (encoderPosCount>100) encoderPosCount = 100;
        if (encoderPosCount<0) encoderPosCount = 0;
        lcd.setCursor(0, 0);
        lcd.print("Zadana T3sp GVS ");
        lcd.setCursor(0, 1);
        lcd.print(encoderPosCount); lcd.print(" \1C          ");
   }

   if (menu == 307){
        if (encoderPosCount>30) encoderPosCount = 30;
        if (encoderPosCount<0) encoderPosCount = 0;
        lcd.setCursor(0, 0);
        lcd.print("Delta dT3sp GVS ");
        lcd.setCursor(0, 1);
        lcd.print(encoderPosCount); lcd.print(" \1C          ");
   }

   if (menu == 308){
        if (encoderPosCount>60000) encoderPosCount = 60000;
        if (encoderPosCount<1) encoderPosCount = 1;
        lcd.setCursor(0, 0);
        lcd.print("Chas koly 'ac'");
        lcd.setCursor(0, 1);
        lcd.print(encoderPosCount); lcd.print(" cek           ");
   }

   if (menu == 309){
        if (encoderPosCount>100) encoderPosCount = 100;
        if (encoderPosCount<0) encoderPosCount = 0;
        lcd.setCursor(0, 0);
        lcd.print("Zadana T2max SO ");
        lcd.setCursor(0, 1);
        lcd.print(encoderPosCount); lcd.print(" \1C          ");
   }

   if (menu == 310){
        if (encoderPosCount>100) encoderPosCount = 100;
        if (encoderPosCount<0) encoderPosCount = 0;
        lcd.setCursor(0, 0);
        lcd.print("Delta dT2max SO ");
        lcd.setCursor(0, 1);
        lcd.print(encoderPosCount); lcd.print(" \1C          ");
   }

  if (menu == 400){
    if (encoderPosCount>5) encoderPosCount = 0;
    if (encoderPosCount<0) encoderPosCount = 5;
    menu_ = encoderPosCount;
       lcd.setCursor(0, 0);
        lcd.print(">");
        switch (menu_) {
          case 0: lcd.print("Kp              ");
          break;
          case 1: lcd.print("Ki              ");
          break;
          case 2: lcd.print("OUTMIN          ");
          break;
          case 3: lcd.print("OUTMAX          ");
          break;
          case 4: lcd.print("dBAND           ");
          break;
          case 5: lcd.print("EXIT            ");
          break;
        }
        
        lcd.setCursor(0, 1);
        switch (menu_) {
          case 5: lcd.print("Kp              ");
          break;
          case 0: lcd.print("Ki              ");
          break;
          case 1: lcd.print("OUTMIN          ");
          break;
          case 2: lcd.print("OUTMAX          ");
          break;
          case 3: lcd.print("dBAND           ");
          break;
          case 4: lcd.print("EXIT            ");
          break;
        }
  }

   if (menu == 401){
        if (encoderPosCount>1000) encoderPosCount = 1000;
        if (encoderPosCount<-1000) encoderPosCount = -1000;
        lcd.setCursor(0, 0);
        lcd.print("Kp -100...100   ");
        lcd.setCursor(0, 1);
        lcd.print(encoderPosCount*0.1); lcd.print("             ");
   }

   if (menu == 402){
        if (encoderPosCount>10000) encoderPosCount = 10000;
        if (encoderPosCount<0) encoderPosCount = 0;
        lcd.setCursor(0, 0);
        lcd.print("Ki 0...10000   ");
        lcd.setCursor(0, 1);
        lcd.print(encoderPosCount); lcd.print("               ");
   }

   if (menu == 403){
        if (encoderPosCount>180) encoderPosCount = 180;
        if (encoderPosCount<0) encoderPosCount = 0;
        lcd.setCursor(0, 0);
        lcd.print("OUTMIN 0...180  ");
        lcd.setCursor(0, 1);
        lcd.print(encoderPosCount); lcd.print(" grad        ");
   }

   if (menu == 404){
        if (encoderPosCount>180) encoderPosCount = 180;
        if (encoderPosCount<0) encoderPosCount = 0;
        lcd.setCursor(0, 0);
        lcd.print("OUTMAX 0...180 ");
        lcd.setCursor(0, 1);
        lcd.print(encoderPosCount); lcd.print(" grad        ");
   }  

   if (menu == 405){
        if (encoderPosCount>200) encoderPosCount = 200;
        if (encoderPosCount<0) encoderPosCount = 0;
        lcd.setCursor(0, 0);
        lcd.print("dBAND 0...200 ");
        lcd.setCursor(0, 1);
        lcd.print(encoderPosCount); lcd.print(" \1C          ");
   }  

 }
}
void func_pisk()
{
  if ((pisk_once)||(astep==300)){
    currentMillis = millis();
     if (currentMillis - piskwaitMillis > piskwait) {
      piskwaitMillis = currentMillis;
      if (!pisk1){
        pisk1 = 1;
        digitalWrite(piskpower, HIGH);
        tone (p, 1000); //пищим на частоте 1000 Гц}
        piskwait = 1000; 
      }else{
         pisk1 = 0;
         noTone(p); //выключаем звук
         digitalWrite(piskpower, LOW);
         piskwait = piskset;
      }
     }
     currentMillis = millis();
     if (currentMillis - piskMillis > 3600000) {
       pisk_once = 0;
       if (astep==300) astep = 0;
       noTone(p); //выключаем звук
       digitalWrite(piskpower, LOW);}
  }else {piskMillis = currentMillis; piskwait = 1000; piskwaitMillis = currentMillis; noTone(p);}
}

Выводы

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

По вопросам заказа подобных проектов обращайтесь по телефонам, указанным на сайте.


Написать отзыв

Примечание: HTML разметка не поддерживается! Используйте обычный текст.
    Плохо           Хорошо
Прото шилд для Arduino UNO

Прото шилд для Arduino UNO

Шилд для прототипирования для Arduino UNO. Призван упростить монтаж небольших любительских проектов ..

49.14грн.

Мини кнопка 12 x 12 x 4,3 мм

Мини кнопка 12 x 12 x 4,3 мм

Миниатюрная кнопка для установки на плату через отверстияРазмеры 12 x 12 x 4,3 мм..

3.14грн.

Бегущие огни на Ардуино

Бегущие огни на Ардуино

Arduino бегущая дорожка из светодиодовДелаем бегущие огни из светодиодов на Arduino. В данном случае..

Корпус пластиковый 100x60x25мм

Корпус пластиковый 100x60x25мм

Универсальный бокс из чорного пластика под самодельное электронное устройствоArduino UNO и Leonardo ..

36.41грн.

Инфракрасный датчик движения HC-SR505

Инфракрасный датчик движения HC-SR505

Миниатюрный экономичный датчик движения.Напряжение питания 4,5 ... 20 ВТок покоя < 60 мкАВыходные..

46.43грн.