Skip to main content

Day 23: Intro to C++ | Data Logging with SD Cards

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:
  1. Format the SD Card
  2. Insert SD card into SD card module for Arduino
  3. Wire up module to Arduino
  4. Plug in Arduino to computer
  5. Write Arduino code
  6. Upload Arduino code
  7. Open serial monitor 
  8. 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
Figure 1.3
SD card module hooked up to Arduino

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);
}

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.

  1. Different includes
    1. <stdio.h> vs <iostream>
  2. Instead of printf, cout is used
  3. Instead of \n, endl is used
  4. To determine number of decimal places to print in C++, we use cout.precision(2);
  5. 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
Example 3.1
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;

Figure 3.2
Whiteboard problem 1

The cin Object

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

Software

1. Modify the program so that it reads the unknown measurement from the keyboard. 

2. Modify the program so that it reads the known measurement from a data file and contains a loop that compares the unknown to all measurements in the file. 

3. Modify the program in problem 2 so that it also prints the minimum distance measurement. 

4. Modify the program in problem 3 so that it prints the entry number with the minimum distance, as in “Known 4 has best match.” 

5. Modify the program in problem 4 so that it prints all entries with the minimum distance.

Comments

Popular posts from this blog

Day 20: Structures, Programming with Pointers

Lecture Structures Structure defines set of data, but individual parts do not have to be the same type. Example 1.1 struct hurricane {  char name[10];  int year,category; }; Within a structure, variables and even arrays can be defined. Structures are also known as aggregate data types since multiple data values can be collected into a single data type. Individual values within a structure are called data members, and each member is given a name. In Example 1.1, the names of the data members are name, year, and category. To refer to a data member, the structure variable name followed by a period and a data member name is used.  Definition and Initialization Define structure. Keyword struct used to define name of structure (aka structure tag) and data members that are included in structure After structure defined, structure variables can be defined using declaration statements. Semicolon required after structure definition. Statements can appear before m...

Day 4: RGB Nightlight | Algorithm Development, Conditional Expressions, Selection Statements

Hardware: RGB Nightlight const int red=11; const int green=10; const int blue=9; const int button=4; boolean lastbutton=LOW; boolean currentbutton=LOW; int ledMode=0; void setup() {   // put your setup code here, to run once:   pinMode(button,INPUT);   pinMode(red,OUTPUT);   pinMode(green,OUTPUT);   pinMode(blue,OUTPUT); } boolean debounce(boolean last) {   boolean current=digitalRead(button);   if(last!=current)   {     delay(5);     current=digitalRead(button);   }   return current; } void setMode(int mode) {   //RED   if(mode==1)   {     digitalWrite(red,HIGH);     digitalWrite(green,LOW);     digitalWrite(blue,LOW);   }   if(mode==2)   {     //orange     digitalWrite(red,HIGH);     analogWrite(green,40);     digitalWrite(blue,LOW);...