/* Datalogger 
Eric Ayars
July '10

This Arduino program controls and reads three axes from an MMA7260 3-axis
accelerometer, and writes the data to a micro-SD card.

It uses the Fat16 library by Will Greiman. Will, if you're reading this, my
hat is off to you. The deeper I get into the library the more impressed I
am by the quality of the work you did. Very nice code!

Errors will be given on the LED.
Here are those errors, in flashes:

2,1 "card.init"
2,2 "Fat16::init"
3,1 "file.open() error"
3,2 "file.close() error"
4,1 "error when trying to write header"
4,2 "error when trying to write data"
4,3 "file.sync() error"


/*  User-selected values

    The following definitions can be set before sending the code to the
    Arduino, and change the datalogger's behavior.

    LOG_INTERVAL is the time in milliseconds between samples. 

    GAIN is the user-selectable range on the MMA7260 accelerometer, and 
        should be set to one of {1, 2, 4, 6}. The range for 1 is actually
        1.5, if you care about that... 
        The default value (if you set something wrong) is 6.

    DEBUG should be set to 0 for optimal data-collection speed. If set to 
        1, the Arduino sends verbose messages to the serial port about 
        everything it is doing. 

    CONVERT_VALUES should be set to 1 to have the Arduino convert raw 
        A/D integers into float SI values of acceleration. Set it to 0 if
        you would prefer the data file to contain raw A/D values.
    TIME_OR_BUTTON changes the stop condition for data collection. If set
        to 1, the Arduino stops after the number of milliseconds set by
        COLLECTION_MILLIS. If set to 0, it stops on a second button press.

    COLLECTION_MILLIS applies only if TIME_OR_BUTTON is set to 1, and is 
        the number of milliseconds to collect data.

#define LOG_INTERVAL 100    
#define GAIN 1
#define DEBUG 0
#define TIME_OR_BUTTON 0
#define COLLECTION_MILLIS 10000 

//  Definitions below this point should probably only be changed if you
//  know what you're doing.
#define LONG_INTERVAL 500   // number of mills to wait, for a longish time
#define SHORT_INTERVAL 100  // number of mills to wait, short time.
#define BUTTON 4            // BUTTON pin
#define LED 6               // LED pin
#define GS1 7               // Gain select 1 pin
#define GS2 8               // Gain select 2 pin
#define SLP 9               // Sleep pin
#define X_ACCEL 2           // x input
#define Y_ACCEL 1           // y input
#define Z_ACCEL 0           // z input

#include <Fat16.h>
#include <Fat16util.h>

unsigned long int logTime = 0;      // time of last log event
unsigned long int currentTime = 0;  // Now, with startTime subtracted
unsigned long int startTime = 0;    // When did we start collection?
int x, y, z;                        // data storage
float conversionFactor;

SdCard card;            // 'card' is of type 'SdCard'.
Fat16 file;             // 'file' is of type 'Fat16'.

boolean lastButtonState = HIGH;

boolean buttonPressed(byte button) {
    // Checks to see if the button was pressed since last check.
    boolean currentButtonState = digitalRead(button);
    boolean state;
    if (lastButtonState && !currentButtonState) {
        // Button was high, is now low. The button has been pressed.
        state = true;
    } else {
        // Not pressed.
        state = false;
    lastButtonState = currentButtonState;
#endif // DEBUG
    return state;

void flashLED(byte count, int interval) {
    // Just what it says: it flashes the LED 'count' times for 
    // the given on/off interval each time.
    byte j;
    Serial.print(count, DEC);
    Serial.print(interval, DEC);
#endif //DEBUG
    for (j=0; j<count; j++) {
        digitalWrite(LED, HIGH);
        digitalWrite(LED, LOW);

void sendError(byte errorGroup, byte errorItem) {
    // Flashes the LED count times, then pauses, then repeats. 
    // Used to tell the user what the problem is.
    Serial.print(errorGroup, DEC);
    Serial.print(", ");
    Serial.print(errorItem, DEC);
#endif //DEBUG
    while (true) {
        flashLED(errorGroup, SHORT_INTERVAL);
        flashLED(errorItem, SHORT_INTERVAL);

#if CONVERT_VALUES  // This function is only needed if you're converting.
float convert(int ADValue) {
    // Converts integer A/D values to float, depending on the value of 
    // conversionFactor.
    float gValue = float(ADValue - 512)*conversionFactor;
    return gValue;

void setup(void) {
    pinMode(LED, OUTPUT);
    pinMode(BUTTON, INPUT);
    pinMode(GS1, OUTPUT);
    pinMode(GS2, OUTPUT);
    pinMode(SLP, OUTPUT);

    // Initialize the SD card or send the error
    if (!card.init()) sendError(2,1);

    // Locate the Fat16 volume or send the error
    if (!Fat16::init(&card)) sendError(2,2);

    // Set gain pins
    // The MMA7260 has two pins that set the gain range
    // 0 0  -> 1.5 g
    // 0 1  -> 2 g
    // 1 0  -> 4 g
    // 1 1  -> 6 g
    switch (GAIN) {
        case 1:
            digitalWrite(GS1, 0);
            digitalWrite(GS2, 0);
            conversionFactor = 0.0394775;
        case 2:
            digitalWrite(GS1, 1);
            digitalWrite(GS2, 0);
            conversionFactor = 0.0526367;
        case 4:
            digitalWrite(GS1, 0);
            digitalWrite(GS2, 1);
            conversionFactor = 0.1052734;
        default:    // 6 g range is default.
            digitalWrite(GS1, 1);
            digitalWrite(GS2, 1);
            conversionFactor = 0.1579101;

    // Start asleep, to save power.
    digitalWrite(SLP, LOW);

    Serial.println("Got through setup()");
#endif //DEBUG
}   // end setup()

 *  Main Loop

void loop(void) {

    // Start by waiting for the "go" button.
    Serial.println("loop, wait for button");
#endif // DEBUG
    while (!buttonPressed(BUTTON)) {
        // Button is not pressed, so just sit here slowly flashing.
        flashLED(1, LONG_INTERVAL);

     * Button has been pressed now, so open/create the file.
     * The "create a new file" code below is directly from
     * Will Greiman's Fat16 examples folder. Sweet code!
    char name[] = "LOGGER00.TXT";
    for (int j=0;j<100;j++) {
        name[6] = j/10 + '0';
        name[7] = j%10 + '0';
        if (file.open(name, O_CREAT | O_EXCL | O_WRITE)) break;
    /* Here's how that works: 
        O_WRITE = 0x02 = 00000010
        O_CREAT = 0x10 = 00010000
        O_EXCL  = 0x20 = 00100000

       So (O_WRITE | O_CREAT | O_EXCL) = 00110010. This set of flags goes
       to the file.open() routine, and it's written so that if both 
       O_CREAT and O_EXCL are set and the file already exists, then 
       file.open() returns false.

       The for loop tries sequential LOGGERxx.CSV filenames until it finds
       one that doesn't exist, in which case it returns true and the break
       statement stops the loop. So each time you take data, it writes
       to a new filename.
    for (byte j=0;j<12;j++) {
#endif // DEBUG
    if (!file.isOpen()) sendError(3,1);

    // Now write the file header
    file.writeError = false; 
    file.println("xyz values in m/s/s");
    file.println("t (s)\tx\ty\tz");
    Serial.println("t (s)\tx\ty\tz");
#endif //DEBUG
    if (file.writeError || !file.sync()) sendError(4,1);

    // File is open and header written, so give a countdown.
    for (int j=5;j>0;j--) {
        flashLED(j, SHORT_INTERVAL);
    // And we're live! Turn on the LED, wake up the accelerometer.
    digitalWrite(LED, HIGH);
    digitalWrite(SLP, HIGH);

     * Here's the data collection. There are two ways of deciding when to
     * stop: "fixed time" (TIME_OR_BUTTON = 1) or "collect until button"
     * (TIME_OR_BUTTON = 0). So start with some compiler directives that
     * put in the right bit of while stop-code for our situation.
    startTime = millis();
    while (millis()-startTime < COLLECTION_MILLIS) {
        // collect data for a set time.
    while (!buttonPressed(BUTTON)) {
        // keep going until button is pressed.
#endif  //TIME_OR_BUTTON

        // This next bit waits around until the current time is an 
        // exact multiple of LOG_INTERVAL.
        do {
            logTime = millis();
        } while (logTime % LOG_INTERVAL);

        // Now is a good time to log data, so...
        currentTime = logTime - startTime;
        x = analogRead(X_ACCEL);
        y = analogRead(Y_ACCEL);
        z = analogRead(Z_ACCEL);
        file.print((float(currentTime) / 1000.0));
#if CONVERT_VALUES  // We're saving converted values instead of raw, so...
#else               // Here's code for saving raw values
        Serial.print(currentTime / 1000);
        Serial.print(currentTime % 1000);
#endif // DEBUG

        // Any problem writing?
        if (file.writeError) sendError(4,2);

    }   // end of whichever while loop is put in by the compiler.
    // Close file, put accelerometer back to sleep.
    if (!file.close()) sendError(3,2);
#endif //DEBUG
    digitalWrite(LED, LOW);
    digitalWrite(SLP, LOW);

}   // end loop()

Generated by GNU enscript 1.6.4.