Опять про автоматизацию твердотоплевного котла
- Войдите на сайт для отправки комментариев
Пт, 30/12/2016 - 16:59
Итак занялся автоматизацией. Все в стадии разработки... Будет перенесено на одну плату после отладки.
Имеется:
1. Arduino UNO
2. Часы реального времени I2C
3. Энкодер
4. Дисплэй 4х20 I2C
5. Серва привод
6. Блок реле и пищялка
Вот что получилось:
#include <Servo.h> #include <Wire.h> #include <LiquidCrystal_I2C.h> #include <RTClib.h> #include <OneWire.h> #include <DallasTemperature.h> #include <RotaryEncoder.h> #define ONE_WIRE_BUS 2 OneWire oneWire(ONE_WIRE_BUS); DallasTemperature sensors(&oneWire); #if defined(ARDUINO_ARCH_SAMD) #define Serial SerialUSB #endif #if defined(ARDUINO) && ARDUINO >= 100 #define printByte(args) write(args); #else #define printByte(args) print(args,BYTE); #endif Servo zaslonka;// Servo RTC_DS1307 rtc; // Clok + EEPROM LiquidCrystal_I2C lcd(0x3F, 20, 4); // Устанавливаем дисплей 2004 Evro byte cel[8] = // символ градуса { 0b01000, 0b10100, 0b01000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000 }; byte rus_i[8] = // И - матрица { B10001, B10001, B10011, B10101, B11001, B10001, B10001, B00000, }; byte rus_yy[8] = // Ы - матрица { B10001, B10001, B10001, B11101, B10011, B10011, B11101, B00000, }; byte rus_p[8] = // П - матрица { B11111, B10001, B10001, B10001, B10001, B10001, B10001, B00000, }; byte rus_ya[8] = // Я - матрица { B01111, B10001, B10001, B01111, B00101, B01001, B10001, B00000, }; byte rus_g[8] = // Г - матрица { B11111, B10001, B10000, B10000, B10000, B10000, B10000, B00000, }; byte rus_l[8] = // Л - матрица { B00110, B01001, B01001, B01001, B01001, B01001, B10001, B00000, }; float temp1 = 20.0; //Температура в комнате float temp2 = 10.0; //Температура носителя Out float temp3 = 10.0; //Температура носителя In int temp_max = 95; // Максимальная температура аварии int temp_min = 35; // Минимальная температура (котел холодный) насос выключяется int temp_val = 60; //Установленная температура long temp_oldtime = 0; // Предыдущее время замера для определения остывания int rezhim = 2; // Режимы котла 0 - котел холодный, 1- растопка, 2 - Нормальная работа, 3 - Режим тления, 4 - Перегрев !!! // 5 - Oxлаждение int zaslonka_val; // положение заслонки 120 -закрыта 0 - открыта int zaslonka_min = 0; // Заслонка закрыта int zaslonka_max = 155; // Заслонка открыта int rezis = 5; //Градусы регулировки +- char KeyPressed = 'N'; // Текущая нажатая кнопка клавиатуры int knopka = 5; // кнопка энкодера int POMPA = 6; int BEEPER = 4; int RELE = 7; int SERVO = 3; RotaryEncoder encoder(A2, A3); //устанавливаем A2, A3 порты для энкодера int result = -1; long lcdUpdate; DeviceAddress sensor1 = {0x28, 0xFF, 0xB6, 0xFB, 0x80, 0x16, 0x04, 0x3B}; // адреса градусников. DeviceAddress sensor2 = {0x28, 0xFF, 0xEE, 0x20, 0x82, 0x16, 0x04, 0x4A}; DeviceAddress sensor3 = {0x28, 0xFF, 0x49, 0xF9, 0x80, 0x16, 0x04, 0xF5}; char* rezhim_t[] = {"KOTE\x0E OCTAHOB\x0E""EH", "PACTO\x0CKA... ", "HOPMA... ", "T\x0E""EH\x09""E... ", "\x0C""EPE\x0DPEB!!! ", "OCT\x0A""BAH\x09""E... "}; //Названия режимов void setup() { sensors.begin(); // Запуск температуры sensors.setResolution(sensor1, 12); // 12 bit sensors.setResolution(sensor2, 12); // 12 bit sensors.setResolution(sensor3, 12); // 12 bit Serial.begin(9600); if (! rtc.begin()) { Serial.println("Couldn't find RTC"); while (1){ tone(BEEPER, 900, 10); }; // stop! No time! } if (! rtc.isrunning()) { Serial.println("RTC is NOT running!"); // following line sets the RTC to the date & time this sketch was compiled rtc.adjust(DateTime(F(__DATE__), F(__TIME__))); // This line sets the RTC with an explicit date & time, for example to set // January 21, 2014 at 3am you would call: // rtc.adjust(DateTime(2014, 1, 21, 3, 0, 0)); } lcd.init(); lcd.backlight(); lcd.createChar(0, cel); // - прописываем символы lcd.createChar(1, rus_i); lcd.createChar(2, rus_yy); lcd.createChar(3, rus_ya); lcd.createChar(4, rus_p); lcd.createChar(5, rus_g); lcd.createChar(6, rus_l); lcd.clear(); lcd.setCursor(0, 0); lcd.print("3A\x0DPY3KA..."); zaslonka.attach(SERVO); // Иницилизация серво zaslonka.write(zaslonka_max); // Установка заслонки в открыто pinMode(knopka, INPUT); digitalWrite(knopka, HIGH); pinMode(POMPA, OUTPUT); digitalWrite(POMPA, HIGH); lcd.clear(); temp_min = rtc.readnvram(0); //Чтение из ЕЕПРОМ temp_val = rtc.readnvram(1); zaslonka_val = rtc.readnvram(2); rezis = rtc.readnvram(4); } void loop() { temeratura_in(); // запрос температуры zaslonka_move(); // перемещение заслонки от температуры GetKey(); if (KeyPressed == 'M') { menuSetup(); } if (KeyPressed == 'X') { // menuRabotaMode(); } if (temp2 > temp_min) { digitalWrite(POMPA, LOW); //Включение помпы отопления } else { digitalWrite(POMPA, HIGH); //Выключение помпы отопления } if (temp2 <= temp_min) { // Eсли температура мненьше минимум то Котел остановлен... rezhim = 0; temp_oldtime = millis(); } if (temp2 > temp_min && temp2 < 60) { // Eсли температура мненьше нормы но выше минимальной то растопка... rezhim = 1; } if (temp2 <= (temp_val + rezis) && temp2 >= (temp_val - rezis)) { // Eсли температура мненьше норма то работа... rezhim = 2; temp_oldtime = millis(); } if (temp2 > (temp_val + 10) && temp2 < temp_max) { // Eсли температура выше нормы то тление... rezhim = 3; } if (temp2 > temp_max) { // Eсли температура выше max то Stop... rezhim = 4; tone(BEEPER, 500, 20); } if (temp2 == temp_val) temp_oldtime = millis(); // Сохраняем время температуры заданной if (temp2 < (temp_val - rezis) && temp2 > (temp_min) && ((millis() - temp_oldtime) >= 1200000)) { // Если в течении 20 минут температура опустилась ниже резиса то режим остывания rezhim = 5; tone(BEEPER, 500, 20); } if (millis() - lcdUpdate >= 100) // обновление дисплея { dislay(); lcdUpdate = millis(); // Обновляем время для дисплэя } } void zaslonka_move() { zaslonka_val = temp2; // Присвоение температуры с датчика выхода воды из котла zaslonka_val = map(zaslonka_val, (temp_val - rezis), (temp_val + rezis), zaslonka_max,zaslonka_min); // Пересчет температуры в соотношении с углом поаорота zaslonka_val = constrain(zaslonka_val, zaslonka_min, zaslonka_max); if (rezhim == 0 || rezhim == 3 || rezhim == 4 || rezhim == 5 ) zaslonka_val = zaslonka_min; // Заслонка закрыта if (rezhim == 1) zaslonka_val = zaslonka_max; // Заслонка открыта zaslonka.write(zaslonka_val); // Перемещение заслонки } void menuSetup() { menu_m: result = 0; int menuSetupMode = 1; lcd.clear(); lcd.setCursor(5, 0); lcd.print ("HACTPO\x09KA"); lcd.setCursor (0, 1); lcd.print (char(126)); lcd.print ("BPEM\x0B"); lcd.setCursor (0, 2); lcd.print (" TEM\x0C""EPATYPA"); lcd.setCursor (0, 3); lcd.print (" COXPAHEH\x09""E"); lcd.setCursor (14, 1); lcd.print (" B\x0AXOD"); delay (300);// От проскакивания кнопки while (1 == 1) { static int pos = 0; encoder.tick(); int newPos = encoder.getPosition(); if (pos < newPos) result++;// | if (pos > newPos) result--;// | крутим меню по кругу if (result > 3) result = 0;// | if (result < 0) result = 3;// | if (pos != newPos) { pos = newPos; if (millis() - lcdUpdate >= 100) // обновление дисплея { lcd.setCursor(0, 1); // Стираем стрелки lcd.print (" "); lcd.setCursor(0, 2); lcd.print (" "); lcd.setCursor(0, 3); lcd.print (" "); lcd.setCursor(14, 1); lcd.print (" "); switch (result) { // Выбираем пункт и ставим стрелку case 0: // Выбор установки времени lcd.setCursor(0, 1); lcd.print (char(126)); break; case 1: // Выбор установки температуры lcd.setCursor(0, 2); lcd.print (char(126)); break; case 2: // Сохранение настроек в ЕЕПРОМ lcd.setCursor(0, 3); lcd.print (char(126)); break; case 3: // выход из меню lcd.setCursor(14, 1); lcd.print (char(126)); break; } lcdUpdate = millis(); // Обновляем время для дисплэя } } GetKey(); // Нажата та ли кнопка на энкодере if (KeyPressed == 'X' && result == 0) { // Выбор установки времени lcd.clear(); lcd.setCursor(0, 0); lcd.print (" HACTPO\x09KA BPEMEH\x09 "); lcd.setCursor(6, 2); DateTime now = rtc.now(); int setupHour = now.hour(); int setupMinute = now.minute(); int menuSetupDataMode = 1; // Подменю 1 -Чясы 2 - минуты 0 - Выход if (setupHour < 10) lcd.print ('0'); lcd.print (setupHour); lcd.print(":"); if (setupMinute < 10) lcd.print ('0'); lcd.print (setupMinute); lcd.setCursor(8, 2); lcd.blink_on(); if (menuSetupDataMode == 1) { while (menuSetupDataMode != 0) { static int pos = 0; encoder.tick(); int newPos = encoder.getPosition(); if (pos < newPos) setupHour++; // | if (pos > newPos) setupHour--; // | крутим меню по кругу чясы if (setupHour > 23) setupHour = 0;// | if (setupHour < 0) setupHour = 23;// | if (pos != newPos) { pos = newPos; if (millis() - lcdUpdate >= 100) // обновление дисплея { lcd.setCursor(6, 2); // if (setupHour < 10) lcd.print ('0'); lcd.print (setupHour); lcdUpdate = millis(); // Обновляем время для дисплэя } } GetKey(); if (KeyPressed == 'X') { menuSetupDataMode ++; delay (50); lcd.blink_off(); break; } } } if (menuSetupDataMode == 2) { lcd.setCursor(11, 2); lcd.blink_on(); while (menuSetupDataMode != 0) { static int pos = 0; encoder.tick(); int newPos = encoder.getPosition(); if (pos < newPos) setupMinute++; // | if (pos > newPos) setupMinute--; // | крутим меню по кругу минуты if (setupMinute > 59) setupMinute = 0;// | if (setupMinute < 0) setupMinute = 59;// | if (pos != newPos) { pos = newPos; if (millis() - lcdUpdate >= 100) // обновление дисплея { lcd.setCursor(9, 2); // Стираем стрелки if (setupMinute < 10) lcd.print ('0'); lcd.print (setupMinute); lcdUpdate = millis(); // Обновляем время для дисплэя } } GetKey(); if (KeyPressed == 'X') { menuSetupDataMode ++; delay (50); lcd.blink_off(); break; } } } if (menuSetupDataMode == 3) { // Третие нажатие rtc.adjust(DateTime(2016, 12, 20, setupHour, setupMinute, 0)); DateTime now = rtc.now(); lcd.clear(); lcd.setCursor(1, 2); lcd.print("BPEM\x0B YCTAHOB\x0E""EHO"); delay(1500); } result = 1; goto menu_m; // переходим в основное меню } if (KeyPressed == 'X' && result == 1) { // Выбор установки температуры lcd.clear(); lcd.setCursor(0, 0); lcd.print ("HACTPO\x09KA TEM\x0C""EPATYP"); int setupTemp_val = temp_val; int setupTemp_min = temp_min; int setupTemp_rezis = rezis; int menuSetupDataMode = 1; // Подменю 1 - температура носителя 2 - температура отключения насоса 0 - Выход lcd.setCursor(0, 1); lcd.print ("TEM\x0C""EPATYPA HOP. "); lcd.setCursor(16, 1); lcd.print (setupTemp_val); lcd.setCursor(0, 2); lcd.print ("TEM\x0C""EPATYPA M\x09H. "); lcd.setCursor(16, 2); lcd.print (setupTemp_min); lcd.setCursor(0, 3); lcd.print ("\x0D""ETEPE3\x09""C "); if (setupTemp_rezis < 10) { lcd.setCursor(16, 3); lcd.print (" "); lcd.print (setupTemp_rezis); } else { lcd.setCursor(16, 3); lcd.print (setupTemp_rezis); } lcd.setCursor(18, 1); lcd.blink_on(); if (menuSetupDataMode == 1) { while (menuSetupDataMode != 0) { static int pos = 0; encoder.tick(); int newPos = encoder.getPosition(); if (pos < newPos) setupTemp_val++; // | if (pos > newPos) setupTemp_val--; // | крутим меню по кругу чясы if (setupTemp_val > 90) setupTemp_val = 40;// | if (setupTemp_val < 40) setupTemp_val = 90;// | if (pos != newPos) { pos = newPos; if (millis() - lcdUpdate >= 100) // обновление дисплея { lcd.setCursor(16, 1); // lcd.print (setupTemp_val); lcdUpdate = millis(); // Обновляем время для дисплэя } } GetKey(); if (KeyPressed == 'X') { menuSetupDataMode ++; delay (50); break; } } } if (menuSetupDataMode == 2) { lcd.setCursor(18, 2); lcd.blink_on(); while (menuSetupDataMode != 0) { static int pos = 0; encoder.tick(); int newPos = encoder.getPosition(); if (pos < newPos) setupTemp_min++; // | if (pos > newPos) setupTemp_min--; // | крутим меню по кругу минуты if (setupTemp_min > 59) setupTemp_min = 25;// | if (setupTemp_min < 25) setupTemp_min = 59;// | if (pos != newPos) { pos = newPos; if (millis() - lcdUpdate >= 100) // обновление дисплея { lcd.setCursor(16, 2); // lcd.print (setupTemp_min); lcdUpdate = millis(); // Обновляем время для дисплэя } } GetKey(); if (KeyPressed == 'X') { menuSetupDataMode ++; delay (50); break; } } } if (menuSetupDataMode == 3) { lcd.setCursor(18, 3); lcd.blink_on(); while (menuSetupDataMode != 0) { static int pos = 0; encoder.tick(); int newPos = encoder.getPosition(); if (pos < newPos) setupTemp_rezis++; // | if (pos > newPos) setupTemp_rezis--; // | крутим меню по кругу чясы if (setupTemp_rezis > 15) setupTemp_rezis = 0;// | if (setupTemp_rezis < 0) setupTemp_rezis = 15;// | if (pos != newPos) { pos = newPos; if (millis() - lcdUpdate >= 100) // обновление дисплея { if (setupTemp_rezis < 10) { lcd.setCursor(16, 3); lcd.print (" "); lcd.print (setupTemp_rezis); } else { lcd.setCursor(16, 3); lcd.print (setupTemp_rezis); } lcdUpdate = millis(); // Обновляем время для дисплэя } } GetKey(); if (KeyPressed == 'X') { menuSetupDataMode ++; delay (50); break; } } } if (menuSetupDataMode == 4) { // Четвертое нажатие lcd.clear(); lcd.blink_off(); lcd.setCursor(0, 2); lcd.print("TEM\x0C""EPATYPA 3A\x0C\x09""CAHA"); temp_val = setupTemp_val; temp_min = setupTemp_min; rezis = setupTemp_rezis; delay(1500); } result = 0; goto menu_m; // переходим в основное меню } if (KeyPressed == 'X' && result == 2) { // Сохранение настроек в ЕЕПРОМ lcd.clear(); lcd.setCursor(0, 0); lcd.print (" COXPAHEH\x09""E HACTPOEK "); rtc.writenvram(0, temp_min); rtc.writenvram(1, temp_val); rtc.writenvram(2, zaslonka_val); rtc.writenvram(4, rezis); delay(250); lcd.setCursor(7, 2); lcd.print ("\x0DOTOBO!"); delay(1500); result == 0; goto menu_m; // переходим в основное меню } if (KeyPressed == 'X' && result == 3) { // выход из меню lcd.clear(); return; } } } void GetKey() { KeyPressed = 'N'; // Ничего не нажато!!! if (digitalRead(knopka) == LOW) { KeyPressed = 'X'; // Нажата коротко delay (100); if (digitalRead(knopka) == LOW) { delay (100); if (digitalRead (knopka) == LOW) { delay (100); if (digitalRead (knopka) == LOW) { delay (100); if (digitalRead (knopka) == LOW) { delay (100); if (digitalRead (knopka) == LOW) { delay (100); KeyPressed = 'M'; // Длительное нажатие кнопки } } } } } return; } } void temeratura_in() // Чтение датчиков температуры { sensors.requestTemperatures(); temp1 = sensors.getTempCByIndex(2); temp2 = sensors.getTempCByIndex(1) + 2.50; // Коррекция температуры на 2.5 гр. temp3 = sensors.getTempCByIndex(0); // Serial.println (temp2); } void time_out() // Вывод Времени { lcd.setCursor(15, 0); DateTime now = rtc.now(); if (now.hour() < 10 ) { lcd.print('0'); } lcd.print(now.hour(), DEC); lcd.print(':'); if (now.minute() < 10 ) { lcd.print('0'); } lcd.print(now.minute(), DEC); } void dislay() //Обновление основного дисплэя { time_out(); lcd.setCursor(0, 0); lcd.print("DOM: "); lcd.print(temp1, 1); // Выводм тепературу xx.x Комнатная lcd.print ("\x08 "); lcd.setCursor(0, 1); lcd.print("B\x0A""X: "); lcd.print(temp2, 1); // Выводм тепературу xx.x Выход из котла lcd.print ("\x08 "); lcd.setCursor(12, 1); lcd.print ("YCT: "); lcd.print(temp_val, 1); // Выводм тепературу xx.x Установленная рабочяя lcd.print ("\x08"); lcd.setCursor(0, 2); lcd.print("BXO: "); lcd.print(temp3, 1); // Выводм тепературу xx.x Вход в котел lcd.print ("\x08"); lcd.setCursor(12, 2); lcd.print ("M\x09H: "); lcd.print(temp_min, 1); // Выводм тепературу xx.x Минимальная для отключения насоса lcd.print ("\x08"); lcd.setCursor(0, 3); lcd.print(rezhim_t[rezhim]); // Вывод режима работы if (zaslonka_val == zaslonka_max) { // Температура меньше минимальной заслонка открыта lcd.setCursor(16, 3); lcd.print(" |"); } else { if (zaslonka_val == zaslonka_min ) { // Температура выше максимальной заслонка закрыта lcd.setCursor(16, 3); lcd.print(" -"); } else { if (zaslonka_val > 100) { // Вывод в градусах угла сервопривода lcd.setCursor(16, 3); lcd.print(zaslonka_val); lcd.print (char(223)); } if (zaslonka_val < 100) { lcd.setCursor(16, 3); lcd.print(" "); lcd.print(zaslonka_val); lcd.print (char(223)); } if (zaslonka_val < 10) { lcd.setCursor(16, 3); lcd.print(" "); lcd.print(zaslonka_val); lcd.print (char(223)); } } } }
Жду критики оптимизации и возможно новых функций...
P.S. Пишу первый раз т.ч. не обесудьте...
ИМХО mq-7 не помешал бы, с аварийной сигнализацией и выводом текущих значений.
Какая серва применена? Насколько хорошо с заслонкой поддувала управляется?
Серва MG-996, заслонку тягает на ура. Про угарный газ подумываю, хочу для начяла температуру трубы мерить и закрывать заслонку при привышении температуры 400С а то трубу жалко))) заказал модуль К пары, закажу и mq-7 попробую.... щяс добиваюсь правельного отображения остывания... чтоб вовремя дров подкинуть. Проблемма в качестве и типе топлива...
Про температуру дымохода я как-то не подумал. У самого котёл с дополнительными каналами, труба вряд ли больше 100-120 прогревается, по крайней мере краска на переходе в дымоход ещё заводская.
Надо бы что-то подобное сколхозить. Да только пока серва и дисплей приедут - зима закончится :))
Ну тут зависит в каком режиме котел использовать... максимум КПД получяешь при максимальном горении в начале растопки и потом в режиме тления... так в растопке если не контролировать то тепература при открытом подувале уползает за 500... дополнительный текмометр щя стоит...труба нержавейка 0.8 х 160 мощь котла 24Кв.

При такой моще тонкая нержавейка должна светиться XD . Дымоход вообще больная тема, остановился на чёрной трубе d150 с толщиной стенки около 4 мм. Нержавейка дороговато, оцинковка сгорает за сезон-два в труху.
Интересно, можно ли состряпать датчик тяги? Особенно с учётом температур и зарастания дымохода всякой гадость.
Разработка есть на неделе опробую.... потом выложу... труба щя греется в принципе 110-240. На таких тепературах я думаю продержится долго... дли на дымохода 7 м. первые 3 чищу раз в 4 месяца... тяга очень хорошая... спичка тухнет даже не успев разгореться.... Какие нить есть предложения по модернизации скетча?
Пока детально не просматривал, тем более сам начинающий :) Закажу серву, как придёт - тогда начну эксперименты, может что полезное в голову и придёт. В любом случае переведу на дисплей 1602, имхо 2004 здесь избыточен. Ну и зелёное земноводное давит)
Пробывал на 1602 мало... показалось... есть еще 4004 но эт точьно много)))
Может кто подскажет более правильный вариан (плавный) регулировки положения заслонки в зависимости от температуры?
Тыц.
И ещё.
Хорошее дело затеяли. Сам всё хожу рядом со своим котлом, но всё ни как не решусь.
Вопросы:
1) доступно несколько библиотек к энкодерам. Файл RotaryEncoder.h из какой?
Я, думая об автоматизации своего ТТ котла, хотел сразу закладывать управление жидкостным теплоаккумулятором и загрузкой топлива. Есть ли у Вас такие планы?
Уже и не помю из какой библиотеки какя попалась сразу заработала.
Про аккумулятор не думаю, резерв будет эл. котел на ночной режим... а для закгрузки топлева в авто режиме не хватает помещения.... поэтому ограничусь напоминание о необходимости доложить дров и режимом управления эл котлом...
Пока в се в режиме отладки и настройки... код постоянно редактирую и улучшаю(с моей точки зрения) добавится измерение температуры на улице и температуры трубы на проверку горения и перегрева (побережем дымоход).