A helping hand required - Noob needs some sketching advice..

Hello People, I am after some help.

Having never programmed a computer or soldered a resistor I have a home project to build a small scale anaerobic digester in my garden. This is working well and I have now written the sketch below to run it..
What I have done works but is not as good as it could be. The coding is a complete mess, but I know no better. I am hoping that some brave soul would take a look and recode it the way it should be done.
The process I am trying to achieve is as follows.

Measure Temperature
LM35 to measure the temperature
2 buttons to change the thermostat temperature
Relay to turn on and off heater
To Run every 10 minutes.

Measure Distance
Measure a distance and activate a valve to release pressure when full. I am currently activating the solenoid for 5mins but what I really want is to measure it and to close when it reaches the value set by variable Tank_Empty

Sonar Distance Sensor
Relay to turn on and off the solenoid valve
Run every 20 minutes

Record Information to SD card
Read the RTC and take the information read and put it on the SD card, together with the temperature and the distance measurements.
Display data on LCD

Display the information on the LCD screen in real time, unlike mine sketch which only updates every time the alarm repeat executes.

I have tried to incorporate functions (which I think are similar to goto subroutines from the old days) but cannot get the right data passed back to the loop. So now my sketch runs from top to bottom.

What I would like is for someone to write it in the correct format as I have spent hours trying to sort this out, but it is really quite hard to google something when you really have no idea what to type in the search box.

Thanks for looking...

Lee

/*
**********************************
****  INCLUDE LIBRARIES HERE  ****
**********************************
*/
 #include <LiquidCrystal.h> // LCD Library
 #include <NewPing.h> // Sonar Library
 #include <Wire.h> // Wire Library
 #include "RTClib.h" // Real Time Clock Library
 #include <EEPROM.h> // EEProm Memory Library
 #include <SD.h>
 #include <Time.h>
 #include <TimeAlarms.h>

/*
********************************
****  VARIABLE DEFINITIONS  ****
********************************
*/ 

#define TRIGGER_PIN  6 // Sonar Trigger 
#define ECHO_PIN     5 // Sonar Echo
#define MAX_DISTANCE 200 // Sonar Maximum Measure Distance
int Distance; // Define Distance Measure Function
float DateandTime; // Define Distance Measure Function
int TimeandDateR; // Define Distance Measure Function
int tempPin = A1;  // make variables// thermistor is at A0
int led =13; // led is at pin
float temp;  // make a variable called temp
int settemp = 40; // make a variable called temp
int swtu = 4;  // switch up is at pin 4
int swtd = 3;   // switch down is at pin 3
#define Relay_1 42 // Arduino Digital I/O pin number (BioGas Dump Valve)
#define Relay_2 44 // Arduino Digital I/O pin number (Immersion Heater)
#define Relay_3 46 // Arduino Digital I/O pin number (Mixer Drive)
#define Relay_4 48 // Arduino Digital I/O pin number (Spare)
#define RELAY_ON 0  // Define Relay On
#define RELAY_OFF 1 // Define Relay Off 
const float Tank_Full = 17; // Define BioGas Store Full Sonic Measurement in CM
const float Tank_Empty = 53; // Define BioGas Store Empty Sonic Measurement in CM 
const float Tank_Diameter = 84; // Define BioGas Store Diameter in CM 
int Tank_Current; // *** Initiate Current Biogas Store Distance
int Tank_Purge;// *** Initiate Purging Biogas Store Distance
const int chipSelect = 53;// *** Select CS Pin for SD Operation*** (10 on UNO 53 on Mega)
File gasdata;
/*
*******************************
****   LIBRARY SETUP HERE  ****
*******************************
*/ 
LiquidCrystal lcd(7, 8, 9, 10, 11, 12); //Define LCD Pin connections
NewPing sonar(TRIGGER_PIN, ECHO_PIN, MAX_DISTANCE); // Define Sonar Details
RTC_DS1307 RTC; // Define Real Time Clock



/*
*****************************
****  SETUP STARTS HERE  ****
*****************************
*/
 void setup() {
 lcd.begin(20, 4); 
  lcd.clear();
   Serial.begin(9600);
 analogReference(INTERNAL1V1); //Set internal 1.1v for temperature sensor 
 Wire.begin();
 RTC.begin();
 EEPROM.write (1,settemp); 
   // *** Initialize Pins so relays are inactive at reset. To Ensure unexpected operation during reboot.
  digitalWrite(Relay_1, RELAY_OFF);
  digitalWrite(Relay_2, RELAY_OFF);
  digitalWrite(Relay_3, RELAY_OFF);
  digitalWrite(Relay_4, RELAY_OFF);
   // *** Then set pins as outputs  
  pinMode(Relay_1, OUTPUT);   
  pinMode(Relay_2, OUTPUT);  
  pinMode(Relay_3, OUTPUT);  
  pinMode(Relay_4, OUTPUT); 
  lcd.clear();
  lcd.setCursor(0,0);
  lcd.print("Initialising Relays");  
  delay(4000);
  pinMode(10, OUTPUT); // Set SD Card CS pin 10 to output
  digitalWrite(10,HIGH);
  Serial.print("Initializing SD card...");
   if (!SD.begin(4)) {
  lcd.begin(20, 4); 
  lcd.clear();
  lcd.setCursor(0,0);
  lcd.print("SD Card Failed");
  Serial.println("Card failed, or not present");
// don't do anything more:
  return;
  }
  Serial.println("card initialized.");
  lcd.clear();
  lcd.setCursor(0,0);
  lcd.print("SD Card Initialized");
  delay(2000);
  LCDtitle();
  Alarm.timerOnce(10, OnceOnly);
  Alarm.timerRepeat(600, Repeats);
 }
/*
****************************
****  LOOP STARTS HERE  ****
****************************
*/

void  loop(){ 
  digitalClockDisplay();
  digitalWrite(Relay_1, RELAY_OFF);// set the Relay OFF
  Alarm.delay(1000); // wait one second between clock display
  //time();
  Serial.println("test");
  settemp = EEPROM.read(1); // read the settemp on the eeprom
  delay (250); // wait for the lcd to refresh every 250 milliseconds
 
}

void Repeats()
{
  digitalClockDisplay();
  Serial.println("Execute Routine");
  execute();
}

void OnceOnly(){
  execute();
}



void printDigits(int digits)
{
  Serial.print(":");
  if(digits < 10)
    Serial.print('0');
  Serial.print(digits);
}

void digitalClockDisplay()
{
  // digital clock display of the time
  Serial.println();
  Serial.print(hour());
  printDigits(minute());
  printDigits(second());
  Serial.println(); 
}
void execute() {
  //Sonar();
  //TimeStamp();
  //Thermostat();
  
  // *** Time Stamp ***
  DateTime now = RTC.now();
  static char DateandTime[20];
  sprintf(DateandTime, "%02u/%02u/%02u,%02u:%02u", now.day(), now.month(), now.year(), now.hour(), now.minute());
  Serial.println(DateandTime);
  //lcd.setCursor(0,0);
  //lcd.print("                    ");
  lcd.setCursor(0,0);
  lcd.print(DateandTime);
  
  // *** Sonar ***
  delay(100);
  int uS = sonar.ping_median(10);
  Distance = (uS / US_ROUNDTRIP_CM);
  lcd.setCursor(0,2);
  lcd.print("                    ");
  lcd.setCursor(0,2);
  lcd.print("Distance ");
  lcd.print(Distance);
  lcd.print("cm");
  Serial.print("Sonar Measurement = ");
  Serial.print(Distance);
  Serial.println("cm");
  //int Tank_Current = Distance;
  float Tank_Current=(uS / US_ROUNDTRIP_CM);
  float Tank_Volume=((Tank_Diameter/2) * (Tank_Diameter/2) * 3.142 * (Tank_Empty - Tank_Current));
  
  // *** Thermostat Start ***
   int tvalue = analogRead(tempPin);  // make tvalue what ever we read on the tempPin
   float temp = (tvalue / 9.31); // the math / conversion to temp
//   lcd.setCursor (0,0); // set the cursor to 0,0
//   lcd.print (temp);  // Print the current temp in f
////   lcd.print ('C');
//  // Serial.println (temp);  // print the temp it the serial monitor
   settemp = EEPROM.read(1); // read the settemp on the eeprom
   delay (1000); // wait for the lcd to refresh every 250 milliseconds
//    if             // if we se the switch up pin reading on 1 or 5 volts
//     (digitalRead(swtu)== 1 )
//   {
//     settemp ++; // add one to the settemp, the settemp is the ideal temperature for you
//   }
// else{}// other wise do nothing
// if
// (digitalRead (swtd) == 1)// if we detect a 1 on the other switch pin
// {(settemp --);// subtract one fromm the settemp
//  }
// else {}// else, do nothing
   if (temp < settemp) // if the temperature is lower
  {digitalWrite(Relay_2, RELAY_ON); // turn on the heater
  }
  else // if that doesn't happen, then turn the heater off
  {digitalWrite(Relay_2, RELAY_OFF);
  }
 lcd.setCursor (0,1); // set the cursor to 0,0
 lcd.print ("                    "); // Print set to and your ideal temperature in f
 lcd.setCursor(0,1);
 lcd.print ("Thermostat Set To "); // Print set to and your ideal temperature in f
 lcd.print (settemp);
 lcd.print ('C');
 //Serial.println(settemp);  // Print the settemp in the serial montior
 
EEPROM.write (1,settemp); /* write the most recent settemp in eeprom data stoage
   so that if the power is disconnected, you settemp is saved!*/
// delay (250); // wait 250 milliseconds
  lcd.setCursor(0,3);
  lcd.print("                    ");
  lcd.setCursor(0,3);
  lcd.print("Temperature= ");
  lcd.print(temp);
  lcd.print("C");
  Serial.print("Temperature = ");
  Serial.print(temp);
  Serial.println("C");
  
// *** Thermostat Finish ***

 if             
     (Distance <= Tank_Full && Distance !=0 )
   {
     digitalWrite(Relay_1, RELAY_ON);
     lcd.clear();
     lcd.setCursor(0,3);
     lcd.print("Tank Purging");
     delay(300000);
     // add one to the settemp, the settemp is the ideal temperature for you
     digitalWrite(Relay_1, RELAY_OFF);
   }
 else{}// other wise do nothing
 
{

  File datagas = SD.open("gaslog.txt", FILE_WRITE); // Open file for writing, remember to close it   

  // if the file is available, write to it:
  if (datagas) {
    DateTime now = RTC.now();
    // log time
    datagas.print(now.unixtime()); // seconds since 1/1/1970
    datagas.print(",");
    datagas.print(now.year(), DEC);
    datagas.print("/");
    datagas.print(now.month(), DEC);
    datagas.print("/");
    datagas.print(now.day(), DEC);
    datagas.print(",");
    datagas.print(now.hour(), DEC);
    datagas.print(":");
    datagas.print(now.minute(), DEC);
    datagas.print(":");
    datagas.print(now.second(), DEC);
    datagas.print(',');
    datagas.print(Tank_Current);
    datagas.print(",");
    datagas.print(Tank_Volume);
    datagas.print(",");
    datagas.println(temp);
    datagas.close();
    Serial.println("Gas Data written to gaslog.txt");
  }  
  // if the file isn't open, pop up an error:
  else {
    Serial.println("error opening gaslog.txt");
  }


}
  
}














/*
********************************
****  FUNCTIONS START HERE  ****
********************************
*/

// *** LCD Title Start ***
void LCDtitle() {
   // set up the LCD's number of columns and rows: 
  lcd.begin(20, 4); 
  lcd.clear();
  lcd.setCursor(0,0);
  lcd.print("   FARM GAS POWER");
  Serial.println("Farm Gas Power");
  delay(1000);
  lcd.setCursor(0,1);
  lcd.print("   Test Digester");
  Serial.println("Test Digester");
  delay(1000);
  lcd.setCursor(0,2);
  lcd.print("  Energy in Motion");
  Serial.println("Energy in Motion");
  delay(1000);
  lcd.setCursor(0,3);
  lcd.print(" Tel: 01264 850159");
  Serial.println("Tel: 01264 850159");
  delay(1000);
//     // scroll 13 positions (string length) to the left 
//  // to move it offscreen left:
//   for (int positionCounter = 0; positionCounter < 19; positionCounter++) {
//     // scroll one position left:
//     lcd.scrollDisplayLeft(); 
//    // wait a bit:
//     delay(150);
//   }
// 
//  // scroll 31 positions (string length + display length) to the right
//   // to move it offscreen right:
//   for (int positionCounter = 0; positionCounter < 39; positionCounter++) {
//     // scroll one position right:
//     lcd.scrollDisplayRight(); 
//    // wait a bit:
//     delay(150);
//   }
//   
//    // scroll 16 positions (display length + string length) to the left
//     // to move it back to center:
//   for (int positionCounter = 0; positionCounter < 20; positionCounter++) {
//     // scroll one position left:
//     lcd.scrollDisplayLeft(); 
//    // wait a bit:
//     delay(150);
// }
}
// *** LCD Title End ***

I do not have time to help improve your code, but I love this comment. Does the code do what it says ? :slight_smile:

// *** Initialize Pins so relays are inactive at reset. To Ensure unexpected operation during reboot.

It use to, especially when I had it set on the RX pin

That certainly made me jump a few times :stuck_out_tongue_closed_eyes:

What was the problem with the Sonar() function that's commented out? Given that most of your variables are global, it looks as though the sonar code could just be cut and pasted into its own function.

The sonar was the the only function that actually did work.

I did try to change the program to use functions, but it ended up getting stuck and not returning any values to the loop. I think I am misunderstanding the variable types and getting the data back.

For example in the attached sketch the function purge executes but then never returns to the loop after completing (unless it never completes :~).

But I needed to get it running so reverted back to one I control by time, which is not ideal..

Digester02_Final.ino (15.3 KB)

Your while loop will indeed lock things up. You're looking for tank_purge to reduce to tank_empty but there's nothing in the loop that will ever change it. The tank level is presumably dropping, but you never measure it again so your sketch doesn't know that. Better this way:

while(Tank_Purge >= Tank_Empty && Tank_Purge !=0)
  {
  unsigned int uS = sonar.ping_median(10); // Send 10 pings and return median, get ping time in microseconds (uS).
  Tank_Purge=(uS / US_ROUNDTRIP_CM);
  }

Edit: removed float from tank_purge - would have masked the real variable

Thanks Wildbill..

But I think you gave me some credit for not being a noob... :slight_smile: But I am still floundering here..

The code below, should

(Fill the tank) Count backwards from 53(Tank_Current) to 17 (Tank_Full)

Then (Purge the tank) Count forwards from 17 (Tank_Full) to 53(Tank_Empty)
(Fill the tank) Count backwards from 53(Tank_Current) to 17 (Tank_Full)

But it stops, am I being dumb?

#include <NewPing.h>

#define RELAY_ON 0  // Define Relay On
#define RELAY_OFF 1 // Define Relay Off 
#define TRIGGER_PIN  5  // Arduino pin tied to trigger pin on the ultrasonic sensor.
#define ECHO_PIN     6  // Arduino pin tied to echo pin on the ultrasonic sensor.
#define MAX_DISTANCE 100 // Maximum distance we want to ping for (in centimeters). Maximum sensor distance is rated at 400-500cm.
#define Relay_1 42 // Arduino Digital I/O pin number (BioGas Dump Valve)
#define Relay_2 44 // Arduino Digital I/O pin number (Immersion Heater)
#define Relay_3 46 // Arduino Digital I/O pin number (Mixer Drive)
#define Relay_4 48 // Arduino Digital I/O pin number (Spare)
//#define Relay_5 36 // Arduino Digital I/O pin number (Spare)
//#define Relay_6 34 // Arduino Digital I/O pin number (Spare)
//#define Relay_7 32 // Arduino Digital I/O pin number (Spare)
//#define Relay_8 30 // Arduino Digital I/O pin number (Spare)
const float Tank_Full = 17; // Define BioGas Store Full Sonic Measurement in CM
const float Tank_Empty = 53; // Define BioGas Store Empty Sonic Measurement in CM 
const float Tank_Diameter = 84; // Define BioGas Store Diameter in CM 
int Tank_Current = 45; // *** Initiate Current Biogas Store Distance
int Tank_Purge;// *** Initiate Purging Biogas Store Distance
int Tank_Volume; // *** Initiate Tank Volume
//int Purge;

int Distance;

NewPing sonar(TRIGGER_PIN, ECHO_PIN, MAX_DISTANCE);

void setup() {
  Serial.begin(9600);
  }

void loop(){
    
 
  Serial.print("Tank Filling ... Height ");
  Serial.print(Tank_Current);
  Serial.println("cm");
  Sonar();
  // Serial Print Results
   //purge();

}

int Sonar() {
  delay(100);
  //int uS = sonar.ping_median(10);
  //Distance = (uS / US_ROUNDTRIP_CM);
  Tank_Current=Tank_Current --;
   while(Tank_Current <= Tank_Full && Tank_Current !=0){
    purge();}
  }

void purge()
{
  digitalWrite(Relay_1, RELAY_ON);
  
//  lcd.clear();
//  lcd.setCursor(0,0);
//  lcd.print("Gas Store Purge");
  delay(50);                      // Wait 50ms between pings (about 20 pings/sec). 29ms should be the shortest delay between pings.
  //unsigned int uS = sonar.ping_median(10); // Send 10 pings and return median, get ping time in microseconds (uS).
  //Tank_Purge=(uS / US_ROUNDTRIP_CM);
  Tank_Purge=Tank_Purge ++;
  Serial.print("Tank Purging ... Height ");
  Serial.print(Tank_Purge);
  Serial.println("cm");
  while(Tank_Purge >= Tank_Empty && Tank_Purge !=0);
  {
  //unsigned int uS = sonar.ping_median(10); // Send 10 pings and return median, get ping time in microseconds (uS).
  //Tank_Purge=(uS / US_ROUNDTRIP_CM);
    Tank_Purge=Tank_Purge ++;
  delay(100);}
  {
 
      }
  // Shut the Valve before returning... 
  digitalWrite(Relay_1, RELAY_OFF);
  Serial.println("Valve Closed"); }
  while(Tank_Purge >= Tank_Empty && Tank_Purge !=0);
  {
    Tank_Purge=Tank_Purge ++;
    delay(100);
}

I have removed the lines that were commented out in order to make things clearer, but it seems to me that this while loop is going to take some time to execute because the only thing that is changing is the Tank_Purge variable and that only increments 10 times per second anyway. What is the purpose of this loop ?

IncidentallyTank_Purge=Tank_Purge ++;is more conventionally written asTank_Purge++;

The whole sketch is supposed to monitor a gas holder rising as it fills using a sonar sensor to measure the distance. When it is full it will open a valve to empty the tank, measuring the distance increase as the gas holder falls.

In the sketch I have commented out the sonar and put in test data to simulate the distance measurement changing

However when it reaches the tank empty distance the function just stops, rather than dropping back to the loop.

The first time sonar is called, tank_current is 45. Tank_full is 17. So this:

  while(Tank_Current <= Tank_Full && Tank_Current !=0)
{
    purge();
}

Doesn't call purge. In the Sonar function, you reduce tank_current each time it is called until it's 17. At this point Purge will be called by the while loop. The problem though is that nothing in the purge function touches tank_current, so on the return from purge, tank_current is still 17 and forever will be. That while loop never exits, as you observe.

OK.. I get that

So when returning to the loop I also need to reset the Tank_Current to the Tank_Purge value?

I am so confused :~

This seems such a simple thing to do, arrrrgg. :slight_smile:

Am I using the wrong commands completely. This is how it works in my head.

Measure the distance.
Record the distance.
If it equals 17 call a function to open a valve and continue measuring until it reaches 53 then shut the valve.
Wait 10 minutes
Return to the top

The Alarms library is great, but you should be sure to check the readme file for it. It has two things that can be gotchas.

First delay() must never be used anywhere in the sketch. You can only use Alarm.delay() in sketches that incorporate the Alarm library. This can be very tricky because other libraries like the LiquidCrystal library may have the delay() instruction which then is expanded into your code.

I make modified versions of any library I am running in conjunction with the Alarm library by replacing delay() with Alarm.delay() in the library cpp file.

Also, for Alarms to work properly, an Alarm.delay() must be called on a regular basis somewhere within the sketch.

Other than watching out for these land mines, I have had excellent success with the Alarms library.

UKHeliBob:
IncidentallyTank_Purge=Tank_Purge ++;is more conventionally written asTank_Purge++;

UKHeliBob's version is the right way to do it but the original code should raise a big red flag because the behaviour of that code is undefined.

Try this:

int Sonar() 
{
delay(100);
int uS = sonar.ping_median(10);
Distance = (uS / US_ROUNDTRIP_CM);
if(Distance <= Tank_Full)
  {
  purge();
  }
}

void purge()
{
digitalWrite(Relay_1, RELAY_ON);
delay(50);                      // Wait 50ms between pings (about 20 pings/sec). 29ms should be the shortest delay between pings.
do
  {
  unsigned int uS = sonar.ping_median(10); // Send 10 pings and return median, get ping time in microseconds (uS).
  Distance=(uS / US_ROUNDTRIP_CM);
  Serial.print("Tank Purging ... Height ");
  Serial.print(Distance);
  Serial.println("cm");
  delay(100);
  }
while(Distance < Tank_Empty);

digitalWrite(Relay_1, RELAY_OFF);  // Shut the Valve before returning... 
Serial.println("Valve Closed"); 
}

It doesn't take care of the ten minute requirement, but I'm not sure it needs to - the purge only happens when the tank is full.

The Alarms library is great, but you should be sure to check the readme file for it. It has two things that can be gotchas.

First delay() must never be used anywhere in the sketch. You can only use Alarm.delay() in sketches that incorporate the Alarm library.

Is this really the case ? It is true that Alarm.delay() must be called in order for the alarms to be checked but that is not the same as saying that delay() must not be used.

From the Alarms Read Me file:

Note that the loop code calls Alarm.delay(1000) - Alarm.delay must be used
instead of the usual arduino delay function because the alarms are serviced in the Alarm.delay
method.
Failing to regularly call Alarm.delay will result in the alarms not being triggered so always use
Alarm.delay instead of delay in sketches that use the Alarms library.

Usually the best thing is to have at least one Alarm.delay() in the void loop(), so it is called frequently.

I went back to research your comment on the web. I could not find anything that specifically says the delay() should never be used. The various documents on the web say the sketch should use Alarm.delay. So I am not sure if delay() poisons the sketch or not.

As debugging step a long time ago I made sure that there were no delay() functions in any of my sketches, and I have had no trouble since.

When I get some time I will insert some delay()s into one of my sketches and test for reliable operation.

Wildbill,

Thanks that is perfect, it looks so simple when you know how..