Lab: Data Logging with SD Cards
SD Cards
- Nonvolatile memory, useful for storing info
- Can both write data to a file and read existing information from SD card
Before doing anything, we first needed to format the SD card. It was quite simple to do, I simply right-clicked the card under "My Computer"and selected the format option. Since the card I was using was 4GB, I used FAT32 for the file system type. Then I just clicked start to format it.
Interfacing with Arduino
- SD cards are 3.3V devices
- SPI: Serial Peripheral Interface bus. SD communication utilizes this
- Arduino comes with SD library, which abstracts away lower-level SPI communication and allows user to easily read and write files on SD card
SD Card SPI Interface
Pins Used:
MOSI: Master Output, Slave Input.
MISO: Master Input, Slave Output
SCLK: Serial Clock
CS: Chip Select
Writing to an SD Card
Now it was time to actually take a shot at writing data to an SD card. I followed all the steps given in the notes, which were:
- Format the SD Card
- Insert SD card into SD card module for Arduino
- Wire up module to Arduino
- Plug in Arduino to computer
- Write Arduino code
- Upload Arduino code
- Open serial monitor
- Watch as serial monitor prints the same thing that it is printing to the SD card (aka, achieve success)
However, frustratingly, despite following steps 1-7 to a T, step 8 went wrong. I got the lovely messages "Card Failure" and "Couldn't open log file." It was the bluetooth all over again. I reformatted the SD card, made sure it was plugged in correctly, double checked the code, and tried again. Nope. I then switched to a different SD card. Still nothing. I reset my Arduino. No dice. I changed the SD card module. Nada. Try as I might, nothing worked.
I scoured the internet for information and saw the suggestion to digitalWrite pin 10 high. I desperately threw this into my code, and still, nothing. At this point, I knew that persisting and continuing to fail was probably going to end in me punching something, so I moved on to the next part, hoping that I'd have more luck reading from an SD card instead.
Update: 11/15/2017, 3:53 PM
Victory! I'm not sure what went wrong during class, but after putting it all together today, it worked! The code remained the same, as did the wiring and the SD card. There is a possibility that I connected VCC to the 3.3V output during class instead of the 5V, but I recall that I also tried connecting it to 5V and it still didn't work. I used different wires this time, so perhaps a bad wire was the issue during class.
| Figure 1.1 Serial monitor display |
| Figure 1.2 CSV File written to SD card |
Code
//Write to SD Card
#include <SD.h>
//The following variables are set by default for the SD card library
//MOSI: pin 11
//MISO: pin 12
//SCLK: pin 13
//However, we need to set the CS pin
const int CS_PIN = 10;
void setup() {
Serial.begin(9600);
Serial.println("Initializing Card");
//CS pin is an output
pinMode(10, OUTPUT);
if(!SD.begin(CS_PIN)) //If SD.begin fails, return error message and end
{
Serial.println("Card Failure");
return;
}
Serial.println("Card Ready"); //Otherwise, execute program
}
void loop() {
long timeStamp=millis();
String dataString="Hello";
//Open a file and write to it
File dataFile=SD.open("log.csv",FILE_WRITE);
if(dataFile)
{
dataFile.print(timeStamp);
dataFile.print(",");
dataFile.println(dataString);
dataFile.close(); //data not written until connection is closed
Serial.print(timeStamp);
Serial.print(",");
Serial.println(dataString);
}
else
{
Serial.println("Couldn't open log file");
}
delay(5000);
}
Reading from an SD Card
| Figure 2.1 Serial monitor printing time values |
| Figure 2.2 CSV file with time values, which was saved to the SD card |
Code
//SD read and write
#include <SD.h>
//Set CS Pin
const int CS_PIN=10;
const int POW_PIN=8;
//Default rate 5 seconds
int refresh_rate=5000;
void setup() {
Serial.begin(9600);
Serial.println("Initializing Card");
//CS pin is output
pinMode(CS_PIN,OUTPUT);
//Card draws power from pin 8, therefore set high
pinMode(POW_PIN,OUTPUT);
digitalWrite(POW_PIN,HIGH);
//Print error message if SD.begin fails
if(!SD.begin(CS_PIN))
{
Serial.println("Card Failure");
return;
}
Serial.println("Card Ready");
//Read configuration information
File commandFile = SD.open("speed.txt");
if(commandFile)
{
Serial.println("Reading Command File");
while(commandFile.available())
{
refresh_rate=commandFile.parseInt();
}
Serial.print("Refresh Rate = ");
Serial.print(refresh_rate);
Serial.println("ms");
commandFile.close();
}
else
{
Serial.println("Could not read command file.");
return;
}
}
void loop() {
long timeStamp=millis();
String dataString="Hello";
//Open file and write to it
File dataFile=SD.open("log.csv",FILE_WRITE);
if(dataFile)
{
dataFile.print(timeStamp);
dataFile.print(",");
dataFile.println(dataString);
dataFile.close(); //Close connection in order to write data
//Print same info to serial monitor for debugging
Serial.print(timeStamp);
Serial.print(",");
Serial.println(dataString);
}
else
{
Serial.println("Couldn't open log file");
}
delay(refresh_rate);
}
#include <SD.h>
//Set CS Pin
const int CS_PIN=10;
const int POW_PIN=8;
//Default rate 5 seconds
int refresh_rate=5000;
void setup() {
Serial.begin(9600);
Serial.println("Initializing Card");
//CS pin is output
pinMode(CS_PIN,OUTPUT);
//Card draws power from pin 8, therefore set high
pinMode(POW_PIN,OUTPUT);
digitalWrite(POW_PIN,HIGH);
//Print error message if SD.begin fails
if(!SD.begin(CS_PIN))
{
Serial.println("Card Failure");
return;
}
Serial.println("Card Ready");
//Read configuration information
File commandFile = SD.open("speed.txt");
if(commandFile)
{
Serial.println("Reading Command File");
while(commandFile.available())
{
refresh_rate=commandFile.parseInt();
}
Serial.print("Refresh Rate = ");
Serial.print(refresh_rate);
Serial.println("ms");
commandFile.close();
}
else
{
Serial.println("Could not read command file.");
return;
}
}
void loop() {
long timeStamp=millis();
String dataString="Hello";
//Open file and write to it
File dataFile=SD.open("log.csv",FILE_WRITE);
if(dataFile)
{
dataFile.print(timeStamp);
dataFile.print(",");
dataFile.println(dataString);
dataFile.close(); //Close connection in order to write data
//Print same info to serial monitor for debugging
Serial.print(timeStamp);
Serial.print(",");
Serial.println(dataString);
}
else
{
Serial.println("Couldn't open log file");
}
delay(refresh_rate);
}
Lecture: Introduction to C++
Object Oriented Programming
Definitions:
Class: Programmer-defined data type that includes both data and functions that operate on the data
Object: Variable of defined class type. Call functions that are defined for their class. Functions are called member functions and operate on the data members of the calling object
Polymorphism: Ability to assign many meanings to same name
Inheritance: Allows class to inherit attributes from an existing class. New class (called child or derived class) inherits all data and functions from existing (parent or base) class, and may have additional data members or member functions of its own.
C++ Program Structure
C++, while similar to C, still has some distinct differences.
Example 1.1
C program to compute distance between two points
/* This program computes the distance between two points. */
#include <stdio.h>
#include <math.h>
int main(void)
{
/* Declare and initialize variables. */
double x1=1, y1=5, x2=4, y2=7,
side_1, side_2, distance;
/* Compute sides of a right triangle. */
side_1 = x2 - x1;
side_2 = y2 - y1;
distance = sqrt(side_1*side_1 + side_2*side_2);
/* Print distance. */
printf("The distance between the two points is %5.2f \n",distance);
/* Exit program. */
return 0;
}
Example 1.2
C++ version of program in Example 1.1
//–––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––
// This program computes the distance between two points.
#include <iostream>
#include <cmath>
using namespace std;
int main(void)
{
// Declare and initialize variables.
double x1=1, y1=5, x2=4, y2=7,
side_1, side_2, distance;
// Compute sides of a right triangle.
side_1 = x2 - x1;
side_2 = y2 - y1;
distance = sqrt(side_1*side_1 + side_2*side_2);
// Print distance.
cout.setf(ios::fixed);
cout.precision(2);
cout << "The distance between the two points is "
<< distance << endl;
// Exit program.
return 0;
}
On our whiteboards, were were supposed to find 5 differences between the two programs. I forgot to take pictures, so here are the 5 differences Anthony and I found.
Example 3.1
Example 1.1
C program to compute distance between two points
/* This program computes the distance between two points. */
#include <stdio.h>
#include <math.h>
int main(void)
{
/* Declare and initialize variables. */
double x1=1, y1=5, x2=4, y2=7,
side_1, side_2, distance;
/* Compute sides of a right triangle. */
side_1 = x2 - x1;
side_2 = y2 - y1;
distance = sqrt(side_1*side_1 + side_2*side_2);
/* Print distance. */
printf("The distance between the two points is %5.2f \n",distance);
/* Exit program. */
return 0;
}
Example 1.2
C++ version of program in Example 1.1
//–––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––
// This program computes the distance between two points.
#include <iostream>
#include <cmath>
using namespace std;
int main(void)
{
// Declare and initialize variables.
double x1=1, y1=5, x2=4, y2=7,
side_1, side_2, distance;
// Compute sides of a right triangle.
side_1 = x2 - x1;
side_2 = y2 - y1;
distance = sqrt(side_1*side_1 + side_2*side_2);
// Print distance.
cout.setf(ios::fixed);
cout.precision(2);
cout << "The distance between the two points is "
<< distance << endl;
// Exit program.
return 0;
}
On our whiteboards, were were supposed to find 5 differences between the two programs. I forgot to take pictures, so here are the 5 differences Anthony and I found.
- Different includes
- <stdio.h> vs <iostream>
- Instead of printf, cout is used
- Instead of \n, endl is used
- To determine number of decimal places to print in C++, we use cout.precision(2);
- cout.setf(ios::fixed) is used in C++
Input and Output
cin and cout are used for standard input and standard output respectively, and are defined in header file iostream.
Preprocessor directive for math library is #include <cmath>
using namespace std; tells the compiler to use the library filenames declared in namespace std.
The cout Object
cout object defined to stream output to standard output device. Stream insertion operator (<<) is used with cout.
Example 2.1
Using cout to output values to screen
cout << "The radius of the circle is" << radius << "centimeters" << endl;
I initially ran into problems using endl because I believed it was end1, which was end followed by the number 1. After about 10 minutes of frustration, I remembered my Matlab days, and tried changing it to be endl, with end followed by the letter l as in lambda. It worked, which is when I realized that endl most likely stands for "end line."
Stream Functions
| Figure 3.1 Commonly used format flags |
Setting precision and using ios::fixed and ios::showpoint
double radius = 10, area;
const double PI=3.141579;
//set format flags
cout.setf(ios::fixed); // setf function is called by cout
cout.setf(ios::showpoint);
cout.precision(2); //set precision
...
cout << "The radius of the circle is: " << radius << " centimeters"
<< endl
<< "The area is " << PI*radius*radius << " square centimeters"
<< endl;
The cin object is defined to stream input from the standard input device. The extraction operator is (>>) and is used with istream objects to input values and assign values to variables. It discards all white space. The input from cin is not read until the enter key is pressed. Values must be separated by white space. I learned this the hard way by accidentally separating them with commas and getting extremely weird numbers.
Example 4.1
Inputting 3 values from keyboard
cin>>var1>>var2>>var3;
Example 4.2
Using cin
int id;
double rate, hours;
char code;
cin >> rate >> hours >> id >> code;
cout << rate << endl << hours << endl << id << endl << code << endl;
| Figure 4.1 Output from example 4.2 |
Homework
Lab Homework 1
The task: Attach a temperature sensor the the Arduino and log the temperature for 1 minute once per second.
I used the I2C temperature sensor for this one, since it conveniently already prints the temperatures in degrees Celsius without having to convert analog readings into temperatures.
| Figure 3.1 Serial monitor displaying timestamps and temperatures in degrees celsius |
| Figure 3.2 CSV file with data logged onto SD card |
| Figure 3.3 I2C Temperature sensor and SD card module hooked up to Arduino |
Code
//Temperature Logger SD Card
#include <SD.h>
#include <Wire.h>
int temp_address=72; //1001000 written as decimal number
const int CS_PIN=10; //Define CS pin
long lastTime=0; //Initialize last time
void setup() {
Serial.begin(9600);
Wire.begin(); //Create wire object
Serial.println("Initializing Card");
pinMode(CS_PIN,OUTPUT); //Set CS pin as output
if(!SD.begin(CS_PIN)) //Print error message if SD.begin fails
{
Serial.println("Card Failure");
return;
}
Serial.println("Card Ready");
}
void loop() {
//Send request and start talking to device at specified address
Wire.beginTransmission(temp_address);
Wire.write(0); //Send bit asking for register zero, the data register
Wire.endTransmission(); //Complete Transmission
//Read temperature from the device
//Request 1 byte from specified address
Wire.requestFrom(temp_address,1);
//Wait for response
while(Wire.available() ==0);
//Get temperature and read it into variable
int temperature=Wire.read();
long timeStamp=millis();
//Find difference between time stamp and the last time
long interval=timeStamp-lastTime;
//Continue for 60 seconds every 1 second
if(timeStamp<=60000 && interval>=1000)
{
//Open a file and write to it
File dataFile=SD.open("temps.csv",FILE_WRITE);
if(dataFile)
{
dataFile.print(timeStamp);
dataFile.print(",");
dataFile.println(temperature);
dataFile.close(); //data not written until connection is closed
//Print information to screen for debugging
Serial.print(timeStamp);
Serial.print(",");
Serial.println(temperature);
}
else //Print error message if file cannot be opened
{
Serial.println("Couldn't open temps file");
}
//Reset the last time
lastTime=timeStamp;
}
//Stop reading if time exceeds 60 seconds
else if(timeStamp>60000)
{
return;
}
}
Lab Homework 2
The task: Set up two buttons that allow you to increase and decrease the time interval for data collection. Attach the two buttons to interrupts.
The video below shows the CSV file that was logged to the SD card. Notice that the intervals between the time stamps change.
| Figure 4.1 This shows the timestamp, interval, and the temperature in degrees celsius |
| Figure 4.2 Interrupt, SD card module, buttons, and I2C temperature sensor hooked up to Arduino |
| Figure 4.3 Close-up view of breadboard |
Code
//Temperature Logger SD Card with Variable Intervals
#include <SD.h>
#include <Wire.h>
int temp_address=72; //1001000 written as decimal number
const int CS_PIN=10; //Define CS pin
long lastTime=0; //Initialize last time
//Define interrupt buttons
const int decreaseButton=0;
const int increaseButton=1;
volatile int interval=1000; //Initial interval
void setup() {
Serial.begin(9600);
Wire.begin(); //Create wire object
Serial.println("Initializing Card");
pinMode(CS_PIN,OUTPUT); //Set CS pin as output
//Attach interrupts
attachInterrupt(decreaseButton,decrease,RISING);
attachInterrupt(increaseButton,increase,RISING);
if(!SD.begin(CS_PIN)) //Print error message if SD.begin fails
{
Serial.println("Card Failure");
return;
}
Serial.println("Card Ready");
}
//Function to decrease interval by 200 milliseconds
void decrease()
{
if(interval>200 && interval<=10000)
{
interval-=200;
}
else if(interval<=200)
{
interval=200; //Minimum interval is 200 milliseconds
}
}
//Function to increase interval by 200 milliseconds
void increase()
{
if(interval<10000 && interval>=200)
{
interval+=200;
}
else if(interval>=10000)
{
interval=10000; //Maximum interval is 10000 milliseconds
}
}
void loop() {
//Send request and start talking to device at specified address
Wire.beginTransmission(temp_address);
Wire.write(0); //Send bit asking for register zero, the data register
Wire.endTransmission(); //Complete Transmission
//Read temperature from the device
//Request 1 byte from specified address
Wire.requestFrom(temp_address,1);
//Wait for response
while(Wire.available() ==0);
//Get temperature and read it into variable
int temperature=Wire.read();
long timeStamp=millis();
//Find difference between time stamp and the last time
long timeDiff=timeStamp-lastTime;
//Continue for 60 seconds, reading after every interval
if(timeStamp<=60000 && timeDiff>=interval)
{
//Open a file and write to it
File dataFile=SD.open("temps.csv",FILE_WRITE);
if(dataFile)
{
dataFile.print(timeStamp);
dataFile.print(",");
dataFile.println(temperature);
dataFile.close(); //data not written until connection is closed
//Print information to screen for debugging
Serial.print(timeStamp);
Serial.print(",");
Serial.print(interval);
Serial.print(",");
Serial.println(temperature);
}
else //Print error message if file cannot be opened
{
Serial.println("Couldn't open temps file");
}
//Reset the last time
lastTime=timeStamp;
}
//Stop reading if time exceeds 60 seconds
else if(timeStamp>60000)
{
return;
}
}
Comments
Post a Comment