Datalogging with an MPU9250 and SD Card at 1KHz with an Arduino UNO

Hi there,

I am a new user of Arduino and have been struggling with improving the sampling rate of my sensors, as well as storing the data at the same speed. I require the sensor to be recording only the accelerometer points at 1KHz, and then to be stored on an SD Card.

I Have done plenty of research on wiring these connects and believe I have done this correctly but is as follows:

I am certain the issue is within my coding, I am not creating any buffer and not storing the information bitwise, and also using print statement. I am working on solving this issue myself but would love some community feedback.

Current packages i’ve been using are the bolderflight MPU9250 Package: GitHub - bolderflight/mpu9250: MPU-9250 sensor driver.

and basic SD Card package (SD Card is compatible) : Arduino - SD
I’ve heard the base package under-performs and other may be better (GitHub - greiman/SdFat: Arduino FAT16/FAT32 exFAT Library)

The code i’m using is as follows:

#include "MPU9250v2.h"
#include <SD.h>

// an MPU9250 object with the MPU-9250 sensor on I2C bus 0 with address 0x68
MPU9250 IMU(Wire,0x68);
int status;
const int chipSelect = 4;

void setup() {
  // serial to display data
  Serial.begin(115200);
  SD.begin(chipSelect);
  
  // while(!Serial) {}

  // start communication with IMU 
  status = IMU.begin();


  // setting the accelerometer full scale range to +/-8G 
  IMU.setAccelRange(MPU9250::ACCEL_RANGE_8G);
  // setting the gyroscope full scale range to +/-500 deg/s
  IMU.setGyroRange(MPU9250::GYRO_RANGE_500DPS);
  // setting DLPF bandwidth to 20 Hz
  IMU.setDlpfBandwidth(MPU9250::DLPF_BANDWIDTH_460HZ);
  IMU.setSrd(0);
}

void loop() {
  
  IMU.readSensor();
  String dataString = "";
  dataString += String(IMU.getAccelX_mss(),2);
  dataString += ",";
  dataString += String(IMU.getAccelY_mss(),2);
  dataString += ",";
  dataString += String(IMU.getAccelZ_mss(),2);



  File dataFile = SD.open("datalog.txt", FILE_WRITE);
  dataFile.println(dataString);
  dataFile.close();
  Serial.println(dataString);
  
  
  
  delay(0);
}

What is the problem?

Note: use of Strings is a bad idea on standard Arduinos. They cause memory problems and program crashes.

At the moment the sensor does not record at 1KHz, say its around 600 Hz and data is saved at an even slower rate :slight_smile:

Solve the data collection rate problem first, without using Strings. Strings are totally unnecessary, waste CPU time and cause problems down the road.

Solve the storage problem second. There is no need to open the file, write, and close the file for every single data entry.

 File dataFile = SD.open("datalog.txt", FILE_WRITE);
  dataFile.println(dataString);
  dataFile.close();

What is the purpose of this line?

  delay(0);

Comment. I really like the Bolderflight MPU9250 library. I have found an issue with the library that you can fix, I already sent a message to the author, 3 months ago.

From time to time the int MPU9250::readSensor() function the readRegisters(ACCEL_OUT, 21, _buffer) command can retrieve the wrong value for _axcounts by skipping the LSB. The _ax counts gets transformed into the _ay data. Which will make the _ay or, if converted to pitch, data do a wonky every once in a while.

I found that by changing the address, add this to h file, to const uint8_t GetAllDataAddress = 0X3A;, making adjustment to the buffer, and changing the ordinal position references in the readsensor function solves the issue.

 // buffer for reading from sensor
    uint8_t _buffer[21]; <<<----- change to 23

You’ll need to change the code to reflect the ordinal positions in

int MPU9250FIFO::readFifo() 

/// 
int MPU9250::readSensor()

As an example original code

int MPU9250::readSensor() {
  _useSPIHS = true; // use the high speed SPI for data readout
  // grab the data from the MPU9250
  if (readRegisters(ACCEL_OUT, 21, _buffer) < 0) {
    return -1;
  }
  // combine into 16 bit values
  _axcounts = (((int16_t)_buffer[0]) << 8) | _buffer[1];  
  _aycounts = (((int16_t)_buffer[2]) << 8) | _buffer[3];
  _azcounts = (((int16_t)_buffer[4]) << 8) | _buffer[5];

[/code] Changed code

int MPU9250::readSensor() {
  _useSPIHS = true; // use the high speed SPI for data readout
  // grab the data from the MPU9250
  if (readRegisters(GetAllDataAddress, 23, _buffer) < 0) {
    return -1;
  }
  // combine into 16 bit values
  _axcounts = (((int16_t)_buffer[2]) << 8) | _buffer[3];  
  _aycounts = (((int16_t)_buffer[4]) << 8) | _buffer[5];
  _azcounts = (((int16_t)_buffer[6]) << 8) | _buffer[7];

You may also realize that the library is doing a lot more then what you want and that cutting out some of the extra work may lend speed to your application.

Hi everybody,

Thanks for the quick replies and great help, I'll look through all of these this morning and see if I can get this resolved!

Thanks you

Hi all,

I've taken your notes on board and understand I need to create a buffer when storing the data to the SD card and not just read and write each data point as this drastically slows the process. Also that I need to not use print and string statements and need to read the data more efficiently.

However after lots of research i am still quite lost, could anyone help me out with this by showing me some examples of more correct code or helping me with this sensor directly? I really need this to work as I need the sensor to apply some data science techniques (which is my area of expertise).

Thanks again for all your help

I need to create a buffer when storing the data to the SD card and not just read and write each data point as this drastically slows the process.

No, you do not need to create a buffer. This is done internally.

Instead of this crash and malfunction causing String usage:

String dataString = "";
  dataString += String(IMU.getAccelX_mss(),2);
  dataString += ",";
  dataString += String(IMU.getAccelY_mss(),2);
  dataString += ",";
  dataString += String(IMU.getAccelZ_mss(),2);
  File dataFile = SD.open("datalog.txt", FILE_WRITE);
  dataFile.println(dataString);
  dataFile.close();

Do this:

  1. open the file ONCE in setup()

  2. in loop(), print data to the file as follows:

  dataFile.print(IMU.getAccelX_mss());
  dataFile.print(",");
  dataFile.print(IMU.getAccelY_mss());
  dataFile.print(",");
  dataFile.println(IMU.getAccelZ_mss()); //adds CR and LF
  1. Close the file ONLY when you are done collecting data

  2. Don't print to the serial monitor at the same time, it just wastes time

Being "quite lost" is a sign that you are in over your head. Start with simpler projects and work your way up.

The ESP32 using freeRTOS has stream buffers that can be ran as background tasks. A stream buffer can be set up in a task to read from where; like reading and buffering data from the MPU9250, whiles a 2nd stream buffer is taking info out of the MPU9250 stream buffer and putting it into the SD card, as fast as it can, and done as a background task.

I have seen, from time to time, mentions of a buffer of some sort that is available for most of the Arduino's.

I used, for the DUE, a stream buffer from the RTOS uMT. Whiles uMt documentation indicated that it can, I'd not run uMT on a MEGA. I have, also, read that a freeRTOS library is available for the Aruino's. I think, from my experience, that a MEGA could run freeRTOS well and, of course, I'd run freeRTOS on a DUE. There are other flavors of Arduino boards but I've not tried them out and do not know about running an event driven/RTOS (Real Time Operating System) 'OS' on them.