Guardar datos GPS y Accelerometro en SD alta velocidad

Buenos dias muchachos. Estoy trabajando en un projecto el cual quisiera grabar los datos a una carta SD a alta velocidad (Sample Rate: 200Hz). Los datos provienen de un MPU9150 y un GPS.Estoy utilizando un Arudino Mega o un Teensy 3.2. Intente con la libreria <SD.h> y es muy lenta al grabar los datos en formato csv.
Viendo el foro me encontre que fatLib publico una nueva libreria fatSD.h en el cual se guardan los datos a una velocidad asombrosa(libreria aqui). Es un poco complicada para mi nivel de programacion ya que soy Nuevo. Alguien sabe como puedo grabar estos datos en la SD con esta velocidad? He leido que se puede creando bloques de 512 bytesy luego transferirlos de tipo binario (bin) pero no me queda muy claro.

Alguien me puede dar alguna idea o sugerencia para este proyecto?

Muchas gracias por su ayuda!

#include "Wire.h"
#include "I2Cdev.h"
#include "MPU6050.h"
#include <Adafruit_GPS.h>
#include <SD.h>
#include <SoftwareSerial.h>

#define LED_PIN 13                  //Blink state writing
#define TIMER 500                   //1000ms = 1sec

/*                      Accelerometer                        */
MPU6050 accelgyro;                  //MPU9250 device
int16_t ax, ay, az;
int16_t gx, gy, gz;
int16_t mx, my, mz;



/*                      Micro-SD                             */
File dataLogger;
const int CS_mega = 53;             //CS for Mega2560

/*                      Blink state                          */
bool blinkState = false;

/*                         GPS                               */
SoftwareSerial mySerial(10, 2);     //GPS software serial TX:D10 RX:D2
Adafruit_GPS GPS(&mySerial);        //Varialbe for GPS
#define GPSECHO  false              //false : turn off echoing the GPS data. True : Debug and listen the raw GPS sentences
boolean usingInterrupt = false;     //Keeps track if we're using the interrupt (off by defult)
void useInterrupt(boolean);         // Func prototype for interrupt
uint32_t timer = millis();

/************************************************************/
/*                        SETUP                             */
/************************************************************/
void setup() {
  Wire.begin();                                      //Join I2C bus
  Serial.begin(115200);                              //Serial communication

  //          Initialize MPU-9150
  Serial.println(F("Initializing MPU-9150..."));
  accelgyro.initialize();                                  //Initialize MPU9250
  Serial.println(F("Testing MPU device connections..."));  //Verify connection
  Serial.println(accelgyro.testConnection() ? "MPU9150 connection successful" : "MPU9150 connection failed");

  //          Initialize MPU-9150
  Serial.println(F("Initializing GPS"));
  GPS.begin(9600);                                   //MTK GPS baud rate
  GPS.sendCommand(PMTK_SET_NMEA_OUTPUT_RMCGGA);      //Initialize RMC and fix data for MTK GPS
  GPS.sendCommand(PMTK_SET_NMEA_UPDATE_1HZ);         //Set up date to 1 Hz (recommended)
  GPS.sendCommand(PGCMD_ANTENNA);                    //Request updates on antena status

  //          Initialize Micro-SD
  Serial.println(F("Initializing SD card..."));
  pinMode(CS_mega, OUTPUT);
  if (!SD.begin(CS_mega)) {
    Serial.println(F("initialization SD failed! Insert SD and restart..."));
    return;
  }
  Serial.println("initialization Micro-SD done.");

  //          Frist row title in Micro-SD
  dataLogger = SD.open("data1.csv", FILE_WRITE);
  dataLogger.println("Date,Hour,Satellites,Longitude,Latitude,Altitude,Angle,Speed,aX,aY,aZ,gX,gY,gZ,mX,mY,mZ");
  dataLogger.close();

  pinMode(LED_PIN, OUTPUT);                           //Blink state LED
  useInterrupt(true);                                 //Interrup go off every 1ms and reads data (to avoid delay fnc)
}


// Interrupt is called once a millisecond, looks for any new GPS data, and stores it
SIGNAL(TIMER0_COMPA_vect) {
  char c = GPS.read();
  // if you want to debug, this is a good time to do it!
#ifdef UDR0
  if (GPSECHO)
    if (c) UDR0 = c;
  // writing direct to UDR0 is much much faster than Serial.print
  // but only one character can be written at a time.
#endif
}

void useInterrupt(boolean v) {
  if (v) {
    // Timer0 is already used for millis() - we'll just interrupt somewhere
    // in the middle and call the "Compare A" function above
    OCR0A = 0xAF;
    TIMSK0 |= _BV(OCIE0A);
    usingInterrupt = true;
  } else {
    // do not call the interrupt function COMPA anymore
    TIMSK0 &= ~_BV(OCIE0A);
    usingInterrupt = false;
  }
}


/************************************************************/
/*                        LOOP                              */
/************************************************************/
void loop() {


  // in case you are not using the interrupt above, you'll
  // need to 'hand query' the GPS, not suggested :(
  if (! usingInterrupt) {
    // read data from the GPS in the 'main loop'
    char c = GPS.read();
    // if you want to debug, this is a good time to do it!
    if (GPSECHO)
      if (c) Serial.print(c);
  }
  // if a sentence is received, check the checksum and parse it
  if (GPS.newNMEAreceived()) {
    if (!GPS.parse(GPS.lastNMEA()))   // this also sets the newNMEAreceived() flag to false
      return;  // we can fail to parse a sentence in which case we should just wait for another
  }

  // if millis() or timer wraps around, we'll just reset it
  if (timer > millis())  timer = millis();

  if (millis() - timer > TIMER) {
    timer = millis(); // reset the timer
    dataLogger = SD.open("data1.csv", FILE_WRITE);
    gpsData (dataLogger);
    getMPU(dataLogger);
    blinkState = !blinkState;         // blink LED to indicate activity
    digitalWrite(LED_PIN, blinkState);
  }
  dataLogger.close();

}



void getMPU(File data)
{
  //accelgyro.getAcceleration(&ax, &ay, &az);
  //accelgyro.getRotation(&gx, &gy, &gz);




  accelgyro.getMotion9(&ax, &ay, &az, &gx, &gy, &gz, &mx, &my, &mz);    //read raw accel/gyro measurements from device
  if (data) {
    int sensAx = (analogRead(A0));

    float voltageAx = (sensAx*(4.0/1023.0));

    
    //data.print((map(vax,0,1023,0,4)/100.000))    ; data.print(","); // aX
    data.print(voltageAx,2);
    data.print((analogRead(A1)))    ; data.print(","); // aY
    data.print((analogRead(A2)))    ; data.print(","); // aZ
    data.print(gx); data.print(",");
    data.print(gx); data.print(",");
    data.print(gx); data.print(",");
    data.print(mx); data.print(",");
    data.print(mx); data.print(",");
    data.print(mx); data.println(",");
    Serial.println(F("Writing Data..."));
  }
  else {
    // if the file didn't open, print an error:
    Serial.println(F("error opening file on SD"));
  }
}

void gpsData (File data)
{
  int sat = GPS.satellites;
  data.print(GPS.day, DEC);       data.print("-");
  data.print(GPS.month, DEC);     data.print("/");
  data.print(GPS.year, DEC);      data.print(",");
  data.print(GPS.hour, DEC);      data.print(":");
  data.print(GPS.minute, DEC);    data.print(":");
  data.print(GPS.seconds, DEC);   data.print(",");
  data.print(sat);                data.print(",");
  data.print(GPS.longitudeDegrees, 4);  data.print(",");
  data.print(GPS.latitudeDegrees, 4);   data.print(",");
  data.print(GPS.altitude);       data.print(",");
  data.print(GPS.angle);          data.print(",");
  data.print(GPS.speed);          data.print(",");
}

Mi pregunta es: los datos del archivo van a ser leidos por un ser humano, o es solo para retener información?

Repetidas veces he dicho que no hay nada más veloz que procesar datos en su forma original (binaria).

He hecho pruebas con la librería SD que viene en la IDE de Arduino, y con Nano (ATmega328P @ 16 MHz) he logrado hasta 57 KB/s de escritura; pero utilizando la función write y arrays de byte, nada de print.

Casualmente también había probado con SdFat, pero por alguna razón la velocidad cayó a 36 KB/s; aún siendo datos binarios.

kammateo:
He leido que se puede creando bloques de 512 bytesy luego transferirlos de tipo binario (bin) pero no me queda muy claro.

Implementar un "búfer de I/O" es algo difícil, pero no imposible.

Gracias Lucario448 por tu respuesta tan rapida!

Lucario448:
Mi pregunta es: los datos del archivo van a ser leidos por un ser humano, o es solo para retener información?

Si, los datos serian guardados en la carta SD y luego convertidos en un archivo .csv para representarlos en Excel.

Tu tienes un ejemplo de como escribiste los datos de un sensor o algo que me puedas facilitar para estudiarlo y modificarlo? Ya que no tengo muy en claro como puedes convertir un array en bin.

Mil gracias de nuevo

print suele ser lento porque además llama implícitamente a flush (forzar la escritura física aunque sea sólo un simple caracter).

Para escribir texto como un array de bytes; las siguientes funciones han de quedar así:

void getMPU(File data)
{

  accelgyro.getMotion9(&ax, &ay, &az, &gx, &gy, &gz, &mx, &my, &mz);    //read raw accel/gyro measurements from device
  if (data) {
    int sensAx = (analogRead(A0));

    float voltageAx = (sensAx * (4.0 / 1023.0));


    sprintf(buffer, "%.2f,%d,%d,%d,%d,%d,%d,%d,%d\r\n", voltageAx, analogRead(A1), analogRead(A2),
            gx, gy, gz, mx, my, mz);

    data.write(buffer, strlen(buffer));
    Serial.println(F("Writing Data..."));
  }

  void gpsData (File data)
  {
    sprintf(buffer, "%02d/%02d/%02d,%02d:%02d:%02d,%d,%.4f,%.4f,%d,%d,%d\r\n", GPS.day, GPS.month, GPS.year,
            GPS.hour, GPS.minute, GPS.seconds, GPS.satellites, GPS.longitudeDegrees, GPS.latitudeDegrees, GPS.altitude,
            GPS.angle, GPS.speed);
  // No te preocupes; el "\r\n" del final, es que genera una nueva línea de texto.

    data.write(buffer, strlen(buffer));

  }

NUEVA VARIABLE GLOBAL:

char buffer[256];

Esta claro que el ejempo es complejo pero ya te da la manera de hacerlo en csv. Porque dices

Si, los datos serian guardados en la carta SD y luego convertidos en un archivo .csv para representarlos en Excel.

los guardas ya en CSV como en tu ejemplo veloz y listo.
El ejemplo puede ser complicado pero solo debes extractar lo que te importa.
Tambien yo hice pruebas y lo que pides no es inalcanzable, ahora bien, no entiendo para que guardar datos a ese ritmo. Es como un osciloscopio en SD.

Verificaste que tu MPU9150 y tu gps entregarán datos a esa tasa de sampleo?

GPS.sendCommand(PMTK_SET_NMEA_UPDATE_1HZ);

GPS adquiere datos a 1Hz por ende guardas 200 datos iguales. Desperdicio!!
Ademas no tiene sentido alguno un dato cada 5 mseg de un gps ni que estuvieras corrigiendo la trayectoria de un misil balístico.

El MPU9150 puede entregarte hasta una tasa de 8000Hz asi que digamos que si.
Pero ya que el GPS no, no tiene mucho sentido tu preocupación por la alta velocidad.

Gracias Lucario448 ,

Lucario448:

void getMPU(File data)

{

...

sprintf(buffer, "%.2f,%d,%d,%d,%d,%d,%d,%d,%d\r\n", voltageAx, analogRead(A1), analogRead(A2),
           gx, gy, gz, mx, my, mz);

data.write(buffer, strlen(buffer));

Si entendi bien;

  • creas un buffer de 256 bytes para almacenar la info.
  • Al utilizar la funcion sprint, creas la cadena de texto en el buffer.
  • data.write(buffer, strlen(buffer)); //Llamas el buffer donde esta la info, y con strlen(buffer) le dices el tamaño de la cadena para ser copiada.

Ahora tengo otra pregunta: Como haces para crear el archive de tipo bin? Asi?
dataLogger = SD.open("archive.bin", FILE,WRITE);

Gracias

@surbyte

surbyte:
Esta claro que el ejempo es complejo pero ya te da la manera de hacerlo en csv. Porque dices los guardas ya en CSV como en tu ejemplo veloz y listo.

Exactamente, el ejemplo es complicado y al llamar la funcion Wire.h para el GPS me hace un conflicto porque al parecer utiliza el mismo timer (tengo que confirmar lo que acabo de decir). Al guardar los datos directamente en csv se me hace muy lento.

surbyte:
no entiendo para que guardar datos a ese ritmo.

Necesito esa velocidad para calcular la fuerza de choque de un barco a alta velocidad. El MPU9150 me da esa tasa de sampleo y el GPS evidentemente no, pero lo importante es que tenga la hora exacta de cuando ocurrio el choque y cuanto tiempo duro (asi sea algunas milesimas de Segundo).

kammateo:
Si entendi bien;

  • creas un buffer de 256 bytes para almacenar la info.
  • Al utilizar la funcion sprint, creas la cadena de texto en el buffer.
  • data.write(buffer, strlen(buffer)); //Llamas el buffer donde esta la info, y con strlen(buffer) le dices el tamaño de la cadena para ser copiada.

Correcto.
En muchos ejemplos se suele usar sizeof para determinar el tamaño de un array; pero como la cadena de texto no necesariamente es de 256 caracteres, se usa strlen en su lugar

kammateo:
Ahora tengo otra pregunta: Como haces para crear el archive de tipo bin? Asi?

dataLogger = SD.open("archive.bin", FILE,WRITE);

En realidad es:

dataLogger = SD.open("archive.bin", FILE_WRITE);

Y sí, así es para lectura y escritura. Además, automáticamente se mueve el puntero al final de este archivo; para agregar bytes/caracteres en vez de sobrescribir.

Y por qué te estoy hablando de "puntero"? Porque la librería SD trata los archivos como un array de bytes, expandible y de acceso aleatorio.
Esta forma de conceptualizar un archivo digital, es la que permite interpretarse tanto como de texto, como binario. Recuerda que un caracter (char) es un byte codificado según la tabla ASCII.

sigues sin entender el punto.
La tasa de cambio de tu GPS es de 1Hz y quieres samplear a 200hz, tendrás 199 datos superfluos!!!

O sea para una lectura del GPS tenddras 200 datos de tu MPU, no le veo sentido

Dado que en este caso parece primar la rapidez, una pequeña pincelada al código de Lucario.
No lo he probado, pero creo que la propia función sprintf devuelve la longitud de la cadena creada, con lo que podemos evitar volver a chequearla con la función strlen. Entonces igual así ganamos una pizca en rendimiento y tamaño de archivo:

    int longitud=sprintf(buffer, "%.2f,%d,%d,%d,%d,%d,%d,%d,%d\r\n", voltageAx, analogRead(A1), analogRead(A2),
            gx, gy, gz, mx, my, mz);

    data.write(buffer, longitud);

Buenas tarde muchachos, Gracias de Nuevo por sus respuestas. Pude hacer algunos codigos con las sugerencias que cada uno de ustedes me ha enviado pero no alcanzo a guardar la informacion a esta velocidad. Me di a la tarea de modificar un poco el codigo que viene en la libreria Sdfat : LowLatencyLoggerMPU6050. Utilizando este ejemplo puedo guardar los datos de un MPU-9150 y de un accelerometro de 125g. Ahora estoy intentando de obtener los datos de un GPS y guardarlos igualmente.
El problema es que cuando guardo los valores del GPS me dan unos valores erroneos, pero si utilizo solo la parte del GPS me dan los datos exactos. Alguno de ustedes tiene una idea?
El codigo acontinuacion hace parte de UserFunctions.cpp de el ejemplo LowLatencyLoggerMPU6050 de la libreria Sdfat (hace poco la actualizaron).

// User data functions.  Modify these functions for your data items.
#include "UserTypes.h"
#include "Wire.h"
#include "I2Cdev.h"
#include "MPU6050.h"

#include <Adafruit_GPS.h>  //**
#define GPSSerial Serial1  //** Hardware serial port for Mega PIN: RX1->19
//------------------------------------------------------------------------------

Adafruit_GPS GPS (&GPSSerial); //** Connect to the GPS on the hardware port
MPU6050 mpu;

static uint32_t startMicros;

#define GPSECHO false         //** To echoing the GPS data to the Serial Console
uint32_t timer = millis();    //** Actual time in the processor
uint32_t interval = 1000;     //** Intercval in milliseconds 1000=1sec

// Acquire a data record.
void acquireData(data_t* data) {
  char c = GPS.read();
  if (GPSECHO)
    if (c) Serial.print(c);
  if (GPS.newNMEAreceived()) 
  {
    //Serial.println(GPS.lastNMEA()); // this also sets the newNMEAreceived() flag to false
    if (!GPS.parse(GPS.lastNMEA())) // this also sets the newNMEAreceived() flag to false
      return; // we can fail to parse a sentence in which case we should just wait for another
  }
  // if millis() or timer wraps around, we'll just reset it
  if (timer > millis()) timer = millis();
  if (millis() - timer > 1000) {
    timer = millis(); // reset the timer
    data->dday = (GPS.day, DEC);
    data->mmonth = (GPS.month, DEC);
    data->yyear = (GPS.year, DEC);
  }
  data->time = micros();
  // mpu.getMotion6(
  //&data->ax, &data->ay, &data->az,
  //   &data->gx, &data->gy, &data->gz,
  //   &data->mx, &data->my, &data->mz);
  data->analog = analogRead(0);
}

// setup AVR I2C
void userSetup() {
  GPS.begin(9600);              //**
  #if I2CDEV_IMPLEMENTATION == I2CDEV_ARDUINO_WIRE
  Wire.begin();
  Wire.setClock(400000);
  #elif I2CDEV_IMPLEMENTATION == I2CDEV_BUILTIN_FASTWIRE
  Fastwire::setup(400, true);
  #endif

  GPS.sendCommand(PMTK_SET_NMEA_OUTPUT_RMCGGA);  //**Minumum recommender data for 10Hz
  GPS.sendCommand(PMTK_SET_NMEA_UPDATE_1HZ); // 1 Hz update rate
  GPS.sendCommand(PGCMD_ANTENNA);               //**Request update on antenna status
  delay(1000);
  mpu.initialize();
}

// Print a data record.
void printData(Print* pr, data_t* data) {
  if (startMicros == 0) {
    startMicros = data->time;
  }
  // pr->print(data->time - startMicros);  pr->write(',');

  pr->print(data->dday);      pr->write('/');
  pr->print(data->mmonth);    pr->write('/');
  pr->println(data->yyear);
  //  pr->print(data->hhour, DEC);      pr->write(':');
  //  pr->print(data->mminute, DEC);    pr->write(':');
  //  pr->println(data->ssecond, DEC);      //pr->write(',');
  //  pr->print(data->analog);    pr->write(',');
  //  pr->print(data->gx);        pr->write(',');
  //  pr->print(data->gy);        pr->write(',');
  //  pr->print(data->gz);        pr->write(',');
  //  pr->print(data->mx);        pr->write(',');
  //  pr->print(data->my);        pr->write(',');
  //  pr->println(data->mz);
}

// Print data header.
void printHeader(Print* pr) {
  startMicros = 0;
  pr->println(F("Sensor,gx,gy,gz,mx,my,mz"));
}

Los valores que obtengo son:
0/5/0
0/37/1
5/-119/0
3/0/0
37/0/0
37/0/96
0/5/0
Estos valores corresponden a la fecha (supuestamente).[/code]