Garduino Software

I’ve broken the sketch for the Garduino into 4 separate files.  The main module, called Garduino, is the flow control of the sketch, and contains the setup and loop methods.  The DataGather file contains the methods related to reading the sensors, and the OutputFormat source focuses on creating the output of the sensor data to the SD card.  The SDFunctions pde file contains the functions to read and write to the SD card.

Garduino

#include <Wire.h>
#include "RTClib.h"
#include <SdFat.h>
#include <SdFatUtil.h>
#include <FreqCounter.h>

int count=0;
char buffer[101];
int bufIdx=0;

#define ADDR_0 7
#define ADDR_1 8
#define ADDR_2 9
#define ANALOG A0
#define TEST_PAUSE 60000
#define PAUSE_ADJUST 6500
#define FREQ_MEASUREMENTS 5
#define DO_FILE

char fileName[] = "LOGGER00.CSV";
boolean debug=false;
unsigned long curTestTime=millis();
unsigned long preTestTime;
unsigned long testTimeDiff=0;

RTC_DS1307 RTC;

Sd2Card card;
SdVolume volume;
SdFile root;
SdFile file;

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

  pinMode(ADDR_0,OUTPUT);
  pinMode(ADDR_1,OUTPUT);
  pinMode(ADDR_2,OUTPUT);

#if defined DO_FILE
  Wire.begin();
  RTC.begin();

  if (! RTC.isrunning()) {
    Serial.println("RTC is NOT running!");
    // following line sets the RTC to the date & time this sketch was compiled
    RTC.adjust(DateTime(__DATE__, __TIME__));
  }
#endif

  Serial.println("Time,L1,L2,M1,M2,M3,T1,T2,T3,T4");

#if defined DO_FILE
  if(!initSD())
  {
    return;
  }

  if(!openFile())
  {
    return;
  }

  file.println("Time,L1,L2,M1,M2,M3,T1,T2,T3,T4");

#endif

}

void loop()
{

  digitalWrite(ADDR_0,LOW);
  digitalWrite(ADDR_1,LOW);
  digitalWrite(ADDR_2,LOW);

  int temp;
  float reading;

  while(wait());

#if defined DO_FILE
  file.writeError=0;
  DateTime now = RTC.now();
  String ts=timeStamp(now);
  Serial.print(ts);

  file.print(ts);
#else
  Serial.print(millis());
#endif

  reading=readFrequency(LOW,LOW,LOW);
  printFloatData(reading);

  reading=readFrequency(HIGH,LOW,LOW);
  printFloatData(reading);

  reading=readFrequency(LOW,HIGH,LOW);
  printFloatData(reading);

  reading=readFrequency(HIGH,HIGH,LOW);
  printFloatData(reading);

  reading=readFrequency(LOW,LOW,HIGH);
  printFloatData(reading);

  temp=readAnalog(LOW,LOW,LOW);
  printIntData(temp);

  temp=readAnalog(HIGH,LOW,LOW);
  printIntData(temp);

  temp=readAnalog(LOW,HIGH,LOW);
  printIntData(temp);

  temp=readAnalog(HIGH,HIGH,LOW);
  printIntData(temp);

  Serial.println("");
//  if(debug)
//    mySerial.print("");

#if defined DO_FILE
  file.println("");
  if(!file.sync())
  {
    Serial.println("Sync Error");
  }

#endif

}

boolean wait()
{

  testTimeDiff+=diff_time(&curTestTime,&preTestTime);
  if(testTimeDiff >= (TEST_PAUSE-PAUSE_ADJUST))
  {
    testTimeDiff=0;
    curTestTime=millis();
    return false;
  }

  return true;
}
unsigned long diff_time(unsigned long *cur,unsigned long *pre)
{
  unsigned long tm_diff;

  *pre=*cur;
  *cur=millis();

  if(*cur >= *pre)
  {
    tm_diff=*cur-*pre;
  }
  else
  {
    tm_diff=(*cur + (34359737 - *pre));
  }

  return tm_diff;
}

This project uses both the SD card in the logger shield, and the FreqCounter library, so the .h files are included at the top.  Most of the setup() is pretty straightforward; it is concerned with initializing the SD card and Real Time Clock (also part of the log shield).   The DO_FILE define allows me to test the code on Arduinos without the SD Logger Shield, and turns off all the SD card related code on demand.  The loop() simply waits for the wait function to return false, and then calls the data gathering commands and prints out the data.  The wait function is the only place where there is any trickiness to the code.  The variable testTimeDiff keeps track of the number of milliseconds from start of the previous test by calling the diff_time function.  The diff_time function normally just subtracts the time from the previous time the function was called, but it also compensates for the rollover of the millis counter, which occurs every 9 hours or so.  There is also the PAUSE_ADJUST define which is subtracted from the TEST_PAUSE.  TEST_PAUSE is set to 60000 milliseconds, or 1 minute, so the polling of the sensors occurs every minute.  When the frequencies are read for the moisture and light sensor, though, the clock is stopped, so I need to subtract the amount of time spent in the frequency counters to accurately pause for 1 minute between each sensor poll.

DataGather

int readAnalog(int S0, int S1, int S2)
{
  digitalWrite(ADDR_0,S0);
  digitalWrite(ADDR_1,S1);
  digitalWrite(ADDR_2,S2);

  return analogRead(ANALOG);

}

float readFrequency(int S0, int S1, int S2)
{
  unsigned long freqValues[5];

  digitalWrite(ADDR_0,S0);
  digitalWrite(ADDR_1,S1);
  digitalWrite(ADDR_2,S2);

  for (int n=0;n<FREQ_MEASUREMENTS;n++)
  {
    FreqCounter::f_comp=10;
    FreqCounter::start(250);  

    while(FreqCounter::f_ready == 0);

    freqValues[n]=FreqCounter::f_freq;

  }

  float freqReading=processValues(freqValues,5);

  return freqReading;

}

float processValues(unsigned long values[],int valSize)
{
  unsigned long max=0;
  int maxIndex;
  unsigned long min=4294967295;
  int minIndex;
  unsigned long total=0;

  for (int n=0;n<valSize;n++)
  {
    if (values[n]>max)
    {
      max=values[n];
      maxIndex=n;
    }

    if(values[n]<min)
    {
      min=values[n];
      minIndex=n;
    }
  }

  for (int n=0;n<valSize;n++)
  {
    if((n!=maxIndex)&&(n!=minIndex))
    {
      total+=values[n];
    }
  }

  float ave=(float)total/(float)(valSize-2);

  return ave;

}

The readAnalog function sets the address pins for the analog multiplexer (4051) and then reads the value of the analog pin (A0).  The various combinations of the 3 address pins select the 4 temperature sensors.  The readFrequency also uses the address pins to select the digital input on the digital multiplexer (74151) to put the pulse train on pin 5.  It then takes 5 measurements, each 250 ms long, and stores them in an array.  The array is passed to the processValues function, where the low and high value is thrown out, and the remaining 3 values are averaged.  During the 250 ms the frequency test is running, the millis() is not accumulating.  Since there are 5 tests for each frequency sensor, and there are 5 frequency sensors tested, a total of 6250 ms is “lost” to the millis() because the timer is turned off, and that value must be removed from the TIME_PAUSE to keep the test running at exactly 1 minute intervals.

OutputFormat

void printFloatData(float data)
{
  // 250ms Gatetime so frequency is 4 times the count
  data*=4.0;
  Serial.print(",");
  Serial.print(data);
/
#if defined DO_FILE
      file.print(",");
      file.print(data);
#endif
}

void printIntData(int data)
{
  Serial.print(",");
  Serial.print(data);
#if defined DO_FILE
      file.print(",");
      file.print(data);
#endif
}

String timeStamp(DateTime stamp)
{
  String time=String("")+int(stamp.year())+"/"+int(stamp.month())+"/"+int(stamp.day())+":"+int(stamp.hour())+":"+int(stamp.minute())+":"+int(stamp.second());
  return time;
}

These functions output the values from the sensors to the Serial port and the SD card.  Note the float data, which comes from the frequency counter, is multiplied by 4.  This is because the frequency tests are 1/4 of a second long, and I want the output value to be in Hertz.

SDFunctions

boolean initSD(void)
{
    // initialize the SD card at SPI_HALF_SPEED to avoid bus errors with
  // breadboards.  use SPI_FULL_SPEED for better performance.
  if (!card.init(SPI_HALF_SPEED))
  {
    Serial.println("card.init failed");
    return false;
  }

  // initialize a FAT volume
  if (!volume.init(&card))
  {
    Serial.println("volume.init failed");
    return false;
  }

  // open root directory
  if (!root.openRoot(&volume))
  {
    Serial.println("openRoot failed");
    return false;
  }

  return true;
}

boolean openFile(void)
{

  // create a new file
  for (uint8_t i = 0; i < 100; i++) {
    fileName[6] = i/10 + '0';
    fileName[7] = i%10 + '0';

    if (file.open(&root, fileName, O_CREAT | O_EXCL | O_WRITE)) break;
  }
  if (!file.isOpen())
  {
    Serial.print(fileName);
    Serial.println("failed on file.create");
    return false;
  }
  Serial.print("Logging to: ");
  Serial.println(fileName);

  return true;
}

These functions are simply stolen from the SDFat examples, and initialize the SD card and the log file.

Advertisements

The Sensor PCB

This Garduino takes input from various sensors and logs it to an SD card on a Logger Shield.  The moisture sensors and light sensors both generate a frequency of pulses as their output, and there is only 1 timer input on the Arduino Uno that can measure this (pin 5), so I use a 74LS151 Multiplexer to put these signals on pin 5 and use Pins 7 (D0),8 (D1), and 9 (D2) to addresses the various inputs.   The LM335 temperature sensor is an analog device which I can simply plug into the analog pins of the arduino and do an analogRead, but the SD shield uses A4 and A5, which leaves me with 4 free analog inputs.  Although I am only supporting 4 temperature sensors now, I decided to use an 4051 analog multiplexer to put all the temperature signals onto A0, leaving A1-A3 free for future purposes.  Pins 7-9 are also used to set the address on the 4051 as well.  Because the electrolysis that occurs on the probes of the moisture sensors when sending a direct current through the damp soil, the moisture readings will change over time, so it is best to test the soil resistance (the de facto measure of soil moisture) with an alternating signal.  The 556 and 555 supply this alternating signal to the 3 moisture probes.  This is the schematic of the complete circuit board.

Garduino Schematic

The left hand ribbon cable connector leads to another ribbon cable connector soldered onto Logger Shield.  Pins 1 & 2 connect to the  Arduino +5V and GND, pin 3 connects to the timer pin 5 on the Arduino, pins 4, 5 and 6 connect to Arduino pins 7, 8, and 9 for the multiplexer addressing, and pin 7 on the connector leads to the A0 pin on the Arduino pin to read the analog values of the temperature sensors.  The 20 pin ribbon cable on the right is to interface to the sensors, which will be discussed in a later blog entries.

I decided to use the EAGLE software to build my first PCB, which was produced by BatchPCB.  It took about 3 weeks to get the board after ordering it, which was apparently fairly fast, but BatchPCB trades time for cost, and the total cost of the board was about $30; far less than you would pay for other services.

This is a picture of the completed board attached to the Arduino Logger Shield.

PCB And Arudino with the Logger Shield

Somehow 2 connections which where on the Schematic did not make it on the Layout and onto the PCB, but luckily, it was fixable.  I still have a lot to learn about EAGLE.

Fixed mistakes in the PCB design

Solar Power

Since the location of the Garduino has no access to power, I decided to Solar Power it.

I bought this 12 V 10 Watt Solar Panel off eBay for about $50.  Since mounting brackets for these panels cost nearly as much as the panel, I decided to make my own with a piece of Home Depot Aluminum, a coupe of U Bolts, and some small L Brackets.

Normally, from what I’ve read, you want to angle the panel at the same angle as your latitude, but it was a whole lot easier for my band saw to cut the aluminum at a 45 degree angle, rather than the 39 degree angle my latitude would indicate.  I’ll have to live with that inefficiency.

Since I want the device to run even when the sun is down, I also needed a battery.  I just picked up a cheap 12 V 7Ah battery off the internet somewhere for that job.  To tie the Arduino, battery and panel together, I bought a solar charge controller off Amazon.  The Kintrex SPC0601 was the cheapest, lowest power one I could find, but it fits the power levels that I am working with.  It basically has 3 connections, one for the battery, one for the panel and one for the load, and it’s job it to manage charging the battery during the day, and running the load off the battery or the panel as appropriate.

This picture shows the Kintrek and the Battery in the water proof box.  It also shows the wires to the various sensors as well as the sensor controller board, which I will discuss in a later post.

One thing to note is the 2.1mm power jack in the picture.  I originally plugged the Arduino Uno directly into that jack, but the voltage regulator on the Arduino got very warm. It got warm because the voltage regulator is very wasteful of my limited power.  Assume that my Arduino and sensor circuitry draws 100mA (probably a little high, but it makes the math easy).  Since the Arduino runs off of 5 V, the voltage regulator has to drop 7 volts (12 – 5 volts) at 100mA, and it has to throw off 7 V X 100mA = .7 Watts of power as heat to supply the Arduino with 5V X 100mA = .5 Watts of power.  That means my efficiency is .5 W/1.2 W (.5 W+ .7 W) or about 42% power efficiency.  That isn’t going to cut it, considering that this setup will also end up powering an XBee that has a max draw of around 200mA.

I started looking into building a DC switching power supply of some sort when I ran across these Universal 12 Volt USB Car Chargers for about $2 on Amazon.   Tearing one open, I saw a nifty little switching power supply with the added bonus of a USB connector so that I could simply use a USB cable to the Arduino for power.

For $2 you don’t get a detailed datasheet that describes things like the maximum current capacity, but the D34063 chip at the center of the supply is rated at 1.5 A, so I feel pretty comfortable that this should be able to handle the load.  Since a switching power supply is turning on and off the current at a pretty high frequency, it is difficult to determine an effective current input to the device, so it is hard to calculate the efficiency of the power supply.  When I’m bored, I might try to hook up my oscilloscope to it to try to get an idea of the efficiency of the device, but I’m sure it is way better than 42%, because the device does not get even a little warm in use.

The final thing I’d like to discuss is the water proof container.  I initially used a Tupperware, some glue and electrical tape, but it was a complete failure.  A couple of rainy days later,  I had water pooling in the container, and a ruined Solar Charge Converter.  Luckily the Arduino stayed dry, but I had to find a better container for the Garduino.

This is the $20 Dri-Box Waterproof Connection Enclosure, and it has been working pretty well so far.  If you notice the earlier picture, where you can see the inside of the box, you can see the channels that the cables run through are filled with a soft rubber which seal around the cables.  I have yet to see any evidence of water inside the container, and we have had some deluges in the time it has been out there.

Project Garduino

In the Spring of 2011, I bought a small greenhouse from Costco so I could plant some veggies early and maybe get some more productivity. About that same time, a friend sent me a link about using an Arduino to control a camera for high speed photography, and I thought that the Arduino would be the perfect tool to understand how effective this new greenhouse is. I currently have drip irrigation in my other vegetable gardens running off a timer and I’ve put the greenhouse on the same water line. Unfortunately, the greenhouse needs a lot more water than the outside garden, so eventually I will be operating a water value with the garduino to control the water going to the 2 gardens.Costco Greenhouse and Garden B

I currently have the Arduino setup to take 2 light readings and 2 soil moisture (1 each inside and outside the greenhouse), and 4 temperature readings (1 soil and 1 air temperature both inside and outside the green house). I’ve already taken some data, and this is what it looks like:

Here are a few things I’ve learned.  First the TSL235 I’m using are too sensitive to use to measure sunlight.  They saturate, and I will have to replace them with TSL230 (the light sensor output frequency is plotted on the left axis).  The moisture sensors are pretty much worthless right now, but I’m going to be trying several designs to hopefully find one that works well.  The temperature sensors are the only thing I’m happy with at this point (they are LM335’s).  What I learned from them is interesting.  During the day, the air temperature inside the greenhouse is up to 20 F warmer than outside (the Temperature is plotted on the right axis), but at night, the air temperature both inside and outside the greenhouse is the same.  The ground temperature, though, is alway about 5 F warmer in the greenhouse than outside it.

Future posts will discuss all the sensors and other elements of my Garduino in detail, and hopefully the improvements I make along the way.