In the third part of the tutorial series, we discussed how to assemble the data logging shield to be versatile for a range of applications. For the feeder project, we added a block of female header pins to that basic data logger configuration. We need to be able to attach at least eight power and eight ground leads to the power and ground pins on the shield, and the easiest way to do that is to give the shield power and ground rails (like the ones on the breadboard). We used a 2 x 13 female header pin block and soldered it to the two rows of holes closest to the power and ground pins.
Begin by soldering the pins in place, the same way you soldered the stacking header pins. Once the block of female header pins is securely soldered to the shield, you need to connect the pins in each rail, so that once you connect one of the pins to power or ground, the entire rail will be connected. My first instinct was to connect them by blobbing on lots of solder to make a bridge between two pins. This was a bad idea. First, it's difficult to do: solder sticks to one pin as long as it can and is reluctant to move towards the pin next to it, leaving you with a huge glob on one pin and not much of a bridge. Realizing that there was probably a better way, I did some research and discovered that even if you manage to make a solder bridge, the connection is often unreliable. Some forums suggest that the best way to connect pins is to weave a bare wire between the pins you want to connect, and then solder the wire into place. I tried that next, and it worked quite well.
I used clipped leads from some resistors for one rail, and it worked, but they were kind of hard to bend into place. For the second rail I used a much thinner wire I found in the lab, and it was much easier to get into place. However, it can be difficult to get the wire to lie flat against the board- instead of trying to bend the whole wire into place around the whole row, try weaving a couple pins, soldering them, weaving a few more, soldering them, etc. And remember that you still don't need to make a solder bridge- the wire is the bridge, and all you need is a drop of solder over each pin to hold the wire in place.
Having never done this before, I wanted to check that my soldering had been successful: that all the pins in a rail are connected, and the rails aren't connected to each other. I checked the connectivity with a multimeter. LadyAda has a very thorough tutorial on how to use multimeters to check for connectivity. Try checking whether the two pins at the ends of each rail are connected (they should be), and whether pins in both rails are connected to each other (they shouldn't be).
| Tests A and C should show connectivity. B should not. |
The next step in assembling the circuit is to connect the power pin on the data logging shield to one of the new rails and the shield's ground pin to the other. You can do this by plugging the ends of jumper wires into the appropriate pins. Remember to use a consistent color-coding for wires so that you don't mix up power and ground.
Now you can put the data logging shield into the Mux shield and connect up the photogates. Each photogate needs its power and ground leads to go into the rails on the data logger and its signal lead to go into the Mux shield.
That's all you need to do for the circuit! Now let's take a look at the code. Here is the sketch I wrote for the feeder project:
*When any of an arbitrary number of photogates experiences a break, this sketch records which gate the break is at, when the break started, and how long it lasted. It uses a data logger with a real-time clock, and records the data to an SD card. It uses the input/output pins of a Mux shield. When opening the SD file in LibreOffice Calc, make sure Character Set is set to "Unicode (UTF-7)" or "Unicode (UTF-8)"; otherwise the file will read as gibberish.*/
//These are the libraries you need to import to run this code.
#include <MuxShield.h>
#include <Wire.h>
#include <SPI.h>
#include <RTClib.h>
#include <SD.h>
//These are the variables you may need to change, to reflect which pins on the Mux shield
//you are using.
#define ARRAY_SIZE 3 //The number of photogates you are using.
int numberOfStations = 3; // The number of photogates you are using (some functions
take different types of inputs)
int photoGatePins[] = {15,13,10}; //An array containing the pins the photogates are
//connected to.
int pinRow[] = {3,2,1}; //An array containing the row each pin is located in.
MuxShield muxShield; //Initializes the mux shield.
RTC_DS1307 RTC; //Initializes the Real Time Clock.
//We initialize these arrays of variables here, and fill them with the appropriate values in the // if statement at the beginning of void loop().
int gateState[ARRAY_SIZE]; //Creates an array of ints, whose length is the number of
// stations.
int lastGateState[ARRAY_SIZE]; //Create another array of ints
uint32_t m1[ARRAY_SIZE]; //m1 and m2 will be filled out later, to calculate elapsed time.
uint32_t m2[ARRAY_SIZE];
uint32_t elapsed[ARRAY_SIZE];
DateTime startTime[ARRAY_SIZE];
boolean firstRun = true;
//This function will be called later. It makes all the values in an array the same. Takes the array, the size of the array, and the position to be duplicated as inputs.
void dupFirstItem(void *array,unsigned int totalLenBytes,unsigned int itemLenBytes){
memcpy( ((char*)array)+itemLenBytes, array, totalLenBytes-itemLenBytes );
}
//Intializing variables related to the SD card. If you sync too often, it uses a lot of power
//and time. But if something goes wrong between syncs, you'll lose the data that was
// collected since the last sync. Choose syncInterval accordingly.
int syncInterval = 10000; //How often (in milliseconds) to write all the logged data
//permanently to the SD card.
uint32_t syncTime = 0; //The last time the SD card was synced.
const int SDchip = 10; //We will use digital pin 10 to communicate with the SD card.
File logfile; //Creates a file on the SD card that we can record data to.
//This function prints an error message whenever there is an error.
void error(char *str) { //If there is an error,
logfile.print("error: "); //record the error message to the SD card.
logfile.println(str);
Serial.print("error: ");
Serial.println(str); //and print it to serial.
while(1);
}
void setup() {
Wire.begin(); //Connect to the Real Time Clock (RTC).
for(int row = 1; row < 4; row++) {
muxShield.setMode(row, DIGITAL_IN); //Sets each row of the Mux Shield to be a digital input.
}
Serial.begin(9600); //Start serial communications.
Serial.print("Initializing SD card...");
//Initialize the SD card now.
pinMode(10, OUTPUT);
if (!SD.begin(SDchip)) { //If the Arduino cannot initialize the SD card,
error("Card failed, or not present"); //print this error message.
Serial.print("Card failed, or not present");
} //If the SD successfully initializes,
Serial.print(" card initialized."); //print this instead.
//Start creating the name of the file that will be written to the SD card.
//The first six characters of the filename are the current date (yymmdd), and the last
// two characters are letters indicating the chronological order of the files
//(aa, ab, ac,...az, ba, bb,...za, zb,...zz). After zz, the program will error.
DateTime startTime = RTC.now(); //Name the current time, so that we can refer to it later.
String y = String(startTime.year(),DEC);
String m = String(startTime.month(),DEC);
String d = String(startTime.day(),DEC);
char filename[]="00000000.CSV"; //Initialize the char array that will become the file name
filename[0] = y[2];
filename[1] = y[3];
if (m.length()==2) { //If the month is two characters long,
filename[2] = m[0]; //make filename[2] equal to m[0]
filename[3] = m[1]; //and make filename[3] equal to m[1].
}
else { //If the month is only one character long,
filename[2] = '0'; //make filename[2] equal to 0
filename[3] = m[0]; //and make filename[3] equal to m.
}
if (d.length() == 2) { //If the day is two characters long,
filename[4] = d[0]; //make filename[4] equal to d[0]
filename[5] = d[1]; //and make filename[5] equal to d[1].
}
else { //If the day is one character long,
filename[4] = '0'; //make filename[4] equal to zero
filename[5] = d[0]; //and make filename[5] equal to d
}
String alpha = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
for (int i = 0; i<26; i++) {
filename[6] = alpha[i];
filename[7] = alpha[25];
if (!SD.exists(filename)){
for (int j=0; j<26; j++) {
filename[7] = alpha[j];
if (! SD.exists(filename)) { //If there's not already a file called filename, make one and
// start writing to it.
logfile = SD.open(filename, FILE_WRITE);
break;
}
}
break;
}
}
//An error message, to display when the sketch can't create a file.
if (! logfile) { //If the sketch fails to make a file,
error("couldn't create file"); //print this error message.
}
Serial.print(" Logging to: "); //Otherwise, print that it's logging,
Serial.println(filename); //and the name of the file it's logging to.
}
void loop() {
//This section fills the global arrays with values (either 1 or 0, depending on the array).
//It runs only once.
if (firstRun == true) {
gateState[0] = 1; //Set the first position in the array to 1
dupFirstItem(gateState, sizeof(gateState),sizeof(gateState[0])); //Make the rest of the
//values in the array the same as the first one.
lastGateState[0] = 1;
dupFirstItem(lastGateState, sizeof(lastGateState), sizeof(lastGateState[0]));
m1[0] = 0;
dupFirstItem(m1, sizeof(m1), sizeof(m1[0]));
m2[0] = 0;
dupFirstItem(m2, sizeof(m2), sizeof(m2[0]));
elapsed[0] = 0;
dupFirstItem(elapsed, sizeof(elapsed), sizeof(elapsed[0]));
startTime[0] = 0;
dupFirstItem(startTime, sizeof(startTime), sizeof(startTime[0]));
firstRun = false; //At the end of the if statement, change firstRun to false so the variables
//won't be reset to these values every time the loop runs.
}
//This section reads the voltage coming in from the photogates and records the
//appropriate data.
for (int x=0; x<numberOfStations; x++) {
//Name the voltage coming in from the photogate pin "gateState"
gateState[x] = muxShield.digitalReadMS(pinRow[x], photoGatePins[x]);
if (gateState[x] != lastGateState[x]) { //If the state has changed
if (gateState[x] == 0) { // and now it's LOW,
m1[x] = millis(); //record the number of milliseconds, m1, since
//starting, for calculating elapsed time.
startTime[x] = RTC.now(); //Get the current time from the RTC, for
//recording when the break started.
}
else { //If the state just changed to HIGH,
DateTime now;
m2[x] = millis(); //Record the time at the end of the break, m2.
elapsed[x] = m2[x]-m1[x]; //Calculate how long the break was.
//These are the things we are recording to the SD card.
logfile.print(x); //x is the station number
logfile.print(',');
logfile.print(startTime[x].unixtime()); //Record the unixtime (seconds since 1-1-1970)
logfile.print(',');
logfile.print(startTime[x].hour(), DEC); //Record the time the break started as
//hour:min:sec
logfile.print(":");
logfile.print(startTime[x].minute(), DEC);
logfile.print(":");
logfile.print(startTime[x].second(), DEC);
logfile.print(",");
logfile.print(startTime[x].month(), DEC); //Record the date the break started as
// month/day/year
logfile.print("/");
logfile.print(startTime[x].day(), DEC);
logfile.print("/");
logfile.print(startTime[x].year(), DEC);
logfile.print(',');
logfile.println(elapsed[x]); //Record the length of the break in milliseconds.
//This is what we print to Serial. It is the same data we logged to the SD card above,
//but will appear in sentence format.
String string1 = "Break at Station "+String(x)+" started at " + String(startTime[x].hour(),DEC);
Serial.print(string1);
String string2 = ":"+String(startTime[x].minute(), DEC) + ":" + String(startTime[x].second(),DEC);
Serial.print(string2);
String string3 = " on "+String(startTime[x].month(),DEC)+"/"+String(startTime[x].day(),DEC);
Serial.print(string3);
String string4 = "/"+String(startTime[x].year(),DEC)+" and lasted "+String(elapsed[x])+" milliseconds.";
Serial.println(string4);
}
lastGateState[x] = gateState[x]; //Now update the lastGateState
}
}
//This section writes data to the SD card.
if ((millis() - syncTime) < syncInterval) return; //if the amount of time that has passed since
//the last upload is less than the syncInterval,
//end the loop.
syncTime = millis(); //Otherwise, update the syncTime to now
logfile.flush(); // and send the data to the SD card.
}
I've commented the sketch pretty thoroughly, so I won't spend a lot more time explaining how it works. However, there are a few parameters that you may need to change, depending on how you choose to configure your circuit. They are located in a block near the top of the code, right after the libraries. You need to set both numberOfStations and #define ARRAY_SIZE to the number of photogates you are using. You also need to fill in the arrays photoGatePins and pinRow. These arrays tell the Arduino which pins of the Mux shield to read. The rows and columns on the female right-angle connector are numbered:
| The locations of the green, white, and blue signal wires in the previous photo. |
In the code as it's written above, the Arduino considers the photogate connected to row 3, pin 15 to be Station 0, the photogate connected to row 2, pin 13 to be Station 1, and the photogate connected to row 1, pin 10 to be Station 2. If you want to use different pins, you will need to change the pin and row coordinates to reflect the change. The Mux shield can accomodate up to 48 pins, as long as you make sure numberOfStations and #define ARRAY_SIZE are accurate. However, if you are using more than 11 photogates, you will need to install more power and ground rails.
When you put the SD card into your computer or SD card reader after running this sketch, the files on it will be named with the date and then a series of letters indicating the order the files were made in. For example, if the sketch made three files on August 14, 2014, the files would be named 140814AA, 140814AB, and 140814AC. When you open a file, you will see something like this:
Each row corresponds to one break in the signal, which for our project means one visit to the feeder. The first column is the station the break occurred at, the second column is Unix time (number of seconds since January 1, 1970), the third and fourth columns are the time and date that the break started, and the fifth column is the length of the break.
One last note: This sketch requires several libraries, which you will need to download in order to be able to run it. Wire.h, SD.h, and SPI.h come with the Arduino package you downloaded originally. RTClib.h can be downloaded from Adafruit and MuxShield.h is from Mayhew Labs.
No comments:
Post a Comment