📜 ⬆️ ⬇️

The story of how I automated a coal boiler

On cold winter evenings, when the temperature outside has reached -40 degrees. I realized that I have to perform a lot of the same type of actions, which, at first glance, it is very easy to automate.

Details about how not a programmer coal boiler animated below.

image
Formulation of the problem

In order to understand the task, I will briefly describe the essence of the work of modern coal-fired boilers, such as: Prometheus, Bosh or Buderus:

In the lower part of the loading chamber there are moving grate bars, which, as the coal burns, must be moved in order for the ash to fall into the sump, and a new coal in its place. For these purposes, there is a lever to the side of the furnace, to which certain efforts must be made.
')
At first everything seemed to be simple: take, for example, a drive from a central lock of a car door or any similar device and use the timer to power it.

Device concept

The control unit, it was decided to choose - Arduino, for nationwide love and a large amount of materials on it. The Internet was ordered by Arduino Uno, as well as a 12V relay unit and a 2.5A power supply unit.



The first experiment showed that the central locking drive lacks effort and leverage. So I had to take a wiper drive from the Lada. As it turned out assembled with rods, it is almost perfect for the task.

From the metal corner under it was prepared mount to the floor and made a removable nozzle on the lever.



It's time to move on to the logic:
1. If you make a little effort, the ash does not make enough space for a new batch of coal and can stop burning.
2. If you make more effort than necessary, you can throw all the coal into the sump.

In addition, there are other factors:
- depending on the temperature outside, the intensity of burning varies and it is necessary to change the frequency of operation;
- with a large fraction of coal or when triggered when not burned fuel, jamming of the lever is possible.

Taking into account these factors, it was decided to add a screen with buttons to the Arduino to enable programming the time, frequency and duration of the pulse.



on the other hand:



And also add a current sensor to determine if the lever is stuck. If it is exceeded, the lever changes direction several times until the stuck piece of coal fails (it starts to shake).



Putting it all in place:


we fit all the elements


check the screen and control


collect everything in a bunch

Code
/ *
Prometey shaker
11/17/2014
* /
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include <EEPROM.h>
#define SHAKERSTEPUPPIN 4 // Pin for the Step ++ button
#define SHAKERSTEPDOWNPIN 5 // Pin for the Step Button--
#define MANUALSHAKE 6 // Manual Start / OK Menu
#define POWERRELE 8 // Power on Relay
int eppromaddr = 0;
int SHAKERSTEP = 300; // Step in seconds with which the shaking period will increase / decrease.
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. Zero value of the ADC for the current sensor (calculated in the setup)
float CurrentLevel = 3; // Threshold value for current (set by software)
byte CURRSHAKE, LEFTSHAKE = 2; // Pin to activate the shaker clockwise + current pin to shake
byte RIGHTSHAKE = 3; // Pin to activate the shaker counterclockwise rotation
byte DOVOD = 12; // Pin to activate the closer (Prometheus)
int buttonState = 0; // The state of the pressed button
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; // Shaker time
byte stpdvd; // Closer working time (Prometheus)
boolean flip = false; // To monitor the activity of the shaker - is it worth it to call the current check function and turn on the reverse.
boolean flipD = false; // To track the closer activity - is it worth it to call the closer function (Prometheus)
byte shakerstepcount = 0;
long temp = 0; // time variable to track time
boolean tempcurr = 0; // time variable to display current measurement message
boolean isinit = true;
LiquidCrystal_I2C lcd (0x27,16,2);
void setup () {
Serial.begin (9600);
// initialize ports for buttons
pinMode (SHAKERSTEPUPPIN, INPUT);
digitalWrite (SHAKERSTEPUPPIN, HIGH);
pinMode (SHAKERSTEPDOWNPIN, INPUT);
digitalWrite (SHAKERSTEPDOWNPIN, HIGH);
pinMode (MANUALSHAKE, INPUT);
digitalWrite (MANUALSHAKE, HIGH);
lcd.init (); // Initialize lcd
lcd.backlight (); // Turn On Backlight
lcd.print ("Version 3.0");
lcd.setCursor (0, 1);
lcd.print ("11/13/2014");
delay (1000);
lcd.clear ();
pinMode (sensorPin0, INPUT); // Curent sensor
digitalWrite (sensorPin0, HIGH); // Turn on the internal pull-up resistor
shaketimer = SHAKERSTEP;
mks100 = 0; // counter hundreds of microseconds, counter overflow in about 5 days
ms10 = 0; // counter of tens of milliseconds, counter overflow after about 16 months
cntr = 0;
flip = 0;
flipD = 0; // just added it here (Prometheus)
pinMode (LEFTSHAKE, OUTPUT); // Prepare the ports to activate the relay. Relay invert, triggered when GND is applied to the control pins.
pinMode (RIGHTSHAKE, OUTPUT); // Prepare the ports to activate the relay. Relay invert, triggered when GND is applied to the control pins.
CURRSHAKE = LEFTSHAKE;
digitalWrite (LEFTSHAKE, HIGH); // Prepare the ports to activate the relay. Relay invert, triggered when GND is applied to the control pins.
digitalWrite (RIGHTSHAKE, HIGH); // Prepare the ports to activate the relay. Relay invert, triggered when GND is applied to the control pins.
pinMode (SHAKERSTEPUPPIN, INPUT); // Prepare ports for the button
digitalWrite (SHAKERSTEPUPPIN, HIGH);
pinMode (SHAKERSTEPDOWNPIN, INPUT); // Prepare ports for the button
digitalWrite (SHAKERSTEPDOWNPIN, HIGH);
pinMode (POWERRELE, OUTPUT); // Power on the relay (Prometheus)
digitalWrite (POWERRELE, HIGH);
pinMode (DOVOD, OUTPUT); // Prepare the port on the closer (Prometheus)
digitalWrite (DOVOD, HIGH);
// Turn on the timer / counter mode we need - normal
TCCR2A = 0; // normal mode (default 1 - PWM with phase correction?)
// Set the timer / counter prescaler to 16 - // this will allow the timer to tick every microsecond
// (assuming that the heart of the microcontroller is beating with
// frequency of 16,000,000 beats per second)
TCCR2B = 2; // 010 - fclk / 8 (default is 100 - fclk / 64)
// TCCR2B = 7; // 111 - fclk / 1024 (the default is 100 - fclk / 64)
TCNT2 = 59; // 55;
TIMSK2 | = (1 << TOIE2); // enable timer / counter interrupt 2 overflow
}
ISR (TIMER2_OVF_vect) {
// first count the counter
TCNT2 = 59; // 55;
// the next 100 microseconds have passed - we increase the count of hundreds of microseconds
mks100 ++;
// if (mks100% 100 == 0) ms10 ++;
cntr ++;
// Are the next 10 ms? - we increase the counter of tens of milliseconds
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 ();
}
// Check the current sensor value. If the value is greater than the threshold (calculated 5A),
// then deactivate the shaker, change CURRSHAKE to the opposite (LEFTSHAKE, RIGHTSHAKE)
// and activate a new one.
}
void start_shake () {
digitalWrite (CURRSHAKE, LOW); // Send a signal to activate the shaker
shaketime = 0;
flip = true;
delay (500);
}
void stop_shake () {
digitalWrite (CURRSHAKE, HIGH); // Send a signal to deactivate the shaker
flip = false;
// stopshake = 0;
}
void startdovod () {
digitalWrite (DOVOD, LOW); // Send a signal to activate the door closer (Prometheus)
flipD = true;
}
void stopdovod () {
digitalWrite (DOVOD, HIGH); // Send a signal to close the deactivation (Prometheus)
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) {
// Manual shake
if ((buttonState == 4) && (! showMenu)) {
start_shake ();
}
// Manual dovod
if ((buttonState == 3) && (! showMenu)) {
startdovod ();
}
// call the menu
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 ();
}
}
// button down in the main menu
if ((showMenu) && (buttonState == 2) && (menuItem <3)) {
menuItem = 2;
lcd.setCursor (0, 1);
}
// button up in the main menu
if ((showMenu) && (buttonState == 1) && (menuItem <3)) {
menuItem = 1;
lcd.setCursor (0, 0);
}
// button down in the current menu
if ((menuItem == 2) && (buttonState == 4)) {
menuItem = 4;
buttonState = 0;
lcdCurrentMenu ();
}
// button up in the current menu
if ((menuItem == 4) && (buttonState == 2)) {
CurrentLevel- = 0.5;
if (CurrentLevel <1) {CurrentLevel = 8;}
lcdCurrentMenu ();
}
// button down in the current menu
if ((menuItem == 4) && (buttonState == 1)) {
CurrentLevel + = 0.5;
if (CurrentLevel> = 8) {CurrentLevel = 1;}
lcdCurrentMenu ();
}
// button Ok in the current menu
if ((menuItem == 4) && (buttonState == 4)) {
buttonState = 0;
menuItem = 1;
lcd.clear ();
lcdMenu ();
}
// button Ok - select timer menu
if ((menuItem == 1) && (buttonState == 4)) {
menuItem = 3;
buttonState = 0;
lcdTimerMenu ();
}
// button down in the timer menu
if ((menuItem == 3) && (buttonState == 2)) {
SHAKERSTEP- = 300;
if (SHAKERSTEP <= 0) {SHAKERSTEP = 3600;}
// if (SHAKERSTEP> 3600) {SHAKERSTEP = 300;}
lcdTimerMenu ();
}
// button up in the timer menu
if ((menuItem == 3) && (buttonState == 1)) {
SHAKERSTEP + = 300;
// if (SHAKERSTEP> 0) {SHAKERSTEP = 3600;}
if (SHAKERSTEP> 3600) {SHAKERSTEP = 300;}
lcdTimerMenu ();
}
// Book OK in the timer menu
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) {// run every 10 seconds
shaketime + = 10;
if (! showMenu) {
lcdTimer ();
lcdCurrent ();
}
}

if ((flip) && (tms10% 100 == 0)) {// execute every 1 sec
stopshake + = 1;
}

if ((flipD) && (tms10% 100 == 0)) {// execute every 1 sec
stpdvd + = 1;
}
if (stopshake> = 3) {// how many seconds to work
stop_shake ();
stopshake = 0;
// activate the closer (Prometheus)
startdovod ();
}
if ((flipD) && (stpdvd> = 4)) {
stopdovod ();
}
if (shaketime> = SHAKERSTEP) {
start_shake ();
}
}
if (flip) {
current_check ();
}
}

Physically everything is ready. After several dances with a tambourine when connecting contacts, the system has earned. At a certain time interval, the timer worked, the lever moved and, when jammed, began to move in the opposite direction. After that, a countdown to the next launch was displayed on the screen.

Debugging

But debugging logic was a little more difficult. The system was made under two boilers: "Prometheus" and "Buderus". They look alike, but the grate system is completely different in practice. “Buderus” allows frequent and prolonged movement of the grate, in contrast to the “Prometheus”, which easily throws out the contents of the firebox. Also in "Prometheus" it is necessary to return the grid-bar lever to the reverse position.

Therefore, in the code for "Prometheus" it was decided to use another possibility of an automobile wiper - the third contact, which returns the wipers to the place.

The system works for the second year. When the street -40 really helps out, but it had to stand up several times during the night and pull this lever. The program code is far from perfect, but unfortunately I am not a programmer, like the person who invented it and began to implement the first option. If there is a constructive criticism, then you can help optimize it for those to whom it can be useful.

Source: https://habr.com/ru/post/262555/


All Articles