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
}
Advertisements

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.