Anyone else seeing ICM-20948 values don't look quite right

I’m using a raspberry pi to read accelerometer/gyroscope/temperature information from an ICM-20948 that I got in the sparkfun sensor kit (https://www.sparkfun.com/products/16156). I have the IMU wired up via the qwiic cable to the SparkFun Qwiic pHAT v2.0 for Raspberry Pi (https://www.sparkfun.com/products/15945).

I wrote a small python script to pull the values for me by using Sparkfun’s arduino library as a reference (https://github.com/sparkfun/SparkFun_IC … inoLibrary).

First I moved the ICM-20948 out of sleep mode by writing to PWR_MGMT_1:

i2cset -y 1 0x69 0x06 0x01

And verified by running:

pi@raspberrypi:~ $ i2cget -y 1 0x69 0x6
0x01

Next I ran my python script:

!/usr/bin/python3

import os
import subprocess as sub

class Icm20948:
    def __init__(self, bout, fss_g, fss_a):
        self.acc_x_raw = int.from_bytes(bout[0:1],byteorder='big', signed=True)
        self.acc_y_raw = int.from_bytes(bout[2:3],byteorder='big', signed=True)
        self.acc_z_raw = int.from_bytes(bout[4:5],byteorder='big', signed=True)
        self.gyr_x_raw = int.from_bytes(bout[6:7],byteorder='big', signed=True)
        self.gyr_y_raw = int.from_bytes(bout[8:9],byteorder='big', signed=True)
        self.gyr_z_raw = int.from_bytes(bout[10:11],byteorder='big', signed=True)

        self.temp_raw = (bout[12] << 8) | (bout[13] & 0xff)

        self.fss_a = ( fss_a[0] >> 1 ) & 0x3
        self.fss_g = ( fss_g[0] >> 1 ) & 0x3

        self.acc_x = 0
        self.acc_y = 0
        self.acc_z = 0
        self.gyr_x = 0
        self.gyr_y = 0
        self.gyr_z = 0

        self.temp_c = 0
        self.temp_f = 0
        self.interpret()

    def interpret(self):
        if self.fss_a == 0:
            self.acc_x = self.acc_x_raw / (16.384)
            self.acc_y = self.acc_y_raw / (16.384)
            self.acc_z = self.acc_z_raw / (16.384)
        if self.fss_g == 0:
            self.gyr_x = self.gyr_x_raw / 131
            self.gyr_y = self.gyr_y_raw / 131
            self.gyr_z = self.gyr_z_raw / 131

        self.temp_c = ( (self.temp_raw - 21) / 333.87 ) + 21
        self.temp_f = self.temp_c*(9.0/5.0) + 32

    def __str__(self):
        s = ""
        s += "Acc: ({0:7.3f}, {1:7.3f}, {2:7.3f} )".format(self.acc_x, self.acc_y, self.acc_z)
        s += ", "
        s += "Gyr: ({0:7.3f}, {1:7.3f}, {2:7.3f} )".format(self.gyr_x, self.gyr_y, self.gyr_z)
        s += ", "
        s += "Temp: {0:7.3f} degC, {1:7.3f} degF".format(self.temp_c, self.temp_f)
        return s

i2c_cmd = ['i2ctransfer', '-y', '1', 'w1@0x69', '0x2d', 'r14@0x69']

compProc = sub.run(i2c_cmd, capture_output=True, universal_newlines=True)

out_bytes = [int(x,0) for x in compProc.stdout.split()]

i2c_fss_g_cmd = ['i2ctransfer', '-y', '1', 'w1@0x69', '0x1', 'r1@0x69']
compProc_fss_g = sub.run(i2c_fss_g_cmd, capture_output=True, universal_newlines=True)
fss_g_bytes = [int(x,0) for x in compProc_fss_g.stdout.split()]

i2c_fss_a_cmd = ['i2ctransfer', '-y', '1', 'w1@0x69', '0x14', 'r1@0x69']
compProc_fss_a = sub.run(i2c_fss_a_cmd, capture_output=True, universal_newlines=True)
fss_a_bytes = [int(x,0) for x in compProc_fss_a.stdout.split()]

icm = Icm20948(out_bytes, fss_g_bytes, fss_a_bytes)
print(icm)

The values I get as output are as follows (with board lying flat, sensor face up):

Acc: ( -0.122,  -0.122,   3.967 ), Gyr: (  0.000,   0.000,  -0.008 ), Temp:  28.078 degC,  82.540 degF

I’m confused by the 3.967 value in the z direction of the accelerometer. I expected 1g, but for some reason I’m seeing almost 4g. 16.384 is the correct sensitivity with full scale value of zero according to the spec. I considered dividing the raw value by 16.384*4, but I’m concerned that I’ll end up measuring 4g units instead of 1g units… Subtracting 4 from the z axis doesn’t seem to make sense because when I turn the sensor on it’s side (any side), the corresponding y or x value also reports almost 4g. Am I doing something wrong with the calculation or setup for this measurement?

Also, while the gyroscope values look normal in my example given that the gyroscope is at rest, the gyroscope is giving me subzero values for dps even when I quickly rotate the board multiple degrees per second with my hand.

Any help/advise would be appreciated in understanding what I might be missing.

In the line below I meant to say that the gyroscope is giving me values less than one, not subzero values.

“Also, while the gyroscope values look normal in my example given that the gyroscope is at rest, the gyroscope is giving me subzero values for dps even when I quickly rotate the board multiple degrees per second with my hand.”

I made a silly mistake in my python script. I indexed bout with the wrong ending offset, and thus ended up with just the high byte of the sensor value in each axis. This explains why the dps values were so small.

The acceleration values seem a bit high now though. Where I was getting ~4g before, I’m now getting ~1000g.

Acc: ( -6.348, -8.301, 1020.508 ), Gyr: ( -0.443, 2.679, -1.176 ), Temp: 29.084 degC, 84.351 degF

Updated script:

!/usr/bin/python3

import os
import subprocess as sub

class Icm20948:
    def __init__(self, bout, fss_g, fss_a):
        self.acc_x_raw = int.from_bytes(bout[0:2],byteorder='big', signed=True)
        self.acc_y_raw = int.from_bytes(bout[2:4],byteorder='big', signed=True)
        self.acc_z_raw = int.from_bytes(bout[4:6],byteorder='big', signed=True)
        self.gyr_x_raw = int.from_bytes(bout[6:8],byteorder='big', signed=True)
        self.gyr_y_raw = int.from_bytes(bout[8:10],byteorder='big', signed=True)
        self.gyr_z_raw = int.from_bytes(bout[10:12],byteorder='big', signed=True)

        self.temp_raw = (bout[12] << 8) | (bout[13] & 0xff)

        self.fss_a = ( fss_a[0] >> 1 ) & 0x3
        self.fss_g = ( fss_g[0] >> 1 ) & 0x3

        self.acc_x = 0
        self.acc_y = 0
        self.acc_z = 0
        self.gyr_x = 0
        self.gyr_y = 0
        self.gyr_z = 0

        self.temp_c = 0
        self.temp_f = 0
        self.interpret()

    def interpret(self):
        if self.fss_a == 0:
            self.acc_x = self.acc_x_raw / (16.384)
            self.acc_y = self.acc_y_raw / (16.384)
            self.acc_z = self.acc_z_raw / (16.384)
        if self.fss_g == 0:
            self.gyr_x = self.gyr_x_raw / 131
            self.gyr_y = self.gyr_y_raw / 131
            self.gyr_z = self.gyr_z_raw / 131

        self.temp_c = ( (self.temp_raw - 21) / 333.87 ) + 21
        self.temp_f = self.temp_c*(9.0/5.0) + 32

    def __str__(self):
        s = ""
        s += "Acc: ({0:7.3f}, {1:7.3f}, {2:7.3f} )".format(self.acc_x, self.acc_y, self.acc_z)
        s += ", "
        s += "Gyr: ({0:7.3f}, {1:7.3f}, {2:7.3f} )".format(self.gyr_x, self.gyr_y, self.gyr_z)
        s += ", "
        s += "Temp: {0:7.3f} degC, {1:7.3f} degF".format(self.temp_c, self.temp_f)
        return s

i2c_cmd = ['i2ctransfer', '-y', '1', 'w1@0x69', '0x2d', 'r14@0x69']

compProc = sub.run(i2c_cmd, capture_output=True, universal_newlines=True)

out_bytes = [int(x,0) for x in compProc.stdout.split()]

i2c_fss_g_cmd = ['i2ctransfer', '-y', '1', 'w1@0x69', '0x1', 'r1@0x69']
compProc_fss_g = sub.run(i2c_fss_g_cmd, capture_output=True, universal_newlines=True)
fss_g_bytes = [int(x,0) for x in compProc_fss_g.stdout.split()]

i2c_fss_a_cmd = ['i2ctransfer', '-y', '1', 'w1@0x69', '0x14', 'r1@0x69']
compProc_fss_a = sub.run(i2c_fss_a_cmd, capture_output=True, universal_newlines=True)
fss_a_bytes = [int(x,0) for x in compProc_fss_a.stdout.split()]

icm = Icm20948(out_bytes, fss_g_bytes, fss_a_bytes)
print(icm)

I figured out why the accelerometer values were so high. I had used a value from the Sparkfun arduino library here ( https://github.com/sparkfun/SparkFun_IC … 48.cpp#L64 ). This line indicates that the accelerometer values should be divided by 16.384 for an fss value of 0. Upon looking through the data sheet, it says the value that should be used is 16,384. I wonder if the arduino library’s accelerometer value is also off by 1000…

Updated python script:

!/usr/bin/python3

import os
import subprocess as sub

class Icm20948:
    def __init__(self, bout, fss_g, fss_a):
        self.acc_x_raw = int.from_bytes(bout[0:2],byteorder='big', signed=True)
        self.acc_y_raw = int.from_bytes(bout[2:4],byteorder='big', signed=True)
        self.acc_z_raw = int.from_bytes(bout[4:6],byteorder='big', signed=True)
        self.gyr_x_raw = int.from_bytes(bout[6:8],byteorder='big', signed=True)
        self.gyr_y_raw = int.from_bytes(bout[8:10],byteorder='big', signed=True)
        self.gyr_z_raw = int.from_bytes(bout[10:12],byteorder='big', signed=True)

        self.temp_raw = (bout[12] << 8) | (bout[13] & 0xff)

        self.fss_a = ( fss_a[0] >> 1 ) & 0x3
        self.fss_g = ( fss_g[0] >> 1 ) & 0x3

        self.acc_x = 0
        self.acc_y = 0
        self.acc_z = 0
        self.gyr_x = 0
        self.gyr_y = 0
        self.gyr_z = 0

        self.temp_c = 0
        self.temp_f = 0
        self.interpret()

    def interpret(self):
        if self.fss_a == 0:
            self.acc_x = self.acc_x_raw / (16384)
            self.acc_y = self.acc_y_raw / (16384)
            self.acc_z = self.acc_z_raw / (16384)
        if self.fss_g == 0:
            self.gyr_x = self.gyr_x_raw / 131
            self.gyr_y = self.gyr_y_raw / 131
            self.gyr_z = self.gyr_z_raw / 131

        self.temp_c = ( (self.temp_raw - 21) / 333.87 ) + 21
        self.temp_f = self.temp_c*(9.0/5.0) + 32

    def __str__(self):
        s = ""
        s += "Acc: ({0:7.3f}, {1:7.3f}, {2:7.3f} )".format(self.acc_x, self.acc_y, self.acc_z)
        s += ", "
        s += "Gyr: ({0:7.3f}, {1:7.3f}, {2:7.3f} )".format(self.gyr_x, self.gyr_y, self.gyr_z)
        s += ", "
        s += "Temp: {0:7.3f} degC, {1:7.3f} degF".format(self.temp_c, self.temp_f)
        return s

i2c_cmd = ['i2ctransfer', '-y', '1', 'w1@0x69', '0x2d', 'r14@0x69']

compProc = sub.run(i2c_cmd, capture_output=True, universal_newlines=True)

out_bytes = [int(x,0) for x in compProc.stdout.split()]

i2c_fss_g_cmd = ['i2ctransfer', '-y', '1', 'w1@0x69', '0x1', 'r1@0x69']
compProc_fss_g = sub.run(i2c_fss_g_cmd, capture_output=True, universal_newlines=True)
fss_g_bytes = [int(x,0) for x in compProc_fss_g.stdout.split()]

i2c_fss_a_cmd = ['i2ctransfer', '-y', '1', 'w1@0x69', '0x14', 'r1@0x69']
compProc_fss_a = sub.run(i2c_fss_a_cmd, capture_output=True, universal_newlines=True)
fss_a_bytes = [int(x,0) for x in compProc_fss_a.stdout.split()]

icm = Icm20948(out_bytes, fss_g_bytes, fss_a_bytes)
print(icm)

Almost forgot to post the new values with the latest script change:

Acc: ( -0.013, -0.034, 1.040 ), Gyr: ( -1.420, 2.183, -1.344 ), Temp: 28.892 degC, 84.006 degF