Холодными зимними вечерами, когда температура на улице достигала -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 #котел #деревенская жизнь
-
Как Сделать Вашу Ит-Инфраструктуру Скучной
19 Oct, 24 -
Как Успешно Научиться Программировать
19 Oct, 24 -
Кубик Рубика. Эволюция
19 Oct, 24