Page 1 of 1

XRAD'S Live Steam Model Gas Valve Controller

Posted: Wed Nov 15, 2023 9:43 pm
by XRAD
This is the third build in the series of Live Steam Model Boiler Controllers (in order top to bottom pic below).

1) live steam condenser pump (basic mapped code to operate a 5 volt mechanical pump)

2) Live steam automatic boiler fill valve controller
viewtopic.php?p=925948#p925948

3) Live Steam Automatic gas valve controller (today's post)

So for this latest build, I need a way to control the gas burner valve. I decided on a servo with 90 degree throw to a valve with 90 off. good match. Why do I need this? because, if the boiler has hit the blow off PSI (usually about 50-60psi), then any additional heating would be wasted AND as the heating gas expands, it cool significantly, lowering the output pressure. So slow and low gas flow when not needed is best. Once you know the blow off pressure (by reading the PSI output), you can now set the 'pressure' with first selection (highlighted by white DOT and RED LED). this is the trigger point. Above this pressure, the BLUE LED comes on and the gas valve is turned down moderately (to the 'servoEnd' position), but not to OFF, obviously. When the pressure is reduced by steam usage below threshold, then the servo arm moves back to initial setting 'servoBegin' setting, and burner turns up to full blast again. I 'could' also trigger the burner to full ON if the boiler automatic filler (unit 2) is triggered, in order to heat the newest 'colder' water up to steam ASAP (but not really necessary as it passes through a pre-heater). ...but that is for another day.... Or.....I could put all the electronics/code into one build....also for another day. The current modular system is just that, modular. If I do not want to use a component, I can leave it out. and it's easy to place around the hull for balance. Everything runs off a 7.4 50c lipo. more than enough juice for hours....

Automatic Gas Valve Controller code is here. If you are new to arduino/C++ coding, you may enjoy reading through this for tips and tricks. Especially how to run a single rotary encoder for multiple tasks. Also, using some basic float variables converted to integers, and how to take an analog sensor environmental reading and convert it to an adjustable output back into the environment. Enjoy!
all three builds.jpg
all three builds.jpg (82.3 KiB) Viewed 467 times

Code: Select all

/*XRAD'S Live Steam Model Boiler Gas Valve Controller 11/11/23

  So I wanted to be able to turn down the burner flame when
  the boiler blow off valve releases.  This will decrease the 
  gas waste.  To do this, I needed a pressure sensor to measure
  boiler pressure, and a servo to control the gas valve.  Sounds
  easy enough, but it was definitely a 'fun' challenge to 
  write the program.
   
  The input/output flow is basically:
  read pressure sensor -> A to D value -> convert value -> set 
  blow off pressure -> set initial servo arm throw -> compare 
  real psi to blowoff setting ->adjust final servo arm throw to 
  turn burner down.
  
  This 'requires' (to save build space) using one rotary encoder 
  for three functions, and writing/reading/clearing 3 values to 
  EEPROM. there is also a selection button for choosing between 
  the 3 switch/case functions.
  
  There is usually a better way to write code, and I am sure
  mine can be comresssed a bit with a class for the rotary encoder,
  and arrays for the hardware/functions, but it is easy to follow as
  is and can be helpful to beginners. Note: PWMServo and Encoder.h
  are part of the Teensy bootloader; and easy to use 'interrupts' 
  come with Encoder.h library.
  
  As always, feel free to use, modify, update...have fun!
  
  Hardware:
  using ADAFRUIT SSD1306 128x64 monochrome I2C display (I modified 
  the base files so the splash screen does not show..these are 
  great displays and always work with GFX Adacode), Teensy 3.2 
  (NLA, but easy to use), basic rotary encoder with built in 
  resistors (on the data and clock wires only, not on switch), 
  one stand alone led, one momentary button, one 0-100 standard 
  1/8th NPT pressure transducer from Amazon (not he best, but 14$), 
  ABS case, and hobby servo 5v.....
  
  things to do: non blocking or mapped servo code for 
  better throw speed control...but not really needed...
*/


#define ENCODER_USE_INTERRUPTS  //must be called before Encoder.h
#include <SPI.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <Encoder.h>
#include <EEPROM.h>
#include <PWMServo.h>


//rotary encoder variables
Encoder encoder(3, 2);  //reverse pins here to reverse rotary knob direction
#define SW 4
unsigned long lastButtonPress = 0;
//variable for rotary encoder LED
#define ledPin 9//ON BLUE when rotary encoder button pressed and EEPROM written,
//also turns ON BLUE when servo activated by pressure

//'selection' push button variables
#define selectionButton 7
#define selectionButtonLed 16  //RED LED turns on when selection button pressed
int selectionButtonState = 1;
int selection = 4;
int count, old_count;
int caseSelection = 3;
int buttonCount = 3;

//EEPROM variables
unsigned int addressPressure = 0;   //where to store blow off value in EEPROM
unsigned int addressGasBegin = 10;  //where to store servo begin value in EEPROM
unsigned int addressGasEnd = 20;    //where to store servo end value in EEPROM
int positionPressure = 0;           //the trigger pressure for turning off the gas via servo arm
int positionGasBegin = 0;           //where to set beginning of servo movement
int positionGasEnd = 0;             //where to set end of servo arm movement
bool allowStorageFlag = false;      //can only clear/save to memory if this is true

//servo variables
PWMServo myservo;       // create servo object to control a servo
int posServoBegin = 0;  // variable to store the servo position
int posServoEnd = 0;

#define SCREEN_WIDTH 128  // OLED display width, in pixels
#define SCREEN_HEIGHT 64  // OLED display height, in pixels

// Declaration for an SSD1306 display connected to I2C (SDA, SCL pins)
#define OLED_RESET 4  // Reset pin # (or -1 if sharing Arduino reset pin)
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);

//variable for pressure sensor 0-100psi
#define sensorPin A0  // input sensor pin

//variables for calculating 0-100 psiVal
const float alpha = 0.95;  // Low Pass Filter alpha (0 - 1 )
const float aRef = 3.3;    // 3.3 for teensy 3.2....5.1 for others?
float filteredVal = 0;     // starting point
float sensorVal;
float voltage;
float psiVal;  //this is rounded in the loop!
int psiOldVal;

//display update timer for psi
unsigned long startMillis;
unsigned long currentMillis;
#define updateTime 1000


void setup() {

  Serial.begin(9600);
  delay(2000);

  myservo.attach(SERVO_PIN_B);  // attaches the servo on pin 10 teensy 3.2 to the servo object

  if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3D)) {  // Address 0x3D for 128x64
    Serial.println(F("SSD1306 allocation failed"));
  }

  pinMode(SW, INPUT_PULLUP);  //rotary encoder button
  pinMode(ledPin, OUTPUT);    // rotary encoder led
  digitalWrite(ledPin, HIGH);

  pinMode(selectionButton, INPUT);  // push button to change selection
  digitalWrite(7, HIGH);
  pinMode(selectionButtonLed, OUTPUT);  //selection button led
  digitalWrite(16, HIGH);

  old_count = count = encoder.read();  //encoder variables

  EEPROMREADSETUP();  //do this only once to update display variables from EEPROM memory

  drawIntro();

  digitalWrite(ledPin, LOW);
  digitalWrite(selectionButtonLed, LOW);

  startMillis = millis();

  updateOLEDPressure();
  updateOLEDGas();
  updatePsi();

  servoControlBegin();
  myservo.write(positionGasBegin);//write the first sevo position
}


void loop() {

  selectionButtonState = digitalRead(selectionButton);
  if (selectionButtonState == 0) {
    buttonCount = buttonCount + 1;
    delay(250);
    caseSelection = buttonCount;
    if (buttonCount >= 4) {
      buttonCount = 0;
      caseSelection = 0;
    }
    //Serial.print("buttonCount: ");
    //Serial.println(buttonCount);
    //Serial.print("caseSelection: ");
    //Serial.println(caseSelection);
  }

  //case 0 allows real time testing of pressure/servo
  //case 1 allows real time postioning of servo
  //case 2 is the end position in degrees for servo
  //default allows normal loop funtion
  switch (caseSelection) {
    case 0:  //boiler pressure sensor functions
      selection = 0;
      allowStorageFlag = true;
      digitalWrite(16, HIGH);
      drawCirclePressure();
      break;
    case 1:  //gas valve servo functions
      selection = 1;
      allowStorageFlag = true;
      digitalWrite(16, HIGH);
      clearCirclePressure();
      drawCircleGasBegin();  //highlights the target value
      break;
    case 2:  //gas valve servo functions
      selection = 2;
      allowStorageFlag = true;
      digitalWrite(16, HIGH);
      clearCircleGasBegin();
      drawCircleGasEnd();  //highlights the target value
      break;
    default:
      selection = 3;
      allowStorageFlag = false;
      digitalWrite(16, LOW);
      clearCircleGasEnd();
      break;
  }

  count = encoder.read();

  if (selection == 0) {
    if (old_count != count)
      countPressure();
  }
  if (selection == 1) {
    if (old_count != count)
      countGasBegin();
  }
  if (selection == 2) {
    if (old_count != count)
      countGasEnd();
  }

  //choosing 'selection == x' alows for specific associated clear/write EEPROM funtions
  if (allowStorageFlag == true) {
    int btnState = digitalRead(SW);
    if (btnState == LOW) {
      if (millis() - lastButtonPress > 50) {
        //Serial.println("Button pressed!")
        digitalWrite(ledPin, HIGH);
        clearEEPROM();  //erase ONLY old data specific to 'selection'
        writeEEPROM();  //write ONLY new data specific to 'selection'
        delay(500);
        digitalWrite(ledPin, LOW);
      }
      lastButtonPress = millis();  // Remember last button press event
    }
  }

  // get a PSI display integer from and analog input
  sensorVal = (float)analogRead(sensorPin);                           // Read pressure sensor val (A0) WAS= (float)analogRead(sensorPin);
  filteredVal = (alpha * filteredVal) + ((1.0 - alpha) * sensorVal);  // Low Pass Filter
  voltage = (filteredVal / 1024.0) * aRef;                            // calculate voltage
  psiVal = (voltage - 0.4784) / 0.0401;                               // x=(y-b)/m
  int roundedUp = ((int)psiVal);
  if (roundedUp != psiOldVal) {
    //Serial.print("psiVal: ");
    //Serial.println(roundedUp);
    //Serial.print("psiOldVal: ");
    //Serial.println(psiOldVal);
    currentMillis = millis();
    if (currentMillis - startMillis >= updateTime) {
      updatePsi();
      psiOldVal = roundedUp;
      startMillis = currentMillis;
    }
  }

  if (selection == 3) {
    if (psiVal > positionPressure) {
      digitalWrite(ledPin, HIGH);
      servoControlEnd();
    }
    if (psiVal < positionPressure) {
      digitalWrite(ledPin, LOW);
      servoControlBegin();
    }
  }
}


//count the rotary ecoder ticks and update position variables
void countPressure() {
  if (count) {
    if (count > old_count)
      positionPressure++;
    else
      positionPressure--;
    if (positionPressure >= 90) {
      positionPressure = 90;
    } else {
      if (positionPressure <= 0) {
        positionPressure = 0;
      }
    }
    display.fillRect(97, 0, 20, 10, SSD1306_BLACK);
    display.display();
    display.setTextSize(1);
    display.setCursor(97, 0);
    display.println(positionPressure);
    display.display();
    //Serial.print("PressureSet = ");
    //Serial.println(positionPressure);
  }
  old_count = count;
}

void countGasBegin() {
  if (count) {
    if (count > old_count)
      positionGasBegin++;
    else positionGasBegin--;
    if (positionGasBegin >= 90) {
      positionGasBegin = 90;
    } else {
      if (positionGasBegin <= 0) {
        positionGasBegin = 0;
      }
    }
    display.fillRect(97, 14, 20, 10, SSD1306_BLACK);
    display.display();
    display.setTextSize(1);
    display.setCursor(97, 14);
    display.println(positionGasBegin);
    display.display();
    //Serial.print("GasSet = ");
    //Serial.println(positionGasBegin);
    myservo.write(positionGasBegin); //used for real time positiong of servo
  }
  old_count = count;
}

void countGasEnd() {
  if (count) {
    if (count > old_count)
      positionGasEnd++;
    else positionGasEnd--;
    if (positionGasEnd >= 90) {
      positionGasEnd = 90;
    } else {
      if (positionGasEnd <= 0) {
        positionGasEnd = 0;
      }
    }
    display.fillRect(97, 30, 20, 10, SSD1306_BLACK);
    display.display();
    display.setTextSize(1);
    display.setCursor(97, 30);
    display.println(positionGasEnd);
    display.display();
    myservo.write(positionGasEnd);
    //Serial.print("GasSet = ");
    //Serial.println(positionGasBegin);
  }
  old_count = count;
}

void updateOLEDPressure() {
  display.setTextSize(1);
  display.setTextColor(WHITE);
  display.setCursor(0, 0);
  display.print("GasOffPressure: ");  //the set value of blow off pressure
  display.setCursor(97, 0);
  display.print(positionPressure);
}

void updateOLEDGas() {
  display.setTextSize(1);
  display.setTextColor(WHITE);
  display.setCursor(0, 15);
  display.print("ServoArmBegin: ");  //how far is servo throw
  display.setCursor(97, 15);
  display.print(positionGasBegin);
  display.setCursor(0, 30);
  display.print("ServoArmEnd: ");  //how far is servo throw
  display.setCursor(97, 30);
  display.print(positionGasEnd);
  display.display();
}

void updatePsi() {
  display.fillRect(80, 45, 40, 15, SSD1306_BLACK);
  display.display();
  display.setTextSize(2);
  display.setCursor(10, 45);
  display.print("psi: ");
  display.setCursor(80, 45);
  display.print(psiVal, 0);
  display.display();
}

void drawIntro() {
  display.clearDisplay();
  display.drawRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, SSD1306_WHITE);
  display.drawRect(1, 1, SCREEN_WIDTH - 2, SCREEN_HEIGHT - 2, SSD1306_WHITE);  //draw double thick rectangle
  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(5, 50);
  display.setTextSize(1);
  display.setTextColor(SSD1306_WHITE);
  display.print("GAS VALVE CONTROLLER");
  display.display();
  delay(3000);
  display.clearDisplay();
}

void drawCirclePressure() {
  display.fillCircle(125, 3, 2, SSD1306_WHITE);
  display.display();
}

void clearCirclePressure() {
  display.fillCircle(125, 3, 2, SSD1306_BLACK);
  display.display();
}

void drawCircleGasBegin() {
  display.fillCircle(125, 18, 2, SSD1306_WHITE);
  display.display();
}

void clearCircleGasBegin() {
  display.fillCircle(125, 18, 2, SSD1306_BLACK);
  display.display();
}

void drawCircleGasEnd() {
  display.fillCircle(125, 33, 2, SSD1306_WHITE);
  display.display();
}

void clearCircleGasEnd() {
  display.fillCircle(125, 33, 2, SSD1306_BLACK);
  display.display();
}

void EEPROMREADSETUP() {                            //read once to udate variables from memory
  positionPressure = EEPROM.read(addressPressure);  // read a byte from the current address of the EEPROM
  //Serial.print("EEPROMvalueBlowOff: ");
  //Serial.println(positionPressure);
  positionGasBegin = EEPROM.read(addressGasBegin);  // read a byte from the current address of the EEPROM
  //Serial.print("EEPROMvalueSetServoArmDegreesBegin: ");
  //Serial.println(positionGasBegin);
  positionGasEnd = EEPROM.read(addressGasEnd);  // read a byte from the current address of the EEPROM
  //Serial.print("EEPROMvalueSetServoArmDegreesEnd: ");
  //Serial.println(positionGasEnd);
}

void EEPROMREAD() {  //specifically read certain addresses based on 'selection'
  if (selection == 0) {
    positionPressure = EEPROM.read(addressPressure);  // read a byte from the current address of the EEPROM
    //Serial.print("EEPROMvalueBlowOff: ");
    //Serial.println(positionPressure);
  }
  if (selection == 1) {
    positionGasBegin = EEPROM.read(addressGasBegin);  // read a byte from the current address of the EEPROM
    //Serial.print("EEPROMvalueSetServoArmDegrees: ");
    //Serial.println(positionGasBegin);
  }
  if (selection == 2) {
    positionGasEnd = EEPROM.read(addressGasEnd);  // read a byte from the current address of the EEPROM
    //Serial.print("EEPROMvalueSetServoArmDegreesEnd: ");
    //Serial.println(positionGasEnd);
  }
}

void clearEEPROM() {
  if (selection == 0) {
    EEPROM.read(addressPressure);      //read address
    EEPROM.write(addressPressure, 0);  //write 0 to address 0
    //Serial.println("EEPROM Pressure erased");
  }
  if (selection == 1) {
    EEPROM.read(addressGasBegin);      //read address
    EEPROM.write(addressGasBegin, 0);  //write 0 to address 0
    //Serial.println("EEPROM Gas erased");
  }
  if (selection == 2) {
    EEPROM.read(addressGasEnd);      //read address
    EEPROM.write(addressGasEnd, 0);  //write 0 to address 0
    //Serial.println("EEPROM Gas erased");
  }
}

void writeEEPROM() {
  if (selection == 0) {
    EEPROM.write(addressPressure, positionPressure);  //write specific data to specific address
    //Serial.println("Button pressed!");
    //Serial.println("Writing to EEPROM...");
    //Serial.print("valueBlowOff: ");
    //Serial.println(positionPressure);
  }
  if (selection == 1) {
    EEPROM.write(addressGasBegin, positionGasBegin);
    //Serial.println("Button pressed!");
    //Serial.println("Writing to EEPROM...");
    //Serial.print("valueSetServoArmDegreesBeginning: ");
    //Serial.println(positionGasBegin);
  }
  if (selection == 2) {
    EEPROM.write(addressGasEnd, positionGasEnd);
    //Serial.println("Button pressed!");
    //Serial.println("Writing to EEPROM...");
    //Serial.print("valueSetServoArmDegreesEnd: ");
    //Serial.println(positionGasEnd);
  }
}

//servo stuff
void servoControlBegin() {
  posServoBegin = positionGasBegin;
  myservo.write(posServoBegin);
}

void servoControlEnd() {
  posServoEnd = positionGasEnd;
  myservo.write(posServoEnd);
}

//end

Re: XRAD'S Live Steam Model Gas Valve Controller

Posted: Wed Nov 15, 2023 9:50 pm
by XRAD
some more build pics:
breadboard to complete. I will post a vid soon...
Gas Controller Bread Board.jpg
Gas Controller Bread Board.jpg (84.13 KiB) Viewed 466 times
Gas Controller Partial asmbly.jpg
Gas Controller Partial asmbly.jpg (99.3 KiB) Viewed 466 times
Gas Controller Full asmbly.jpg
Gas Controller Full asmbly.jpg (120.63 KiB) Viewed 466 times

Re: XRAD'S Live Steam Model Gas Valve Controller

Posted: Thu Nov 16, 2023 6:40 am
by adafruit_support_bill
Nice work as usual, XRAD. Looking forward to seeing some live steam!

Re: XRAD'S Live Steam Model Gas Valve Controller

Posted: Sun Nov 19, 2023 4:45 pm
by XRAD
Thx Bill! Working on a video. Updated the code a bit. Got rid of some intermediate variables and servo bounce.

Code: Select all

/*XRAD'S Live Steam Model Boiler Gas Valve Controller 11/11/23

  So I wanted to be able to turn down the burner flame when
  the boiler blow off valve releases.  This will decrease the 
  gas waste.  To do this, I needed a pressure sensor to measure
  boiler pressure, and a servo to control the gas valve.  Sounds
  easy enough, but it was definitely a 'fun' challenge to 
  write the program.
   
  The input/output flow is basically:
  read pressure sensor -> A to D value -> convert value -> set 
  blow off pressure -> set initial servo arm throw -> compare 
  real psi to blowoff setting ->adjust final servo arm throw to 
  turn burner down.
  
  This 'requires' (to save build space) using one rotary encoder 
  for three functions, and writing/reading/clearing 3 values to 
  EEPROM. there is also a selection button for choosing between 
  the 3 switch/case functions.
  
  There is usually a better way to write code, and I am sure
  mine can be comresssed a bit with a class for the rotary encoder,
  and arrays for the hardware/functions, but it is easy to follow as
  is and can be helpful to beginners. Note: PWMServo and Encoder.h
  are part of the Teensy bootloader; and easy to use 'interrupts' 
  come with Encoder.h library.
  
  As always, feel free to use, modify, update...have fun!
  
  Hardware:
  using ADAFRUIT SSD1306 128x64 monochrome I2C display (I modified 
  the base files so the splash screen does not show..these are 
  great displays and always work with GFX Adacode), Teensy 3.2 
  (NLA, but easy to use), basic rotary encoder with built in 
  resistors (on the data and clock wires only, not on switch), 
  one stand alone led, one momentary button, one 0-100 standard 
  1/8th NPT pressure transducer from Amazon (not he best, but 14$), 
  ABS case, and hobby servo 5v.....
  
  things to do: non blocking or mapped servo code for 
  better throw speed control...but not really needed...
*/


#define ENCODER_USE_INTERRUPTS  //must be called before Encoder.h
#include <SPI.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <Encoder.h>
#include <EEPROM.h>
#include <PWMServo.h>


//rotary encoder variables
Encoder encoder(3, 2);  //reverse pins here to reverse rotary knob direction
#define SW 4
int count, oldCount;
unsigned long lastButtonPress = 0;
//variable for rotary encoder LED
#define ledPin 9//ON BLUE when rotary encoder button pressed and EEPROM written,
//also ON BLUE when servo is activated by pressure setting

//'selection' push button variables
#define selectionButton 7
#define selectionButtonLed 16  //RED LED turns on when selection button pressed
int selectionButtonState = 1;
int selection = 4;
int caseSelection = 3;
int buttonCount = 3;

//EEPROM variables
unsigned int addressPressure = 0;   //where to store blow off value in EEPROM
unsigned int addressGasBegin = 10;  //where to store servo begin value in EEPROM
unsigned int addressGasEnd = 20;    //where to store servo end value in EEPROM
int positionPressure = 0;           //the trigger pressure for turning off the gas via servo arm
int positionGasBegin = 0;           //where to set beginning of servo movement
int positionGasEnd = 0;             //where to set end of servo arm movement
bool allowStorageFlag = false;      //can only clear/save to memory if this is true

//refresh 'selection 0' one time variables
bool selectionRunOnce = false;

//servo variables
PWMServo myservo;       // create servo object to control a servo

#define SCREEN_WIDTH 128  // OLED display width, in pixels
#define SCREEN_HEIGHT 64  // OLED display height, in pixels

// Declaration for an SSD1306 display connected to I2C (SDA, SCL pins)
#define OLED_RESET 4  // Reset pin # (or -1 if sharing Arduino reset pin)
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);

//variable for pressure sensor 0-100psi
#define sensorPin A0  // input sensor pin

//variables for calculating 0-100 psiVal
const float alpha = 0.95;  // Low Pass Filter alpha (0 - 1 )
const float aRef = 3.3;    // 3.3 for teensy 3.2....5.1 for others?
float filteredVal = 0;     // starting point
float sensorVal;
float voltage;
float psiVal;  //this is rounded in the loop!
int psiOldVal;

//update display timer for psi
unsigned long startMillis;
unsigned long currentMillis;
#define updateTime 1000


void setup() {

  Serial.begin(9600);
  delay(2000);

  myservo.attach(SERVO_PIN_B);  // attaches the servo on pin 10 teensy 3.2 to the servo object

  if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3D)) {  // Address 0x3D for 128x64
    Serial.println(F("SSD1306 allocation failed"));
  }

  pinMode(SW, INPUT_PULLUP);  //rotary encoder button
  pinMode(ledPin, OUTPUT);    // rotary encoder led
  digitalWrite(ledPin, HIGH);

  pinMode(selectionButton, INPUT);  // push button to change selection
  digitalWrite(7, HIGH);
  pinMode(selectionButtonLed, OUTPUT);  //selection button led
  digitalWrite(16, HIGH);

  oldCount = count = encoder.read();  //set encoder variables

  EEPROMREADSETUP();  //do this only once to update display variables from EEPROM memory

  drawIntro();

  digitalWrite(ledPin, LOW);
  digitalWrite(selectionButtonLed, LOW);

  startMillis = millis();

  updateOLEDPressure();
  updateOLEDGas();
  updatePsi();

  //servoControlBegin();
  myservo.write(positionGasBegin);//write the first sevo position
}


void loop() {

  selectionButtonState = digitalRead(selectionButton);
  if (selectionButtonState == 0) {
    buttonCount = buttonCount + 1;
    delay(250);
    caseSelection = buttonCount;
    if (buttonCount >= 4) {
      buttonCount = 0;
      caseSelection = 0;
    }
    //Serial.print("buttonCount: ");
    //Serial.println(buttonCount);
    //Serial.print("caseSelection: ");
    //Serial.println(caseSelection);
  }

  //case 0 allows for setting gas off pressure
  //case 1 allows real time postioning of servo begin
  //case 2 allows real time postioning of servo end
  //default allows normal loop funtion
  switch (caseSelection) {
    case 0:  //boiler pressure sensor functions
      selection = 0;
      allowStorageFlag = true;
      if (oldCount != count){
      countPressure();
      }
      digitalWrite(16, HIGH);
      drawCirclePressure();
      break;
    case 1:  //gas valve servo functions
      selection = 1;
      allowStorageFlag = true;
      if (oldCount != count){
      countGasBegin();
      }
      digitalWrite(16, HIGH);
      clearCirclePressure();
      drawCircleGasBegin();  //highlights the target value
      break;
    case 2:  //gas valve servo functions
      selection = 2;
      allowStorageFlag = true;
      selectionRunOnce = true;
      if (oldCount != count){
      countGasEnd();
      }
      digitalWrite(16, HIGH);
      clearCircleGasBegin();
      drawCircleGasEnd();  //highlights the target value
      break;
    default:
      selection = 3;
      allowStorageFlag = false;
      digitalWrite(16, LOW);
      clearCircleGasEnd();
      EEPROMREAD();
      break;
  }

  count = encoder.read();//keep this active in the loop

  //choosing selection == 'x' alows for specificly associated clear/write EEPROM funtions
  if (allowStorageFlag == true) {
    int btnState = digitalRead(SW);
    if (btnState == LOW) {
      if (millis() - lastButtonPress > 50) {
        //Serial.println("Button pressed!")
        digitalWrite(ledPin, HIGH);
        clearEEPROM();  //erase ONLY old data specific to 'selection'
        writeEEPROM();  //write ONLY new data specific to 'selection'
        delay(500);
        digitalWrite(ledPin, LOW);
      }
      lastButtonPress = millis();  // Remember last button press event
    }
  }

  // get a PSI display integer from an analog input
  sensorVal = (float)analogRead(sensorPin);                           // Read pressure sensor val (A0) WAS= (float)analogRead(sensorPin);
  filteredVal = (alpha * filteredVal) + ((1.0 - alpha) * sensorVal);  // Low Pass Filter
  voltage = (filteredVal / 1024.0) * aRef;                            // calculate voltage
  psiVal = (voltage - 0.4784) / 0.0401;                               // x=(y-b)/m..basically a linear set of values
  int roundedUp = ((int)psiVal);
  if (roundedUp != psiOldVal) {
    //Serial.print("psiVal: ");
    //Serial.println(roundedUp);
    //Serial.print("psiOldVal: ");
    //Serial.println(psiOldVal);
    currentMillis = millis();
    if (currentMillis - startMillis >= updateTime) {
      updatePsi();
      psiOldVal = roundedUp;
      startMillis = currentMillis;
    }
  }

  if (selection == 3) {
    if (psiVal > positionPressure-1) {//works better for some reason, adjust as needed
      digitalWrite(ledPin, HIGH); 
      myservo.write(positionGasEnd);
    }
    if (psiVal < positionPressure-5) {//don't need to turn gas up right away, wait for psi drop
      digitalWrite(ledPin, LOW); 
      myservo.write(positionGasBegin);
    }
  }
}//end loop


//count the rotary ecoder ticks and update variables
void countPressure() {
  if (count) {
    if (count > oldCount)
      positionPressure++;
    else
      positionPressure--;
    if (positionPressure >= 90) {
      positionPressure = 90;
    } else {
      if (positionPressure <= 0) {
        positionPressure = 0;
      }
    }
    display.fillRect(97, 0, 20, 10, SSD1306_BLACK);
    display.display();
    display.setTextSize(1);
    display.setCursor(97, 0);
    display.println(positionPressure);
    display.display();
    //Serial.print("PressureSet = ");
    //Serial.println(positionPressure);
  }
  oldCount = count;
}

void countGasBegin() {
  if (count) {
    if (count > oldCount)
      positionGasBegin++;
    else positionGasBegin--;
    if (positionGasBegin >= 90) {
      positionGasBegin = 90;
    } else {
      if (positionGasBegin <= 0) {
        positionGasBegin = 0;
      }
    }
    display.fillRect(97, 14, 20, 10, SSD1306_BLACK);
    display.display();
    display.setTextSize(1);
    display.setCursor(97, 14);
    display.println(positionGasBegin);
    display.display();
    //Serial.print("GasSet = ");
    //Serial.println(positionGasBegin);
    myservo.write(positionGasBegin); //used for real time positiong of servo
  }
  oldCount = count;
}

void countGasEnd() {
  if (count) {
    if (count > oldCount)
      positionGasEnd++;
    else positionGasEnd--;
    if (positionGasEnd >= 90) {
      positionGasEnd = 90;
    } else {
      if (positionGasEnd <= 0) {
        positionGasEnd = 0;
      }
    }
    display.fillRect(97, 30, 20, 10, SSD1306_BLACK);
    display.display();
    display.setTextSize(1);
    display.setCursor(97, 30);
    display.println(positionGasEnd);
    display.display();
    myservo.write(positionGasEnd);
    //Serial.print("GasSet = ");
    //Serial.println(positionGasBegin);
  }
  oldCount = count;
}

void updateOLEDPressure() {
  display.setTextSize(1);
  display.setTextColor(WHITE);
  display.setCursor(0, 0);
  display.print("GasOffPressure: ");  //the set value of blow off pressure
  display.setCursor(97, 0);
  display.print(positionPressure);
}

void updateOLEDGas() {
  display.setTextSize(1);
  display.setTextColor(WHITE);
  display.setCursor(0, 15);
  display.print("ServoArmBegin: ");  
  display.setCursor(97, 15);
  display.print(positionGasBegin);
  display.setCursor(0, 30);
  display.print("ServoArmEnd: "); 
  display.setCursor(97, 30);
  display.print(positionGasEnd);
  display.display();
}

void updatePsi() {
  display.fillRect(80, 45, 40, 15, SSD1306_BLACK);
  display.display();
  display.setTextSize(2);
  display.setCursor(10, 45);
  display.print("psi: ");
  display.setCursor(80, 45);
  display.print(psiVal, 0);
  display.display();
}

void drawIntro() {//draw some things once
  display.clearDisplay();
  display.drawRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, SSD1306_WHITE);
  display.drawRect(1, 1, SCREEN_WIDTH - 2, SCREEN_HEIGHT - 2, SSD1306_WHITE);  //draw double thick rectangle
  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(5, 50);
  display.setTextSize(1);
  display.setTextColor(SSD1306_WHITE);
  display.print("GAS VALVE CONTROLLER");
  display.display();
  delay(3000);
  display.clearDisplay();
}

void drawCirclePressure() {
  display.fillCircle(125, 3, 2, SSD1306_WHITE);
  display.display();
}

void clearCirclePressure() {
  display.fillCircle(125, 3, 2, SSD1306_BLACK);
  display.display();
}

void drawCircleGasBegin() {
  display.fillCircle(125, 18, 2, SSD1306_WHITE);
  display.display();
}

void clearCircleGasBegin() {
  display.fillCircle(125, 18, 2, SSD1306_BLACK);
  display.display();
}

void drawCircleGasEnd() {
  display.fillCircle(125, 33, 2, SSD1306_WHITE);
  display.display();
}

void clearCircleGasEnd() {
  display.fillCircle(125, 33, 2, SSD1306_BLACK);
  display.display();
}

void EEPROMREADSETUP() {                            //read once to udate variables from memory
  positionPressure = EEPROM.read(addressPressure);  // read a byte from the current address of the EEPROM
  //Serial.print("EEPROMvalueBlowOff: ");
  //Serial.println(positionPressure);
  positionGasBegin = EEPROM.read(addressGasBegin);
  positionGasEnd = EEPROM.read(addressGasEnd);  
}

void EEPROMREAD() {  //specifically read certain addresses based on 'selection'
  if (selection == 0) {
    positionPressure = EEPROM.read(addressPressure); 
  }
  if (selection == 1) {
    positionGasBegin = EEPROM.read(addressGasBegin);  
  }
  if (selection == 2) {
    positionGasEnd = EEPROM.read(addressGasEnd); 
  }

  if ((selection == 3) && (selectionRunOnce == true)) {
    positionPressure = EEPROM.read(addressPressure);  
    positionGasBegin = EEPROM.read(addressGasBegin); 
    positionGasEnd = EEPROM.read(addressGasEnd); 
    
    display.fillRect(97, 0, 20, 10, SSD1306_BLACK);
    display.display();
    display.setTextSize(1);
    display.setCursor(97, 0);
    display.println(positionPressure);
    display.display();

    display.fillRect(97, 14, 20, 10, SSD1306_BLACK);
    display.display();
    display.setTextSize(1);
    display.setCursor(97, 14);
    display.println(positionGasBegin);
    display.display();

    display.fillRect(97, 30, 20, 10, SSD1306_BLACK);
    display.display();
    display.setTextSize(1);
    display.setCursor(97, 30);
    display.println(positionGasEnd);
    display.display();

    selectionRunOnce = false;
  }
}

void clearEEPROM() {
  if (selection == 0) {
    EEPROM.read(addressPressure);      //read address
    EEPROM.write(addressPressure, 0);  //write 0 to address 0
    //Serial.println("EEPROM Pressure erased");
  }
  if (selection == 1) {
    EEPROM.read(addressGasBegin);    
    EEPROM.write(addressGasBegin, 0);  
  }
  if (selection == 2) {
    EEPROM.read(addressGasEnd);      
    EEPROM.write(addressGasEnd, 0);  
  }
}

void writeEEPROM() {
  if (selection == 0) {
    EEPROM.write(addressPressure, positionPressure);  //write specific data to specific address
  }
  if (selection == 1) {
    EEPROM.write(addressGasBegin, positionGasBegin);
  }
  if (selection == 2) {
    EEPROM.write(addressGasEnd, positionGasEnd);
  }
}
 
//end



Re: XRAD'S Live Steam Model Gas Valve Controller

Posted: Sat Dec 02, 2023 1:42 pm
by XRAD
video link below. I did change this:

Code: Select all

if (selection == 3) {
    if (psiVal > positionPressure-1) {//works better for some reason, adjust as needed
      digitalWrite(ledPin, HIGH); 
      myservo.write(positionGasEnd);
    }
    if (psiVal < positionPressure-5) {//don't need to turn gas up right away, wait for psi drop
      digitalWrite(ledPin, LOW); 
      myservo.write(positionGasBegin);
    }
  }
}//end loop
to this:

Code: Select all

if (selection == 3) {
    if (psiVal > positionPressure) {
      digitalWrite(ledPin, HIGH); 
      myservo.write(positionGasEnd);
    }
    if (psiVal < positionPressure-5) {//don't need to turn gas up right away, wait for psi drop
      digitalWrite(ledPin, LOW); 
      myservo.write(positionGasBegin);
    }
  }
}//end loop
there is a 1000millis timer before new PSI is calculated, and sometimes the servo activation does not exactly match real PSI, but it's only off by +/-1. I use 5psi difference before reactivating servo, as a small buffer.

another one of my low quality videos.... enjoy!:
https://www.youtube.com/watch?v=AeafY-Q1x0g