Go Down

Topic: Saving Data from Accelerometer to SD Card - Sampling Frequency (Read 5741 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.

umairkhan

Hi All,

& hi Liuzengqiang i read ur answer so my query is more related with ur answer

First of all I would like to mention what Setup I am using : Arduino Nano, Accelerometer MPU6500 & MicroSD Card Adapter for collecting data

Actually I collect accelerometer data & save in sd card as a .csv file and when I open file in my PC to check collected data so i observe some issue mention below:-

1. Sampling Time Interval is always irregular between samples
2. Everytime very first sample time interval have very high difference from just second sample time.
3. And approx. after every particular sample time interval I see a spike in time interval.


Want Solution :

1. First, how can i get regular sample time interval between accelerometer data?

2. Second, can u tell me with my current Setup is it possible to get 1ms or 2ms sample time interval between data or need to move at higher config setup?

I am also sharing my code & .csv file data below plz have a look

Thanks a lot to all for ur kind reply in advance:)

CODE BELOW:

// Accelerometer with SD Card Arduino Sketch
// Last Updated 23 Sep 2018

#include <SPI.h>
#include <SD.h>
#include <Wire.h>

File myFile;

String accState = "STOP";
String header="ON";
String initial_mode = "idle";
int calibState = 1, offset_Counts = 0;
const int dataDelay = 0;

int start_stop_CMD = 11;
int finish_CMD = 7;
int run_LED = 9;
int stop_LED = 10;
int pin_CS = 5;
int interupt_CMD = 2;

const int MPU_addr = 0b1101000;  // Address of MPU in Hex:0x68
int16_t RawAcX, RawAcY, RawAcZ, RawTmp, RawGyX, RawGyY, RawGyZ;  // Initiate 16 bit interger variables
double AcX, AcY, AcZ, Tmp, GyX, GyY, GyZ, AcT, pitch, roll, yaw, X_offset, Y_offset, Z_offset, Xangle, Yangle, Zangle;  // Initiate float variables

void setup() {
pinMode(start_stop_CMD,INPUT_PULLUP);
pinMode(finish_CMD,INPUT_PULLUP);
pinMode(run_LED,OUTPUT);
pinMode(stop_LED,OUTPUT);
pinMode(interupt_CMD,OUTPUT);

Wire.begin();
Wire.beginTransmission(MPU_addr); //This is the I2C address of the MPU (b1101000/b1101001 for AC0 low/high datasheet sec. 9.2)
Wire.write(0x6B); //Accessing the register 6B - Power Management (Sec. 4.28)
Wire.write(0b00000000); //Setting SLEEP register to 0. (Required; see Note on p. 9)
Wire.endTransmission();  
Wire.beginTransmission(MPU_addr); //I2C address of the MPU
Wire.write(0x1C); //Accessing the register 1C - Acccelerometer Configuration (Sec. 4.5)
Wire.write(0b00000000); //Setting the accel to +/- 2g
Wire.endTransmission();

Serial.begin(115200);

Serial.print("Initializing SD card...");
while (!SD.begin(pin_CS)){
  Serial.println("initialization failed!");
  errorBlink();
  }
Serial.println("initialization done.");
SD.remove("data123.csv");
myFile = SD.open("data123.csv", FILE_WRITE);

//***********For Initial Calibration X, Y axis should be parallel & Z axis should be perpendicular to Earth's gravity of Accelerometer**************//
// Expected values for initial calibration of Accelerometer are :-
// X-axis = 0g
// Y-axis = 0g
// Z-axis = 1g #Note:(Z-axis = -1g, for reverse Z-axis with reference to Earth's gravity)
// If intially we get some more or less 'g' than our expected values so we add or subtract offset as per our calculate values below:

while (offset_Counts <= 10){
  Wire.beginTransmission(MPU_addr);
  Wire.write(0x3B); // Starting with register 0x3B (ACCEL_XOUT_H)
  Wire.endTransmission(false);
  Wire.requestFrom(MPU_addr,6,true);  // Request total of 14 registers
  RawAcX=Wire.read()<<8|Wire.read();  // 0x3B (ACCEL_XOUT_H) & 0x3C (ACCEL_XOUT_L)
  RawAcY=Wire.read()<<8|Wire.read();  // 0x3D (ACCEL_YOUT_H) & 0x3E (ACCEL_YOUT_L)
  RawAcZ=Wire.read()<<8|Wire.read();  // 0x3F (ACCEL_ZOUT_H) & 0x40 (ACCEL_ZOUT_L)

  AcX=RawAcX/16384.0;
  AcY=RawAcY/16384.0;
  AcZ=RawAcZ/16384.0;

  if (offset_Counts > 0){  // First reading is getting wrong evertime that' why we skipped first reading(Work Around, issue pending).
    X_offset+=AcX;
    Y_offset+=AcY;
    Z_offset+=AcZ + 1;  // Initially we have reversed Z-axis with reference to Earth's gravity that's why added 1.  
    }
  offset_Counts += 1;
  }
X_offset = X_offset/10.0;
Y_offset = Y_offset/10.0;
Z_offset = Z_offset/10.0;

}

void loop() {

if (accState == "STOP" && initial_mode == "idle"){
  digitalWrite(run_LED, LOW);  //Stop LED Turn ON & Run LED Turn OFF indicate device is in initial mode
  digitalWrite(stop_LED, HIGH);
  String intial_mode = "ready";}

if (digitalRead(start_stop_CMD) == LOW && accState == "STOP"){
  accState = "RUN";
  Serial.println("Device is turned ON");
  digitalWrite(run_LED, HIGH);  //Stop LED Turn OFF & Run LED Turn ON indicate device is in RUN mode
  digitalWrite(stop_LED, LOW);
  while (digitalRead(start_stop_CMD) == LOW);}  // Wait for state change to HIGH
 
else if (digitalRead(start_stop_CMD) == LOW && accState == "RUN"){
  accState = "STOP";
  Serial.println("Device is turned OFF");
  digitalWrite(run_LED, LOW);  //Stop LED Turn ON & Run LED Turn OFF indicate device is in STOP mode
  digitalWrite(stop_LED, HIGH);
  while (digitalRead(start_stop_CMD) == LOW);}  // Wait for state change to HIGH
 
else if (digitalRead(finish_CMD) == LOW && accState == "STOP"){
  myFile.close();
  Serial.println("END PROGRAM");
  LEDBlink();
  while (1);}  // To break void loop is quite difficult that's why we are using while loop to stop void loop

if (accState == "RUN"){
  if (myFile){
    int16_t timeStart = millis();  // Timer start function

    Wire.beginTransmission(MPU_addr);
    Wire.write(0x3B);  // Starting with register 0x3B (ACCEL_XOUT_H)
    Wire.endTransmission(false);
    Wire.requestFrom(MPU_addr,6,true);  // Request total of 14 registers
    RawAcX=Wire.read()<<8|Wire.read();  // 0x3B (ACCEL_XOUT_H) & 0x3C (ACCEL_XOUT_L)
    RawAcY=Wire.read()<<8|Wire.read();  // 0x3D (ACCEL_YOUT_H) & 0x3E (ACCEL_YOUT_L)
    RawAcZ=Wire.read()<<8|Wire.read();  // 0x3F (ACCEL_ZOUT_H) & 0x40 (ACCEL_ZOUT_L)

    AcX=RawAcX/16384.0;
    AcY=RawAcY/16384.0;
    AcZ=RawAcZ/16384.0;    

    AcX=AcX-X_offset;
    AcY=AcY-Y_offset;  // Setting up calculated Offset.
    AcZ=AcZ-Z_offset;

    if (header == "ON"){
      myFile.print("Time");
      myFile.print(" , ");        
      myFile.print("X_axis");
      myFile.print(" , ");
      myFile.print("Y_axis");
      myFile.print(" , ");        
      myFile.println("Z_axis");
      header = "OFF";}

    myFile.print(timeStart);
    myFile.print(" , ");
    myFile.println(AcX, DEC);}
    myFile.println(" , ");}
    myFile.print(" , ");
    myFile.print(AcY);
    myFile.print(" , ");
    myFile.println(AcZ);}

  else{
    Serial.println("Error opening file");
    errorBlink();}
  }
}

void LEDBlink(){  //Make LED Blink
digitalWrite(run_LED, HIGH);
digitalWrite(stop_LED, HIGH);
delay(100);
digitalWrite(run_LED, LOW);
digitalWrite(stop_LED, LOW);
delay(100);
digitalWrite(run_LED, HIGH);
digitalWrite(stop_LED, HIGH);
delay(100);
digitalWrite(run_LED, LOW);
digitalWrite(stop_LED, LOW);
delay(100);
digitalWrite(run_LED, HIGH);
digitalWrite(stop_LED, HIGH);
delay(100);
digitalWrite(run_LED, LOW);
digitalWrite(stop_LED, LOW);
delay(100);
}

void errorBlink(){
  digitalWrite(stop_LED, HIGH);
  delay(50);
  digitalWrite(stop_LED, LOW);
  delay(50);
  digitalWrite(stop_LED, HIGH);
  delay(50);
  digitalWrite(stop_LED, LOW);
  delay(50);
}

void calibBlink(){
digitalWrite(run_LED, HIGH);
digitalWrite(stop_LED, HIGH);
delay(250);
digitalWrite(run_LED, LOW);
digitalWrite(stop_LED, LOW);
delay (250);
}

Idahowalker

Consider changing some of the mpu6050 settings as well for better/worse performance.

PWR_MGMT_1 address hex=0x6b, decimal 107

bit7         
DEVICE_RESET

bit 6
SLEEP

bit5
CYCLE

bit4
unused

bit 2,1,0
CLKSEL (2-0)

SMPLRT_DIV
Sample Rate Divider
8 bit value determined by
SampleRate=Gryoscope Output Rate/(1+SMPLRT_DIV)
Gyroscope Output Rate=8Khz default
see DLPF_CFG register (26) to change)
SMPLRT_DIV = 8 bit value

CONFIG (26)
Configures Frame Synchronization (FSYNC) pin and
Digital Low Pass Filter (DLPF) for gyros and accelerometers
bit2,1,0 DLPF_CONFIG

         Accel         Gyro
DLPF_CFG Hz  Delay(ms) Hz   Delay(ms)   Fs(kHz)
0        260 0         256  0.98        8
1        184 2.0       188  1.9         1
2        94  3.0       98   2.8         1
3        44  4.9       42   4.8         1
4        21  8.5       20   8.3         1
5        10  13.8      10   13.4        1
6        5   19.0      5    18.6        1
7 RESERVED


GYRO_CONFIG
Used to trigger gyro self test and full scale range
FS_SEL bits4,3
FS_SEL  Full Scale Range
0       +/- 250 deg/sec
1       +/- 500 deg/sec
2       +/- 1000 deg/sec
3       +/- 2000 deg/sec
decomal 24=+/- 2000 deg/sec

INT_ENABLE
enables interrupt generation by interrupt sources
bit4 FIFO_OFLOW_EN when 1 enables FIFO buffer to generate interrupy
bit3 I2C_MST_INT_EN when 1 enables I2C Master interrupt sources to generate interrupt
bit0 DATA_RDY_EN when 1 this enables a Data Ready Interrupt, which occurs each time
a write operation to all the sensor registets has been completed

A few notes from my use of a mpu6050 on settings that made a difference.


Go Up