# SparkFun 9DoF IMU Breakout - ICM-20948 Compass?

**URL:** <https://community.sparkfun.com/t/sparkfun-9dof-imu-breakout-icm-20948-compass/47457>\
**Category:** IMU & Motion\
**Created:** [May 22, 2024, 1:49am UTC](https://community.sparkfun.com/t/sparkfun-9dof-imu-breakout-icm-20948-compass/47457 "2024-05-22T01:49:06Z")\
**Posts on this page:** 9\
**Page:** 1

<div class="post-metadata">

**Author:** ![dalepa](https://avatars.discourse-cdn.com/v4/letter/d/4491bb/32.png) [@dalepa](https://community.sparkfun.com/u/dalepa)\
**Post date:** [May 22, 2024, 1:49am UTC](https://community.sparkfun.com/t/sparkfun-9dof-imu-breakout-icm-20948-compass/47457/1 "2024-05-22T01:49:06Z")

</div>

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

---

<div class="post-metadata">

**Author:** ![aliarifat794](https://avatars.discourse-cdn.com/v4/letter/a/eb8c5e/32.png) [@aliarifat794](https://community.sparkfun.com/u/aliarifat794)\
**Post date:** [May 22, 2024, 8:04am UTC](https://community.sparkfun.com/t/sparkfun-9dof-imu-breakout-icm-20948-compass/47457/2 "2024-05-22T08:04:06Z")

</div>

You can try this code.

```auto
#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](https://github.com/sparkfun/SparkFun_ICM-20948_ArduinoLibrary/tree/main/examples)

---

<div class="post-metadata">

**Author:** ![dalepa](https://avatars.discourse-cdn.com/v4/letter/d/4491bb/32.png) [@dalepa](https://community.sparkfun.com/u/dalepa)\
**Post date:** [May 22, 2024, 5:00pm UTC](https://community.sparkfun.com/t/sparkfun-9dof-imu-breakout-icm-20948-compass/47457/3 "2024-05-22T17:00:08Z")

</div>

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?

 ![Screenshot 2024-05-22 115117.png](https://canada1.discourse-cdn.com/sparkfun/original/3X/6/c/6c8ae0435a88f5ffd8b69668c579c27c381e9c2c.png)

—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](https://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

}

}

---

<div class="post-metadata">

**Author:** ![dalepa](https://avatars.discourse-cdn.com/v4/letter/d/4491bb/32.png) [@dalepa](https://community.sparkfun.com/u/dalepa)\
**Post date:** [May 23, 2024, 2:04am UTC](https://community.sparkfun.com/t/sparkfun-9dof-imu-breakout-icm-20948-compass/47457/4 "2024-05-23T02:04:12Z")

</div>

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;

---

<div class="post-metadata">

**Author:** ![jremington](https://yyz1.discourse-cdn.com/sparkfun/user_avatar/community.sparkfun.com/jremington/32/6595_2.png) [@jremington](https://community.sparkfun.com/u/jremington)\
**Post date:** [May 23, 2024, 1:25pm UTC](https://community.sparkfun.com/t/sparkfun-9dof-imu-breakout-icm-20948-compass/47457/5 "2024-05-23T13:25:13Z")

</div>

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/](https://thecavepearlproject.org/2015/05/22/calibrating-any-compass-or-accelerometer-for-arduino/)

A working example of tilt-compensated compass for that sensor is posted here: [https://github.com/jremington/ICM\_20948-AHRS](https://github.com/jremington/ICM_20948-AHRS)

---

<div class="post-metadata">

**Author:** ![dalepa](https://avatars.discourse-cdn.com/v4/letter/d/4491bb/32.png) [@dalepa](https://community.sparkfun.com/u/dalepa)\
**Post date:** [May 24, 2024, 1:08am UTC](https://community.sparkfun.com/t/sparkfun-9dof-imu-breakout-icm-20948-compass/47457/6 "2024-05-24T01:08:55Z")

</div>

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.

---

<div class="post-metadata">

**Author:** ![dalepa](https://avatars.discourse-cdn.com/v4/letter/d/4491bb/32.png) [@dalepa](https://community.sparkfun.com/u/dalepa)\
**Post date:** [May 24, 2024, 1:24am UTC](https://community.sparkfun.com/t/sparkfun-9dof-imu-breakout-icm-20948-compass/47457/7 "2024-05-24T01:24:05Z")

</div>

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](https://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](https://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);

}

---

<div class="post-metadata">

**Author:** ![dalepa](https://avatars.discourse-cdn.com/v4/letter/d/4491bb/32.png) [@dalepa](https://community.sparkfun.com/u/dalepa)\
**Post date:** [May 24, 2024, 1:54am UTC](https://community.sparkfun.com/t/sparkfun-9dof-imu-breakout-icm-20948-compass/47457/8 "2024-05-24T01:54:46Z")

</div>

I give up,

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

---

<div class="post-metadata">

**Author:** ![jremington](https://yyz1.discourse-cdn.com/sparkfun/user_avatar/community.sparkfun.com/jremington/32/6595_2.png) [@jremington](https://community.sparkfun.com/u/jremington)\
**Post date:** [May 24, 2024, 7:53pm UTC](https://community.sparkfun.com/t/sparkfun-9dof-imu-breakout-icm-20948-compass/47457/9 "2024-05-24T19:53:53Z")

</div>

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.
