Удаленный мониторинг температуры

Проект 2. Отопление на Arduino

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

Из этой статьи вы узнаете:

Схема отопления
Список оборудования и ПО

Приветствую друзья, с вами автор блога, Гридин Семён. В просторах интернета я наткнулся на замечательный проект по отоплению на Arduino. Сразу хочу поставить все точки над i. Статья не моя, и проект не мой, даже картинки не мои. Это произведение автора под ником Vanalaizer, первоисточник размещён на Geektimes.

К сожалению, связаться мне с ним не удалось. Этот пост я пишу в основном, чтобы зафиксировать на сайте и закрепить для себя и для вас основные аспекты проекта.

Ну а вам решать, какой текст вам удобнее всего читать — мой или уважаемого Vanalaizer’a. А ему кстати говоря отдельное спасибо за проект. Так что не бросайтесь на меня тухлыми помидорами=)). Я не специально.

Схема отопления

Основной принцип данного проекта — это процесс регулирования электрокотла с помощью самой простой и приземлённой платой Arduino UNO.

Вот непосредственно сам шкаф управления, «мозги» на основе Arduino.

Это как раз делалось для дома в деревне. Регулирование температуры ведётся в одной комнате.

Какое же само оборудование? Я не буду расписывать большие тексты. Напишу конкретно. Для системы требуется следующее.

  • Электрокотёл на 4-6 кВт
  • рециркуляционный насос

Сама конструкция котла состоит и металлического цилиндра с тремя нагревательными ТЭНами по 2 кВт. Отсюда следует вывод, что система должна быть трёхфазная на каждой фазе по ТЭНу. Дополнительно в корпус электрокотла врезаны 2 датчика, резистивный датчик температуры и датчик, замыкающийся при перегреве.

Система управления на Arduino не совсем простая, включать и выключать реле по PID-закону. Лучше всего два ТЭНа включать сразу, а одним поддерживать температуру, это самый надёжный и наиболее точный способ.

Ток потребления нагревателя составляет примерно 8 А. Лучше всего управлять твердотельными реле с запасом по мощности, примерно ампер 15-20 и с охлаждающим радиатором.

Есть готовые модули Solid State Relay, проще говоря — электросхема, собранная на мощном тиристоре, в корпусе, похожем на обычное реле. Из его плюсов — нет механики, ничего не залипнет. Не создаёт мощных ЭМ-помех, что важно для ethernet’a, о котором ниже.

Они уже содержат схему, которая включает и отключает реле при проходе нуля. На реле есть индикатор включения. Ну и ещё они беззвучные, хотя для нашего случае это не так и актуально. Были установлены SSR-25DA, что по-русски означает — твердотельное реле с постоянным управляющим током 3-5В и током нагрузки до 25А.

К ним есть штатные радиаторы, они должны быть установлены — ОБЯЗАТЕЛЬНО!! Не забываем смазать термопастой, для улучшения теплообмена.

Для измерения температуры были использованы цифровые датчики температуры на базе DS18B20.

Список оборудования и ПО

Что же применялось в конечном итоге:

  • Плата arduino. Использовалась UNO r3
  • Ethernet Shield
  • Витая пара
  • Датчики температуры
  • Блок питания на 110-240 — 12В 2А
  • Стабилизатор LM7805
  • Реле SSR-40DA 3 шт
  • Радиаторы для реле

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

Теперь нам надо как-то положить данные из arduino в эту базу. Для этого, естественно, понадобится как минимум связать arduino с интернетом.

Для этого нам и понадобится Ethernet Shield и его библиотека. Установлен простенький роутер. Тянем стандартную витую пару к роутеру и добавляем в программу передачу данных. Передача идёт через вызов странички на PHP с параметрами — данными. Создаём страничку с именем temp.php на нашем инет-сервере.

PHP

После этого мы имеем данные о температурах и мощности работы котла, чтобы каждый раз не лазить в базу, а посмотреть последние данные, автор написал “временный” скрипт на php, gettemp.php

PHP

Вот и практическое применение языка PHP в сфере автоматизации. Честно говоря, пока не углублялся в такие дебри, но всё равно интересно!!!

Ну и, сам скетч.

Arduino

Здесь я останавливаюсь, спасибо автору за его проект. Было бы интересно пообщаться, я всегда на связи. Спасибо за внимание! Всего доброго!!!

С уважением, Гридин Семён

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

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

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

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

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

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

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

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

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

Для зарядки и защиты аккумулятора я применю два модуля, при чем один из них в необычном включении. Первый полноценно заряжает аккумулятор и раздает напряжение на контроллер, а второй будет только раздавать напряжение на серво-двигатель. Это сделано, ибо тока в 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; byte addr2; float celsius; int dsreset = {0, 0}; int servodelay; unsigned long prevMillis_ds = {0, 0, 0}; //datchiki DS18B20 byte degree = {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 = {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; unsigned long curMS; 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>999.0)||(String(celsius,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 >= 1000) { prevMillis_ds = currentMillis; celsius = 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, int num) { byte present = 0; int temp; unsigned long curMillis_ds; if (dsreset==0){ ds.reset(); ds.select(addr); ds.write(0x44); dsreset = 1; } curMillis_ds = millis(); if (curMillis_ds — prevMillis_ds >= 1000) { prevMillis_ds = curMillis_ds; dsreset = 0; present = ds.reset(); ds.select(addr); ds.write(0xBE); temp = (ds.read() | ds.read()<<8); //Принимаем два байта температуры if (present==1) celsius = (float)temp / 16.0; else celsius = 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; else error = celsius — 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<1000.0){ if (celsius > T3sp) KZ1AUTO = 0; if (celsius < T3sp — dT3sp) KZ1AUTO = 1; }else{ KZ1AUTO = 1; } if (celsius > T2max+10.0) { pisk_once = 1; piskset = 3000; }else pisk_once = 0; if (astep==100){ //bezydarnost if (Kp>=0.0) error = T1sp — celsius; else error = celsius — T1sp; errSum = ((K1 — (abs(Kp) * error))/Ki) * 100.0; //bezydarnost PIDOUT = OUTMIN; if (celsius > T2rozpaleno){ currentMillis = millis(); if (currentMillis — rozpalenoMillis > t2rozpaleno*1000) { astep = 200; potyhloMillis = currentMillis; } }else rozpalenoMillis = currentMillis; } if (astep==200){ if ((abs(T1sp — celsius))>derror) func_pid(); if (celsius < T1potyhlo){ currentMillis = millis(); if (currentMillis — potyhloMillis > t1potyhlo*1000) { astep = 300; piskwait = 1000; piskset = 15000; piskwaitMillis = currentMillis; } }else potyhloMillis = currentMillis; if (celsius > T2max) astep=400; } if (astep==300){ avto=0; MANOUT = OUTMIN; KZ1MAN = 0; } if (astep==400){ PIDOUT = OUTMIN; if (celsius < 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; if (String(celsius,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; if (celsius<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; if (celsius<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);} }