Sparkfun Weather Station

I installed the Sparkfun Weather Meters mainly for the rain gauge.  It is quite expensive, and didn’t even come with the small screws which hold the anemometer and wind vane to the support arm.  It does seem to be well made, and makes my system look like a weather station.

Sparkfun Weather Meters in the wild

The datasheet shows that the rain gauge and the anemometer are magnetic reed switches; every 0.011″ of rain causes a momentary contact and a 2.4km/h wind will cause a momentary contact every second for the anemometer.  These inputs will be connected to Arduino interrupts.  The wind vane uses an array of resistors which can be read through the ADC of the Arduino, but the datasheet is wrong for the wind vane.  At 315°, the datasheet states that the voltage should be 4.78V, but in actuality, with a 10K resistor in series with a 64.9K resistor, the voltage drop across the vane will be 4.33V.

The following code setups up the inputs and the interrupts to read this device.  Note that setting a pin to INPUT and then writing a HIGH value to it turns on the internal 20K pull up resistor in the Arduino, which prevents the need for external pull up resistors on the interrupt driven devices.  The wind vane requires a 10K resistor in series with the vane’s resistors, and to save power, this resistor and the vane are powered from a digital output, which is driven HIGH only when the reading is to be taken.

#define ANEMOMETER_PIN 3
#define ANEMOMETER_INT 1
#define VANE_PWR 4
#define VANE_PIN A0
#define RAIN_GAUGE_PIN 2
#define RAIN_GAUGE_INT 0

void setupWeatherInts()
{
  pinMode(ANEMOMETER_PIN,INPUT);
  digitalWrite(ANEMOMETER_PIN,HIGH);  // Turn on the internal Pull Up Resistor
  pinMode(RAIN_GAUGE_PIN,INPUT);
  digitalWrite(RAIN_GAUGE_PIN,HIGH);  // Turn on the internal Pull Up Resistor
  pinMode(VANE_PWR,OUTPUT);
  digitalWrite(VANE_PWR,LOW);
  attachInterrupt(ANEMOMETER_INT,anemometerClick,FALLING);
  attachInterrupt(RAIN_GAUGE_INT,rainGageClick,FALLING);
  interrupts();
}

In order for the anemometer to be accurate, any surrounding objects must be 4 time as far as they are higher than the anemometer.  For example, if my anemometer is 5 feet off the ground, and my house is 25 feet tall, the anemometer would need to be 80 feet from the house to be able to measure the wind speed accurately.  In my case, nothing close to this leeway is possible, but I still setup the anemometer just for the educational experience.

Every rotation of the anemometer causes a call to the interrupt service routine anemometerClick().  Switch debouncing is accomplished by assuming any interrupt that occurs within 500 uS of the previous one is a bounce and should be ignored.  This limits the max speed this configuration can measure to about 2900 miles per hour, but since I don’t live on Jupiter I think I can live with this compromise.  The getUnitWind() function returns the average wind speed since the last poll, which is every 60 seconds.  The getGust function uses the shortest time between 2 interrupts to calculate the fastest wind speed since the last poll.

#define WIND_FACTOR 2.4
#define TEST_PAUSE 60000

volatile unsigned long anem_count=0;
volatile unsigned long anem_last=0;
volatile unsigned long anem_min=0xffffffff;

double getUnitWind()
{
  unsigned long reading=anem_count;
  anem_count=0;
  return (WIND_FACTOR*reading)/(TEST_PAUSE/1000);
}

double getGust()
{

  unsigned long reading=anem_min;
  anem_min=0xffffffff;
  double time=reading/1000000.0;

  return (1/(reading/1000000.0))*WIND_FACTOR;
}

void anemometerClick()
{
  long thisTime=micros()-anem_last;
  anem_last=micros();
  if(thisTime>500)
  {
    anem_count++;
    if(thisTime<anem_min)
    {
      anem_min=thisTime;
    }

  }
}

When the system included the logger shield, SRAM memory was so tight that I had to move variable data from SRAM to FLASH.  Using PROGMEM allowed me to move the vane directions and the expected values out of SRAM, and in itself saved almost 100 bytes of the 2000 the Arduino has (moving String values in addition freed up about 500 bytes of SRAM).  The pgm_read_word function pulls the values out of FLASH and into SRAM when needed, but only works with integers, so the actual wind directions are stored as 10X their value.

As mentioned in the MCP9700 post, the analogReference for the Wind Vane must be switched from the 1.1V the MCP9700 was using to the 5V default value.  Since it takes some time for the ADC to adjust to the new reference, the first 10 adc readings are just thrown out.

To save battery power, the wind vane is powered by the digital pin VANE_PWR right before it is used.  a 100ms delay is inserted after the power pin is driven high just to left things settle down.  The rest of the function compares the ADC value to the ideal values in vaneValues, and returns the corresponding wind direction.


static int vaneValues[] PROGMEM={66,84,92,127,184,244,287,406,461,600,631,702,786,827,889,946};
static int vaneDirections[] PROGMEM={1125,675,900,1575,1350,2025,1800,225,450,2475,2250,3375,0,2925,3150,2700};

double getWindVane()
{
  analogReference(DEFAULT);
  digitalWrite(VANE_PWR,HIGH);
  delay(100);
  for(int n=0;n<10;n++)
  {
    analogRead(VANE_PIN);
  }

  unsigned int reading=analogRead(VANE_PIN);
  digitalWrite(VANE_PWR,LOW);
  unsigned int lastDiff=2048;

  for (int n=0;n<16;n++)
  {
    int diff=reading-pgm_read_word(&vaneValues[n]);
    diff=abs(diff);
    if(diff==0)
       return pgm_read_word(&vaneDirections[n])/10.0;

    if(diff>lastDiff)
    {
      return pgm_read_word(&vaneDirections[n-1])/10.0;
    }

    lastDiff=diff;
 }

  return pgm_read_word(&vaneDirections[15])/10.0;

}

The rain gauge work much like the anemometer in that it drives an interrupt, and a 500us debounce time is used.  The interrupt the simply counts the number of switch contacts that occurred since the last poll and multiplies that by the number of mm of rain each switch contact represents.

#define RAIN_FACTOR 0.2794

volatile unsigned long rain_count=0;
volatile unsigned long rain_last=0;

double getUnitRain()
{

  unsigned long reading=rain_count;
  rain_count=0;
  double unit_rain=reading*RAIN_FACTOR;

  return unit_rain;
}

void rainGageClick()
{
    long thisTime=micros()-rain_last;
    rain_last=micros();
    if(thisTime>500)
    {
      rain_count++;
    }
}
Advertisements

MCP9700

Soil temperature is one of the things the Garduino monitors.  Unfortunately, soil is a harsh environment and replacing a $1.50 TMP36 everything time one stopped working was becoming a bit of a drag, so I looked for something cheaper.  The MCP9700 is a temperature sensor available from Mouser.com for $0.34, and at that price, I feel a little more free to experiment with waterproofing these sensors.

These devices are pretty nice to work with, since they can take a supply voltage of 2.1 to 5.5V and the output pin will be at .5V at 0° C and rise 0.01V for every rise in 1°C.  This document  describes how to compensate for 2nd order effects to increase accuracy further.

It is critical to put a capacitor between the Vout pin in ground (about 2nF), because without it, there is a 27kHz wave that appears on the output.  In my tests, Vout oscillated 0.0575 Volt P-P, which represents a variation of 5.75 °C from reading to reading.  With the capacitor between Vout and GND, the variation between readings drops to about 0.5°C.

The code for reading the temperature of these devices is pretty straightforward:

float readMCP9700(int pin,float offset)
{
  analogReference(INTERNAL);
  for (int n=0;n<5;n++)
    analogRead(pin);

  int adc=analogRead(pin);
  float tSensor=((adc*(1.1/1024.0))-0.5+offset)*100;
  float error=244e-6*(125-tSensor)*(tSensor - -40.0) + 2E-12*(tSensor - -40.0)-2.0;
  float temp=tSensor-error;

  return temp;
}

In my application, I have some ADC calls done against the 5V analogReference, and the analogReads for the MCP9700 are done against the internal 1.1V reference.  According to the ATMega328 datasheet, when switching between references, you should throw out the reading immediately after the switch, because it will be inaccurate.  In this function, I throw out  5 readings after setting the analogReference, just to give it extra time to settle down, and then I take the real measurement.  The 2nd order error is calculated as described in the accuracy compensation document mentioned above.

I also pass in an offset to allow me to adjust each individual sensor against a know temperature.  In my case, I really didn’t have a know temperature reading I thought was any more accurate than anything else I had, so I setup 4 MCP9700s and let them read the same temperature.  I averaged these readings, and I pass in the offset for each individual MCP9700 from that average, so, if nothing else, the temperatures read between these 4 devices will be consistent.  The offsets were on the order of 0.01V.

MAX44009

One of the questions I wanted to answer with my Garduino was how much light is lost through the plastic covering of the Greenhouse.  What I have found is it is very difficult to find a light sensor that has the dynamic range from darkness to full sun, but I finally stumbled on the MAX44009, which has a range from 0.045 to 180,000 Lux.  The downside of this device is no one is making a breakout board for it, so I was on my own.

Homemade breakout board for the MAX44009

Yes, this beautiful homemade PCB does work.  I made it by the laser toner transfer method, but my home printer, a Brother HL-4040CN did not work; the toner did not stick to the copper board no matter how long I put the iron on it.  I ended up creating a PDF file with Gerber2PDF and printing the image on some HP printers at work.  Unfortunately,  in my rush to not be caught screwing around with something that wasn’t work related, I forgot to make a mirror image of the PCB, so everything is backwards on the PCB.  Even with the HP toner, the toner transfer was not very good, but, considering the fact that the MAX44009 chip is 2mm x 2mm and the pads for the 6 pins are 0.36mm x 0.48 mm, the toner transfer was accurate enough to work.  Adding to the fact that this was the first time I used my hot air re-work station to do something constructive, you could have knocked me off my chair with a feather when the final assembly actually returned valid data.

It wasn’t a complete Eureka moment, though, because while the data sheet says the I2C address can be selected (with the AD pin) to be 148 or 150, that does not seem to be true.  I found this important utility, I2CScanner which runs through every I2C address, and shows which addresses a device is responding to.  This really saved me, because it showed that the real address of the MAX44009 was 203 (I forgot to note the address when AD is low, so I’ll document that when I make the improved version of the PCB).  Once I had the right address, interfacing with the MAX44009 is very straightforward, since it uses the I2C Wire Arduino  library:

#define MAX_ADDR 203

float getLux()
{
int luxHigh=readI2CAddr(0x03);
int luxLow=readI2CAddr(0x04);
int exponent=(luxHigh&0xf0)>>4;
int mant=(luxHigh&0x0f)<<4|luxLow;
return (float)(pow(2,exponent)*mant)*0.045;

}

int readI2CAddr(int addr)
{

Wire.beginTransmission(MAX_ADDR);
Wire.write(addr);
Wire.endTransmission();

Wire.requestFrom(MAX_ADDR,1);

int n=0;
for(n=0;n<25;n++)
{
if(Wire.available())
break;

delay(100);
}

if (n==25)
return 0;
else
return Wire.read();
}

I was expecting to have to handled the situation where I would have to programatically change the sensitivity of the device so that I would have to set longer integration times in low light conditions, and shorter ones in bright conditions, but the device handles that all by itself.  The device can be configured in a manual mode so that the user has control of exactly what integration time will be used for each reading, but in my case, the automatic setting behaved the way I wished.

I’ve created an Eagle library that contains the land pattern for the MAX44009.

I really like the I2C bus, but its major disadvantage is the max length which is reportedly pretty short.  I currently have the MAX44009 sitting off of about 2.5 M of 6 conductor satin cable, connected to the same 3.3V bi-directional voltage translator circuit my BMP085 is connected to, and it is working well.  I expect when I have to connect another MAX44009 to measure the light loss from inside the Greenhouse, though, the length will become a problem.  I will be researching some I2C bus lengthing techniques and will report on them in the future.

The waterproof enclosure for the MAX44009 consists of a plastic food container with a clear plastic cover, and the bottom cut out for ventilation.

 

This is the PDF for the Break Out Board that I used for the Toner Transfer method to create the PCB.

BMP085

The BMP085 is a I2C Barometric Pressure/Temperature sensor available from both Sparkfun and eBay.  The Sparkfun breakout board is very expensive, about $20, but they have excellent instructions on how to use it.  You can get the BMP085 chip on eBay for about $4 and the breakout board for about $6 for 3, but you have to be able to solder SMD to use them.

BMP085 breakout board soldered into my weather station interface board

Sparkfun has an excellent tutorial whose code I pretty much just used verbatim in my application. The only thing I changed were the bmp085Read and bmp085ReadInt functions, since as written, they will retry forever for the bmp085 to respond.  Since my device is outside, and attached to an I2C bus that is about 2.5 M long (for the Max44009 light sensor), sometimes I get a failure in reading the responses from the BMP085.  I just changed the lines that read


  while(!Wire.available())
    ;

To read:


  int n=0;
  for(n=0;n<25;n++)
  {
    if(Wire.available())
      break;

    delay(100);
  }

  if (n==25)
  {
    return 0;
  }

This way if my BMP085 malfunctions, the whole system doesn’t hang.  The other code adjustment was to put in the altitude compensation in the calculations:

#define ALT 142.0

float getPressure()
{
  long pa=bmp085GetPressure(bmp085ReadUP());

  float press=((float)pa/pow((1-ALT/44330),5.255))/100.0;
  return press;
}

Before I put in the compensation for altitude, my pressure was always too low.  Once I compensated for my 142 M altitude, the results from this device are spot on.  I’m very happy with the accuracy of this chip.

The other trick to using this device is the fact that it is a 3.3V device, and you can’t simply connect it up to a 5V I2C Arduino connection and hope for it to have a long life.  This document gives a very detailed discussion on setting up a bi-directional voltage level translator for the I2C bus.  The highlighted part of the schematic below shows the voltage level translator (the transistors are a couple of SN7000 MOSFETs):

Bi-Directional voltage translator for the I2C bus.

RHT22 / RHT03

Over the last year my Garduino project has morphed into more of simply a weather station.  While I still have plans to activate a water valve with the Arduino, my failure in creating a reliable moisture sensor has led me to more or less indirectly guess the soil moisture my measuring the temperature, rain, and humidity.

To measure the temperature and humidity, I’m using the RHT03 (referred to as an RHT22 and DHT-22 as well), available from Sparkfun, and on eBay.

RHT03

RHT03 in breadboard connected to the Saleae Logic Analyzer

Most of my work on the RHT03 is based on the work of Craig Ringer and Nethoncho.   The RHT03 is a bit of an odd device, with kind of a non-standard signalling protocol.  All signalling is done over 1 data line, and the Arduino sets that signal line to an OUTPUT and pulls the line low for about 3ms, and then high for about 30us.  At that point, the Arduino sets the data line pin to an INPUT, and the RHT03 takes control of that pin; pulling the line low for 80us, then high for 80us, and then sending the temperature and humidity in a series of pulses.  High pulses of 26us represent a 0 and 80us represents a 1.  You can try to decipher the whole protocol by reading the datasheet, if you are not too picky about grammar (and if you are reading my blog, you must not be). The following is the code I am using:

#define RHT22_PIN 7
unsigned int temp;
unsigned int humidity;
void setup()
{
 Serial.begin(9600);
 Serial.println("Begin");
}
void loop()
{
 Serial.println("Reading sensor...");

 boolean success = readRHT03();
 if (success) {
   char buf[128];
   sprintf(buf, "Unit 1 Reading: %i.%i degrees C at %i.%i relative humidity", temp/10, abs(temp%10), humidity/10, humidity%10);
   Serial.println(buf);
 }
 delay(2000);
}
boolean readRHT03()
{
 unsigned int rht22_timings[88];
 initRHT03();

 pinMode(6,OUTPUT);
 pinMode(5,OUTPUT);
 digitalWrite(5,LOW);

 pinMode(RHT22_PIN, OUTPUT);
 digitalWrite(RHT22_PIN, LOW);
 delayMicroseconds(3000);

 digitalWrite(RHT22_PIN, HIGH);
 delayMicroseconds(30);
 pinMode(RHT22_PIN, INPUT);
 pinMode(6,OUTPUT);
 pinMode(5,OUTPUT);
 digitalWrite(5,LOW);

 int state=digitalRead(RHT22_PIN);
 unsigned int counter=0;
 unsigned int signalLineChanges=0;
 TCNT1=0;
 digitalWrite(5,HIGH);
 cli();
 while (counter!=0xffff)
 {
   counter++;
   if(state!=digitalRead(RHT22_PIN))
   {
     state=digitalRead(RHT22_PIN);
     digitalWrite(6,state);
     rht22_timings[signalLineChanges] = TCNT1;
     TCNT1=0;
     counter=0;
     signalLineChanges++;
   }
   if(signalLineChanges==83)
   break;
 }
 sei();
 digitalWrite(5,LOW);
 boolean errorFlag=false;
 if (signalLineChanges != 83)
 {
   temp=-1;
   humidity=-1;
   errorFlag=true;
 }
 else
 {
   errorFlag=!(getHumidityAndTemp(rht22_timings));
 }

 return !errorFlag;
}
void initRHT03()
{
 //for (int i = 0; i < 86; i++) { rht22_timings[i] = 0; }

 TCCR1A=0;
 TCCR1B=_BV(CS10);
 pinMode(RHT22_PIN,INPUT);
 digitalWrite(RHT22_PIN,HIGH);

}
// DEBUG routine: dump timings array to serial
void debugPrintTimings(unsigned int rht22_timings[]) { // XXX DEBUG
for (int i = 0; i < 88; i++) { // XXX DEBUG
 if (i%10==0) { Serial.print("\n\t"); }
 char buf[24];
 sprintf(buf, i%2==0 ? "H[%02i]: %-3i " : "L[%02i]: %-3i ", i, rht22_timings[i]);
 Serial.print(buf);
 } // XXX DEBUG
 Serial.print("\n"); // XXX DEBUG
}
boolean getHumidityAndTemp(unsigned int rht22_timings[])
{
  // 25us = 400, 70us = 1120;
  humidity=0;
  for(int i=0;i<32;i+=2)
  {
    if(rht22_timings[i+3]>750)
    {

      humidity|=(1 << (15-(i/2)));
    }
  }
  
  temp=0;
  for(int i=0;i<32;i+=2)
  {

    if(rht22_timings[i+35]>750)
      temp|=(1<<(15-(i/2)));
  }

  int cksum=0;
  for(int i=0;i<16;i+=2)
  {

    if(rht22_timings[i+67]>750)
      cksum|=(1<<(7-(i/2)));
  }

  int cChksum=((humidity >> 8)+(humidity & 0xff) + (temp >> 8) + (temp &0xff)) & 0xFF;  
  
  if(temp & 0x8000)
    temp=(temp & 0x7fff)*-1;
    
  if(cChksum == cksum)
    return true;
  
  return false;
}

Signal handling is done in the readRHT03 function.  After sending the start sending signal, the Arduino uses Timer/Counter 1 to time the incoming pulses.  Originally, I was trying to use micros() to measure the timing, but I got some very erratic results.  This gave me the opportunity to use my new Saleae Logic Analyzer.  As you can see in the code, I have Pin 6 follow the state of the data input from the RHT03.  Normally, there is a 15us delay between the time the RHT03 changes state and the time Pin 6 changes state (the time to execute the if statement and the digitalWrite()), but sometimes it is longer.  The highlighted pulse on Channel 1, though, shows the delay is at least 10us longer.  To make a long story short, this is caused by interrupts in the Arduino, and turning off the interrupts before I start timing the pulses fixes that problem.  Functions like millis() and micros() need interrupts to be update, though, so I could not use them with the interrupts disabled.  This means I had to use the hardware timer/counter to time the pulse length, which is what the references to the TCCR1A, TCCR1B and TCNT1 do.  Additionally, to measure the time the interrupts are disabled, I set Pin 5 (Channel 2) to go high when the interrupts are disabled and low when they are re-enabled, which gave me about 4ms of “interrupt-less” time.  Since my weather station is going to be polled at 60 second intervals, I now know that the timers won’t be running for about 4ms of that time, so I have to set my timers to 59996 to get a 60 second poll.

Anyone using this code should pull out all the references to Pin 5 and 6, unless they want to use them for debugging as well.


Note the short pulse caused by the Arduino handling other interrupts.

The other thing to note is the temperature is pretty inaccurate when the device is in direct sunlight.  My original design had the RHT03 sharing the waterproof container of my MAX44009 light sensor, but even though it is open to the air at the bottom on the container, the temperature inside the container got well above 110°F on bright sunny 90°F+ days.  I’m now extending the cable to the RHT03 and putting it in it’s own container, shaded by the solar cell.  I will update on how well that works.

RHT03 Mount

Mount for the RHT03 to keep it out of the rain and the sun.

 

Sunny Location

The RHT03 is in the container on the left with the blue top. This container also houses the MAX44009 sun sensor.

Shade RGT03

New location of the RHT03, under the black cover.

Logger Shield and FreqCounter for the Mega 2560

In order to allow me to use the XBee radios to remotely download the logged data to my PC, I’m going to have to use an Arduino Mega 2560.  The Uno just doesn’t have the interrupts to use with the frequency counter, as well as a software serial port for the XBee, so the extra UARTs on the Mega are required.  While the Logger Shield from ladyada.net physically fits into the Mega, the SPI and I2C pins are different between the Uno and the Mega.

You must first lop off the pins for the Uno’s I2C interface (A4 and A5), and the SPI bus ( D10,11,12, and 13):

Modified Logger Shield

Solder wires to DO, DI, CS, CLK, SDA and SCL pads on the Logger Shield and connect as follows:

DO – D50

DI – D51

CS – D53

CLK – D52

SDA – D20

SCL – D21

The libraries the run unmodified.

Mega 2560 Logger

I will incorporate this into a new PCB at some point.

One other change I had to make in order to support the Arduino Mega 2560 was a change to the FreqCounter Library.  While the Uno version of the FreqCounter Library uses T1 on the D5 pin, this pin is not available on the Mega.  You have to use T5 on the Mega which means you input your pulse into pin D47 of the Mega.  FreqCounter.cpp , must be modified as follows to use the correct control registers for the Mega (note the new defines for the control registers depending on the type of Arduino):

/*
  FreqCounter.h -
  Using Counter1 for counting Frequency on T1 / PD5 / digitalPin 5
  Using Timer2 for Gatetime generation

  Martin Nawrath KHM LAB3
  Kunsthochschule für Medien Köln
  Academy of Media Arts
  http://www.khm.de
  http://interface.khm.de/index.php/labor/experimente/	

  History:
  	Dec/08 - V1.0
  	Oct/10 - V1.1    removed occasional glitches through interference with timer0
  	                 set intterrupt timebase to 1ms
  	                 works with atmega328 

  This library is free software; you can redistribute it and/or
  modify it under the terms of the GNU Lesser General Public
  License as published by the Free Software Foundation; either
  version 2.1 of the License, or (at your option) any later version.

  This library is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  Lesser General Public License for more details.

  You should have received a copy of the GNU Lesser General Public
  License along with this library; if not, write to the Free Software
  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
*/

#include <FreqCounter.h>

unsigned long FreqCounter::f_freq;

volatile unsigned char FreqCounter::f_ready;
volatile unsigned char FreqCounter::f_mlt;
volatile unsigned int FreqCounter::f_tics;
volatile unsigned int FreqCounter::f_period;
volatile unsigned int FreqCounter::f_comp;

void FreqCounter::start(int ms) {

// The Mega2560 does not have T1 connected to anything, so it must use T5, on pin 47, as the counter input
#if defined (__AVR_ATmega168__) || defined (__AVR_ATmega48__) || defined (__AVR_ATmega88__) || defined (__AVR_ATmega328P__) || (__AVR_ATmega1280__)
	#define TCCRXA TCCR1A
	#define TCCRXB TCCR1B
	#define TCNTX TCNT1
	#define CSX0 CS10
	#define CSX1 CS11
	#define CSX2 CS12
	#define TIFRX TIFR1
	#define TOVX TOV1
#elif defined (__AVR_ATmega2560__)
	#define TCCRXA TCCR5A
	#define TCCRXB TCCR5B
	#define TCNTX TCNT5
	#define CSX0 CS50
	#define CSX1 CS51
	#define CSX2 CS52
	#define TIFRX TIFR5
	#define TOVX TOV5
#endif

    TIMSK0 &=~(1<<TOIE0);       // disable Timer0  //disable  millis and delay
    delayMicroseconds(50);      // wait if any ints are pending

    f_period=ms;

    if (f_comp ==0) f_comp=1;  // 0 is not allowed in del us

    // hardware counter setup ( refer atmega168.pdf chapter 16-bit counter1)
    TCCRXA=0;                  // reset timer/counter1 control register A
    TCCRXB=0;              	   // reset timer/counter1 control register A
    TCNTX=0;           		   // counter value = 0
    // set timer/counter1 hardware as counter , counts events on pin T1 ( arduino pin 5)
    // normal mode, wgm10 .. wgm13 = 0

    TCCRXB |=  (1<<CSX0) ;// External clock source on T1 pin. Clock on rising edge.
    TCCRXB |=  (1<<CSX1) ;
    TCCRXB |=  (1<<CSX2) ;

    // timer2 setup / is used for frequency measurement gatetime generation
    TCCR2A=0;
    TCCR2B=0;

    // timer 2 presaler set to 128 / timer 2 clock = 16Mhz / 256 = 62500 Hz
    TCCR2B |=  (1<<CS20) ;
    TCCR2B &= ~(1<<CS21) ;
    TCCR2B |=  (1<<CS22) ;

    //set timer2 to CTC Mode with OCR2A is top counter value
    TCCR2A &= ~(1<<WGM20) ;
    TCCR2A |=  (1<<WGM21) ;
    TCCR2A &= ~(1<<WGM22) ;
    OCR2A = 124;                // CTC divider by 125

    f_ready=0;                  // reset period measure flag
    f_tics=0;                   // reset interrupt counter
    GTCCR = (1<<PSRASY);        // reset presacler counting
    TCNT2=0;                    // timer2=0
    TCNTX=0;                    // Counter1 = 0

    TIMSK2 |=(1<<OCIE2A);       // enable Timer2 Interrupt

                                // External clock source on T1 pin. Clock on rising edge.
    TCCRXB |= (1<<CSX2) | (1<<CSX1) | (1<<CSX0);        //   start counting now     

}
//******************************************************************
//  Timer2 Interrupt Service is invoked by hardware Timer2 every 1ms = 1000 Hz
//  16Mhz / 128 / 125 = 1000 Hz
//  here the gatetime generation for freq. measurement takes place: 

ISR(TIMER2_COMPA_vect) {
										// multiple 2ms = gate time = 100 ms
if (FreqCounter::f_tics >= FreqCounter::f_period) {
                            			// end of gate time, measurement ready

   										// GateCalibration Value, set to zero error with reference frequency counter
    //  delayMicroseconds(FreqCounter::f_comp); // 0.01=1/ 0.1=12 / 1=120 sec
    delayMicroseconds(FreqCounter::f_comp);
    TCCRXB = TCCRXB & ~7;   			// Gate Off  / Counter T1 stopped
    TIMSK2 &= ~(1<<OCIE2A);    			// disable Timer2 Interrupt
    TIMSK0 |=(1<<TOIE0);     			// enable Timer0 again // millis and delay

                                        // calculate now frequeny value
    FreqCounter::f_freq=0x10000 * FreqCounter::f_mlt;  // mult #overflows by 65636
    FreqCounter::f_freq += TCNTX;      	// add counter1 value
    FreqCounter::f_mlt=0;

    FreqCounter::f_ready=1;             // set global flag for end count period

    }
    FreqCounter::f_tics++;            	// count number of interrupt events
    if (TIFRX & 1) {          			// if Timer/Counter 1 overflow flag
    FreqCounter::f_mlt++;               // count number of Counter1 overflows
    TIFRX =(1<<TOVX);        			// clear Timer/Counter 1 overflow flag
    }
    // PORTB = PORTB ^ 32;  				// int activity test
}

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.