История О Том, Как Я Автоматизировал Угольный Котел

Холодными зимними вечерами, когда температура на улице достигала -40 градусов.

Я понял, что мне приходится выполнять множество однотипных действий, которые, на первый взгляд, очень легко автоматизировать.

Подробности о том, как непрограммист воплотил в жизнь угольный котел, — ниже.



История о том, как я автоматизировал угольный котел



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

Для этих целей сбоку на плите имеется рычаг, к которому нужно приложить некоторое усилие.

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



Концепция устройства
В качестве блока управления было решено выбрать Arduino, из-за всенародной любви и большого количества материалов по ней.

В интернете была заказана Arduino Uno, а также блок реле на 12В и блок питания на 2,5А.



История о том, как я автоматизировал угольный котел

Первый эксперимент показал, что приводу центрального замка не хватает усилия и рычага.

Поэтому пришлось взять привод дворников от Жигулей.

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

Для крепления к полу использовался металлический уголок, а для рычага было сделано съемное крепление.



История о том, как я автоматизировал угольный котел

Пришло время перейти к логике: 1. Если приложить мало усилий, зола не освободит места для новой порции угля и может остановить горение.

2. Если приложить больше усилий, чем необходимо, можно выбросить весь уголь в отстойник.

Помимо этого, есть и другие факторы: — в зависимости от температуры на улице меняется интенсивность горения и необходимо менять частоту работы; — при большой доле угля или при срабатывании на несгоревшем топливе рычаг может заклинить.

Принимая во внимание эти факторы, было решено добавить в Arduino экран с кнопками, позволяющими программировать время, частоту и длительность импульса.



История о том, как я автоматизировал угольный котел

на другой стороне:

История о том, как я автоматизировал угольный котел

А также добавьте датчик тока, чтобы определить, застрял ли рычаг.

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



История о том, как я автоматизировал угольный котел

Ставим все на место:

История о том, как я автоматизировал угольный котел

настроить все элементы

История о том, как я автоматизировал угольный котел

проверка экрана и элементов управления

История о том, как я автоматизировал угольный котел

собрать все это вместе Код /* Прометей шейкер 17.11.2014 */ #включать #включать #включать #define SHAKERSTEPUPPIN 4 // Пин для кнопки Step++ #define SHAKERSTEPDOWNPIN 5 // Пин для кнопки «Шаг» — #define MANUALSHAKE 6 // Ручной запуск/Меню ОК #define POWERRELE 8 // Питание реле интервал эппромаддр = 0; int ШЕЙКЕРШТЕП = 300; // Шаг в секундах, с которым период встряхивания будет увеличиваться/уменьшаться.

int датчикPin0 = A1; // выбираем входной контакт для потенциометра значение шага с плавающей запятой = 0,0986328125; // значение шага на один из 0.1023 ((50A * 2 + 1)/1024) интервал нулевого уровня = 514; // Нулевой уровень.

Нулевое значение АЦП для датчика тока (рассчитывается в настройке) поплавок CurrentLevel = 3; // Пороговое значение тока (задается программно) байт CURRSHAKE, LEFTSHAKE = 2; // Пин для активации вращения шейкера по часовой стрелке + текущий встряхивающий пин байт RIGHTSHAKE = 3; // Пин для активации шейкера, вращение против часовой стрелки байт ДОВОД = 12; // Пин для активации доводчика (Прометей) int buttonState = 0; // Состояние нажатой кнопки логическая кнопкаPressed = false; логическое showMenu = false; байтовый элемент меню = 0; энергозависимый длинный mks100; энергозависимый длинный мс10; изменчивый интервал между центрами; длинный tmillis,tms10=0; беззнаковый длинный таймер шейки, время шейки = 0; байтовое стоп-трясение; // Время работы шейкера байтовый stpdvd; // Время работы доводчика (Прометей) логический переворот = ложь; // Для мониторинга активности шейкера - стоит ли вызывать текущую функцию проверки и включать реверс.

логическое значение FlipD = ложь; // Для отслеживания активности доводчика - стоит ли вызывать функцию включения доводчика (Прометей) байтовый шейкерстепкаунт = 0; длительная температура = 0; // временная переменная для отслеживания времени логическое значение tempcurr = 0; // временная переменная для отображения текущего сообщения об измерении логическое значение isinit = правда; LiquidCrystal_I2C ЖК (0x27,16,2); недействительная настройка() { Серийный.

начало(9600); // инициализация портов для кнопок pinMode(SHAKERSTEPUPPIN, INPUT); digitalWrite(SHAKERSTEPUPPIN, ВЫСОКИЙ); pinMode(SHAKERSTEPDOWNPIN, INPUT); digitalWrite(SHAKERSTEPDOWNPIN, ВЫСОКИЙ); pinMode(MANUALSHAKE, INPUT); digitalWrite(MANUALSHAKE, ВЫСОКИЙ); ЖК.

инит(); // Инициализируем ЖК-дисплей ЖК.

подсветка(); // Включаем подсветку lcd.print("Версия 3.0"); lcd.setCursor(0, 1); lcd.print("13.11.2014"); задержка(1000); ЖК.

Очистить(); pinMode (датчикPin0, INPUT); // Датчик тока digitalWrite(sensorPin0, ВЫСОКИЙ); // Включаем внутренний подтягивающий резистор шейктаймер = ШЕЙКЕРШАГ; мкс100 = 0; // счетчик сотен микросекунд, счетчик переполняется примерно через 5 дней мс10 = 0; // счетчик десятков миллисекунд, счетчик переполняется примерно через 16 месяцев центр = 0; флип = 0; флипД = 0; // только что добавил это сюда (Прометей) pinMode(LEFTSHAKE, ВЫХОД); // Подготовьте порты для активации реле.

Реле инвертировано и активируется, когда на управляющие контакты подается GND. pinMode(RIGHTSHAKE, ВЫХОД); // Подготовьте порты для активации реле.

Реле инвертировано и активируется, когда на управляющие контакты подается GND. КАРШЕЙК = ЛЕВЫЙ КЕЙК; digitalWrite(LEFTSHAKE, ВЫСОКИЙ); // Подготовьте порты для активации реле.

Реле инвертировано и активируется, когда на управляющие контакты подается GND. digitalWrite(RIGHTSHAKE, ВЫСОКИЙ); // Подготовьте порты для активации реле.

Реле инвертировано и активируется, когда на управляющие контакты подается GND. pinMode(SHAKERSTEPUPPIN, INPUT); // Подготавливаем порты для кнопки digitalWrite(SHAKERSTEPUPPIN, ВЫСОКИЙ); pinMode(SHAKERSTEPDOWNPIN, INPUT); // Подготавливаем порты для кнопки digitalWrite(SHAKERSTEPDOWNPIN, ВЫСОКИЙ); pinMode(POWERRELE, ВЫХОД); // Подача питания на реле (Прометей) digitalWrite(POWERRELE, ВЫСОКИЙ); pinMode(ДОВОД, ВЫХОД); // Подготавливаем порт для доводчика (Прометей) digitalWrite(ДОВОД, ВЫСОКИЙ); // Включаем нужный нам режим таймера/счетчика - нормальный ТСКР2А = 0; //нормальный режим (по умолчанию 1 - ШИМ с фазовой коррекцией?) // Установите прескалер таймера/счетчика на 16 - // это позволит таймеру «тикать» каждую микросекунду // (при условии, что сердце микроконтроллера бьется // частота 16 000 000 ударов в секунду) TCCR2B = 2; // 010 - fclk/8 (по умолчанию 100 - fclk/64) //TCCR2B = 7; // 111 - fclk/1024 (по умолчанию 100 - fclk/64) TCNT2=59;//55; ТИМСК2 |= (1 << TOIE2); // enable timer/counter 2 overflow interruption } ISR(TIMER2_OVF_vect) { // первым делом заряжаем счетчик TCNT2=59;//55; //прошло еще 100 микросекунд - увеличиваем счетчик на сотни микросекунд мкс100++; // if(mks1000==0) ms10++; центр++; // прошло еще 10 мс? — увеличить счетчик десятков миллисекунд если(центр> 99) { мс10++; центр = 0; } } поплавок getCurrent() { int SensorRead = 0; для (int i=0; я <= 4; i++){ SensorRead+=analogRead(sensorPin0); } SensorRead = SensorRead / 5; // lcd.setCursor(8,1); // lcd.print(sensorRead); // lcd.print(" "); return (abs(sensorRead - ZeroLevel)) * StepValue; // возвращаем abs(analogRead(sensorPin0) - ZeroLevel) * StepValue; } недействительный current_check() { float CurrentValue = getCurrent(); если (CurrentValue > = CurrentLevel) { стоп_встряска(); if (CURRSHAKE == LEFTSHAKE) { CURRSHAKE = RIGHTSHAKE; } еще { КАРШЕЙК = ЛЕВЫЙ КЕЙК; } start_shake(); } // Проверяем значение текущего датчика.

Если значение превышает пороговое значение (расчетное 5А), // затем деактивируем шейкер, меняем CURRSHAKE на противоположный (LEFTSHAKE, RIGHTSHAKE) // и активируем его снова.

} недействительный start_shake () { digitalWrite(CURRSHAKE, LOW); // Даем сигнал на активацию шейкера время встряхивания = 0; флип = правда; задержка(500); } недействительный stop_shake () { digitalWrite(CURRSHAKE, ВЫСОКИЙ); // Даем сигнал на деактивацию шейкера флип = ложь; // стоп-коктейль = 0; } недействительный стартдов () { digitalWrite(ДОВОД, НИЗКИЙ); // Даем сигнал на активацию доводчика (Прометей) флипД = правда; } недействительный стопдовод() { digitalWrite(ДОВОД, ВЫСОКИЙ); // Даем сигнал на деактивацию доводчика (Прометей) флипД = ложь; } недействительный lcdTimer() { lcd.setCursor(0, 0); lcd.print("встряхнуть:"); lcd.setCursor(6, 0); lcd.print(SHAKERSTEP/60 - время встряхивания/60); ЖК.

принт("/"); lcd.print(SHAKERSTEP/60); ЖК.

принт(" "); } недействительный ЖККуррент () { lcd.setCursor(0, 1); lcd.print("C:"); lcd.setCursor(2, 1); lcd.print(getCurrent(), 1); ЖК.

принт("/"); lcd.print(CurrentLevel, 1); } int buttonRead() { ИНТ readState = 0; INT ReturnCode = 0; readState = !digitalRead(SHAKERSTEPUPPIN); Код возврата = readState; readState = !digitalRead(SHAKERSTEPDOWNPIN) * 2; returnCode+=readState; readState = !digitalRead(MANUALSHAKE) * 4; returnCode+=readState; если (returnCode > 0) { задержка (200); } //lcd.setCursor(0,0);lcd.print(returnCode); вернуть код возврата; } недействительный ЖКМеню () { lcd.setCursor(0, 0); lcd.print("1.Установлен таймер"); lcd.setCursor(0, 1); lcd.print("2.Текущий набор"); lcd.setCursor(0, 0); } недействительный lcdCurrentMenu () { ЖК.

Очистить(); lcd.setCursor(0, 0); lcd.print("Текущий уровень:"); lcd.print(CurrentLevel,1); } недействительный lcdTimerMenu () { ЖК.

Очистить(); lcd.setCursor(0, 0); lcd.print("Установлен таймер:"); lcd.print(SHAKERSTEP/60); } void buttonProcced (байт buttonState) { // Ручное встряхивание if ((buttonState == 4)&&(!showMenu)) { start_shake(); } // Ручной довод if ((buttonState == 3)&&(!showMenu)) { стартдовод(); } //вызов меню если (buttonState == 3) { элемент меню = 1; ЖК.

Очистить(); шоуменю = !шоуменю; если (шоуМеню) { ЖК.

мигать(); ЖКМеню(); } если (!showMenu) { байт lowByte = ((SHAKERSTEP > > 0) & 0xFF); байт highByte = ((SHAKERSTEP > > 8) & 0xFF); EEPROM.write(eppromaddr, lowByte); EEPROM.write(eppromaddr+1, highByte); EEPROM.write(eppromaddr+2, CurrentLevel*10); ЖК.

noBlink(); ЖКТаймер(); ЖКТекущий(); } } // кнопка вниз в главном меню if ((showMenu)&&(buttonState == 2)&&(menuItem < 3)) { элемент меню = 2; lcd.setCursor(0, 1); } // кнопка вверх в главном меню if ((showMenu)&&(buttonState == 1)&&(menuItem < 3)) { элемент меню = 1; lcd.setCursor(0, 0); } // кнопка вниз в текущем меню if ((menuItem == 2)&&(buttonState == 4)) { элемент меню = 4; КнопкаСостояние = 0; жкдкуррентменю(); } // кнопка вверх в текущем меню if ((menuItem == 4)&&(buttonState == 2)) { ТекущийУровень-=0,5; если (CurrentLevel<1) {CurrentLevel=8;} жкдкуррентменю(); } // кнопка вниз в текущем меню if ((menuItem == 4)&&(buttonState == 1)) { ТекущийУровень+=0,5; если (CurrentLevel> =8) {CurrentLevel=1;} жкдкуррентменю(); } // Кнопка ОК в текущем меню if ((menuItem == 4)&&(buttonState == 4)) { кнопкаСостояние = 0; элемент меню = 1; ЖК.

Очистить(); ЖКМеню(); } //Кнопка ОК - выбор меню таймера if ((menuItem == 1)&&(buttonState == 4)) { пункт меню = 3; КнопкаСостояние = 0; ЖКТимерМеню(); } // кнопка вниз в меню таймера if ((menuItem == 3)&&(buttonState == 2)) { ШЕЙКЕРШАГ-=300; если (SHAKERSTEP <= 0) {SHAKERSTEP = 3600;} // если (SHAKERSTEP > 3600) {SHAKERSTEP = 300;} ЖКТимерМеню(); } // кнопка вверх в меню таймера if ((menuItem == 3)&&(buttonState == 1)) { ШЕЙКЕРШАГ+=300; // если (SHAKERSTEP > 0) {SHAKERSTEP = 3600;} если (SHAKERSTEP > 3600) {SHAKERSTEP = 300;} ЖКТимерМеню(); } // Кнопка ОК в меню таймера if ((menuItem == 3)&&(buttonState == 4)) { элемент меню = 1; ЖК.

Очистить(); ЖКМеню(); } } недействительные инициализации () { Нулевой уровень = 0; для (int i=0; я <= 9; i++){ ZeroLevel+=analogRead(sensorPin0); } Нулевой уровень = Нулевой уровень/10; lcd.print("zerolevel=");lcd.print(ZeroLevel); // значение шага =; lcd.setCursor(0,1); lcd.print("stepvalue=");lcd.print(stepValue); задержка(1000); ЖК.

Очистить(); байт lowByte = EEPROM.read(eppromaddr); байт highByte = EEPROM.read(eppromaddr + 1); ШАКЕРШАГ = ((lowByte << 0) & 0xFF) + ((highByte << 8) & 0xFF00); CurrentLevel = EEPROM.read(eppromaddr+2)/10; ЖКТаймер(); ЖКТекущий(); исинит = ложь; } недействительный цикл() { если (isinit) { inits(); } buttonState = buttonRead(); если (buttonState > 0) { buttonProcced(buttonState); } если (ms10> tms10) { тмс10 = мс10; if (tms1000==0) { // выполняем каждые 10 секунд время встряхивания += 10; если (!showMenu) { ЖКТаймер(); ЖКТекущий(); } } if ((flip)&&(tms100==0)) { // выполняем каждую 1 секунду стоп-тряска += 1; } if ((flipD)&&(tms100==0)) { // выполнение каждые 1 секунду стпдвд += 1; } if (stopshake> =3) { // сколько секунд работать стоп_встряска(); стоп-тряска = 0; // активируем доводчик (Прометей) стартдовод(); } if ((flipD)&&(stpdvd> =4)) { стопдовод(); } if (shaketime> =SHAKERSTEP) { start_shake(); } } если (перевернуть) { текущая_проверка(); } } Физически все готово.

После нескольких танцев с бубном при соединении контактов система заработала.

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

После чего на экране отобразился обратный отсчет до следующего запуска.



Отладка
А вот отладка логики оказалась немного сложнее.

Система изготавливалась для двух котлов: «Прометей» и «Будерус».

С виду они похожи, но на практике система решеток совершенно другая.

«Будерус» допускает частое и продолжительное движение колосниковой решетки, в отличие от «Прометея», который легко выбрасывает содержимое топки.

Также в «Прометее» необходимо вернуть рычаг решетки в обратное положение.

Поэтому в коде для Прометея было решено использовать еще одну особенность дворника автомобиля — третий контакт, который возвращает дворники на место.

Система работает уже два года.

Когда на улице -40 это очень помогает, а то приходилось вставать и дергать этот рычаг несколько раз за ночь.

Программный код далек от идеала, но я, к сожалению, не программист, как человек, который это придумал и начал реализовывать первый вариант. Если есть конструктивная критика, вы можете помочь оптимизировать ее для тех, кому она может оказаться полезной.

Теги: #Сделай сам #рутинная автоматизация #arduino #котел #деревенская жизнь

Вместе с данным постом часто просматривают:

Автор Статьи


Зарегистрирован: 2019-12-10 15:07:06
Баллов опыта: 0
Всего постов на сайте: 0
Всего комментарий на сайте: 0
Dima Manisha

Dima Manisha

Эксперт Wmlog. Профессиональный веб-мастер, SEO-специалист, дизайнер, маркетолог и интернет-предприниматель.