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.