6

On an Arduino Pro Mini, I'm using Jeff Rowberg's stuff in i2cdevlib to read from the MPU6050, and using SdFat to write data to an SD card. Each part in isolation is fine, but when both DAQ and logging are in use the Arduino locks up after a few minutes, then happily restarts when reset is pressed.

I've tried disabling the interrupt and manually polling mpu.getIntStatus() in loop(), and I've also dropped the FIFO rate from 100Hz to 10Hz by changing the config in MPU6050_6Axis_MotionApps20.h (and confirmed that it has actually dropped) along with changing the SPI speed from SPI_FULL_SPEED to SPI_SIXTEENTH_SPEED and changing the I2C wire speed from 400kHz to 50kHz. All of these were done progressively and showed no significant change in the run times, which are scattered pretty randomly between <1 and 20 minutes. I also changed the datalogging to doing a filesystem flush on every cycle from doing it every 100 cycles, which helped performance but might have meant lots of internal buffer allocation/deallocation and memory leak risk.

I've tried putting a watchdog in but have encountered a widely known issue with this board that the watchdog is useless with the stock bootloader. I'm now working on that so I can try and use it to debug where in code the hang is, but I suspect it's going to be deep in one of the two third party libraries and I'm not going to be able to do much with it. I've attached the present iteration of code below. Does anyone have thoughts on where to go from here? Has anyone done similar with this kit?

#include "I2Cdev.h"

//#include <helper_3dmath.h>
//#include <MPU6050.h>
#include <MPU6050_6Axis_MotionApps20.h>
//#include <MPU6050_9Axis_MotionApps41.h>

// Arduino Wire library is required if I2Cdev I2CDEV_ARDUINO_WIRE implementation
// is used in I2Cdev.h
#if I2CDEV_IMPLEMENTATION == I2CDEV_ARDUINO_WIRE
    #include "Wire.h"
#endif

//Includes for SdFat

#include <FreeStack.h>
#include <MinimumSerial.h>
#include <SdFat.h>
#include <SdFatConfig.h>
#include <SdFatUtil.h>
#include <SystemInclude.h>

//Includes for watchdog
#include <avr/wdt.h>

// ************* Defines for MPU6050 *****************
#define INTERRUPT_PIN 2  // use pin 2 on Arduino Uno & most boards


// ************* Variables for MPU6050 *****************

// class default I2C address is 0x68
// specific I2C addresses may be passed as a parameter here
// AD0 low = 0x68 (default for SparkFun breakout and InvenSense evaluation board)
// AD0 high = 0x69
MPU6050 mpu;
//MPU6050 mpu(0x69); // <-- use for AD0 high

// MPU control/status vars
bool dmpReady = false;  // set true if DMP init was successful
uint8_t mpuIntStatus;   // holds actual interrupt status byte from MPU
uint8_t devStatus;      // return status after each device operation (0 = success, !0 = error)
uint16_t packetSize;    // expected DMP packet size (default is 42 bytes)
uint16_t fifoCount;     // count of all bytes currently in FIFO
uint8_t fifoBuffer[64]; // FIFO storage buffer

// orientation/motion vars
//Quaternion q;           // [w, x, y, z]         quaternion container
VectorInt16 aa;         // [x, y, z]            accel sensor measurements
VectorInt16 gyro;       // [x, y, z]            gyro measurements

//VectorInt16 aaReal;     // [x, y, z]            gravity-free accel sensor measurements
//VectorInt16 aaWorld;    // [x, y, z]            world-frame accel sensor measurements
//VectorFloat gravity;    // [x, y, z]            gravity vector
//float euler[3];         // [psi, theta, phi]    Euler angle container
//float ypr[3];           // [yaw, pitch, roll]   yaw/pitch/roll container and gravity vector

// ************* Defines for SdFat *******************


// The chip select line for the SD card
#define SD_CHIP_SELECT 4
// Log file base name.  Must be six characters or less.
#define FILE_BASE_NAME "Data"

// ************ Variables for SdFat *******************
//
// Set DISABLE_CHIP_SELECT to disable a second SPI device.
// For example, with the Ethernet shield, set DISABLE_CHIP_SELECT
// to 10 to disable the Ethernet controller.
const int8_t DISABLE_CHIP_SELECT = -1;
//
// Test with reduced SPI speed for breadboards.
// Change spiSpeed to SPI_FULL_SPEED for better performance
// Use SPI_QUARTER_SPEED for even slower SPI bus speed
const uint8_t spiSpeed = SPI_SIXTEENTH_SPEED;
// File system object.
SdFat sd;
// Log file.
SdFile file;

int sd_writes_since_flush = 0;

// Serial streams
ArduinoOutStream cout(Serial);

// input buffer for line
char cinBuf[40];
ArduinoInStream cin(Serial, cinBuf, sizeof(cinBuf));

// ******************************* Misc *******************************
int crash_timer = 0;


// ================================================================
// ===               INTERRUPT DETECTION ROUTINE                ===
// ================================================================

volatile bool mpuInterrupt = false;     // indicates whether MPU interrupt pin has gone high
void dmpDataReady() {
    mpuInterrupt = true;
}

//******************* Setup ***************************

void setup() {
  // put your setup code here, to run once:

  // Set up serial comms
  Serial.begin(250000);
  while (!Serial); // wait for Leonardo enumeration, others continue immediately
  Serial.println(F("Serial initialised"));

  // initialize device
  Serial.println(F("Initializing MPU.."));



  // Set up comms to the MPU6050
  Wire.begin();
  Wire.setClock(50000); //400000= 400kHz I2C clock. Comment this line if having compilation difficulties

  mpu.initialize();

  pinMode(INTERRUPT_PIN, INPUT);
  Serial.println(F("Initializing DMP.."));

  devStatus = mpu.dmpInitialize();

    // make sure it worked (returns 0 if so)
    if (devStatus == 0) {
        // turn on the DMP, now that it's ready
        Serial.println(F("Enabling DMP..."));
        mpu.setDMPEnabled(true);

        // enable Arduino interrupt detection
        Serial.println(F("Enabling interrupt detection (Arduino external interrupt 0)..."));
        attachInterrupt(digitalPinToInterrupt(INTERRUPT_PIN), dmpDataReady, RISING);
        mpuIntStatus = mpu.getIntStatus();

        // set our DMP Ready flag so the main loop() function knows it's okay to use it
        Serial.println(F("DMP ready! Waiting for first interrupt..."));
        dmpReady = true;

        // get expected DMP packet size for later comparison
        packetSize = mpu.dmpGetFIFOPacketSize();
    } else {
        // ERROR!
        // 1 = initial memory load failed
        // 2 = DMP configuration updates failed
        // (if it's going to break, usually the code will be 1)
        Serial.print(F("DMP Initialization failed (code "));
        Serial.print(devStatus);
        Serial.println(F(")"));
    }



  // verify connection
  Serial.println(F("Testing device connections..."));
  Serial.println(mpu.testConnection() ? F("MPU6050 connection successful") : F("MPU6050 connection failed"));



  Serial.println(F("Initialising SD card.."));
  if (!sd.begin(SD_CHIP_SELECT, spiSpeed)) {
    if (sd.card()->errorCode()) {
      Serial.println (F(
             "\nSD initialization failed.\n"
             "Do not reformat the card!\n"
             "Is the card correctly inserted?\n"
             "Is chipSelect set to the correct value?\n"
             "Does another SPI device need to be disabled?\n"
             "Is there a wiring/soldering problem?\n"));
      //Serial.println(F("\nerrorCode: ") << hex << showbase);
      //Serial.println(sd.card()->errorCode());
      //Serial.println(F(", errorData: ") << int(sd.card()->errorData());
      //Serial.println(noshowbase);
      sd.initErrorHalt();
    }
    Serial.println(F("\nCard successfully initialized.\n"));
    if (sd.vol()->fatType() == 0) {
      Serial.println(F("Can't find a valid FAT16/FAT32 partition.\n"));
      sd.initErrorHalt();
    }
    if (!sd.vwd()->isOpen()) {
      Serial.println(F("Can't open root directory.\n"));
      sd.initErrorHalt();
    }
    Serial.println(F("Can't determine error type\n"));
    sd.initErrorHalt();
  }
  Serial.println(F("Card successfully initialized."));

  getFile();

  //Serial.println(F("Enabling 2s watchdog"));
  /* Watchdog is set to 2s.
   *  We tick every 10ms so initially it was set to 60ms, however the watchdog stays enabled
   *  after reboot, and since the code takes more than 60ms to come back up we ended up in a boot loop.
   */
  //wdt_enable(WDTO_2S);

}

void getFile()
{
    const uint8_t BASE_NAME_SIZE = sizeof(FILE_BASE_NAME) - 1;
    char fileName[13] = FILE_BASE_NAME "00.csv";

    // Find an unused file name.
  if (BASE_NAME_SIZE > 6) {
    sd.errorHalt(F("FILE_BASE_NAME too long"));
  }
  while (sd.exists(fileName)) {
    if (fileName[BASE_NAME_SIZE + 1] != '9') {
      fileName[BASE_NAME_SIZE + 1]++;
    } else if (fileName[BASE_NAME_SIZE] != '9') {
      fileName[BASE_NAME_SIZE + 1] = '0';
      fileName[BASE_NAME_SIZE]++;
    } else {
      sd.errorHalt(F("Can't create file name. May have used them all."));
    }
  }

  Serial.print(F("Trying to create file "));
  Serial.println(fileName);

  if (!file.open(fileName, O_CREAT | O_WRITE | O_EXCL)) {
      sd.errorHalt(F("file.open failed"));
    }

  file.println(F("Time/ms,acc_x,acc_y,acc_z,gyr_x,gyr_y,gyr_z"));

    // Force data to SD and update the directory entry to avoid data loss.
  if (!file.sync() || file.getWriteError()) {
    sd.errorHalt(F("write error"));
  }
  Serial.print(F("File opened; "));
  Serial.println(fileName);

}

void loop() {
  // put your main code here, to run repeatedly:
 // wdt_reset(); //reset watchdog
  // wait for MPU interrupt or extra packet(s) available

    int loopTO = 0;

    while (!mpuInterrupt && fifoCount < packetSize) {
        if (loopTO++ > 100000)
        {
          Serial.println(F("mpuInterrupt died?"));
          loopTO=0;
        }
    }

    // reset interrupt flag and get INT_STATUS byte
    mpuInterrupt = false;
    mpuIntStatus = mpu.getIntStatus();

    // get current FIFO count
    fifoCount = mpu.getFIFOCount();

    // check for overflow (this should never happen unless our code is too inefficient)
    if ((mpuIntStatus & 0x10) || fifoCount == 1024) {
        // reset so we can continue cleanly
        mpu.resetFIFO();
        Serial.println(F("FIFO overflow!"));

    // otherwise, check for DMP data ready interrupt (this should happen frequently)
    } else if (mpuIntStatus & 0x02) {
        // wait for correct available data length, should be a VERY short wait
        loopTO=0;
        while (fifoCount < packetSize) 
        {
          fifoCount = mpu.getFIFOCount();
          if (loopTO++ > 100000)
          {
            Serial.println(F("fifo no longer getting data?"));
            loopTO=0;
          }
        }

        // read a packet from FIFO
        mpu.getFIFOBytes(fifoBuffer, packetSize);

        // track FIFO count here in case there is > 1 packet available
        // (this lets us immediately read more without waiting for an interrupt)
        fifoCount -= packetSize;

        mpu.dmpGetAccel(&aa, fifoBuffer);

        //Gyro data is in the packet that comes back. More easily obtained using
        //dmpGetGyro(int32_t *data, const uint8_t* packet)
        //or dmpGetGyro(VectorInt16 *v, const uint8_t* packet)
        //These take the same parameters as the two versions of dmpGetAccel
        mpu.dmpGetGyro(&gyro, fifoBuffer);


        file.print(millis());
        file.print(",");
        file.print(aa.x);
        file.print(",");
        file.print(aa.y);
        file.print(",");
        file.print(aa.z);
        file.print(",");
        file.print(gyro.x);
        file.print(",");
        file.print(gyro.y);
        file.print(",");
        file.println(gyro.z);


        //Either sync here or in the 100th iteration bit.
        if (!file.sync() || file.getWriteError()) {
          sd.errorHalt(F("write error"));
        }

        if (sd_writes_since_flush++ > 10) //was 100 at 100Hz but at 10Hz that gets silly
        {
           // Force data to SD and update the directory entry to avoid data loss.
           sd_writes_since_flush = 0;
          /*
          if (!file.sync() || file.getWriteError()) {
            sd.errorHalt(F("write error"));
          }
          */
          //Also output so we know something's happening
          Serial.print(millis());

        Serial.print("\t");
        Serial.print(aa.x);
        Serial.print("\t");
        Serial.print(aa.y);
        Serial.print("\t");
        Serial.print(aa.z);
        Serial.print("\t");
        Serial.print(gyro.x);
        Serial.print("\t");
        Serial.print(gyro.y);
        Serial.print("\t");
        Serial.println(gyro.z);


        //Force a crash after 10s to test the watchdog
        //if (crash_timer++>10) while(1);

        }
        /*
        Serial.print(millis());

        Serial.print("\t");
        Serial.print(aa.x);
        Serial.print("\t");
        Serial.print(aa.y);
        Serial.print("\t");
        Serial.print(aa.z);
        Serial.print("\t");
        Serial.print(gyro.x);
        Serial.print("\t");
        Serial.print(gyro.y);
        Serial.print("\t");
        Serial.println(gyro.z);
*/
        // blink LED to indicate activity
        //blinkState = !blinkState;
        //digitalWrite(LED_PIN, blinkState);
    }

}
6
  • A SD memory card is 3.3V and the MPU-6050 is 3.3V. If your Pro Mini runs at 5V and 16MHz, you need levels shifters for the SD and for I2C.
    – Jot
    Commented Apr 12, 2017 at 22:31
  • Ta. Yeah it is a 5V board. I've confirmed the SD card module does have level shifters. Can't confirm it with the MPU-6050 module. However, now I've got the watchdog working I can see where it's crashing- it's in the guts of the SdFat library, "bool SdSpiCard::writeBlock(uint32_t blockNumber, const uint8_t* src)" so it doesn't look like a 3.3V/5V issue Commented Apr 12, 2017 at 23:58
  • Not every SD memory card is compatible. At least use the SD formatter: forum.arduino.cc/index.php?topic=228201.0 If that doesn't help, try another SD memory card. Could you return the the Arduino default SD library ? It is a slower, but it is reliable. If you use a breadboard, there might be bad connections. It is better to use a level shifter for the I2C bus and, if possible, exchange the outdated MPU-6050 for a MPU-9250.
    – Jot
    Commented Apr 14, 2017 at 7:40
  • Yeah I used the recommended format utility- given each test successfully records hundreds of thousands of lines of data to the card there doesn't seem a fundamental incompatibility. Also the hangs are only happening when both I2C and SPI comms are in use- each bit in isolation is fine. Re libraries, I was given to understand the standard lib is a fork of SdFat that's several years old and has a number of bugs that've since been resolved in the master? Commented Apr 14, 2017 at 8:45
  • The Arduino SD library is made by Bill Greiman github.com/greiman and he has continued to work on it which is now his SdFat library. However, I just want to get rid of everything unknown. When you have a I2C level shifter and everything is connected properly, there is no conflict between I2C and the SPI bus. At this moment I can only see troubles with 3.3V or 5V or voltage drops, but I don't know which modules you have and how you have connected it. Could you do a test by lowering the 5V for the Pro Mini to about 3.5V ?
    – Jot
    Commented Apr 15, 2017 at 10:15

3 Answers 3

2

This seems to be fixed. I've connected the modules together using short soldered leads rather than the breadboard and it's just run for 18 and a half hours before I decided to stop the test. Previously the maximum run time was less than an hour. So even with SPI at sixteenth speed and I2C at 50kHz instead of 400kHz, it seems a breadboard is too flaky when both comms mechanisms are in use. I note I didn't actually say I was on breadboard- in hindsight maybe that would've prompted immediate comment but I thought at slow data rates I'd be ruling out such things.

0

It seems that, from my experiments, this has something more to do with the serial buffer. If you print more than 55 characters in Serial.print (); using the MPU6050 and the SDCard, stack overflow errors occur. Try limit the buffer to 55 using the snprintf(); function; instead of Serial.print(); directly. Or make the serial output divided by 55 if your serial output is more than 55 per line.

0

I had a similar problem with an SD-Card and CAN-BUS Module on an Arduino Mega Pro Mini.

My solution was to add a 470uF capacitor between VCC and GND. Problem solved!

That was the only capacitor I tried, maybe a smaller value would have done the job. My project used about 200mA.

What I found surprising was that I had to add this capacitor when I used the USB from my PC for the power supply. The PC power supply and motherboard are high quality.

When I powered my project with a linear regulator (12V to 5V) and a switching converter all worked fine. But both had already a 470uF capacitor on the 5V side.

It's obvious that this won't be the answer in all scenarios. But it might be one of the possible answers for a system which hangs for no obvious reason.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.