SparkFun 9DoF IMU Breakout - ICM-20948 Compass?

is there a simple compass demo that works and compensated for tilt using the ICM-20948?

You can try this code.

#include <Wire.h>
#include <ICM-20948.h>

ICM_20948_I2C myICM;  // Create an ICM_20948 object

void setup() {
  Serial.begin(115200);
  Wire.begin();

  if (myICM.begin(Wire) != ICM_20948_Stat_Ok) {
    Serial.println("ICM-20948 not detected at default I2C address. Please check wiring.");
    while (1) ; // Halt
  }

  Serial.println("ICM-20948 detected.");

  myICM.initialize();
}

void loop() {
  if (myICM.dataReady()) {
    myICM.getAGMT(); // Update accelerometer, gyroscope, magnetometer, and temperature data

    // Get accelerometer readings
    float ax = myICM.accX();
    float ay = myICM.accY();
    float az = myICM.accZ();

    // Get magnetometer readings
    float mx = myICM.magX();
    float my = myICM.magY();
    float mz = myICM.magZ();

    // Calculate pitch and roll (in radians)
    float pitch = atan2(-ax, sqrt(ay * ay + az * az));
    float roll = atan2(ay, az);

    // Calculate tilt-compensated magnetic field values
    float mx_comp = mx * cos(pitch) + mz * sin(pitch);
    float my_comp = mx * sin(roll) * sin(pitch) + my * cos(roll) - mz * sin(roll) * cos(pitch);

    // Calculate heading (in degrees)
    float heading = atan2(my_comp, mx_comp) * 180 / PI;
    if (heading < 0) {
      heading += 360;
    }

    Serial.print("Heading: ");
    Serial.print(heading);
    Serial.println(" degrees");

    delay(500); // Update every 500 ms
  }
}

You need to install this library:

https://github.com/sparkfun/SparkFun_IC … n/examples

I replaced the “-” with "_ " for the lib include and removed initialize() function which didn’t exist…

This almost works, however when rotating the sensor at a constant rate, the degrees from 150-360 is off, while 0-150 appears to be correct. (see image)

Ideas on why this would occur?

—updated code—

#include <Wire.h>

#include <ICM_20948.h>

ICM_20948_I2C myICM; // Create an ICM_20948 object

void setup() {

Serial.begin(115200);

Wire.begin();

if (myICM.begin(Wire) != ICM_20948_Stat_Ok) {

Serial.println(“ICM-20948 not detected at default I2C address. Please check wiring.”);

while (1) ; // Halt

}

Serial.println(“ICM-20948 detected.”);

//myICM.initialize();

}

String getCardinalDirection(int degree) {

if (degree >= 337.5 || degree < 22.5) {

return “N”;

} else if (degree >= 22.5 && degree < 67.5) {

return “NE”;

} else if (degree >= 67.5 && degree < 112.5) {

return “E”;

} else if (degree >= 112.5 && degree < 157.5) {

return “SE”;

} else if (degree >= 157.5 && degree < 202.5) {

return “S”;

} else if (degree >= 202.5 && degree < 247.5) {

return “SW”;

} else if (degree >= 247.5 && degree < 292.5) {

return “W”;

} else if (degree >= 292.5 && degree < 337.5) {

return “NW”;

} else {

return “Unknown”;

}

}

void loop() {

if (myICM.dataReady()) {

myICM.getAGMT(); // Update accelerometer, gyroscope, magnetometer, and temperature data

// Get accelerometer readings

float ax = myICM.accX();

float ay = myICM.accY();

float az = myICM.accZ();

// Get magnetometer readings

float mx = myICM.magX();

float my = myICM.magY();

float mz = myICM.magZ();

// Calculate pitch and roll (in radians)

float pitch = atan2(-ax, sqrt(ay * ay + az * az));

float roll = atan2(ay, az);

// Calculate tilt-compensated magnetic field values

float mx_comp = mx * cos(pitch) + mz * sin(pitch);

float my_comp = mx * sin(roll) * sin(pitch) + my * cos(roll) - mz * sin(roll) * cos(pitch);

// Calculate heading (in degrees)

float heading = atan2(my_comp, mx_comp) * 180 / PI;

if (heading < 0) {

heading += 360;

}

Serial.printf(“%s, %f\n”,getCardinalDirection(heading),heading);

//printDirection(heading);

delay(200); // Update every 500 ms

}

}

The MX, MY raw data looks ok when plotted, but the atan2 function appears not calculate correctly when the heading from 360-260 . could this be a esp32 bug?

heading = atan2(my, mx) * 180.0 / PI;

Have you carefully calibrated the magnetometer? If not, you will get the best results by following this excellent tutorial:

https://thecavepearlproject.org/2015/05 … r-arduino/

A working example of tilt-compensated compass for that sensor is posted here: https://github.com/jremington/ICM_20948-AHRS

If I rotate the sensor while flat, the results are not correct. However, if I tilt the sensor slightly, then rotate it. the data looks perfect.

Trying the ICM_20948-AHRS code now.

Still not working even with ICM_20948-AHRS code.

still no go with this code wither, if the sensor is flat, not tilted at least 10 degrees, the heading is way off…

but tilted, the data looks great.

frustrating.

#include <Wire.h>

#include <ICM_20948.h>

#include <math.h>

ICM_20948_I2C myICM; // Create an ICM_20948 object

void setup() {

Serial.begin(115200);

Wire.begin();

//Wire.setClock(400000);

if (myICM.begin(Wire) != ICM_20948_Stat_Ok) {

Serial.println(“ICM-20948 not detected at default I2C address. Please check wiring.”);

while (1) ; // Halt

}

Serial.println(“ICM-20948 detected.”);

//myICM.initialize();

calibrateMagnetometer();

}

int cnt=0;

float heading=0;

void calibrateMagnetometer() {

myICM.startupMagnetometer();

Serial.println(“Calibrating magnetometer. Move it in a figure-eight motion.”);

delay(5000); // Give time to move in figure-eight

}

float calculateCompassHeading(float magX, float magY, float magZ) {

// Declare the variable heading_rad

float heading_rad;

// Calculate horizontal magnitude of the magnetic field

float mag_h = sqrt(magX * magX + magY * magY);

// Handle the special case of a flat sensor (mag_h is very small)

if (mag_h < 0.1) { // Adjust the threshold as needed

// Use the average of x and y components for a more stable heading

heading_rad = atan2(magY + magX, 0); // Use the full std::math namespace

} else {

// Calculate the tilt angle using the arctangent function

float tiltAngle = atan2(magZ, mag_h);

// Calculate the corrected heading using the tilt-adjusted horizontal components

heading_rad = atan2(magY * cos(tiltAngle) - magZ * sin(tiltAngle),magX);

}

// Convert to degrees and constrain to 0-360 range

float heading_deg = degrees(heading_rad);

heading_deg = (int) (heading_deg + 360) % 360;

return heading_deg;

}

void loop() {

if (myICM.dataReady()) {

myICM.getAGMT(); // Update accelerometer, gyroscope, magnetometer, and temperature data

// Get magnetometer readings

float mx = myICM.magX();

float my = myICM.magY();

float mz = myICM.magZ();

heading = calculateCompassHeading(mx,my,mz);

Serial.printf(“%f\n”,heading);

}

delay(50);

}

I give up,

im just going to mount the sensor vertically. seems to work perfectly vertically.

You are not calibrating the magnetometer correctly, and as a result, you are not seeing useful results.

The “figure 8 motion” method does not work well and sometimes, not at all.