У холодні зимові вечори, коли температура на вулиці сягала -40 градусів. Я зрозумів, що мені доводиться здійснювати дуже багато однотипних дій, які, на перший погляд, дуже просто автоматизувати.
Подробиці про те, як не програміст вугільний котел оживляв нижче.
Показ завдання
Для того, щоб зрозуміти завдання, я в коротці опишу суть роботи сучасних вугільних котлів, таких як: Прометею, Bosh або Buderus:
У нижній частині завантажувальної камери знаходяться рухомі колосники, які по мірі горіння вугілля необхідно ворушити, для того щоб зола випала у відстійник, а на її місце потрапило нове вугілля. Для цих цілей збоку від печі є важіль, до якого необхідно докласти певних зусиль.
По початку здалося все просто: беремо, наприклад, привід від центр.замка автомобільних дверей або будь-яке схоже пристосування і по таймеру подаємо на нього харчування.
Концепція пристрою
Керуючим блоком було вирішено вибрати - Arduino, за всенародну любов і велику кількість матеріалів по ньому. В інтернеті замовили Arduino Uno, а також блок реле на 12В і блок живлення на 2,5А.
Перший же експеримент показав, що приводу центрального замку не вистачає зусиль і важеля. Тому довелося брати привід склоочисника від жигулі. Як з'ясувалося в зборі з тягами він майже ідеально підходить під поставлене завдання.
З металевого куточка під нього було підготовлено кріплення до підлоги і зроблена знімна насадка на важіль.
Настав час переходити до логіки:
1. Якщо докласти мало зусиль, то зола недостатньо звільнить місце для нової партії вугілля і може призупинити горіння.
2. Якщо докласти зусиль більше ніж необхідно, то можна викинути у відстійник все вугілля.
Крім цього, є ще такі фактори:
- залежно від температури на вулиці, змінюється інтенсивність горіння і необхідно змінювати частоту спрацювання;
- при великій фракції вугілля або при спрацюванні при не догорілому паливі, можливе заїдання важеля.
З урахуванням цих факторів, до Arduino було вирішено додати екран з кнопками для можливості програмування часу, частоти і тривалості імпульсу.
з іншого боку:
А також додати датчик струму для визначення заїдання важеля. При перевищенні якого важіль змінює напрямок руху кілька разів, поки застряг шматок вугілля не провалитися (починає трястися).
Збираємо все на місце:
підганяємо всі елементи
перевіряємо екран і керування
збираємо все до купи
Код
/*
Prometey shaker
17.11.2014
*/
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include <EEPROM.h>
# define SHAKERSTEPUPPIN 4//Пін для кнопки Крок++
# define SHAKERSTEPDOWNPIN 5//Пін для кнопки Кроку--
# define MANUALSHAKE 6//Ручний запуск/меню OK
# define POWERRELE 8//Харчування на Релі
int eppromaddr = 0;
int SHAKERSTEP = 300 ;//Крок у секундах з яким буде збільшуватися/зменшуватися період струшування.
int sensorPin0 = A1; // select the input pin for the potentiometer
float stepValue = 0.0986328125; // step value per one of 0..1023 ((50A * 2 + 1)/1024)
int ZeroLevel = 514; // Zero level. Нулевоз значення АЦП для датчика струму (вираховується в setup)
float CurrentLevel = 3 ;//Порогове значення для струму (встановлено програмно)
byte CURRSHAKE, LEFTSHAKE = 2 ;//Пін для активації струшувача обертання за годинниковою стрілкою + поточний пін для струшування
byte RIGHTSHAKE = 3 ;//Пін для активації струшувача обертання проти годинникової стрілки
byte DOVOD = 12 ;//Пін для активації доводчика (Прометей)
int buttonState = 0 ;//Стан натиснутої кнопки
boolean buttonPressed = false;
boolean showMenu = false;
byte menuItem = 0;
volatile long mks100;
volatile long ms10;
volatile int cntr;
long tmillis,tms10=0;
unsigned long shaketimer, shaketime = 0;
byte stopshake ;//Час роботи шейкера
byte stpdvd ;//Час роботи доводчика (Прометей)
boolean flip = false ;//Для відстеження активності шейкера - чи варто викликати функцію перевірки струму і включати реверс.
boolean flipD = false ;//Для відстеження активності доводчика - чи варто викликати функцію включення доводчика (Прометей)
byte shakerstepcount = 0;
long temp = 0 ;//тимчасова змінна для відстеження часу
boolean tempcurr = 0 ;//тимчасова змінна для виводу повідомлення про вимірювання струму
boolean isinit = true;
LiquidCrystal_I2C lcd(0x27,16,2);
void setup() {
Serial.begin(9600);
//ініціалізація портів для кнопок
pinMode(SHAKERSTEPUPPIN, INPUT);
digitalWrite(SHAKERSTEPUPPIN, HIGH);
pinMode(SHAKERSTEPDOWNPIN, INPUT);
digitalWrite(SHAKERSTEPDOWNPIN, HIGH);
pinMode(MANUALSHAKE, INPUT);
digitalWrite(MANUALSHAKE, HIGH);
//Ініціалізація lcd
lcd.backlight ()//Вмикаємо підсвічування
lcd.print(«Version 3.0»);
lcd.setCursor(0, 1);
lcd.print(«13.11.2014»);
delay(1000);
lcd.clear();
pinMode(sensorPin0, INPUT); // Curent sensor
digitalWrite (sensorPin0, HIGH) ;//Вмикаємо внутрішній підтягуючий резистор
shaketimer = SHAKERSTEP;
mks100 = 0 ;//лічильник сотень мікросекунд, переповнення лічильника приблизно через 5 діб
ms10 = 0 ;//лічильник десятків мілісекунд, переповнення лічильника приблизно через 16 місяців
cntr = 0;
flip = 0;
flipD = 0 ;//просто додав це тут (Прометей)
pinMode (LEFTSHAKE, OUTPUT) ;//Готуємо порти для активації реле. Реле інвертне, спрацьовує при подачі GND на контрольні піни.
pinMode (RIGHTSHAKE, OUTPUT) ;//Готуємо порти для активації реле. Реле інвертне, спрацьовує при подачі GND на контрольні піни.
CURRSHAKE = LEFTSHAKE;
digitalWrite (LEFTSHAKE, HIGH) ;//Готуємо порти для активації реле. Реле інвертне, спрацьовує при подачі GND на контрольні піни.
digitalWrite (RIGHTSHAKE, HIGH) ;//Готуємо порти для активації реле. Реле інвертне, спрацьовує при подачі GND на контрольні піни.
pinMode (SHAKERSTEPUPPIN, INPUT) ;//Готуємо порти для кнопки
digitalWrite(SHAKERSTEPUPPIN, HIGH);
pinMode (SHAKERSTEPDOWNPIN, INPUT) ;//Готуємо порти для кнопки
digitalWrite(SHAKERSTEPDOWNPIN, HIGH);
pinMode (POWERRELE, OUTPUT) ;//Подаємо харчування на релі (Прометей)
digitalWrite(POWERRELE, HIGH);
pinMode (DOVOD, OUTPUT) ;//Готуємо порт на доводчик (Прометей)
digitalWrite(DOVOD, HIGH);
//Вмикаємо потрібний нам режим таймера/лічильника - нормальний
TCCR2A = 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;
TIMSK2 |= (1 < < TOIE2) ;//дозволяємо переривання таймера/лічильника 2 за переповненням
}
ISR(TIMER2_OVF_vect) {
//перш за все взводимо лічильник
TCNT2=59;//55;
//пройшли чергові 100 мксек - збільшуємо лічильник сотень мікросекунд
mks100++;
// if(mks100%100==0) ms10++;
cntr++;
//пройшли чергові 10 мсек? - збільшуємо лічильник десятків мілісекунд
if(cntr>99) {
ms10++;
cntr = 0;
}
}
float getCurrent() {
int sensorRead = 0;
for (int i=0; i <= 4; i++){
sensorRead+=analogRead(sensorPin0);
}
sensorRead = sensorRead / 5;
// lcd.setCursor(8,1);
// lcd.print(sensorRead);
// lcd.print("" "");
return (abs(sensorRead — ZeroLevel)) * stepValue;
// return abs(analogRead(sensorPin0) — ZeroLevel) * stepValue;
}
void current_check() {
float CurrentValue = getCurrent();
if (CurrentValue >= CurrentLevel) {
stop_shake();
if (CURRSHAKE == LEFTSHAKE) {
CURRSHAKE = RIGHTSHAKE;
} else {
CURRSHAKE = LEFTSHAKE;
}
start_shake();
}
//Перевіряємо значення датчика струму. Якщо значення більше порогового (розрахункове 5А),
//або деактивуємо шейкер, змінюємо CURRSHAKE на протилежне (LEFTSHAKE, RIGHTSHAKE)
//і активуємо по-новій.
}
void start_shake() {
digitalWrite (CURRSHAKE, LOW) ;//Подаємо сигнал на активацію шейкера
shaketime = 0;
flip = true;
delay(500);
}
void stop_shake() {
digitalWrite (CURRSHAKE, HIGH) ;//Подаємо сигнал на деактивацію шейкера
flip = false;
// stopshake = 0;
}
void startdovod() {
digitalWrite (DOVOD, LOW) ;//Подаємо сигнал на активацію доводчика (Прометей)
flipD = true;
}
void stopdovod() {
digitalWrite (DOVOD, HIGH) ;//Подаємо сигнал на деактивацію доводчика (Прометей)
flipD = false;
}
void lcdTimer() {
lcd.setCursor(0, 0);
lcd.print(«shake:»);
lcd.setCursor(6, 0);
lcd.print(SHAKERSTEP/60 — shaketime/60);
lcd.print(""/"");
lcd.print(SHAKERSTEP/60);
lcd.print("" "");
}
void lcdCurrent() {
lcd.setCursor(0, 1);
lcd.print(«C:»);
lcd.setCursor(2, 1);
lcd.print(getCurrent(), 1);
lcd.print(""/"");
lcd.print(CurrentLevel, 1);
}
int buttonRead() {
int readState = 0;
int returnCode = 0;
readState = !digitalRead(SHAKERSTEPUPPIN);
returnCode = readState;
readState = !digitalRead(SHAKERSTEPDOWNPIN) * 2;
returnCode+=readState;
readState = !digitalRead(MANUALSHAKE) * 4;
returnCode+=readState;
if (returnCode > 0) { delay(200); }
//lcd.setCursor(0,0);lcd.print(returnCode);
return returnCode;
}
void lcdMenu() {
lcd.setCursor(0, 0);
lcd.print(«1.Timer set»);
lcd.setCursor(0, 1);
lcd.print(«2.Current set»);
lcd.setCursor(0, 0);
}
void lcdCurrentMenu() {
lcd.clear();
lcd.setCursor(0, 0);
lcd.print(«Current lvl:»);
lcd.print(CurrentLevel,1);
}
void lcdTimerMenu() {
lcd.clear();
lcd.setCursor(0, 0);
lcd.print(«Timer set:»);
lcd.print(SHAKERSTEP/60);
}
void buttonProcced(byte buttonState) {
//Ручний shake
if ((buttonState == 4)&&(!showMenu)) {
start_shake();
}
//Ручний dovod
if ((buttonState == 3)&&(!showMenu)) {
startdovod();
}
//виклик меню
if (buttonState == 3) {
menuItem = 1;
lcd.clear();
showMenu = !showMenu;
if (showMenu) {
lcd.blink();
lcdMenu();
}
if (!showMenu) {
byte lowByte = ((SHAKERSTEP >> 0) & 0xFF);
byte highByte = ((SHAKERSTEP >> 8) & 0xFF);
EEPROM.write(eppromaddr, lowByte);
EEPROM.write(eppromaddr+1, highByte);
EEPROM.write(eppromaddr+2, CurrentLevel*10);
lcd.noBlink();
lcdTimer();
lcdCurrent();
}
}
//кнопка вниз в основному меню
if ((showMenu)&&(buttonState == 2)&&(menuItem < 3)) {
menuItem = 2;
lcd.setCursor(0, 1);
}
//кнопка вгору в основному меню
if ((showMenu)&&(buttonState == 1)&&(menuItem < 3)) {
menuItem = 1;
lcd.setCursor(0, 0);
}
//кнопка вниз у меню струму
if ((menuItem == 2)&&(buttonState == 4)) {
menuItem = 4;
buttonState = 0;
lcdCurrentMenu();
}
//кнопка вгору у меню струму
if ((menuItem == 4)&&(buttonState == 2)) {
CurrentLevel-=0.5;
if (CurrentLevel<1) {CurrentLevel=8;}
lcdCurrentMenu();
}
//кнопка вниз у меню струму
if ((menuItem == 4)&&(buttonState == 1)) {
CurrentLevel+=0.5;
if (CurrentLevel>=8) {CurrentLevel=1;}
lcdCurrentMenu();
}
//кнопка Ok у меню струму
if ((menuItem == 4)&&(buttonState == 4)) {
buttonState = 0;
menuItem = 1;
lcd.clear();
lcdMenu();
}
//кнопка Ok - вибір меню таймера
if ((menuItem == 1)&&(buttonState == 4)) {
menuItem = 3;
buttonState = 0;
lcdTimerMenu();
}
//кнопка вниз у меню таймера
if ((menuItem == 3)&&(buttonState == 2)) {
SHAKERSTEP-=300;
if (SHAKERSTEP <= 0) {SHAKERSTEP = 3600;}
// if (SHAKERSTEP > 3600) {SHAKERSTEP = 300;}
lcdTimerMenu();
}
//кнопка вгору у меню таймера
if ((menuItem == 3)&&(buttonState == 1)) {
SHAKERSTEP+=300;
// if (SHAKERSTEP > 0) {SHAKERSTEP = 3600;}
if (SHAKERSTEP > 3600) {SHAKERSTEP = 300;}
lcdTimerMenu();
}
//книпка Ok у меню таймера
if ((menuItem == 3)&&(buttonState == 4)) {
menuItem = 1;
lcd.clear();
lcdMenu();
}
}
void inits() {
ZeroLevel = 0;
for (int i=0; i <= 9; i++){
ZeroLevel+=analogRead(sensorPin0);
}
ZeroLevel = ZeroLevel/10;
lcd.print(«zerolevel=»);lcd.print(ZeroLevel);
// stepValue =;
lcd.setCursor(0,1);
lcd.print(«stepvalue=»);lcd.print(stepValue);
delay(1000);
lcd.clear();
byte lowByte = EEPROM.read(eppromaddr);
byte highByte = EEPROM.read(eppromaddr + 1);
SHAKERSTEP = ((lowByte << 0) & 0xFF) + ((highByte << 8) & 0xFF00);
CurrentLevel = EEPROM.read(eppromaddr+2)/10;
lcdTimer();
lcdCurrent();
isinit = false;
}
void loop() {
if (isinit) { inits(); }
buttonState = buttonRead();
if (buttonState > 0) { buttonProcced(buttonState); }
if (ms10>tms10) {
tms10 = ms10;
if (tms10% 1000 = = 0) {//виконання кожні 10 сек
shaketime += 10;
if (!showMenu) {
lcdTimer();
lcdCurrent();
}
}
if ((flip) & (tms10% 100 = = 0)) {//виконання кожну 1 сек
stopshake += 1;
}
if ((flipD) & (tms10% 100 = = 0)) {//виконання кожну 1 сек
stpdvd += 1;
}
if (stopshake > = 3) {//скільки секунд працювати
stop_shake();
stopshake = 0;
//активуємо доводчик (Прометей)
startdovod();
}
if ((flipD)&&(stpdvd>=4)) {
stopdovod();
}
if (shaketime>=SHAKERSTEP) {
start_shake();
}
}
if (flip) {
current_check();
}
}
Фізично все готово. Після кількох танців з бубном при з'єднанні контактів, система запрацювала. У певний проміжок за часом спрацьовував таймер, важіль ворушився і при заїданні починав рух у зворотному напрямку. Після чого на екрані висвічувався зворотний відлік до наступного запуску.
Зневаджування
Але налагодити логіку виявилося трохи складніше. Система робилася під два котли: «Прометей» і «Buderus». З вигляду вони схожі, але система колосників абсолютно різна на практиці. «Buderus», допускає часте і тривале ворушення колосниками, на відміну від «Прометея», який легко викидає вміст топки. Також в «Прометеї» необхідно обов'язково повернути у зворотне положення важіль колосників.
Тому в коді для «Прометея» було вирішено задіяти ще одну можливість автомобільного склоочисника - третій контакт, який повертає двірники на місце.
Система працює другий рік. Коли на вулиці -40 реально виручає, а то доводилося кілька разів за ніч вставати і смикати цей важіль. Код програми далеко не ідеальний, але на жаль я не програміст, як і та людина яка це придумала і почала втілювати перший варіант. Якщо буде конструктивна критика - то можете допомогти його оптимізувати для тих, кому він може стати в нагоді.
