Arduino mysteriously halts while logging accelerometer and gyroscope data to SD

Hi,

I'm trying to log yaw, pitch and roll data from an IMU (MPU6050, using the Sparkfun breakout board (https://www.sparkfun.com/products/11028)) to a microSD card (using the Sparkfun microSD Shield (SparkFun microSD Shield - DEV-12761 - SparkFun Electronics)). I'm using an Arduino Uno R3, Arduino 1.0.5, and some Sparkfun logic level converters between the Arduino and the sensor.

My sketch uses Jeff Rowberg's I2Cdevlib (i2cdevlib/Arduino/MPU6050 at master · jrowberg/i2cdevlib · GitHub) to communicate with the MPU6050. For some reason, my sketch just halts after around half a minute, sometimes more, sometimes less.

The example sketch in the library (at https://github.com/jrowberg/i2cdevlib/blob/master/Arduino/MPU6050/Examples/MPU6050_DMP6/MPU6050_DMP6.ino) seems to be able to run indefinitely. My sketch is essentially the same as this example, but with uneeded output formats removed, and logging to an SD card instead of outputting sensor readings to Serial. My sketch also closes and reopens the log file on the card every 10 seconds, to ensure that data is actually written onto the card, and writes a timestamp to the log.

Suspecting a problem with either the microSD shield or the card itself, I tried just writing data continuously to the card - nothing went wrong with this.

Suspecting a shortage of RAM, I've tried monitoring memory usage using MemoryFree.h (Arduino Playground - AvailableMemory). There is a constant 746 bytes available on the Arduino, right up to when it crashes, so memory is probably not the problem.

I've also tried using the Fastwire I2C implementation in I2Cdevlib (as opposed to the default Wire.h), but even the example sketch doesn't work with it - it just stops after announcing its intention to initialise the processor on the sensor. After enabling debugging output in I2Cdevlib, it just ends up repeatedly printing I2C (0x68) reading 2 bytes from 0x72.... Done (-1 read).

When I tried running my sketch using the Wire.h I2C implementation and with debugging output enabled, to see if there were any abnormalities in the communication between the Arduino and sensor, it ran for a record 245 seconds, with no apparent abnormalities prior to halting. There were over 1000 FIFO overflows, which aren't supposed to happen at all, but because they don't occur without debugging output enabled, I think they were just caused by the delay in writing the debugging output via Serial.

My sketch:

#include <SD.h>
#include <Wire.h>
#include <I2Cdev.h>
#include <MPU6050_6Axis_MotionApps20.h>

#include <MemoryFree.h>

const int CS = 8;

File logFile;
MPU6050 mpu;

char filename[] = "000";

uint8_t deviceStatus;     // 0 indicates success
uint8_t mpuIntStatus;     // interrupt status byte
uint16_t packetSize;      // expected DMP packet size
uint16_t fifoCount;       // number of bytes in FIFO
uint8_t fifoBuffer[64];   // FIFO storage buffer

unsigned long startTime = 0;
unsigned long previousTime = 0;
unsigned long currentTime = 0;

Quaternion q;          // [w, x, y, z]         quaternion container
VectorFloat gravity;   // [x, y, z]            gravity vector
float ypr[3];          // [yaw, pitch, roll]   yaw/pitch/roll container and gravity vector

void error()
{
  while (true);
}

volatile bool mpuInterrupt = false;
void dmpDataReady()
{
  mpuInterrupt = true;
}

void setup()
{
  Serial.begin(115200);
  
  // -------------------------------------------------SD
  Serial.print(F("Initialising SD card... "));
  pinMode(10, OUTPUT);
  pinMode(CS, OUTPUT);
  if (!SD.begin(CS))
  {
    Serial.println(F("failed"));
    error();
  }
  Serial.println(F("succeeded"));
  
  // -------------------------------------------------MPU
  Serial.print(F("Initialising IMU... "));
  Wire.begin();
  TWBR = 24;
  mpu.initialize();
  Serial.println(F("done"));
  
  // test connection
  Serial.print(F("Testing IMU connection... "));
  Serial.println(mpu.testConnection() ? F("succeeded") : F("failed"));
  
  // -------------------------------------------------DMP
  Serial.print(F("Initialising DMP... "));
  deviceStatus = mpu.dmpInitialize();
  if (deviceStatus == 0)
  {
    // turn DMP on
    mpu.setDMPEnabled(true);
    
    // set interrupt
    attachInterrupt(0, dmpDataReady, RISING);
    mpuIntStatus = mpu.getIntStatus();
    
    // get expected DMP packet size
    packetSize = mpu.dmpGetFIFOPacketSize();
    
    Serial.println(F("succeeded"));
  }
  else
  {
    Serial.println(F("failed"));
    error();
  }
  
  // -------------------------------------------------LOG
  // look for unused filename
  Serial.print(F("Opening log... "));
  logFile = SD.open(filename, FILE_WRITE);
  Serial.print(F("succeeded opening "));
  Serial.println(filename);
  
  startTime = millis();
  previousTime = startTime;
  logFile.print(F("T"));
  logFile.println(startTime);
  
  Serial.print(F("S"));
  Serial.println(previousTime);
}

void loop()
{
  // wait until MPU is ready
  while (!mpuInterrupt && fifoCount < packetSize);
  
  // reset interrupt flag and get INT_STATUS byte
  mpuInterrupt = false;
  mpuIntStatus = mpu.getIntStatus();
  
  // get current FIFO count
  fifoCount = mpu.getFIFOCount();
  
  // check for overflow
  if ((mpuIntStatus & 0x10) || fifoCount == 1024)
  {
    mpu.resetFIFO();
    Serial.println(F("ERROR: FIFO overflow"));
  }
  else if (mpuIntStatus & 0x02)  // check for data ready interrupt
  {
    // wait for correct data length
    while (fifoCount < packetSize)
      fifoCount = mpu.getFIFOCount();
    
    // read packet from FIFO
    mpu.getFIFOBytes(fifoBuffer, packetSize);
    
    // track FIFO count in case more than 1 packet is available
    // (allows us to read more without waiting for interrupt)
    fifoCount -= packetSize;
    
    // get data
    mpu.dmpGetQuaternion(&q, fifoBuffer);
    mpu.dmpGetGravity(&gravity, &q);
    mpu.dmpGetYawPitchRoll(ypr, &q, &gravity);
    
    // log data
    logFile.println(ypr[0] * 180/M_PI);
    logFile.println(ypr[1] * 180/M_PI);
    logFile.println(ypr[2] * 180/M_PI);
  }
  
  currentTime = millis();
  Serial.print(F("M: "));
  Serial.print(freeMemory());
  Serial.print(F(" P: "));
  Serial.print(previousTime);
  Serial.print(F(" C: "));
  Serial.println(currentTime);
  if (currentTime >= (previousTime + 10000))
  {
    Serial.println(F("CYCLE"));
    
    // close log to ensure changes are written
    logFile.close();
    
    // open log again
    logFile = SD.open(filename, FILE_WRITE);
    logFile.print(F("T"));
    logFile.println(currentTime);
    
    previousTime = currentTime;
  }
}

Would anyone have any ideas about what could be causing the Arduino to halt? Thanks.

For some reason, my sketch just halts after around half a minute

I see comments like that, and I start looking for "int"s where they shouldn't be.
Just a thought.

"int"s - integer declarations?
I'm not really sure what you mean.

yhls:
"int"s - integer declarations?
I'm not really sure what you mean.

An int takes up two bytes. If you're not storing a value between 256 and 32767, or 256 and 65535 for unsigned, then you should use byte (or char for -128 to 127) as it takes up half the memory (and calculations are faster).

On example is:

int myPin = 4;

would be more efficient as:

byte myPin = 4;

and even more efficient as:

const byte myPin = 4;

as it won't use RAM at all for that.

I was thinking more along the lines of "around half a minute" is roughly 32.767 seconds.

AWOL:
I was thinking more along the lines of "around half a minute" is roughly 32.767 seconds.

True... but I do see some unsigned long's up the top of his code related to time stuff...

  // look for unused filename

No, that is not what is happening.

  while (!mpuInterrupt && fifoCount < packetSize);

This look ass-backwards to me. Instead of doing nothing until the condition is true, do something only when the condition IS true.

fifoCount and packetSize don't change during the while loop.

  if ((mpuIntStatus & 0x10) || fifoCount == 1024)

So, you are allowing for the fifo queue to use 1/2 the Arduino's memory. The SD class uses 1/4 of it. That doesn't leave room for much else. Which just may be why the Arduino hangs after a while.

Thanks for your replies.

By "around half a minute", I guess I meant "sometime in the vicinity of half a minute" - the time at which it stops isn't very consistent. 20s is very common, as is a minute. Still, nice coincidence :slight_smile:

As for memory use by variables, I guess I could save a few bytes by using unsigned ints instead of unsigned longs to store the output of millis(), and I don't actually need startTime, but seeing as freeMemory() from MemoryFree.h tells me I have more than 740 bytes free, I don't think a few bytes will make a big difference.

  // look for unused filename

Oops - remnant from an older version of the code.

  while (!mpuInterrupt && fifoCount < packetSize);

I originally executed the sensor related code only when the interrupt flag was set, but I decided to use exactly what the example in the library used in case something I did was causing it to stop. I've now put all the sensor related code under

  if (mpuInterrupt || fifoCount >= packetSize)

and it still freezes.

  if ((mpuIntStatus & 0x10) || fifoCount == 1024)

Actually, the FIFO queue is on the MPU6050 itself, so doesn't take up any RAM. fifoCount just stores its current size. The buffer I use on the Arduino for data from the sensor (fifoBuffer) takes up 64 bytes (following the library example).

yhls:
Would anyone have any ideas about what could be causing the Arduino to halt? Thanks.

There is no way for fifoCount to get updated after you enter this loop, so if it was less than packetSize when you enter the loop the loop will never end:

  while (!mpuInterrupt && fifoCount < packetSize);

I'm not sure what the purpose of that loop is since you already have code to spin until fifoCount reaches the right value, but it seems to me that you could either delete the whole line, or replace it with the following to get a similar effect:

  while (!mpuInterrupt);

The whole concept of spinning until you get an interrupt strikes me as fundamentally daft, though. Surely the point of this API you're using is to make it possible for your sketch to handle IMU input as a background activity as it becomes available and this approach is defeating the whole point of that, and adding complexity and bugs with it. If you're going to poll the IMU then just do that and handle the input when it's available - no need for any of these blocking loops.