Опять про автоматизацию твердотоплевного котла
- Войдите на сайт для отправки комментариев
Пт, 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 из какой?
Я, думая об автоматизации своего ТТ котла, хотел сразу закладывать управление жидкостным теплоаккумулятором и загрузкой топлива. Есть ли у Вас такие планы?
Уже и не помю из какой библиотеки какя попалась сразу заработала.
Про аккумулятор не думаю, резерв будет эл. котел на ночной режим... а для закгрузки топлева в авто режиме не хватает помещения.... поэтому ограничусь напоминание о необходимости доложить дров и режимом управления эл котлом...
Пока в се в режиме отладки и настройки... код постоянно редактирую и улучшаю(с моей точки зрения) добавится измерение температуры на улице и температуры трубы на проверку горения и перегрева (побережем дымоход).