Сбой при записи данных MPU-6050 на SD-карту

На Arduino Pro Mini я использую материал Джеффа Роуберга в i2cdevlib для чтения из MPU6050 и использую SdFat для записи данных на SD-карту. Каждая часть в отдельности хороша, но когда используются и DAQ, и протоколирование, Arduino блокируется через несколько минут, а затем успешно перезагружается при нажатии сброса.

Я пытался отключить прерывание и вручную опрашивать mpu.getIntStatus() в loop(), а также снизил частоту FIFO со 100 Гц до 10 Гц. изменив конфигурацию в MPU6050_6Axis_MotionApps20.h (и подтвердив, что она действительно упала), изменив скорость SPI с SPI_FULL_SPEED на SPI_SIXTEENTH_SPEED и изменив скорость провода I2C с 400 кГц на 50 кГц. Все это делалось постепенно и не показало значительных изменений во времени выполнения, которое довольно случайным образом разбросано между <1 и 20 минутами. Я также изменил журналирование данных на выполнение сброса файловой системы в каждом цикле, а не каждые 100 циклов, что повысило производительность, но могло означать большое выделение/освобождение внутреннего буфера и риск утечки памяти.

Я пытался установить сторожевой таймер, но столкнулся с широко известной проблемой этой платы, заключающейся в том, что сторожевой таймер бесполезен со стандартным загрузчиком. Сейчас я работаю над этим, поэтому я могу попытаться использовать его для отладки того, где в коде есть зависание, но я подозреваю, что это будет глубоко в одной из двух сторонних библиотек, и я не смогу сделать много с этим. Я прикрепил текущую итерацию кода ниже. У кого-нибудь есть мысли, куда идти отсюда? Кто-нибудь делал подобное с этим комплектом?

#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

Обсуждение

Карта памяти SD — 3,3 В, а MPU-6050 — 3,3 В. Если ваш Pro Mini работает на 5 В и 16 МГц, вам нужны переключатели уровней для SD и для I2C., @Jot

Та. Да, это плата 5V. Я подтвердил, что модуль SD-карты имеет переключатели уровней. Не могу подтвердить это с модулем MPU-6050. Однако теперь, когда у меня работает сторожевой таймер, я вижу, где он дает сбой - он находится во внутренностях библиотеки SdFat, "bool SdSpiCard::writeBlock(uint32_t blockNumber, const uint8_t* src)", так что это не похоже на Проблема с 3,3 В/5 В, @Craig Graham

Не каждая карта памяти SD совместима. По крайней мере, используйте форматтер SD: https://forum.arduino.cc/index.php?topic=228201.0 Если это не поможет, попробуйте другую карту памяти SD. Не могли бы вы вернуть библиотеку SD по умолчанию для Arduino? Он медленнее, но надежен. Если вы используете макетную плату, могут быть плохие соединения. Для шины I2C лучше использовать сдвигатель уровня и по возможности заменить устаревший MPU-6050 на MPU-9250., @Jot

Да, я использовал рекомендуемую утилиту форматирования - учитывая, что каждый тест успешно записывает сотни тысяч строк данных на карту, фундаментальная несовместимость не кажется. Кроме того, зависания происходят только тогда, когда используются как I2C, так и SPI-коммуникации - каждый бит в отдельности в порядке. Что касается библиотек, мне дали понять, что стандартная библиотека - это ответвление SdFat, которому несколько лет, и в нем есть ряд ошибок, которые с тех пор были устранены в мастере?, @Craig Graham

Библиотека Arduino SD создана Биллом Грейманом https://github.com/greiman, и он продолжает работать над ней, которая теперь является его библиотекой SdFat. Однако я просто хочу избавиться от всего неизвестного. Когда у вас есть переключатель уровня I2C и все подключено правильно, между I2C и шиной SPI нет конфликта. На данный момент я вижу только проблемы с 3,3В или 5В или падением напряжения, но я не знаю, какие у вас модули и как вы их подключили. Не могли бы вы сделать тест, понизив 5В для Pro Mini примерно до 3,5В?, @Jot

Трудно, потому что я использую кабель FTDI и смотрю на вывод на ПК, чтобы увидеть, не перезагружается ли он в какой-либо момент. Однако я, похоже, решил эту проблему - сделал еще один набор модулей с припаянными звеньями, и он просто работал в течение 18 часов, прежде чем я остановил тест. Таким образом, даже замедляя SPI и I2C, макетная плата все еще делала что-то, что вызывало зависания, хотя во время работы не было очевидного повреждения данных., @Craig Graham


3 ответа


Лучший ответ:

2

Похоже, это исправлено. Я соединил модули вместе, используя короткие припаянные провода, а не макетную плату, и он проработал всего 18 с половиной часов, прежде чем я решил остановить тест. Ранее максимальное время работы составляло менее часа. Таким образом, даже с SPI на шестнадцатой скорости и I2C на 50 кГц вместо 400 кГц кажется, что макет слишком ненадежный, когда используются оба механизма связи. Я отмечаю, что на самом деле я не сказал, что был на макетной плате - оглядываясь назад, возможно, это вызвало бы немедленный комментарий, но я думал, что при низких скоростях передачи данных я бы исключил такие вещи.

,

0

Судя по моим экспериментам, это больше связано с последовательным буфером. Если вы напечатаете более 55 символов в Serial.print(); при использовании MPU6050 и SDCard возникают ошибки переполнения стека. Попробуйте ограничить буфер до 55 с помощью функции snprintf(); функция; вместо Serial.print(); напрямую. Или разделите последовательный вывод на 55, если ваш последовательный вывод больше 55 на строку.

,

0

У меня была аналогичная проблема с SD-картой и модулем CAN-BUS на Arduino Mega Pro Mini.

Мое решение состояло в том, чтобы добавить конденсатор емкостью 470 мкФ между VCC и GND. Проблема решена!

Это был единственный конденсатор, который я пробовал, возможно, с задачей справился бы и меньший номинал. В моем проекте использовалось около 200 мА.

Что меня удивило, так это то, что мне пришлось добавить этот конденсатор, когда я использовал USB от своего ПК в качестве источника питания. Блок питания ПК и материнская плата высокого качества.

Когда я запитал свой проект с помощью линейного стабилизатора (от 12 В до 5 В) и импульсного преобразователя, все работало нормально. Но у обоих уже был конденсатор на 470 мкФ на стороне 5 В.

Очевидно, что это не будет решением во всех сценариях. Но это может быть одним из возможных ответов для системы, которая зависает без видимой причины.

,