Environment Control for Incubator - Please review my coding.

Hello all you wonderful people!

So, I am hooked. The Arduino platform is by far the greatest thing since Lego or Scalextrics! I have had so much fun and learned so much about everything from coding to electronics since I first stumbled upon this little gem. I have the UNO R2 by the way, well, I have three. I fried the ATMEGA chip on one and... long story. I now have three boards and three spare chips.

Down to business though. I could really use some advice on my current project. It is a controller which reads the temperature and humidity and switches on either a fan or a heating pad to keep the environment within a certain range. It is for an incubator which houses plant cultures.

Firstly, I worry that my code, although it may appear to be correct and it runs fine, actually isn't. I don't know how well my sensor averaging works to be honest - the temperature sometimes hangs on a round figure for a good 10 loops.

As far as physical wiring goes, well, it is wired correctly-ish. I know I am being naughty for not using pull down resistors and simple stuff like that but everything seems to work so.... bleh!

Here is my code, and please, feel free to share it around!!!

// TEMP SENSOR IS 3.3V!!! Don't mess that up!
// Connect Temp Pins SDA and SCL to A4 and A5 respectively.

//Humidity is 5v.
// Connect Analogue Pin on TRH sensor to A0

//LCD uses PWM pin 2 as RX

//Relays are on Pins 4 and 7


#include <SoftwareSerial.h>
#include <Wire.h>


///////////////////////////////////////////////////////////////////////////////
///////////////////////// setup ///////////////////////////////////////////////
int relay1 = 4;
int relay2 = 7;

const int numReadings1 = 10;

int readings1[numReadings1];      // reading from analogue in
int index1 = 0;                  //  index of current reading
float total1 = 0;                  // running total
float average1 = 0;                // average

const int numReadings2 = 10;

int readings2[numReadings2];      
int index2 = 0;                  
float total2 = 0;                  
float average2 = 0;                

byte msb;
byte lsb;
int temperature;
float celcius;

float adcValue = 0;
float voltage = 0;
float percentRH = 0;
SoftwareSerial LCD2(3,2); // pin 2 = TX
int tmp102Address = 0x91 >> 1;

void setup()
{
  pinMode(relay1, OUTPUT);
  pinMode(relay2, OUTPUT);

  LCD2.begin(9600); //serial connection baud
  Wire.begin(); // for the I2C thingy
  delay(500); // take a breath

  LCD2.write(254); // Special Character
  LCD2.write(128); // move cursor to beginning of first line
  LCD2.write("      Auto      ");
  LCD2.write("  Enviro Control");

  delay(2000);

  LCD2.write(254); 
  LCD2.write(128);
  LCD2.write("                ");
  LCD2.write("                ");

  LCD2.write(254); 
  LCD2.write(128);
  LCD2.write("  Incubator V1  ");
  LCD2.write("Make Life Easier");

  delay(2000);

  LCD2.write(254); 
  LCD2.write(128);
  LCD2.write("                ");
  LCD2.write("                ");

  LCD2.write(254); 
  LCD2.write(128);
  LCD2.write(" Sensor Values  ");
  LCD2.write(" Are Aggregated ");

  delay(2000);
}

//-------------------------------------------------------------------------------------------


void loop(){ 
  // Pulled this from the sparkfun example I think...

  Wire.requestFrom(tmp102Address,2); 
  if (2 <= Wire.available())  // if two bytes were received 
  {
    msb = Wire.read();  // receive high byte (full degrees)
    lsb = Wire.read();  // receive low byte (fraction degrees) 
    temperature = ((msb) << 4);  // MSB
    temperature |= (lsb >> 4);   // LSB
    celcius = temperature*0.0625;
  }

  // this seems to be correct.
  percentRH = (analogRead(0) / 675.84 * 100.0);
  delay(100);


  // This part here averages out the sensor readings. I found it on a tut online. No credit to me.

  total1= total1 - readings1[index1];         
  // read from the sensor:  
  readings1[index1] = percentRH; 
  // add the reading to the total:
  total1= total1 + readings1[index1];       
  // advance to the next position in the array:  
  index1 = index1 + 1;                    

  // if we're at the end of the array...
  if (index1 >= numReadings1)              
    // ...wrap around to the beginning: 
    index1 = 0;                           

  // calculate the average:
  average1 = total1 / numReadings1;         

  /////////////////^HUMIDITY///////////////////////////

  /////////|TEMPERATURE//////////////////////

  total2= total2 - readings2[index2];         
  readings2[index2] = celcius; 
  total2= total2 + readings2[index2];       
  index2 = index2 + 1;                    
  if (index2 >= numReadings2)              
    index2 = 0;                           
  average2 = total2 / numReadings2;         



  LCD2.write(254); 
  LCD2.write(128);
  LCD2.write("                ");
  LCD2.write("                ");

  LCD2.write(254); 
  LCD2.write(128);
  LCD2.write("Temp (c)=       " );
  LCD2.write("Humidity=       " );

  LCD2.write(254); 
  LCD2.write(138);
  LCD2.print(average2);
  LCD2.print((char)223); // Degrees symbol
  LCD2.write(254);
  LCD2.write(202);
  LCD2.print(average1);
  LCD2.print("%");
  delay(1000);

  ////////////////////// RELAYS //////////////////////////

  if(average1 > 90) // if it is more than 90% switch the fan on
  {
    digitalWrite(relay1, HIGH);
  }
  else
  {
    digitalWrite(relay1, LOW);
  }

  if(average2 < 25) // If it is cooler than 25 switch the heater on
  {
    digitalWrite(relay2, HIGH);
  }
  else
  {
    digitalWrite(relay2, LOW);
  }
}

Alrighty, so it looks messy, granted, but how is it? Is it going to perform correctly -I sort of need it to, the cultures will die if things fail. As it is, it appears to do what it is meant to, so I guess it works... right? Right???

Any hints on making things smoother, more accurate or even corrections??? I would really appreciate a guiding hand on this! I'll do what I can to pay it forward to the community in the future. I know that using delay(xx); is a little naughty but I still trying to get my head around the millis(); option :frowning:

Um, some details on the hardware: Humidity sensor is HMZ-333a. Temp sensor is TMP102. The LCD is the sparkfun 16x2 serial enabled LCD (easier to work with). The relays are NPA-AS3. The Fan is 12v, with its own power supply and the heating pad is 240v with its own power supply. I'm from South Africa, if that makes a difference :wink:

Thank You! Please grill me, shout at me, correct me and critique me - its how we learn right!

KD.

    msb = Wire.read();  // receive high byte (full degrees)
    lsb = Wire.read();  // receive low byte (fraction degrees)

There is no way that msb and lsb need to be global variables. They should be local to this block. Reducing the scope of variables is desirable.

  // this seems to be correct.
  percentRH = (analogRead(0) / 675.84 * 100.0);

There is no clue as to what is attached to analog pin 0, so we'll have to take your word for it.

  delay(100);

Well, OK. I can't, for the life of me, imagine why.

  LCD2.write(254); 
  LCD2.write(138);
  LCD2.print(average2);
  LCD2.print((char)223); // Degrees symbol
  LCD2.write(254);
  LCD2.write(202);
  LCD2.print(average1);
  LCD2.print("%");

I've already forgotten what average1 and average2 are. averageT and averageH would help ME remember.

It really isn't clear why you need to keep an array of values. You subtract a value from totalN, replace some element in the array with a new value, and add the new value to totalN. Simply aassigning the new value plus 9/10 of the old value value to totalN would be sufficient, and require far less SRAM.

  if(average2 < 25) // If it is cooler than 25 switch the heater on
  {
    digitalWrite(relay2, HIGH);
  }
  else
  {
    digitalWrite(relay2, LOW);
  }

Turning the heater on when the temperature is 24 or lower and off when it is 26 or higher is generally better. as it is, the heater will be turned on and off quite often when the temperature is nearly 25.

Paul, thank you! This is exactly what I'm after, not just a "here is a correction to your code, plug it in and be done with it."
I've got enough to work through this evening based on your recommendations and I will do so - as in, researching topics such as the global variables issue and cleaning up the nonsensical variable naming system I've adopted. Of course I'm not esspecially good with this considering I'm still finding my feet but I will persist and come back at ya with some elegant code. Hopefully! I still leave out semicolons left right and centre resulting in... well, embarrassment.

Thank you again for the advice. I really appreciate it.

KD.

Right, after much reading and some more learning I have made the suggested changes, as well as one or two additions.

I am not sure what the norm is regarding posting revisions - whether snippets of corrected code should be posted or the whole thing again...? Anyhow, here be the new version!

// TEMP SENSOR IS 3.3V!!! Don't mess that up!
// Connect Temp Pins SDA and SCL to A4 and A5 respectively.

//Humidity is 5v.
// Connect Analogue Pin on TRH sensor to A0

//LCD uses PWM pin 2 as RX

//Relays are on Pins 4 and 7


#include <SoftwareSerial.h>
#include <Wire.h>


///////////////////////////////////////////////////////////////////////////////
///////////////////////// setup ///////////////////////////////////////////////
int relay1 = 4;
int relay2 = 7;

float TempSmoothedVal = 0;
int TempSamples = 5;

float RHSmoothedVal = 0;
int RHSamples = 5;

int temperature;
float celcius;

float adcValue = 0;
float voltage = 0;
float percentRH = 0;
SoftwareSerial LCD2(3,2); // pin 2 = TX
int tmp102Address = 0x91 >> 1;

void setup()
{
  pinMode(relay1, OUTPUT);
  pinMode(relay2, OUTPUT);

  LCD2.begin(9600); //serial connection baud
  Wire.begin(); // for the I2C thingy
  delay(500); // take a breath

  LCD2.write(254); // Special Character
  LCD2.write(128); // move cursor to beginning of first line
  LCD2.write("      Auto      ");
  LCD2.write("  Enviro Control");

  delay(2000);

  LCD2.write(254); 
  LCD2.write(128);
  LCD2.write("                ");
  LCD2.write("                ");

  LCD2.write(254); 
  LCD2.write(128);
  LCD2.write("  Incubator V1  ");
  LCD2.write("Make Life Easier");

  delay(2000);

  LCD2.write(254); 
  LCD2.write(128);
  LCD2.write("                ");
  LCD2.write("                ");

  LCD2.write(254); 
  LCD2.write(128);
  LCD2.write(" Sensor Values  ");
  LCD2.write(" Are Aggregated ");

  delay(2000);
}

//-------------------------------------------------------------------------------------------


void loop(){ 
  // Pulled this from the sparkfun example and made msb and lsb local variables as advised by PaulS.

  Wire.requestFrom(tmp102Address,2); 
  if (2 <= Wire.available())  // if two bytes were received 
  {
    byte msb;
    byte lsb;
    msb = Wire.read();  // receive high byte (full degrees)
    lsb = Wire.read();  // receive low byte (fraction degrees) 
    temperature = ((msb) << 4);  // MSB
    temperature |= (lsb >> 4);   // LSB
    celcius = temperature*0.0625;
  }

  // this seems to be correct.
  percentRH = (analogRead(0) / 675.84 * 100.0);

  TempSmoothedVal = TempSmoothedVal + ((celcius - TempSmoothedVal)/TempSamples);

  RHSmoothedVal = RHSmoothedVal + ((percentRH - RHSmoothedVal)/RHSamples);


  LCD2.write(254); 
  LCD2.write(128);
  LCD2.write("                ");
  LCD2.write("                ");

  LCD2.write(254); 
  LCD2.write(128);
  LCD2.write("Temp (c)=       " );
  LCD2.write("Humidity=       " );

  LCD2.write(254); 
  LCD2.write(138);
  LCD2.print(TempSmoothedVal);
  LCD2.print((char)223); // Degrees symbol
  LCD2.write(254);
  LCD2.write(202);
  LCD2.print(RHSmoothedVal);
  LCD2.print("%");
  delay(1000);

  ////////////////////// RELAYS //////////////////////////

  if(RHSmoothedVal > 90) // if it is more than 90% switch the fan on
  {
    digitalWrite(relay1, HIGH);
  }
  else if(RHSmoothedVal < 85)
  {
    digitalWrite(relay1, LOW);
  }

  if(TempSmoothedVal < 25) // If it is cooler than 25 switch the heater on
  {
    digitalWrite(relay2, HIGH);
  }
  else if(TempSmoothedVal > 28)
  {
    digitalWrite(relay2, LOW);
  }
}

Paul, regarding your suggestions, I read up on global and local variable scopes and I think what I have done is correct - I placed them within the "wire.request-if-loop".

Regarding what is connected to pin 0, I don't fully understand - have I made a general error regarding proper formatting of my code or do I need to declare something attached to pin 0? Apologies if this seems like a really stupid question.

That "delay(100);" has been removed. I can't recall why I put it there in the first place. Maybe to give the sensor a little rest :wink:
Sorted out my naming system and completely rethought the averaging system. I feel that this new one may be more effecient.... but my gut feel has been wrong before.... eg: delay(100);

Your advice regarding the switching if the relays has been fully implemented and makes complete sense. Reckon this will extend the life of all components involved in the process!

Any more advice will be very appreciated guys and gals!

I have decided to post a picture of this device. I have gotten over the potential embarrassment of showing a messy thing such as this to a community of precise and logical thinkers and designers and thought it may provide some entertainment value!

The Top View.

The view of... well its a "PCB" I made by drilling holes into perspex and connecting wires all over the place... like an old telephone exchange or a legit Moog synth.

And the view of the inside. That white pipe houses the sensors and is inserted into the incubator.

The style of this design I have decided to call "Shabeen Chic*" - I term I coined just recently.

    byte msb;
    byte lsb;
    msb = Wire.read();  // receive high byte (full degrees)
    lsb = Wire.read();  // receive low byte (fraction degrees)

4 lines of code, where 2 would do:

    byte msb = Wire.read();  // receive high byte (full degrees)
    byte lsb = Wire.read();  // receive low byte (fraction degrees)

Does it matter? Functionally, no. Esthetically, yes (at least to me).

The temperature and celcius variables do not need to have global scope, either.

While it works the way you have it, the smaller the scope, the less chance of accidentally changing something in unintended ways.

My preference is that you re-post code just like you did it. All the code, in a new reply.

Regarding what is connected to pin 0, I don't fully understand - have I made a general error regarding proper formatting of my code or do I need to declare something attached to pin 0? Apologies if this seems like a really stupid question.

Magic numbers, like the 0 in the analogRead() call should be avoided. Add a global variable, or a static local variable, using the const keyword, that reflects what is attached to the pin, and use that name in place of the 0.

  static const byte humidityPin = 0;
  percentRH = (analogRead(humidityPin) / 675.84 * 100.0);

Now, there is no doubt that you are reading from the correct pin.

I have decided to post a picture of this device. I have gotten over the potential embarrassment of showing a messy thing such as this to a community of precise and logical thinkers and designers and thought it may provide some entertainment value!

Always great to see pictures of projects. Thanks.

First, thanks for getting back to us with the results. I can't speak for everyone, but I alwways like to see someone "get it" and tell us about it.

I have decided to post a picture of this device. I have gotten over the potential embarrassment of showing a messy thing such as this to a community of precise and logical thinkers and designers and thought it may provide some entertainment value!

Hey, you haven't seen some of the hardware rat's nests I've produced, so don't you be embarrassed about your efforts. Many of my projects remain as components mounted on a prototyping shield and small breadboard. Too much bother to enclose and solder it all, but too useful to dismantle.

Good job! Seriously!

There are some style type issues that you could address - they won't change the way the sketch functions but may make it easier for whoever has to maintain it later - you, probably.

Names like relay1 and relay2 don't give much of a clue what they're controlling. Why LCD2 - is there another?

Lots of magic numbers in the code - 223, 675.84, 90, 85 etc. Better to define them as consts with a nice name in your setup section - easier to find and change later.

For personal preference I'd break out the code in loop into separate functions - perhaps one to handle getting the smoothed temperature, one for the humidity and one to handle the relays. No functional gain, just easier to read.