/*WaterWeeder Controller
NOTE: at low temperatures, resolution is several degrees/bit but in operating range (80-120)
it is 0.2C/bit.Could be improved by adding some white noise.
*/
//Declare global variables
float tempset = 85; //set point constant in degrees C
float HL = 4; //lower limit hysteresis degrees C
float HH = 2; //upper limit hysteresis degrees C
int timestartpulse = 1500; //initial open pulse to close a few percent from fully opened
int timeinclow = 50; //pulse duration factor for closing to BLUE wire on valve to heat more
int timeinchigh = 100; //pulse duration factor for opening to RED wire on valve to cool
long resetarray = 0; //if first time through or after burner outage reset array to first spotgrab
float acgrab = 0; //for large data grabs
float acgrabref = 0; //for large data grabs
long ngrab = 0L; //counter for large data grabs
long num4datagrabmax = 1000L; //counter for data input average sets overall loop speed
int timelow = 0; //pulse duration for closing to BLUE wire on valve 100ms = ~13 pulses; 200=~9pulses; 75 = ~17p; 50-~23p; 30 = ~35p; 20 = ~58; 10 = ~163
int timehigh = 0; //pulse duration for opening to RED wire on valve
int run = 0; //guessing the state of the signal for burnerstatus for now - run=1, pause = 0 like a switch
int n = 0; //general purpose counter
int skiptime = 2000; // time to avoid adjustments while heat changes occur
long millisold = 0; // for setting start of skiptime interval
int blink = 100; //for LED warnings
int shorttime = 50; //for buzzer
int midtime = 100; //for buzzer
int longtime = 200; //for buzzer
int lowtone = 2000;//2000 for lowtemp buzzer
int midtone = 3200;//3200 for mid tone
int hightone = 4200;//4200 for overtemp burner off or water off buzzer
/*
int lowtone = 0;//2000 for lowtemp buzzer
int midtone = 0;//3200 for mid tone
int hightone = 0;//4200 for overtemp burner off or water off buzzer
*/
int close = 2; //ckise signal to motor interface board
int open = 3; //open signal to motor interface board
int closedvalvesw = 4; //digital sense if valve is fully closed (low)
int openedvalvesw = 5; //digital sense if valve is fully open (low)
int closedstatus = HIGH; // valve status variable for detecting closed
int openedstatus = HIGH; // valve status variable for detecting open
int burner = 1; //status of burner on
int water = 1; //status of water on
int burnerstatus = 13; //digital pin to detect if burner is on or off to run or pause this controller
int waterstatus = 12;
int buzzer = 11;
int ledYellow = 6;
int ledBlue = 7;
int ledGreen = 8;
int ledOrange = 9;
int ledRed = 10;
int memYellow = 1; // used to keep state for alternate use blinking
int memBlue = 1; // used to keep state for alternate use blinking
int memGreen = 1; // used to keep state for alternate use blinking
int memOrange = 1; // used to keep state for alternate use blinking
int memRed = 1; // used to keep state for alternate use blinking
int therm = A0;
int thermref = A1;
int batt = A2; //currently divided by 3 so at 12 volts it's over the ADC range
float battscale = 3.132; //empirical reading on divider compared to battery pack output
float battlow = 11; //low battery voltage threshold
float battstatus = 0;
float Rtherm = 0.000000;
float Rthermref = 3283.8; //by hp 9974A at rt
float steinA = 0.9112058723e-3; // Constants for S-H Equation T-1/(A+B*lnR+C*(lnR)^3)
float steinB = 2.086952234e-4;
float steinC = 1.488304536e-7;
float adconv = 0.0048875855; // 5/1023
// float runave = 0.00000;
float spotgrab = 0.00000;
// float tempave = 0.00000;
float tempspot = 0.00000;
float tempdiff = 0.00000;
//DISPLAY
//Declare global variables for display
// Arduino UNO with SSD1306 128x64 I2C OLED display
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 64 // OLED display height, in pixels
#define OLED_RESET 4 // Reset pin # (or -1 if sharing Arduino reset pin
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
// Initialise variables for display sketch
int ns; //segment counter
int nd; //digit counter
int na; // array counter
int w = SSD1306_WHITE;
int b = SSD1306_BLACK;
int zero[7]{ w, w, w, w, w, w, b }; //don't need one bc it is a special case
int two[7]{ w, w, b, w, w, b, w }; //don't need one bc it is a special case
int three[7]{ w, w, w, w, b, b, w };
int four[7]{ b, w, w, b, b, w, w };
int five[7]{ w, b, w, w, b, w, w };
int six[7]{ w, b, w, w, w, w, w };
int seven[7]{ w, w, w, b, b, b, b };
int eight[7]{ w, w, w, w, w, w, w };
int nine[7]{ w, w, w, w, b, w, w };
int color[7]{ b, b, b, b, b, b, b };
int xoffdigits[4]{ -8, 19, 55, 99 }; //dec,
int xoff = 0;
int yoff = 4; //fixed for this display and program
int digit[4]{ 0, 0, 0, 0 }; // parsed digits left to right (array position 0-3) hun,ten,one,dec
int inttemp = 0; //integer tempx10 variable
/* * * * * Subrountines * * * * *
void on9()
{ // Begin on9 Function
digitalWrite(LED9, HIGH); // Turns LED9 ON
return;
} // End on9 Function
*/
void setuptext(int textsize) {
display.clearDisplay();// Clear the last image shown on screen
display.setTextSize(textsize);// Normal 1:1 pixel scale
display.setTextColor(SSD1306_WHITE);// Draw white text
display.setCursor(0, 0);// Start at top-left corner
}
void show() { // Often used sequence - Function to simplify code
display.display();
delay(1);
display.fillScreen(SSD1306_BLACK);
}
//Execute setup once
void setup() {
// Declaration for an SSD1306 display connected to I2C (SDA, SCL pins)
Serial.begin(9600);
// SSD1306_SWITCHCAPVCC = generate display voltage from 3.3V internally
if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { // Old Address 0x3D for 128x64
Serial.println(F("SSD1306 allocation failed"));
for (;;)
; // Don't proceed, loop forever
}
pinMode(burnerstatus, INPUT); // Set pin to input with pull-up enabled
pinMode(waterstatus, INPUT); // Set pin to input with pull-up enabled
pinMode(ledYellow, OUTPUT);
digitalWrite(ledYellow, HIGH); //led is off when "led" is high
pinMode(ledBlue, OUTPUT);
digitalWrite(ledBlue, HIGH); //led is off when "led" is high
pinMode(ledGreen, OUTPUT);
digitalWrite(ledGreen, HIGH); //led is off when "led" is high
pinMode(ledOrange, OUTPUT);
digitalWrite(ledOrange, HIGH); //led is off when "led" is high
pinMode(ledRed, OUTPUT);
digitalWrite(ledRed, HIGH); //led is off when "led" is high
digitalWrite(ledYellow, LOW); //led is off when "led" is high
delay(100);
digitalWrite(ledBlue, LOW); //led is off when "led" is high
delay(100);
digitalWrite(ledGreen, LOW); //led is off when "led" is high
delay(100);
digitalWrite(ledOrange, LOW); //led is off when "led" is high
delay(100);
digitalWrite(ledRed, LOW); //led is off when "led" is high
delay(100);
digitalWrite(ledYellow, HIGH); //led is off when "led" is high
digitalWrite(ledBlue, HIGH); //led is off when "led" is high
digitalWrite(ledGreen, HIGH); //led is off when "led" is high
digitalWrite(ledOrange, HIGH); //led is off when "led" is high
digitalWrite(ledRed, HIGH); //led is off when "led" is high
pinMode(close, OUTPUT); //close signal to blue motor lead through power transistors
pinMode(open, OUTPUT); //open signal
pinMode(closedvalvesw, INPUT_PULLUP); // Set pin to input with pull-up enabled diode protected line in
pinMode(openedvalvesw, INPUT_PULLUP); // Set pin to input with pull-up enabled diode protected line in
setuptext(3);
display.println(F("WATER")); display.print(F("WEEDER"));
display.setTextSize(1); display.print(F("tm"));
display.setTextSize(3); display.println();
display.setTextSize(2); display.print(F("BayArmour"));
display.setTextSize(1); display.print(F("tm"));
show();
delay(5000);
setuptext(3);
display.println(F("WAITING"));// Inform the display of the text you want to print
display.println(F("TO OPEN"));
show();// Display the message
digitalWrite(close, LOW);
delay(10);
digitalWrite(open, HIGH); //(never have both high at same time)
//Open valve fully at start then close to start position
//while (openedstatus == HIGH) {openedstatus = digitalRead(openedvalvesw); }
delay(100);
setuptext(3);
display.println(F("VALVE "));
display.println(F("OPENED"));
show();
digitalWrite(open, LOW);
delay(10);
openedstatus = HIGH; digitalWrite(close, HIGH);//reset opened status bc cannot be open if closed
delay(timestartpulse); //closes valve to ~1% to eliminate dead zone
digitalWrite(close, LOW);
setuptext(3);
display.println(F("NOW AT"));
display.println(F("START"));
show();
delay(100);
}
//****Main Program*********
void loop() {
//IS BURNER ON?
burner = digitalRead(burnerstatus);
water = digitalRead(waterstatus); // Serial.print(water); Serial.print(" <--water burner--> "); Serial.println(burner);
while (burner == LOW || water == LOW) {
digitalWrite(ledBlue, burner);
digitalWrite(ledYellow, water);
burner = digitalRead(burnerstatus);
water = digitalRead(waterstatus);
tone(buzzer, lowtone, longtime); //Plays tone for mseconds
delay(1.5 * longtime);
digitalWrite(ledBlue, burner);
digitalWrite(ledYellow, water);
tone(buzzer, hightone, longtime); //Plays tone for mseconds
delay(1.5 * longtime); //reduce irritation
tone(buzzer, lowtone, longtime); //Plays tone for mseconds
delay(1.5 * longtime); //reduce irritation
digitalWrite(ledBlue, HIGH);
digitalWrite(ledYellow, HIGH);
delay(100); //blink
}
//*****acquire and compute temperature**********
ngrab = 0;
acgrab = 0;
acgrabref = 0; //initialize accumulator and counter for data grab
while (ngrab < num4datagrabmax) { //read spotgrab
acgrab = acgrab + analogRead(therm);
acgrabref = acgrabref + analogRead(thermref);
ngrab++;
}
Rtherm = Rthermref * ((acgrabref / acgrab) - 1); //compute Rtherm
//compute spot temperature
tempspot = (1 / (steinA + steinB * log(Rtherm) + steinC * log(Rtherm) * log(Rtherm) * log(Rtherm))) - 273.15;
//**********Control Section
if ((millis() - millisold) > skiptime) { //controlls rate of adjustments independent of measurements
//Battery Check
battstatus = battscale * adconv * analogRead(batt) ;
if (battstatus < battlow) {
// Serial.print("Vbatt--> "); Serial.println(battscale * adconv * analogRead(batt));
memYellow = digitalRead(ledYellow);
memBlue = digitalRead(ledBlue);
memGreen = digitalRead(ledGreen);
memOrange = digitalRead(ledOrange);
memRed = digitalRead(ledRed);
digitalWrite(ledYellow, LOW); //led is off when "led" is high
digitalWrite(ledBlue, LOW); //led is off when "led" is high
digitalWrite(ledGreen, LOW); //led is off when "led" is high
digitalWrite(ledOrange, LOW); //led is off when "led" is high
digitalWrite(ledRed, LOW); //led is off when "led" is high
delay(0.2 * blink);
//tone(buzzer, midtone, shorttime);
digitalWrite(ledYellow, memYellow); //led is off when "led" is high
digitalWrite(ledBlue, memBlue); //led is off when "led" is high
digitalWrite(ledGreen, memGreen); //led is off when "led" is high
digitalWrite(ledOrange, memOrange); //led is off when "led" is high
digitalWrite(ledRed, memRed); //led is off when "led" is high
setuptext(3);
display.println(F("BATTERY"));display.print(F(" "));
display.print(F(battstatus,1)); display.println(F("v"));
show();
delay(200);
}
tempdiff = tempset - tempspot; //compute current pulse time
if (tempdiff > 0) {
timelow = timeinclow * tempdiff;
} else {
timehigh = timeinchigh * abs(tempdiff);
}
//open or close valve
//need to test valve status while valve is being actuated in that direction
if (tempspot <= tempset - HL) { //CLOSE VALVE DETECT CLOSED STATUS
digitalWrite(ledRed, HIGH); //shut off other zone led
digitalWrite(ledOrange, HIGH); //shut off other zone led
digitalWrite(ledGreen, LOW); //warn temp low
n = 0;
while (n < 1) {
tone(buzzer, hightone, longtime); //Plays too cold tone for mseconds
delay(2 * longtime); //reduce irritation
tone(buzzer, lowtone, 1.5 * longtime); //Plays tone for mseconds
n++;
}
digitalWrite(open, LOW);//(never have both high at same time)
delay(10);
digitalWrite(close, HIGH);
delay(timelow); // wait for time interval set for temperature is too low
openedstatus = HIGH; closedstatus = digitalRead(closedvalvesw); //reset opened status as cannot be opened if partly closed
digitalWrite(close, LOW); //sense closed status then turn off
setuptext(3);
display.println(F("CLOSING"));
show();
delay(500);
}
if (tempspot >= tempset + HH) { //OPEN VALVE DETECT OPENED STATUS
digitalWrite(ledGreen, HIGH); //shut off other zone led
digitalWrite(ledOrange, HIGH); //shut off other zone led
digitalWrite(ledRed, LOW); //warn temp high
n = 0;
while (n < 1) {
tone(buzzer, lowtone, 2 * longtime); //Plays too hot tone for mseconds
delay(2 * longtime); //reduce irritation
tone(buzzer, hightone, longtime); //Plays tone for mseconds
delay(longtime); //reduce irritation
n++;
}
digitalWrite(close, LOW);
delay(10);
digitalWrite(open, HIGH); //(never have both high at same time)
delay(timehigh); // wait for time interval set for temperature is too high
closedstatus = HIGH; openedstatus = digitalRead(openedvalvesw); //reset closed status as cannot be closed if partly opened
digitalWrite(open, LOW); //shuts off power to valve motor
setuptext(3);
display.println(F("OPENING"));
show();
delay(500);
}
if (tempspot > tempset - HL && tempspot < tempset + HH) {
digitalWrite(ledGreen, HIGH); //shut off other zone led
digitalWrite(ledRed, HIGH); //shut off other zone led
digitalWrite(ledOrange, LOW); //warn temp high
setuptext(3);
display.println(F("HOLDING"));
show();
delay(500);
}
millisold = millis();
}
if (closedstatus==LOW) {
setuptext(3);
display.println(F("Maximum")); display.println(F(" Closed"));// Inform the display of the text you want to print
show();
delay(500);
}
if (openedstatus==LOW) {
setuptext(3);
display.println(F("Maximum")); display.println(F(" Opened"));// Inform the display of the text you want to print
show();
delay(500);
}
//control adjustments done
//***************display.fillRect(0+xoff, 0+yoff, 0,0,cw); //example display line*****
//Parse digits of spot temperature
//DEBUG****************************************************************************
//tempspot=-923.36;all numbers being displayed as expected 5/21/2026
//DEBUG****************************************************************************
inttemp = 10.0 * (.05 + (tempspot)); //convert spot temp to x10 integer rep for display routine
na = 3;
while (na >= 0) {
digit[na] = abs(inttemp % 10); //division by ten and take remainder
inttemp = inttemp / 10; //right shift off digit
na--;
}
//clear display ram buffer and screen
display.clearDisplay();
display.fillScreen(SSD1306_BLACK);
//display decimal point
display.fillRect(89, 53, 6, 6, w);
//display hundreds digit -special case can only be nothing or 1 or "-" or hightemp hash
nd = 0;
xoff = xoffdigits[nd];
if (digit[nd] == 1) { //display msd "1"
display.fillRect(11 + xoff, 0 + yoff, 7, 55, w);
display.fillRect(8 + xoff, 0 + yoff, 3, 6, w);
display.fillRect(8 + xoff, 49 + yoff, 13, 6, w);
}
if (digit[nd] > 1) { //indicator > 200C
display.drawFastVLine(8 + xoff, -4 + yoff, 64, w);
display.drawFastVLine(11 + xoff, -4 + yoff, 64, w);
display.drawFastVLine(14 + xoff, -4 + yoff, 64, w);
}
if (tempspot < 0) { // put negative sign in msd position
display.fillRect(8 + xoff, 30 + yoff, 13, 6, w);
}
nd = 1;
while (nd < 4) { //each parsed digit
xoff = xoffdigits[nd];
if (digit[nd] == 0) { //assign segment on/off array to color array c[]
na = 0;
while (na < 7) {
color[na] = zero[na];
na++;
}
}
if (digit[nd] == 2) { //assign segment on/off array to color array c[]
na = 0;
while (na < 7) {
color[na] = two[na];
na++;
}
}
if (digit[nd] == 3) { //assign segment on/off array to color array c[]
na = 0;
while (na < 7) {
color[na] = three[na];
na++;
}
}
if (digit[nd] == 4) { //assign segment on/off array to color array c[]
na = 0;
while (na < 7) {
color[na] = four[na];
na++;
}
}
if (digit[nd] == 5) { //assign segment on/off array to color array c[]
na = 0;
while (na < 7) {
color[na] = five[na];
na++;
}
}
if (digit[nd] == 6) { //assign segment on/off array to color array c[]
na = 0;
while (na < 7) {
color[na] = six[na];
na++;
}
}
if (digit[nd] == 7) { //assign segment on/off array to color array c[]
na = 0;
while (na < 7) {
color[na] = seven[na];
na++;
}
}
if (digit[nd] == 8) { //assign segment on/off array to color array c[]
na = 0;
while (na < 7) {
color[na] = eight[na];
na++;
}
}
if (digit[nd] == 9) { //assign segment on/off array to color array c[]
na = 0;
while (na < 7) {
color[na] = nine[na];
na++;
}
}
//turn on/off segments for digit[nd] if not equal to 1
if (digit[nd] != 1) {
if (color[0] != b) {
display.fillRect(0 + xoff, 0 + yoff, 29, 6, color[0]); //1st segment
}
if (color[1] != b) {
display.fillRect(23 + xoff, 0 + yoff, 6, 31, color[1]); //2
}
if (color[2] != b) {
display.fillRect(23 + xoff, 25 + yoff, 6, 30, color[2]); //3
}
if (color[3] != b) {
display.fillRect(0 + xoff, 49 + yoff, 29, 6, color[3]); //4
}
if (color[4] != b) {
display.fillRect(0 + xoff, 25 + yoff, 6, 30, color[4]); //5
}
if (color[5] != b) {
display.fillRect(0 + xoff, 0 + yoff, 6, 31, color[5]); //6
}
if (color[6] != b) {
display.fillRect(6 + xoff, 25 + yoff, 17, 6, color[6]); //7
}
}
if (digit[nd] == 1) { //special case assign segment on/off array to proper color
display.fillRect(11 + xoff, 0 + yoff, 7, 55, w);
display.fillRect(8 + xoff, 0 + yoff, 3, 6, w);
display.fillRect(8 + xoff, 49 + yoff, 13, 6, w);
}
nd++;
}
show(); //moves ram buffer to display chip
delay(1000);
}