Go Down

Topic: Saving Data from Accelerometer to SD Card - Sampling Frequency (Read 4531 times) previous topic - next topic

AccoPro

Hey guys,

thanks for taking your time to read about my problem.

I am involved in a project about a flexible robot, that we need to characterize. To do so, I wanted to measure the acceleration at different points of the robot, and compare them to the theoretical values of a computational model to obtain insights into the validity of the model, but also to improve the performance. Mostly the data should show the differences of a "stiff" robot compared to a real-world "flexible" robot and all the bad things that come with it, like vibrations, overshooting and stuff like that.

So what I am working with right now is a Arduino UNO, a SD-Card-Reader (SPI) by LCSOFT, a 6D IMU (MPU6050, I2C), a switch, and a battery. The idea is, to put everything on the robot, log the data, and then evaluate the data using Matlab.

So far I managed to read the RAW-values of the MPU6050 and store them to the SD-card at about 600Hz. A single datapoint consists of the sample number, the sample time, the time that passed since the last sample, the 3D Accelerometer data, and the 3D Gyro-data.

The problem now is, that the time between two consecutive samples varies extremely, with some values peaking up to 200-300ms. That is way to much for the analysis I had in mind. I have attached a plot of one of the better samples, and a plot of one of the worse samples.

You can see, that most of the samples have a sampleperiod of about 2-3ms. Everytime I save data to the SD, there is a sampling period of about 7ms, which is okay, I guess. But then again I have these random peak values, that I have no idea where they are coming from. And they are making all the data useless.

My best guess was, that the SD-Card is bad, but all the SDs I tried have the same problem. Maybe some of you can have a look at the code and tell me what I did wrong. I should mention, that I first saw an Arduino last week, so I may have overlooked some things that may seem very obvious to you.


There is one more thing that I would like to get some informations about, and that is the way I save the data. I use the String class to convert all the values into Strings, then append all of them into a DataString and then save that string to the SD.
As I found out, that part of my code really is slowing down everything, meaning, that if I only save one of the values instead of six, I have rates of 1800 to 2000 Hz.
I thought about using a char array to improve performance or to try and save the data more directly, but I would be very happy if you could tell me some things I can try, to improve the performance. Every tip is welcome :)


Code: [Select]
/*
 *  Sensor: MPU 6050
 */

#include <SPI.h>
//#include <SD.h>
#include <SdFat.h>
SdFat SD;
#include<Wire.h>
#include "MPU6050_6Axis_MotionApps20.h"

const int chipSelect = 10; //SD Card CS
const int MPU = 0x68; // I2C address of the sensor
//int16_t AcX, AcY, AcZ, Tmp, GyX, GyY, GyZ;
int16_t ax, ay, az, gx, gy, gz;
int SwitchPin = 3;
int StepsSinceLastFlush = 1;
int WaitingLine = 0;

MPU6050 mpu;

char filename[12] = "DATA001.csv";
char numb[4];
String str;

String dataString = "";

unsigned long SamplingTime;
unsigned long SampleCounter = 1;
unsigned long starttime = 0;




void setup()
{
  Serial.begin(9600);
  setParametersForMPU();
  pinMode(SwitchPin, INPUT);
  SearchSensor(); // I2C Scanner
  InitializeSDCard();
}



void loop()
{

  if (digitalRead(SwitchPin) == 0) {

    filename[4] = '0';
    filename[5] = '0';
    filename[6] = '1';

    for (int m = 1; m < 999; m++) { //Maximum 999 Files.

      str = String(m);
      str.toCharArray(numb, 4);
      if (m < 10) {
        filename[6] = numb[0];
      }
      else if (m < 100) {
        filename[6] = numb[1];
        filename[5] = numb[0];
      }
      else {
        filename[4] = numb[0];
        filename[5] = numb[1];
        filename[6] = numb[2];
      }
      if (!SD.exists(filename)) {
        break;
      }

    }

    Serial.println();
    Serial.print("Writing to file:  ");
    Serial.println(filename);



    Serial.print("Starting Data at: ");
    Serial.print(millis());
    Serial.println(" ms");
    int SwitchOn = digitalRead(SwitchPin);
    SamplingTime = 0;
    File dataFile = SD.open(filename, FILE_WRITE);
    starttime = millis();

    while (digitalRead(SwitchPin) == 0) { //start and stop data with switch

      for (int counter = 1; counter < 300; counter++) { //switch is read every 300 samples
        //Improves Performance?--


        mpu.getMotion6(&ax, &ay, &az, &gx, &gy, &gz); //Raw Data


      

        dataString = "";
        dataString += String(SampleCounter);
        dataString += ",";
        dataString += String(millis());
        dataString += ",";
        dataString += String(millis() - SamplingTime);
        SamplingTime = millis();
        dataString += ",";
        dataString += String(ax);
        dataString += ",";
        dataString += String(ay);
        dataString += ",";
        dataString += String(az);
        dataString += ",";
        dataString += String(gx);
        dataString += ",";
        dataString += String(gy);
        dataString += ",";
        dataString += String(gz);
        //Find more efficient way to save the data

        dataFile.println(dataString); //Saves the datastring to SD or some other place?





        //Serial.println(sizeof(dataString));


        if (StepsSinceLastFlush >= 54) { //54 in this case is a random value
          //works fine, because as I see it each DataString has a size of 6 bytes
          //so the SD Card always saves about 300 bytes of the 512 bytes possible

          dataFile.flush();
          StepsSinceLastFlush = 1;
        }

        StepsSinceLastFlush++;
        SampleCounter++;


      }
      
    }


    dataFile.close(); //Save everything to SD

    Serial.println ();
    Serial.print ("Stop Data at:   ");
    unsigned long endtime = millis();

    Serial.println(endtime);
    Serial.println("Finished writing file");
    Serial.print(((endtime - starttime) * 0.001) );
    Serial.println("   Seconds");
    Serial.print((float)SampleCounter / ((float)(endtime - starttime) * 0.001) );
    Serial.println("   Hz");

  }
  else { //Waiting Line



    while (!SD.begin(chipSelect)) {
      Serial.println("");
      Serial.println(F("Card failed, or not present"));
      delay(4000);
    

    }
    if (WaitingLine == 0) {
      Serial.println(F("Switch is off"));
      delay(1000);
      Serial.println("Ready...");
      WaitingLine++;
    }
    if (WaitingLine > 40) {
      Serial.println(F("."));
      WaitingLine = 0;
      delay(1000);
    }
    else {
      Serial.print(F("."));
      delay(1000);
      WaitingLine++;
    }
  }

}




//--------------------------------------------
//            FUNCTIONS




void InitializeSDCard()  
{
  Serial.println(F("Initializing SD card..."));
  while (!SD.begin(chipSelect)) {

    Serial.println(F("Card failed, or not present"));
    delay(4000);


  }
  Serial.println(F("card initialized."));
}










void SearchSensor() { //I2CScanner Adaption


  Serial.println("Scanning...");
  byte error, address;
  int nDevices;

  nDevices = 0;
  for (address = 1; address < 127; address++ )
  {
    // The i2c_scanner uses the return value of
    // the Write.endTransmisstion to see if
    // a device did acknowledge to the address.
    Wire.beginTransmission(address);
    error = Wire.endTransmission();

    if (error == 0)
    {
      Serial.print("I2C device found at address 0x");
      if (address < 16)
        Serial.print("0");
      Serial.print(address, HEX);
      Serial.println("  !");

      nDevices++;
    }
    else if (error == 4)
    {
      Serial.print("Unknow error at address 0x");
      if (address < 16)
        Serial.print("0");
      Serial.println(address, HEX);
    }
  }
  if (nDevices == 0) {
    Serial.println("No I2C devices found\n");
    delay(10000);
  }
  else
    Serial.println("done\n");



}



void setParametersForMPU() { // No idea...
  mpu.initialize();
  //mpu.reset();
  //delay(100);
  //mpu.resetSensors();

  mpu.setFullScaleAccelRange(11);
  mpu.setFullScaleGyroRange(11);

  uint8_t rateset = 7;
  mpu.setRate(rateset);

  mpu.setSleepEnabled(false);
  //mpu.setDLPFMode(MPU6050_DLPF_BW_42);
  mpu.setXAccelOffset(3648);
  mpu.setYAccelOffset(-2789);
  mpu.setZAccelOffset(1010);
  mpu.setXGyroOffset(107);
  mpu.setYGyroOffset(-42);
  mpu.setZGyroOffset(44);
  mpu.setAuxVDDIOLevel(1);//Sets High Level of Voltage
  //mpu.setDLPFMode(0);
  //mpu.setMasterClockSpeed(9);
  //uint8_t x = mpu.getAccelerometerPowerOnDelay();
  //Serial.println(x);
}




rw950431


If you have only integer values you can try snprintf() but arduino version doesnt support floats or doubles.

If you do need to handle floats the article at http://dereenigne.org/arduino/arduino-float-to-string has some useful information about the dtostrf() function.

AccoPro

Okay, thanks for the tip! I actually only have to handle integer values. So snprintf() should work fine.
I have implemented snprintf() in my code to substitute the DataString stuff. It now looks something like this:

Code: [Select]

snprintf(DataLine, 100, "%l,%l,%l,%d,%d,%d,%d,%d,%d", SampleCounter,millis(),millis() - SamplingTime, ax,ay,az,gx,gy,gz);
SamplingTime = millis();
dataFile.println(DataLine);


I do not have the Arduino at hand right now, but tomorrow morning I will post the results. At least the code compiles  :)

liuzengqiang

One of the graphs shows almost periodic spikes. When you write to SD, you actually place your data in a buffer. When the buffer if full, the card is written, which is where the spikes are. Try sdfat library instead. SD library is an outdated version of sdfat, wrapped in an easy layer of function calls. If you want hundreds of data points and several seconds of recording time, try a larger arduino such as arduino MEGA2560. It has 8KB SRAM. Say each data point is 3 integers of acceleration and 1 integer of millisecond tick (only last two bytes), you have 8 bytes of data per point. Then MEGA2560 can store up to 1000 data points so that is several seconds of data.

You can also try a bluetooth wireless module and print data to it at 115200 baud rate. If you print the integers, it's going to be about 5 char per data so about 20 (3D and time tick) and you still get about 500 samples per second.

Normally you can't really get high sampling rate. These breakout boards have capacitors to limit the sampling rate to less than 100 points a second.
Serial LCD keypad panel,phi_prompt user interface library,SDI-12 USB Adapter

AccoPro

Thanks for the tip with the snprintf(). Code already runs much better. I attached two plots of the sample time. The close up shows, that I get the samples pretty consistently at 2ms. But again there is that one time where everything slows down. I read, that that may be due to internal memory management of the SD card. I may have to get a better SD card.

Right now I am trying to use the internal FIFO of the MPU6050, and buffer the data. But I don't understand the concepts completely. I I have something like 100 samples and I save them to the SD in one chunk, wouldn't that take more time, than just doing it more frequently? Because I can not save data, while I am saving, because I can not run functions in parallel, or can I?

@liudr:    I am using SDFat right now. Just in the simplest way possible. With snprintf() I get more than 1000 Datapoints a second, the problem is only, that they are not evenly distributed.
Using bluetooth is not an option right now. I could use a cable and try to get the Data over Serial, but that is way slower than storing it to the SD...


liuzengqiang

Serial is not necessarily slower. You can get maybe tens of KB per second onto an SD card with occasional slowdown when the sd write occurs. You can get sometimes 1Mbits per second with arduino UNO's serial chip. I routinely use 200,000 baud rate for my MEGA 2560, which uses the same USB serial chip as UNO (ATMEGA16U2). That is almost 20KB/second. You can further increase the serial baud rate as long as you are still getting consistent results (think about matching the frequency of 16MHz MCU with baud rate, such as 400,000 etc.) There is even 1Mb/s bootloader so you can certainly do that fast. SD card on the other hand, is not at that speed yet.

BTW, you can use full speed SPI bus, which may increase write speed a bit. Also, if you are doing CRC, use larger CRC table in SRAM to speed up. But the bottle neck is always there, the data dump to SD card.

Remember, sd card is a block device, i.e. one block gets written at a time. Serial is a stream. You can easily keep a constant flow with a stream and block write on the PC side.
Serial LCD keypad panel,phi_prompt user interface library,SDI-12 USB Adapter

SirJeff

Hey,

we have in our lab a Masurement about acceleration to.

We also geting peaks whitch are not explainable...for now...

But

we found out that the Frequency shifter and / or the motor producing
noice which influenz the acceloration.

Therefor we will put a mattal net arround it.
After that we will put ground on that.

Go Up