So I wanted longer run times with my live steam models. That requires an automatic way to refill the boiler as water is used up. The challenge with this build was that I only have ONE sensor in the boiler! So rather than just filling UP to the sensor, a timer is needed to fill beyond the sensor level. Otherwise, the pump valve would cycle too often. Yes, 2 sensors would have made this easier, and at the same time a boring build challenge!
Hardware:
ADAFRUIT SSD1306 monochrome display
Teensy 3.2
basic rotary encoder with built in resistors
15K resistor for sensor
2N3904 transistor
x2 470ohm resistors
RED and BLUE 5v LED
BUILD: wiring pretty easy: all connections are in the sketch. See pic for sensor diagram. It looks like TWO sensors in the water, but one is connected to outside of copper boiler tank.
When water level is below the sensor pin, pump turns ON. When water level covers the two leads, the transistor completes the circuit to the Teensy sensor pin and pump turn OFF. Timer is using 'while'..so nothing else can happen while pump is running.
Obviously, this sketch design can be used for many things that need a 'do something for this long...' timer setup, rather than just the 'do something every 'x' seconds... EEPROM saves the entered timer setting so you don't have to reset it again for 99 years....
Enjoy
little video:
https://www.youtube.com/watch?v=TrY132VywmE
Code: Select all
/*XRAD'S Model Live Steam Boiler Filler Pump Controller
So I wanted longer run times with my live steam models. That
requires an automatic way to refill the boiler as water is used up.
5/22/2022
Hardware:
using an ADAFRUIT SSD1306 monochrome display (I modified the
base files so the splash screen does not show), Teensy 3.2, basic
rotary encoder with built in resistors, 15K resistor for sensor
circuit on base of 2N3904 transistor, and x2 470ohm resistors
(one for each LED). wiring pretty easy: 5v to the collector.
emitter to Teensy sensor pin. Base via 15K resistor to water tank.
Another 5v lead to water tank. When water level covers the two leads,
the transistor completes the circuit to the Teensy sensor pin(pump OFF).
Timer is using 'while'..so nothing else can happen while pump is running.
Obviously, this sketch design can be used for many things that need a
'do something for this long...' timer setup, rather than just the
'do something every 'x' seconds... EEPROM saves the entered timer
setting so you don't have to reset it again for 99 years....The challenge
with this build was that I only have ONE sensor in the boiler! So
rather than just filling UP to the sensor, a timer is needed to fill
beyond the sensor level.... Enjoy....
*/
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <EEPROM.h>
#include <Servo.h>
int address = 0; //where to store data in EEPROM
int value = 0;//initial EEPROM value
#define SCREEN_WIDTH 128 // display pixel width
#define SCREEN_HEIGHT 64 // display pixel height
//Adafruit SSD1306 display connected using hardware SPI, adjust for your needs
#define OLED_MOSI 11
#define OLED_CLK 14
#define OLED_DC 6
#define OLED_CS 7
#define OLED_RESET 8
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT,
OLED_MOSI, OLED_CLK, OLED_DC, OLED_RESET, OLED_CS);
// Define rotary encoder pins
#define CLK 2
#define DT 3
#define SW 4
// Rotary encoder variables
int counter; //this is the stored rotary data for pump timer
int currentStateCLK;
int lastStateCLK;
String currentDir = "";
unsigned long lastButtonPress = 0;
//define water sensor pins
#define SENSOR 19
#define REDLED 20
#define BLUELED 21
char* WaterLevelLine[] = {" READING", " NORMAL", " LOW", " FILLING"};
int WaterLevelMenu = 0;//for saving the chosen element, begin at '0'
char* WaterPumpLine[] = {" OFF", " ON"};
int WaterPumpMenu = 0;//for saving the chosen element, begin at '0'
//servo/pump variables
Servo rotServo;
int posRot = 20;//on boot, should be same as last position to avoid jump start
bool servoOpen = false;
bool servoClosed = true;
int starttime;
int endtime;
bool pumpState = true;
void servoTest() {
for (int x = 0; x < 1; x++) {
for (posRot = 20; posRot <= 180; posRot += 1) {
rotServo.write(posRot);
delay(5);
}
for (posRot = 180; posRot >= 20; posRot -= 1) {
rotServo.write(posRot);
delay(5);
}
}
}
void printWaterLevelMenu() {
display.setCursor(75, 10);
display.setTextSize(1);
display.setTextColor(SSD1306_WHITE);
display.print(WaterLevelLine[WaterLevelMenu]);
display.display();//need to call this to show the display buffer!
}
void printWaterPumpMenu() {
display.fillRect(75, 30, 50, 10, SSD1306_BLACK);
display.setCursor(75, 30);
display.setTextSize(1);
display.setTextColor(SSD1306_WHITE);
display.print(WaterPumpLine[WaterPumpMenu]);
display.display();
}
void EEPROMREAD() {
value = EEPROM.read(address);// read a byte from the current address of the EEPROM
//Serial.print(address);
//Serial.print("\t");
//Serial.print(value, DEC);
//Serial.println();
address = address + 1;//advance to next address
if (address == EEPROM.length()) {
address = 0;
}
counter = value;//get the value from EEPROM and update
Serial.println("Reading EEPROM value...");
Serial.print("value: ");
Serial.println(value);
delay(50);//not really needed...
}
void clearEEPROM() {
for (int i = 0 ; i < EEPROM.length() ; i++) {
if (EEPROM.read(i) != 0) { //skip already "empty" addresses
EEPROM.write(i, 0); //write 0 to address i
}
}
Serial.println("EEPROM erased");
address = 0; //reset address counter
}
void valveOpen() {
if (servoOpen == false) {
//open pump valve
for (posRot = 20; posRot <= 180; posRot += 1) {
rotServo.write(posRot);
delay(5);
}
servoOpen = true;
}
}
void valveClose() {
if (servoOpen == true) {
for (posRot = 180; posRot >= 20; posRot -= 1) {
rotServo.write(posRot);
delay(5);
}
servoOpen = false;
}
}
void setup() {
Serial.begin(9600);
pinMode(CLK, INPUT);
pinMode(DT, INPUT);
pinMode(SW, INPUT_PULLUP); //can do this on Teensy 3.2
lastStateCLK = digitalRead(CLK);// Read the initial state of CLK for rotary encoder
pinMode(SENSOR, INPUT_PULLDOWN);//can do this on Teensy 3.2
pinMode(REDLED, OUTPUT);
pinMode(BLUELED, OUTPUT);
rotServo.attach(22);
rotServo.write(posRot); //initial position
display.begin(SSD1306_SWITCHCAPVCC);
display.clearDisplay(); // Clear the buffer
display.drawRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, SSD1306_WHITE); //draw double thick rectangle
display.drawRect(1, 1, SCREEN_WIDTH - 2, SCREEN_HEIGHT - 2, SSD1306_WHITE);
display.setCursor(10, 10);
display.setTextSize(2);
display.setTextColor(SSD1306_WHITE);
display.print(" XRAD'S");
display.setCursor(10, 35);
display.setTextSize(1);
display.setTextColor(SSD1306_WHITE);
display.print("MODEL STEAM BOILER");
display.setCursor(10, 50);
display.setTextSize(1);
display.setTextColor(SSD1306_WHITE);
display.print(" PUMP CONTROLLER");
display.display();//need to call this to show the display buffer!
delay(4000);
//servoTest();//see if your servo is working
display.clearDisplay(); // Clear the display buffer
display.drawRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, SSD1306_WHITE); //draw double thick rectangle
display.drawRect(1, 1, SCREEN_WIDTH - 2, SCREEN_HEIGHT - 2, SSD1306_WHITE);
display.setCursor(5, 10);
display.setTextSize(1);
display.setTextColor(SSD1306_WHITE);
display.print("WATER LEVEL: ");
display.fillRect(75, 10, 50, 10, SSD1306_BLACK);//clear the text box
printWaterLevelMenu();//write in text box
display.setCursor(5, 30);
display.setTextSize(1);
display.print("WATER PUMP: ");
//display.fillRect(75, 30, 50, 10, SSD1306_BLACK);
printWaterPumpMenu();//write in text box
EEPROMREAD();//let's get the old pump delay setting from EEPROM
display.setCursor(5, 50);
display.setTextSize(1);
display.print("PUMP RUN TIME: ");
display.fillRect(90, 47, 30, 13, SSD1306_BLACK);
display.setCursor(100, 50);
display.println(counter);
display.display();
delay(2000);
}
void loop() {
//Rotary encoder to set pump 'run' time, and SW to save timer setting!
currentStateCLK = digitalRead(CLK);
if (currentStateCLK != lastStateCLK && currentStateCLK == 1) {
if (digitalRead(DT) != currentStateCLK) {
counter ++;
currentDir = "CW";
if (counter >= 40) {
counter = 40;//40 second MAX timer
}
}
else {
counter --;
currentDir = "CCW";
if (counter <= 1) {
counter = 1;
}
}
//Serial.print("Direction: ");
//Serial.print(currentDir);
//Serial.print(" Counter: ");
//Serial.println(counter);
display.fillRect(90, 47, 30, 13, SSD1306_BLACK);
display.display();
display.setCursor(100, 50);
display.println(counter);
display.display();
delay(1);// Put in a slight delay to help debounce the rotary reading, adust if needed
}
lastStateCLK = currentStateCLK;// remember last CLK state
//Rotary knob 'SW' push button is for saving the pump run timer (seconds),
//which is then saved as 'value' to EEPROM so that you do not have to reset it
//on each boot...
int btnState = digitalRead(SW);//look for button press
//If LOW signal, button is pressed
if (btnState == LOW) {
if (millis() - lastButtonPress > 50) {
Serial.println("Button pressed!");
clearEEPROM();//erase old data
display.fillRect(90, 47, 30, 13, SSD1306_WHITE);
display.setCursor(100, 50);
display.setTextColor(SSD1306_BLACK);
display.println(counter);
display.display();
value = counter;
EEPROM.write(address, value);//write new data
Serial.println("Writing to EEPROM...");
Serial.print("value: ");
Serial.println(value);
//EEPROMREAD();//debug read back the new value just to be sure
delay(1000); //show the white filled rectangle for 'x' millis
display.fillRect(90, 47, 30, 13, SSD1306_BLACK);
display.setCursor(100, 50);
display.setTextColor(SSD1306_WHITE);
display.println(counter);
display.display();
}
lastButtonPress = millis(); // Remember last button press event
}
//read the boiler water level and do something
int sensorState = digitalRead(SENSOR);
if (sensorState == HIGH) {
digitalWrite(REDLED, HIGH);
digitalWrite(BLUELED, LOW);
display.fillRect(75, 10, 50, 10, SSD1306_BLACK);//clear the text box
WaterLevelMenu = 1;//{" READING", " NORMAL", " LOW", " FILLING"};
printWaterLevelMenu();
WaterPumpMenu = 0;
printWaterPumpMenu();
valveClose();
}
else if (sensorState == LOW) {
digitalWrite(REDLED, LOW);
digitalWrite(BLUELED, HIGH);
display.fillRect(75, 10, 50, 10, SSD1306_BLACK);//clear the text box
WaterLevelMenu = 2;
printWaterLevelMenu();
delay(1000);
WaterPumpMenu = 1;
printWaterPumpMenu();
starttime = millis();
endtime = starttime;
pumpState = true;
while ((endtime - starttime) <= (counter * 1000) && (pumpState == true)) // do this loop for up to 1000mS
{
valveOpen();
display.fillRect(75, 10, 50, 10, SSD1306_BLACK);//clear the text box
WaterLevelMenu = 3;
printWaterLevelMenu();
endtime = millis();
}
pumpState = false;
valveClose();
}
}