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