MPU9250 Library and FreeRTOS Conflict: Sensor Readings Fail in Task but Work in Loop

I am using the MPU9250 sensor with FreeRTOS on an Arduino IDE with a ESP32. The sensor readings work correctly when accessed directly in the loop() function, but fail to provide accurate values when accessed from a task created with FreeRTOS.

#include <Wire.h>          // Library for I2C communication
#include <MPU9250.h>      // Include the MPU9250 library
#include <SD.h>           // Library for SD card handling
#include <SPI.h>          // Library for SPI communication

#define SD_CS_PIN 5       // Define the chip select pin for the SD card
#define QUEUE_LENGTH 10   // Define the length of the queue for data

#define CALIBRATION false // Set to true to enable sensor calibration

MPU9250 mpu;            // Create an instance of the MPU9250 class
File dataFile;          // File object to handle SD card file operations
QueueHandle_t dataQueue;  // Handle for the queue used to pass data between tasks

void setup() {
  Serial.begin(115200);  // Initialize serial communication at 115200 baud rate

  // Initialize I2C communication
  Wire.begin();
  delay(2000);  // Wait for 2 seconds to ensure the I2C bus is ready

  // Initialize the MPU9250 sensor
  if (!mpu.setup(0x68)) {  // Change to your sensor's I2C address if needed
    while (1) {
      Serial.println("Failed to connect to MPU9250. Check the connection.");
      delay(5000);  // Wait for 5 seconds before retrying
    }
  }

  Serial.println("MPU9250 found!");

  // Initialize the SD card
  while (!SD.begin(SD_CS_PIN)) {
    Serial.println("Card initialization failed or not present");
    delay(500);  // Wait for 0.5 seconds before retrying
  }
  Serial.println("Card initialized.");

  // Create a new file on the SD card
  dataFile = SD.open("/data.txt", FILE_WRITE);
  while (!dataFile) {
    Serial.println("Failed to create file");
    delay(500);  // Wait for 0.5 seconds before retrying
  }

  // Create a queue to hold QUEUE_LENGTH strings of size 100
  dataQueue = xQueueCreate(QUEUE_LENGTH, sizeof(char[100]));
  while (dataQueue == NULL) {
    Serial.println("Failed to create queue");
    delay(500);  // Wait for 0.5 seconds before retrying
  }

  // Perform calibration if needed
  if (CALIBRATION) {
    calibrateMPU();
  }

  // Create tasks
  xTaskCreatePinnedToCore(
    readMPUTask,       // Task function
    "ReadMPU",         // Task name
    10000,             // Stack size in words
    NULL,              // Task parameter
    1,                 // Task priority
    NULL,              // Task handle
    0);                // Core where the task should run

  xTaskCreatePinnedToCore(
    logDataTask,       // Task function
    "LogData",         // Task name
    10000,             // Stack size in words
    NULL,              // Task parameter
    1,                 // Task priority
    NULL,              // Task handle
    1);                // Core where the task should run
}

void loop() {
  // Nothing to do here, tasks are running independently
}

void readMPUTask(void *pvParameters) {
  char dataString[100];  // Buffer to store the formatted data string

  while (1) {
    // Check if the MPU9250 sensor has new data
    if (mpu.update()) {
        static uint32_t prev_ms = millis();
        if (millis() > prev_ms + 100) {  // Update interval of 100 ms
          // Format the data into a string
          snprintf(dataString, sizeof(dataString), "%f,%f,%f", mpu.getYaw(), mpu.getPitch(), mpu.getRoll());
          prev_ms = millis();
          Serial.println(dataString);  // Print data to the serial monitor

          // Send the data to the logging task
          xQueueSend(dataQueue, &dataString, portMAX_DELAY);
        }
    }

    // Delay before the next read
    vTaskDelay(100 / portTICK_PERIOD_MS);  
  }
}

void logDataTask(void *pvParameters) {
  char dataString[100];  // Buffer to receive data from the queue

  while (1) {
    // Receive data from the queue
    if (xQueueReceive(dataQueue, &dataString, portMAX_DELAY)) {
      // Write the data to the SD card
      if (dataFile) {
        dataFile.println(dataString);
        dataFile.flush();  // Ensure data is written to the card
      }
    }
  }
}

void calibrateMPU() {
  // Start calibration for accelerometer and gyroscope
  Serial.println("Accelerometer and gyroscope calibration will start in 5 seconds.");
  Serial.println("Keep the device still on a flat plane.");
  mpu.verbose(true);
  delay(5000);
  mpu.calibrateAccelGyro();

  // Start calibration for magnetometer
  Serial.println("Magnetometer calibration will start in 5 seconds.");
  Serial.println("Move the device in a figure-eight pattern to complete.");
  delay(5000);
  mpu.calibrateMag();

  // Display calibration parameters
  print_calibration();
  mpu.verbose(false);
}

void print_calibration() {
  Serial.println("< Calibration Parameters >");
  Serial.println("Accelerometer Bias [g]: ");
  Serial.print(mpu.getAccBiasX() * 1000.f / (float)MPU9250::CALIB_ACCEL_SENSITIVITY);
  Serial.print(", ");
  Serial.print(mpu.getAccBiasY() * 1000.f / (float)MPU9250::CALIB_ACCEL_SENSITIVITY);
  Serial.print(", ");
  Serial.print(mpu.getAccBiasZ() * 1000.f / (float)MPU9250::CALIB_ACCEL_SENSITIVITY);
  Serial.println();
  Serial.println("Gyroscope Bias [deg/s]: ");
  Serial.print(mpu.getGyroBiasX() / (float)MPU9250::CALIB_GYRO_SENSITIVITY);
  Serial.print(", ");
  Serial.print(mpu.getGyroBiasY() / (float)MPU9250::CALIB_GYRO_SENSITIVITY);
  Serial.print(", ");
  Serial.print(mpu.getGyroBiasZ() / (float)MPU9250::CALIB_GYRO_SENSITIVITY);
  Serial.println();
  Serial.println("Magnetometer Bias [mG]: ");
  Serial.print(mpu.getMagBiasX());
  Serial.print(", ");
  Serial.print(mpu.getMagBiasY());
  Serial.print(", ");
  Serial.print(mpu.getMagBiasZ());
  Serial.println();
  Serial.println("Magnetometer Scale []: ");
  Serial.print(mpu.getMagScaleX());
  Serial.print(", ");
  Serial.print(mpu.getMagScaleY());
  Serial.print(", ");
  Serial.print(mpu.getMagScaleZ());
  Serial.println();
}

OUTPUT on Serial Monitor with acess in task:
-7.510000,-0.000000,0.000000
-7.510000,-0.000000,0.000000
-7.510000,-0.000000,0.000000
...

The WiFi things run at Core0 and Arduino runs at Core1. Please keep it that way.
I think you can just use xTaskCreate().

Can you avoid using millis() for a timer, you have now a multitasking FreeRTOS system.
This is not how to use millis: if (millis() > prev_ms + 100) {
Always use: if(millis() - previousMillis >= interval)

Can you add a delay(10); or delay(100); in the loop() ?
The loop() is also a task, and you have it running at 100% doing nothing.

But I can not tell if one of these things causes the wrong values :grimacing:

Hello! Thanks for the suggestions. I still haven't solved the problem.

I made the changes you suggested, but nothing changed. The code is as follows:

// Creates the tasks
xTaskCreatePinnedToCore(
  readMPUTask, /* Task function. */
  "ReadMPU",   /* Task name. */
  10000,       /* Stack size in words. */
  NULL,        /* Task input parameter. */
  5,           /* Task priority. */
  NULL,        /* Task handler. */
  1);          /* Core where the task should run */

xTaskCreatePinnedToCore(
  logDataTask, /* Task function. */
  "LogData",   /* Task name. */
  10000,       /* Stack size in words. */
  NULL,        /* Task input parameter. */
  5,           /* Task priority. */
  NULL,        /* Task handler. */
  1);          /* Core where the task should run */
}

void loop() {
  // Nothing to do here, tasks are running independently
  delay(100);
}

void readMPUTask(void *pvParameters) {
  char dataString[100];

  while (1) {
    // Checks if the MPU9250 has been updated
    if (mpu.update()) {
      // Formats the data into a string
      snprintf(dataString, sizeof(dataString), "%f,%f,%f", mpu.getYaw(), mpu.getPitch(), mpu.getRoll());
      Serial.println(dataString);

      // Sends the data to the logging task
      xQueueSend(dataQueue, &dataString, portMAX_DELAY);
    }

    // Waits before reading again
    vTaskDelay(100 / portTICK_PERIOD_MS);
  }
}

void logDataTask(void *pvParameters) {
  char dataString[100];

  while (1) {
    // Receives the data from the reading task
    if (xQueueReceive(dataQueue, &dataString, portMAX_DELAY)) {
      // Writes the data to the SD card
      if (dataFile) {
        dataFile.println(dataString);
        dataFile.flush();  // Ensures the data is written to the card
      }
    }
  }
}

An interesting thing happened: when I added a delay in the loop, the tasks were no longer executed. They only resumed when I gave them higher priority.

I think that the loop() runs at priority 1.
I don't understand what is going on. A delay() in the loop() is good.

Which board do you have ? Can you give a link to where you bought it ?
Do you use the Arduino IDE ?
Which boards do you have installed ? and which board have you selected ?

The most common way is the boards by Espressif: https://dl.espressif.com/dl/package_esp32_index.json
Then the "ESP32 Dev Module" or a "DO-IT ESP32 DEVKIT V1" are the most common boards.

Which MPU9250 library do you use ?
Is the MPU9250 in I2C mode ?

Where did you buy the MPU-9250 ? Can you give a link to where you bought it.
How is it powered ? With 5V or 3.3V ? How long are the wires for the I2C bus ?

If nothing helps, then we have to turn everything upside down and start from scratch. Something weird is going on.

Sure, here's the translation:


Unfortunately, I don't have the links; the components came from my college. I can assure you the ESP32 is a dev module; I can see the markings, but it might be a clone. The MPU is powered at 3.3V, and the wires are about 15 cm long. The library is GitHub - hideakitai/MPU9250: Arduino library for MPU9250 Nine-Axis (Gyro + Accelerometer + Compass) MEMS MotionTracking™ Device and operates in I2C mode. I am using Arduino IDE V2.

Can you show a photo ?
I like to know if there is a voltage regulator on the MPU-9250 module and how it is powered and if you use a breadboard (a breadboard can have bad contacts). Is the MPU-9250 a module from Ebay/Amazon/AliExpress ? Then it might be counterfeit.
Is there "ESP-WROOM-32" written on the metal block of the ESP32 ? That is the most common ESP32 module.

Is everything updated to the newest version ? The Arduino IDE, the boards and the libraries ?
Can confirm that you have the boards "https://dl.espressif.com/dl/package_esp32_index.json" in the preferences ?

That library uses millis() in the wrong way. I can not see other problems, but I prefer to try a different library.
This library is less complex: https://github.com/wollewald/MPU9250_WE

You have a vTaskDelay in the readMPUTask(), that delay should not be there when I look at the examples for that library.

From now on, keep it as simple as possible:
Don't use a queue yet.
Set the priority of all tasks to 1.
Use xTaskCreate().
Put a delay in the loop().
Don't use a printf(), and if you use it, put the return value of the functions in variables first.
Don't use the SD card yet.

Can you create a small test with one task and let a led blink in that task ?

A very few others have problems with the timing of the I2C bus on a ESP32. Perhaps the priority of 5 triggers that issue.
The Arduino mode for a ESP32 has been updated to version 3, there might still be bugs.
I think you have a combination (hardware or software or both) that does not work.
I'm afraid that there is a obvious problem in the sketch that I don't see. Hopefully someone else checks your sketch.

Hello, friend!
I studied the library and FreeRTOS, and apparently it deals with the millis that you mentioned when executed in an environment with more tasks, causing the watchdog of the reading function to be exceeded. I was able to solve the problem using this suggested library. I leave the functional code below for the next desperate ones:

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

#define SD_CS_PIN 5
#define QUEUE_LENGTH 10  // Tamanho da fila

#define CALIBRACAO true
#define MONITOR true

void SaveMPU9250DataTask(void *pvParameters);
void initializeMPU9250();
void calibrateMPU9250();

#define MPU9250_ADDR 0x68
MPU9250_WE myMPU9250 = MPU9250_WE(MPU9250_ADDR);  // Crie uma instância da classe MPU9250_WE
File dataFileINS;

void setup() {
  Serial.begin(115200);  // Inicializa a comunicação serial
  delay(2000);
  Serial.println("=========  Inicializando Sistemas  =========");

  // Inicializa o I2C
  Wire.begin();
  delay(2000);

  initializeMPU9250();

  // Inicializa o cartão SD
  while (!SD.begin(SD_CS_PIN)) {
    Serial.println("Falha no cartão, ou não presente");
    delay(5000);
  }
  Serial.println("Cartão inicializado.");

  // Cria um novo arquivo
  dataFileINS = SD.open("/data.txt", FILE_WRITE);
  while (!dataFileINS) {
    Serial.println("Falha ao criar o arquivo");
    delay(5000);
  }

  // Realiza a calibração
  if (CALIBRACAO) {
    calibrateMPU9250();
  }

  // Cria as tarefas
  xTaskCreatePinnedToCore(
    SaveMPU9250DataTask, /* Função da tarefa. */
    "ReadMPU",           /* Nome da tarefa. */
    8192,                /* Tamanho da pilha em palavras. */
    NULL,                /* Parâmetro de entrada da tarefa. */
    5,                   /* Prioridade da tarefa. */
    NULL,                /* Manipulador da tarefa. */
    1);                  /* Núcleo onde a tarefa deve ser executada */
}

void loop() {
  // Nada a fazer aqui, as tarefas estão rodando independentemente
  delay(100);
}

void SaveMPU9250DataTask(void *pvParameters) {
  char dataString[155];

  while (1) {
    xyzFloat gValue = myMPU9250.getGValues();
    xyzFloat gyr = myMPU9250.getGyrValues();
    xyzFloat magValue = myMPU9250.getMagValues();
    xyzFloat angles = myMPU9250.getAngles();

    float temp = myMPU9250.getTemperature();
    float resultantG = myMPU9250.getResultantG(gValue);
    float pitch = myMPU9250.getPitch();
    float roll = myMPU9250.getRoll();

    // Formata os dados em uma string
    snprintf(dataString, sizeof(dataString), "%f, %f, %f, %f, %f, %f, %f, %f, %f, %f, %f, %f, %f, %f, %f",
             gValue.x, gValue.y, gValue.z, resultantG,
             gyr.x, gyr.y, gyr.z,
             magValue.x, magValue.y, magValue.z,
             temp,
             angles.x, angles.y, angles.z, pitch, roll);

    dataFileINS.println(dataString);
    dataFileINS.flush();  // Garante que os dados sejam gravados no cartão

    if (MONITOR) {
      Serial.println("=========  SaveMPU9250DataTask  =========");

      Serial.println("Acceleration in g (x,y,z):");
      Serial.print(gValue.x);
      Serial.print("   ");
      Serial.print(gValue.y);
      Serial.print("   ");
      Serial.println(gValue.z);
      Serial.print("Resultant g: ");
      Serial.println(resultantG);

      Serial.println("Gyroscope data in degrees/s: ");
      Serial.print(gyr.x);
      Serial.print("   ");
      Serial.print(gyr.y);
      Serial.print("   ");
      Serial.println(gyr.z);

      Serial.println("Magnetometer Data in µTesla: ");
      Serial.print(magValue.x);
      Serial.print("   ");
      Serial.print(magValue.y);
      Serial.print("   ");
      Serial.println(magValue.z);

      Serial.print("Temperature in °C: ");
      Serial.println(temp);

      Serial.println();

      Serial.print("Angle x = ");
      Serial.print(angles.x);
      Serial.print("  |  Angle y = ");
      Serial.print(angles.y);
      Serial.print("  |  Angle z = ");
      Serial.println(angles.z);

      Serial.print("Pitch   = ");
      Serial.print(pitch);
      Serial.print("  |  Roll    = ");
      Serial.println(roll);

      Serial.println();

      Serial.println(dataString);

      Serial.println();
    }

    // Aguarda antes de ler novamente
    vTaskDelay(1000 / portTICK_PERIOD_MS);
  }
}

void initializeMPU9250() {
  // Inicializa o MPU9250
  while (!myMPU9250.init()) {
    Serial.println("MPU9250 não responde!");
    delay(5000);
  }

  Serial.println("MPU9250 encontrado!");

  while (!myMPU9250.initMagnetometer()) {
    Serial.println("Magnetometro não responde!");
    delay(5000);
  }
  Serial.println("Magnetometro encontrado");

  // Configurações do MPU9250
  myMPU9250.enableGyrDLPF();
  myMPU9250.setGyrDLPF(MPU9250_DLPF_6);
  myMPU9250.setSampleRateDivider(5);
  myMPU9250.setGyrRange(MPU9250_GYRO_RANGE_250);
  myMPU9250.setAccRange(MPU9250_ACC_RANGE_2G);
  myMPU9250.enableAccDLPF(true);
  myMPU9250.setAccDLPF(MPU9250_DLPF_6);
  myMPU9250.setMagOpMode(AK8963_CONT_MODE_100HZ);
  delay(1000);
}

void calibrateMPU9250() {
  Serial.println("Mantenha o cubesat parado! Iniciando a calibração...");
  delay(3000);
  myMPU9250.autoOffsets();
  Serial.println("Feito!");
}

Thank you for your help! Keep coding.

1 Like