You can download the complete notes for this project in a pdf form from here: SWHController_Article.pdf

Download the pdf button


The main project page is here: An Arduino Based Solar Water Heater Controller.

The project describing the same solar water heater controller system for the lovely flowery perfect world where designers and computer developers live is described here: Perfect World Solar Water Heater Controller.


Divider


In the real world there are imperfections. These require some modification of the perfect world solution described previously. This section describes the modifications I made to the solar water heater controller to suit the conditions and challenges encountered as I installed it into our solar water heater system. If you are looking at this project and attempting it on your own system you will find that some of the challenges mentioned here don’t apply.

The description of the “perfect world solution” (for this solar water heater controller that is) can be found here: Perfect World Solar Water Heater Controller.

What Challenges the Real World Has in Store

When the time comes to install a retrofit system like this there are all sorts of challenges that rear their heads when you are scrambling around on the roof trying to connect this to that while still ensuring it will work properly. Here are the main issues I grappled with with my system.

  • Dealing with an existing system restricts some of your options to non-ideal solutions.
  • Placement of the temperature sensors are non-ideal.
  • Agreement between temperature sensors. I found that even a selection of thermistors that were supposed to be the same did not agree and so I compared the temperature sensors against each other by putting the sensors together in a glass of water. By taking a number of readings at different temperatures I was able to develop an expression to relate the wayward sensor to the others. This appears as a one-line adjustment in the code.
  • Mystery temperature sensors. When I finally got onto the roof and tried to install my temperature sensors I found it very hard to get a good location and ensure good contact with the water pipes. Seeing as the original controller had two thermistors already installed in some purpose made sockets I decided to use these. The problem was that these were unknown sensors with unknown curves. I needed to test these and develop the expressions for these sensors and add that to the code. This appears as a second function for converting the thermistor voltage readings to temperatures for the Tout and Tcyl sensors. This process is described here: Finding an Unknown Thermistor’s resistance curve.
  • Electrical noise and interference. All my testing had been done with a 12V battery because the 12V supplies had not arrived from Banggood at that time. This meant that there was very little electrical noise anywhere in the system. Once the system was running with the transformer, and connected to the pump, the action of the relay would sometimes upset the LCD display. All attempts to improve this never quite solved it. An oscilloscope indicated that after the addition of a number of 0.1μF capacitors (aka “Fairy-dust” according to Adafruit) the system noise was pretty minimal. Because the Arduino and everything else was not being upset by the noise, I concluded that the LCD screen itself was just too sensitive and if I was the build the system again I would use a different display. Incidentally I tried using a separate 5V supply to the Arduino and fully isolating the Arduino and 5V system from the relay but this made no difference.
  • A solar water heater panel is a very effective absorber, trying to simulate its performance is very hard. The Stagnation Temperature Sensor (Tstag) had a critical role in the solar water heater controller logic in the “Perfect World” system. I had thought it would be easy to simulate the stagnation temperature of the panel by making a small black metal panel, mounting the sensor on it, and encapsulating it in a glass boiling tube. When I tried it out initially it looked promising. The plate got very hot with little need for careful arrangement (60°C). Unfortunately when it was mounted on the roof next to the panel I very soon found out just how effective at capturing heat the panels are. The stagnation temperature sensor was reading a high temperature, but the panel inlet and outlet temperature were at least 10°C higher so the logic did not work. I changed the set points so the stagnation temperature would not prevent the pump from running except on days when it would be bitterly cold, gray, dismal, and there would be no solar heat to capture. Because the stagnation temperature sensor still loses heat quite quickly, it still has a governing role detecting frost risk conditions.

Arrangement of the System

The original system has a not got ideal locations for the temperature sensors, and so there is some lag between target temperatures on the panel being reached and the temperature reaching the sensors when the pump is off. This is because the heat has to travel through the metal pipework and stationary water to get to the sensor.

No temperature sensors are located in the hot water storage tank either. The temperature used is on a mixing junction where the hot water is drawn from the tank to be circulated through the panel and where fresh make up water is added. This has the advantage in being responsive to cold water being added to the system. Newly added cold water will be drawn into the solar water heater circulation system before going into the hot water storage tank.

Real World System Components
Click on the image for a high resolution version.

Where it would have been nice to have the stagnation temperature sensor (Tstag) attached to a bit of solar panel absorber within the panel enclosure, this was not possible. The best approximation was mounting it on the roof beside the panel where it would be exposed to the same amount of sunlight, wind, rain, and all other weather conditions affecting the solar panel.

Existing Equipment

Because I was retrofitting the controller to an existing system there were a number of key items that were reused (aside from the obvious ones like the panel and the hot water storage tank).

Pump and it’s Protection

The pump is a Wilo Star RS 25/6 circulation pump. These are pretty common and well made. It has run for many years and never missed a beat and it is likely to run happily for many years yet. When I was trying to solve the electrical noise issue one of my first thoughts was “Grrrr! Noisy cheapo pump.” This wasn’t the case at all, the Wilo pump was well protected with built in capacitors and compliant with various standards concerning electrical noise and interference. So, good pump, no problems there.

The pump is set for 99W output and so will draw no more than 0.41A.

If you are interested, here is the pump curve from the Wilo Star RS 25/6 datasheet.

Wilo Star RS-25/6 Pump Curves

Source: Wilo Datasheet: Star RS-25/6

Thermistors

As discussed above, I decided to make use of the existing Thermistors because they were designed to fit snuggly into the sensor pockets in the pipe fittings around the solar water heater system. I probably would have struggled to get as good thermal contact between the sensors and the pocket walls as the original sensors were getting.

The thermistors were unknown so I had to investigate how their resistance changed with temperature in order to find the Beta value for the thermistors and hence the expression for their temperature readings. I used a method as described here: Finding an Unknown Thermistor’s resistance curve.

Because the thermistors were already mounted in a place where I had no control over the temperature being measured, I took a number of readings over the course of a day from when the system was cold to when it was running full power on a bright sunny day. My method was to disconnect the sensor and read its resistance and then reconnect it to the existing controller (it was still able to read the sensors) and get it to display the temperature. When the system was running full power during the heat of the day, I would bypass the controller and connect the pump directly to the mains supply so the water would still circulate.

The end result was a series of readings spanning the typical range of temperatures the system would be likely to encounter.

I was able to calculate a Beta value of 3410K.

Now that the system is installed and running I have observed that the two existing thermistors appear to read consistently 1.5°C lower than the temperature readings from the other thermistors with the known Beta Value. I suspect the problem is not with the method used to determine the Beta Value but with the readings from the original controller. Given that the Senztek controller was not all that well set, I shouldn’t be surprised that it doesn’t calculate the temperatures from the thermistors correctly.

Logic for the Real World System

The logic shown here differs a little from the “perfect world” solar water heater controller system in that the Stagnation temperature sensor’s role has been diminished by using set points that mean that it has the pump enabled most of the time. Also, the inlet temperature sensor is only for reporting now rather than having a role governing the pump operation. This change was partly due to the mismatch between the inlet and outlet temperature sensors and the potential risk of this causing poor performance. Instead the temperature differential between the mixer at the base of the hot water storage cylinder and the solar water heater collector outlet was used to govern the pump’s operation.

Logic for controlling the Solar Water Heater Pump in the real world arrangement
Click on the image for a readable version of this image

Circuit for the Real World System

The circuit diagram and perf’ board arrangement for the system are shown below. This is unchanged from the circuit for the “Perfect World” solar water heater controller.

Solar Water Heater Controller Circuit Schematic
Circuit Diagram - Click on the image for Hi-res version.

Solar Water Heater Controller Perf Board Layout
Circuit Board Layout – perf’ board - Click on the image for Hi-res version.

At the heart of the system is an Arduino Nano. The demands on the Arduino are not large and so any Arduino could be used.

Opto Isolated Relay Module

An opto-isolated relay module from Banggood was selected to control the pump. Flyback diodes and various LEDs are included in the package. All it requires is a ground connection, 5v supply, and a signal. Using the jumper on the module the relay can be set to trigger with a high or a low signal. The manufacturer claims the relay can handle 240V 10A AC. I have seen a few comments in various forums that suggest the terminal connections themselves are not capable of carrying that sort of current, but the pump used in our solar water heater system only draws 0.5A anyway, so the module is quite suitable for the task.

The LCD screen is a typical 16x2 type. I have not connected the back light because that would be a little pointless.

The other components of significance are the thermistors and the 5V regulator.

Here is a list of the components used.

  • Arduino Nano.
  • LCD display – 16x2. This is not the i2c version – although I would suggest the 12c version might be better.
  • Two NTC 10kΩ thermistors with Beta Values of 4100K.
  • Two NTC 10kΩ thermistors with Beta Values of 3410K from the original system.
  • 12V DC Plug pack supply.
  • A button.
  • Two LEDs.
  • MC78T05CT regulator capable of outputting 3A at 5V not that we’re using anywhere near that much.
  • A 2200μF electrolytic capacitor.
  • A 100μF capacitor.
  • some 10kΩ resistors.
  • A couple of 330Ω resistors.
  • A 1kΩ resistor.
  • A 1N4001 diode acting as a flyback diode for the relay coil, but probably completely superfluous with the flyback diode built into the relay module already.
  • A handful of 0.1μF capacitors liberally sprinkled throughout the circuit like “fairydust” as suggested by Adafruit.

Coding for the Real World System

Here is the code for the real world system.

Hopefully the comments through the sketch will explain how it works.

You can download the sketch from here: SWHControllerv5.ino

The sketch does not use any libraries that don’t come with the standard Arduino IDE. I think it works on older versions of the Arduino IDE too. In short, there is nothing particularly fancy in this coding.

/*
 * SWHControllerv5.ino
 * Hamish Trolove 2017 - www.techmonkeybusiness.com
 * 
 * This sketch is to run a solar water heater and optimise the solar heat
 * yield.  It is also designed to be easily modified to suit local conditions.
 * A process similar to the Debounce routine in the Arduino Cookbook (pg155)
 * has been used to protect against short cycling of the pump. In addition to
 * this the Array average process (https://www.arduino.cc/en/tutorial/smoothing) 
 * has been used on all data to smooth the temperature readings and gain better
 * resolution.
 * 
 * This version reduces the effect of the Stagnation temperature for running
 * the pump while heating.  The reason for this is that the Stagnation Temperature 
 * enclosure as built cannot quite get up to the temperature of the panel which 
 * suggests it suffers from too much heat loss.  If the stagnation temperature
 * sensor can be installed in the Solar Water Heater enclosure then
 * SWHControllerv4.ino or SWHControllerv4a.ino would be able to be used.
 * 
 * In this version Tcyl and Tout are the previous controller's sensors which appear to be
 * 10kohm NTC thermistors with Beta values of 3410.
 * 
 * Tin has also been sidelined and in this version is only supplying information
 * for display on the screen.  It is not used in the logic during normal conditions.
 * 
 * There are four thermistors which are located;
 * Solar Water Heater Inlet - Tin
 * Solar Water Heater Outlet - Tout
 * Tank freshwater and SWH intake junction - Tcyl
 * and Tstag on a black plate in a pyrex tube to measure stagnation
 * temperatures and frost conditions.
 * 
 * The temperatures will control the operation of a pump through an optically isolated relay.
 * 
 * Each thermistor reading is displayed on the 16x2 LCD screen.  
 * 
 * Four buttons are connected.  At this stage only one is used and that is an override to turn
 * on the pump.
 * 
 * Circuit is described on www.techmonkeybusiness.com.
 * 
 * Connections are:
 * 
 * 10K B=4100 NTC Thermistors with 10K resistors in voltage dividers on A0, A1, A2, and A3
 * Push buttons with pull up resistors on D02, D03, D04, and D05
 * LED to indicate Frost Protection mode on D13
 * Pump relay control line on D06 (also connected to an LED)
 * LCD connections:
 * D12 to LCD RS pin
 * D11 to LCD E pin
 * D10 to LCD D7 pin
 * D09 to LCD D6 pin
 * D08 to LCD D5 pin
 * D07 to LCD D4 pin
 * 
 *The LCD panel is not lit.
 */

#include <LiquidCrystal.h>
#include <math.h>  // We need this to calculate temperature values from thermistors. 
const int ThermPinA = A0;  // The Tin thermistor on Analogue Pin 0
const int ThermPinB = A1;  // The Tout thermistor on Analogue Pin 1
const int ThermPinC = A2;  // The Tcyl thermistor on Analogue Pin 2
const int ThermPinD = A3;  // The Tstag thermistor on Analogue Pin 3

const int ButtonA = 02; // Some button pins defined (Pump Override)
const int ButtonB = 03;
const int ButtonC = 04;
const int ButtonD = 05;

const int LEDFrost = 13; // Front protection mode indicator LED
const int LEDPump = 6; // Pump running indicator LED.

boolean PumpFlag = false; //This is the flag to control the pump.  All
                          //control logic acts on this flag.
boolean FrostRisk = false; //Frost risk flag for driving LED.
boolean PumpFlagOld = false; //Flag to hold previous PumpFlag State
boolean ConfirmPumpRun = false; //Once all debouncing has occurred this is
                                //the flag to run the pump.

int CycleCount = 0;  //A counter for the number of times the loop has run for
                     //while awaiting stable readings.
int ThermValue = 0; // Somewhere to stick the raw Analogue Pin Value.
double Tin = 0.00; // Calculated Temperature Values after smoothing
double Tout = 0.00;
double Tcyl = 0.00;
double Tstag = 0.00;

//Arrays to hold historic data
const int numReadings = 40;

double TinArray[numReadings];      // the readings from the Temperature sensors
double ToutArray[numReadings];
double TcylArray[numReadings];
double TstagArray[numReadings];

double Tintotal = 800;   // the running totals for the temperature readings
double Touttotal = 800;  //Starting value is 20 x 40 ie starting value
double Tcyltotal = 800;  //in arrays.  If the arrays had been loaded with
double Tstagtotal = 800; //zeros then these figures would also be zero.

int readIndex = 0;              // the index of the Historic Data Array Pointer

double FrostTemp = 4.00; //Temperature below which Frost is a risk.
                      //Adjust for local conditions.
double SWHFrostThresh = 5.00; //Temperature at SWH at which pump
                      //will start if running in Frost protection mode.
double SWHOutHeatedTemp = 8.00; //Temperature which will indicate that
                      //hot water is in SWH for frost protection.                      
double SWHCylTdiff = 2.50; //Temperature difference between cylinder
                      //and solar water heater before pumping starts.
double SWHTdiff = 1.50;  //Min temperature difference between cylinder
                      //and solar water heater before pumping is stopped.

LiquidCrystal lcd( 12, 11, 7, 8, 9, 10 );   //Pins for the LCD Screen





void setup()
{
  lcd.begin(16, 2);  //set up the LCD's number of columns and rows:
  lcd.setCursor(0, 0);
  lcd.print("Starting");  //Print a message to the LCD
  pinMode(ThermPinA, INPUT);
  pinMode(ThermPinB, INPUT);
  pinMode(ThermPinC, INPUT);
  pinMode(ThermPinD, INPUT);
  pinMode(ButtonA, INPUT);  //This is the pump override button.
  pinMode(ButtonB, INPUT);  //spare
  pinMode(ButtonC, INPUT);  //spare
  pinMode(ButtonD, INPUT);  //spare
  pinMode(LEDFrost, OUTPUT);
  pinMode(LEDPump, OUTPUT);

  //Load the arrays with normal(ish) values to begin with.
  for (int Indexer = 0; Indexer < numReadings; Indexer++)
  {
    TinArray[Indexer] = 20.00;
    ToutArray[Indexer] = 20.00;
    TcylArray[Indexer] = 20.00;
    TstagArray[Indexer] = 20.00;
  }
  delay(2000);
}

void loop()
{
  //Look to see if the pump override button pressed.
  if(digitalRead(ButtonA)==HIGH)
  {
    ConfirmPumpRun = true;
  }
  else  //if pump override button not pressed then all is running automatically.
  {
    //The debounce process looks to see if the PumpFlag has remained stable for
    //eight seconds or to a count of about 40 ... more or less.

    for(CycleCount = 0; CycleCount < 40; CycleCount++)
    {
      PumpFlagOld = PumpFlag;


      //The next few bits are the smoothing process.
      //Remove the reading being replaced from the running total.
      Tintotal =  Tintotal - TinArray[readIndex];    // the running totals for
      Touttotal = Touttotal - ToutArray[readIndex];   //the temperature readings
      Tcyltotal = Tcyltotal - TcylArray[readIndex];
      Tstagtotal = Tstagtotal - TstagArray[readIndex];
      
      
      //Read the Thermistor Data into the arrays
      ThermValue = analogRead(ThermPinA);  //What is the raw value from the Voltage Divider?
      TinArray[readIndex] = TempCalc(ThermValue);  //Just duck out to the calculation routine.      
      ThermValue = analogRead(ThermPinB);
      ToutArray[readIndex] = TempCalc2(ThermValue);  //Note using TempCalc2 function for Beta 3410
      ThermValue = analogRead(ThermPinC);
      TcylArray[readIndex] = TempCalc2(ThermValue);  //Note using TempCalc2 function for Beta 3410
      ThermValue = analogRead(ThermPinD);
      TstagArray[readIndex] = TempCalc(ThermValue);

      //By comparison with the other thermistors, Tin reads low.  The following lines
      //have been added to correct this value.  Comment out as desired.
      TinArray[readIndex] =  TinArray[readIndex] + 0.0092 * TinArray[readIndex] + 1.28;
           

      //Add the new readings to the total
      Tintotal =  Tintotal + TinArray[readIndex];    //This process has swapped an old
      Touttotal = Touttotal + ToutArray[readIndex];  //value for a new one and updated the
      Tcyltotal = Tcyltotal + TcylArray[readIndex];  //total.  It is efficient with regard
      Tstagtotal = Tstagtotal + TstagArray[readIndex]; //to minimised double handling.
    
      //move the pointer along a bit.
      readIndex = readIndex + 1;
      //check to see if we've exceeded the array positions
      if (readIndex >= numReadings)
      {
        readIndex = 0;  //reset the pointer position to start of arrays.
      }

      //Calculate the 8 second average for use in the control logic to follow.
      Tin = Tintotal / numReadings; // Calculated Mean Temperature Values
      Tout = Touttotal / numReadings;
      Tcyl = Tcyltotal / numReadings;
      Tstag = Tstagtotal / numReadings;
   
      
      //Check whether there is a frost risk.
      if(Tstag < FrostTemp)
      {
        //Frost risk conditions are present.
        FrostRisk = true;
        //Has the solar water heater cooled to SWHFrostThresh temperature?
        //This is indicated by either Tin or Tout being below the threshhold Temp.
        if( Tout < SWHFrostThresh || Tin < SWHFrostThresh)
        {
          PumpFlag = true;  //Both SWH sensors are cold, turn on the pump
        }
        else
        {
          //The panel is still a little warm
          if(Tout > SWHOutHeatedTemp)
          {
            //The SWH has now got a fresh shot of hot water in it so the 
            //pump can be turned off.
            PumpFlag = false;
          }
        }
      }
      else //The conditions are not at risk of freezing
      {
        FrostRisk = false;
        //Check to see if the stagnation temperature is greater than 15degrees
        //or greater than 60% of the cylinder intake temperature.
        //If it is then enable the the pump.  If it's not then it's
        //probably a pretty awful day.
        if(Tstag > 0.6* Tcyl || Tstag > 15.00)
        {
          //Is the SWH outlet temperature greated than the Tank cylinder temp
          //plus the threshhold value SWHCylTdiff.
          if(Tout > Tcyl + SWHCylTdiff)
          {
            //The SWH is hotter than cylinder by an appropriate margin so
            //start the pump.
            PumpFlag = true;
          }
          if(Tout < Tcyl + SWHTdiff)
          {
            //The temperature difference across the panel is too low.  The quality of solar
            //intensity is too low.  Turn off the pump.
            PumpFlag = false;
          }

        }
        else
        {
          //There's no point running pump on a dull day.
          PumpFlag = false;
        }
      }
      //Checking the stability of the Pump state for both normal and frost operation.
      
      if(PumpFlagOld != PumpFlag)
      {
        CycleCount = 0;  //Unstable conditions therefore reset the counter.
      }

      // Display the readings on the LCD Display
      lcd.clear();
      lcd.setCursor(0, 0);
      lcd.print(Tin);
      lcd.setCursor(7, 0);
      lcd.print(Tout);
      lcd.setCursor(0, 1);
      lcd.print(Tcyl);
      lcd.setCursor(7, 1);
      lcd.print(Tstag);
      delay(200);
    
    }


      
    ConfirmPumpRun = PumpFlag;  //At last a status is decided for normal operation.
  }


  digitalWrite(LEDPump, ConfirmPumpRun); //This makes the pump run and lights pump LED.
  digitalWrite(LEDFrost,FrostRisk);


}


double TempCalc(int ThermIn) //For Thermocouples Tin and Tstag with beta values 4100
{
  double TempVal = 1/((1/298.00)+(1/4100.00)*log(1024.00/ThermIn-1.00));
  TempVal = TempVal - 273.00;  //Note the extra decimal places force float usage.
  return TempVal;
}

double TempCalc2(int ThermIn2) //For Thermocouples Tout and Tcyl with beta values 3410
{
  double TempVal = 1/((1/298.00)+(1/3410.00)*log(1024.00/ThermIn2-1.00));
  TempVal = TempVal - 273.00;  //Note the extra decimal places force float usage.
  return TempVal;
}

What Could be Better.

So this project was a one off, and I have no intention of going through it again to make it all perfect. But having gone through the project here are a few thoughts about what could be done better if I were to do it again.

  • Initially I designed the system for a 12v supply. As it happened there was nothing in the system that could not have run off a 5V supply. Rather than dump energy through the regulators on the board and on the Arduino, I should have just used a 5V wall transformer adapter thing.
  • It would have been nice to have the same sensors throughout the system. If they had all been calibrated and compared, then there would be the ability to use more tightly defined set points rather than having to account for the uncertainty created by having mismatched temperature sensors.
  • A session calibrating the sensors properly – as mentioned above this would have allowed me to define the set points more tightly and so potentially squeeze just a little more heat out of the sun.
  • A better display – Yes…The display. Well, there are several solutions that are possible here. I do not have an i2c version of the 16x2 LCD display to try but the i2c pins on the Arduino are available for use so it would be a relatively simple modification to the circuit and coding to cater for this. An alternative would be to use a simple 4 digit display and a couple of extra buttons to step through the sensor readings. The other alternative would be to not use a display at all, although the display is quite useful when setting up the system and tuning it.
  • Solid State Relays. To reduce noise in the system solid state relays would be a useful addition. Because the pump’s current draw is relatively low, it is not hard to find suitably rated solid state relays to do the job. The modules available use all the same connections as the opto-isolated module used in this project. The only change would be inverting the trigger signal because the solid state relays modules that I have encountered all seem to use a Low trigger.
  • Stagnation Temperature sensor mounted in the SWH enclosure and using the same materials/ absorptive surfaces as the actual panels. Yep, accurately simulating the panel would be fantastic, but it would be expensive and probably not gain enough of a performance advantage to make it worthwhile.

Isn’t hindsight is a wonderful thing?


Divider


The information provides is accurate to the best of my knowledge, but describes what I did and what worked for me. No responsibility is accepted for any failure of equipment or damage caused by attempting to follow these instructions. You’re probably working with main electricity, so watch what you’re doing with that stuff. It bits hard. The information is also provided in an as-is-where-is basis and as such I cannot provide support to help you solve your own problems you may encounter.