Працюємо з 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 размітка не підтримується! Використовуйте звичайтий текст.
    Погано           Добре
W5500 модуль Ethernet

W5500 модуль Ethernet

Модуль Ethernet на основі мікросхеми W5500.Відрізняється від попередніх версій Ethernet контролерів ..

137.47грн.

Держатель 8мм линейных направляющих CNC или 3d принтера 2шт.

Держатель 8мм линейных направляющих CNC или 3d принтера 2шт.

Тримач для кріплення циліндричних направляючих каретки CNC-станка або 3d-принтераПід діаметр направл..

80.49грн.

Вольтметр цифровий 2,5 ... 30 В

Вольтметр цифровий 2,5 ... 30 В

Простий та зручний вольтметр з отворами під кріплення болтами або шурупамиЖивиться вимірюваною напру..

46.56грн.

Измерение уровня шума Arduino Nano

Измерение уровня шума Arduino Nano

Знадобилось мені заміряти фоновий рівень звуку для проекту розумного дому і виявилось, що це не прос..

Набір перетинок (папа-папа) 40шт 10см

Набір перетинок (папа-папа) 40шт 10см

Набір з'єднувальних дротів для поєднання плат контролера з периферією без пайки. Підходять під станд..

30.03грн.