Issue reading and writing to ICM-20948 magnetometer internal registers

Hi all, 

Firstly I would like to state that this is one of the first projects I have done using Zephyr RTOS, so please consider me a novice (this is almost certainly an issue with my code, not software or hardware issues). I am using the nRF52DK (nRF52832) with the nRF Connect SDK v2.5.0.

I am currently trying to get readings from the magnetometer on the ICM20948 9-axis IMU (Sparkfun board) via I2C. I have read the gyroscope and accelerometer readings successfully using i2c_write_dt() and i2c_burst_read_dt() from the device.h API. However, reading the magnetometer readings is not so straightforward. The magnetometer is controlled as a slave by the IMU. I am having trouble getting the magnetometer to write the internal register values to the EXT_SLV_SENS_DATA_XX registers.

To begin, I am just trying to read the magnetometer ID (0x09) from the internal "who am I" register (main.c lines 55-182). After this, I will worry about reading the actual magnetometer measurements, and status registers. 

I am having a very strange issue when I try to use i2c_write_dt() to modify the I2C_SLV0_ADDR, I2C_SLV0_REG, I2C_SLV0_DO and I2C_SLV0_CTRL registers. If I write the bit definition  0x00 to any of these registers, using i2c_burst_read_dt() I can verify they are written correctly. However, if I write any other value, i2c_burst_read_dt() reveals that a completely different bit definition has been written. Even more strangely, I can write any value I like to the I2C_SLV0_DO register and this works successfully (output in bold below), the same cannot be said for the other registers.

I have attached my code below in its entirety. Some parts are commented out - ignore these parts as they either don't work or were used for debugging. I have also attached an image of the serial output which includes printed statements of the expected register bit definitions after each write command. Any questions please ask and I'll do my best to respond. Thanks in advance for any help - it is greatly appreciated! Slight smile 

Kind regards,

Guy

main.c:

////////////////////////////////////////////////////////////
// Main C Program for ICM-20948 9-axis IMU 
////////////////////////////////////////////////////////////

#include <stdio.h>
#include <stdint.h>

#include <zephyr/kernel.h>
#include <zephyr/device.h>
#include <zephyr/devicetree.h>
#include <zephyr/drivers/i2c.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/logging/log.h>
#include <zephyr/sys/printk.h>

LOG_MODULE_REGISTER(ICM20948,LOG_LEVEL_DBG);

#define SLEEP_TIME_MS 1000

// Defining I2C device via node on device tree overlay file
#define I2C0_NODE DT_NODELABEL(icm20948)
static const struct i2c_dt_spec i2c_dev = I2C_DT_SPEC_GET(I2C0_NODE);


#include "icm20948.c"


int main(void) {
        
        icm20948_init();        //initialises icm20948 and tests who am i?
        k_msleep(100);
        icm20948_reset();       //resets internal registers   
        k_msleep(100);
        icm20948_init();        //initialises icm20948 and tests who am i?
        k_msleep(100);
      
        icm20948_wake();  
        k_msleep(100);
        //icm20948_configure();  // configure icm20948 here, eg, accelerometer sensetivity

        icm20948_mag_init();            // initialise magnetometer
        k_msleep(100);











        printk("\nstart of mag whoami \n");

        
        //4. Write to I2C_SLV0_ADDR to write mode, and slave address to the magnetometer (0x0C)
        //5. Write to I2C_SLV0_REG, setting the address to be written as the address of CTRL2 in the magnetometer
        //6. Populate ICM20948_I2C_SLV0_DO with the desired control setting
        //7.  Write to ICM20948_I2C_SLV0_CTRL,  enabling the mag and setting the grouping and bytes to be read
        //8. Switch I2C_SLV0_ADDR to read mode 
        //      and I2C_SLV0_REG to HXL
        //9. EXT_SLV_SENS_DATA_(00 to 06) will now be populated with HXL to ST2. It should be noted that reading ST2 is necessary as this automatically triggers the data to update in the axis registers.



        icm20948_reg_bank_select(3);
        int ret;



        printk("\r\r\rpost zeroes checks:\n");

        //reset regs to 0x00
        //specifying address of slave device we want to write to (0x0C for magnetometer), and write bit (MSB low ie 0x00)
        uint8_t buff19[] = {ICM20948_I2C_SLV0_ADDR , 0x00};
        ret = i2c_write_dt(&i2c_dev,buff19,sizeof(buff19));
        if(ret != 0){
            printk("Failed to write to I2C device address 0x%c at Reg. 0x%c\n",i2c_dev.addr,ICM20948_I2C_SLV0_ADDR);
        }
        icm20948_read_single_reg(3,  ICM20948_I2C_SLV0_ADDR); printk("ADDR expect\t\t\t\t 0x00\n\n");

        // internal mag address we want to write to
        uint8_t buff20[] = {ICM20948_I2C_SLV0_REG , 0x00};
        ret = i2c_write_dt(&i2c_dev,buff20,sizeof(buff20));
        if(ret != 0){
            printk("Failed to write to I2C device address 0x%c at Reg. 0x%c\n",i2c_dev.addr,ICM20948_I2C_SLV0_REG);
        }
        icm20948_read_single_reg(3,  ICM20948_I2C_SLV0_REG); printk("REG expect\t\t\t\t 0x00\n\n");

        // disabling power down mode and setting continous measurement mode
        uint8_t buff30[] = {ICM20948_I2C_SLV0_DO , 0x00};// THIS IS WORKING?!?!
        ret = i2c_write_dt(&i2c_dev,buff30,sizeof(buff30));
        if(ret != 0){
            printk("Failed to write to I2C device address 0x%c at Reg. 0x%c\n",i2c_dev.addr,ICM20948_I2C_SLV0_DO);
        }
        icm20948_read_single_reg(3,  ICM20948_I2C_SLV0_DO); printk("DO expect\t\t\t\t 0x00\n\n");

        // enabling read of 1 byte
        uint8_t buff40[] = {ICM20948_I2C_SLV0_CTRL , 0x00};
        ret = i2c_write_dt(&i2c_dev,buff40,sizeof(buff40));
        if(ret != 0){
            printk("Failed to write to I2C device address 0x%c at Reg. 0x%c\n",i2c_dev.addr,ICM20948_I2C_SLV0_CTRL);
        }
        icm20948_read_single_reg(3,  ICM20948_I2C_SLV0_CTRL); printk("CTRL expect\t\t\t\t 0x00\n\n\n\n");

        k_msleep(100);


        printk("\r\r\n\npost setup checks:\n");
        // Disabling power down mode and setting sample rate to continuous:
        //specifying address of slave device we want to write to (0x0C for magnetometer), and write bit (MSB low ie 0x00)
        uint8_t buff1[] = {ICM20948_I2C_SLV0_ADDR , ICM20948_MAG_I2C_ADDR};
        ret = i2c_write_dt(&i2c_dev,buff1,sizeof(buff1));
        if(ret != 0){
            printk("Failed to write to I2C device address 0x%c at Reg. 0x%c\n",i2c_dev.addr,ICM20948_I2C_SLV0_ADDR);
        }        k_msleep(100);
        icm20948_read_single_reg(3,  ICM20948_I2C_SLV0_ADDR); printk("ADDR expect\t\t\t\t 0x0C\n\n");

        // internal mag address we want to write to
        uint8_t buff2[] = {ICM20948_I2C_SLV0_REG , ICM20948_MAG_INTERNAL_REG_CTRL_2};
        ret = i2c_write_dt(&i2c_dev,buff2,sizeof(buff2));
        if(ret != 0){
            printk("Failed to write to I2C device address 0x%c at Reg. 0x%c\n",i2c_dev.addr,ICM20948_I2C_SLV0_REG);
        }
        icm20948_read_single_reg(3,  ICM20948_I2C_SLV0_REG); printk("REG expect\t\t\t\t 0x31\n\n");

        // disabling power down mode and setting continous measurement mode
        uint8_t buff3[] = {ICM20948_I2C_SLV0_DO , ICM20948_MAG_INTERNAL_REG_CTRL_2_BIT_100KHZ};// THIS IS WORKING?!?!
        ret = i2c_write_dt(&i2c_dev,buff3,sizeof(buff3));
        if(ret != 0){
            printk("Failed to write to I2C device address 0x%c at Reg. 0x%c\n",i2c_dev.addr,ICM20948_I2C_SLV0_DO);
        }
        icm20948_read_single_reg(3,  ICM20948_I2C_SLV0_DO); printk("DO expect\t\t\t\t 0x08\n\n");

        // enabling read of 1 byte
        uint8_t buff4[] = {ICM20948_I2C_SLV0_CTRL , 0x80|0x01};
        ret = i2c_write_dt(&i2c_dev,buff4,sizeof(buff4));
        if(ret != 0){
            printk("Failed to write to I2C device address 0x%c at Reg. 0x%c\n",i2c_dev.addr,ICM20948_I2C_SLV0_CTRL);
        }
        icm20948_read_single_reg(3,  ICM20948_I2C_SLV0_CTRL); printk("CTRL expect\t\t\t\t 0x81\n\n\n\n");



        printk("\r\rstart of read whoami:\n");
        
        //specifying address of slave device we want to read from (0x0C for magnetometer), and read bit (MSB high ie 0x80)
        uint8_t buff5[] = {ICM20948_I2C_SLV0_ADDR , 0x80|ICM20948_MAG_I2C_ADDR};
        ret = i2c_write_dt(&i2c_dev,buff5,sizeof(buff5));
        if(ret != 0){
            printk("Failed to write to I2C device address 0x%c at Reg. 0x%c\n",i2c_dev.addr,ICM20948_I2C_SLV0_ADDR);
        }
        icm20948_read_single_reg(3,  ICM20948_I2C_SLV0_ADDR); printk("ADDR expect\t\t\t\t 0x8C\n\n");

         // internal mag address we want to read from
        uint8_t buff6[] = {ICM20948_I2C_SLV0_REG , ICM20948_MAG_INTERNAL_REG_WHO_AM_I};
        ret = i2c_write_dt(&i2c_dev,buff6,sizeof(buff6));
        if(ret != 0){
            printk("Failed to write to I2C device address 0x%c at Reg. 0x%c\n",i2c_dev.addr,ICM20948_I2C_SLV0_REG);
        }
         icm20948_read_single_reg(3,  ICM20948_I2C_SLV0_REG); printk("REG expect\t\t\t\t 0x01\n\n\n\n");



        uint8_t mag_id[1] = {0};
        ret = i2c_burst_read_dt(&i2c_dev, ICM20948_MAG_REG_1, mag_id  ,sizeof(mag_id));
        if(ret != 0){
                printk("Failed to read to I2C device address 0x%c at Reg. 0x%c\n",i2c_dev.addr,ICM20948_MAG_REG_1);
        }
        if (mag_id[0] == 0x09) {
                printk("Verified who am i, (mag):\t 0x%.2X \n", mag_id[0]);
        }
        else {
                printk("WHO AM I?!?!?! (mag):\t  0x%.2X\n...(expected ID 0x09)\n", mag_id[0]);
        }




        printk("end of mag whoami \n");
        k_msleep(100);














        k_msleep(100);
        printk("\nEntered main loop.\r");//for debug
        // main loop
//        while(true){
//                
//                //icm20948_wake();  
//                k_msleep(100); // takes about 50 microseconds to wake sensors 
//
//                icm20948_mag_init();            // initialise magnetometer
//                k_msleep(100);
//
//                //Read gyro, accelerometer, and temperature
//		icm20948_get_data();
//
//                //Read magnetometer
//		icm20948_mag_get_data();
//
//
//
//               // icm20948_sleep();
//
//                k_msleep(800);
//        }


        
        
        
        printk("\nEnd of program.\n_______________________________\n");
        return 0;
}

icm30948.c:

////////////////////////////////////////////////////////////
// Function Definitions for ICM-20948 9-axis IMU 
////////////////////////////////////////////////////////////

#include <stdio.h>
#include <stdint.h>

#include "icm20948.h"

/*************************************************************************************/
// User functions:
/*************************************************************************************/

// Initialise icm20948 and verify with icm20948 "who am i"
void icm20948_init(void) {  
    if (!device_is_ready(i2c_dev.bus)){
	    printk("I2C bus %s is not ready!\n\r",i2c_dev.bus->name);
        return;
    } 
    else {
        uint8_t icm20948_whoami[1] = {0}; // verifing with who am i - expect to read 0xEA 
        icm20948_reg_bank_select(0); //select user bank 0
        //Do a burst read of 1 byte
        int ret = i2c_burst_read_dt(&i2c_dev, ICM20948_WHO_AM_I,icm20948_whoami,sizeof(icm20948_whoami));
        if(ret != 0){
            printk("Failed to read to I2C device address 0x%c at Reg. 0x%c\n",i2c_dev.addr,ICM20948_WHO_AM_I);
            return;
        }
        printk("_______________________________\n");
        printk("I2C bus %s is ready!\n\r",i2c_dev.bus->name);
        printk("Who am I:\t 0x%X\n",     (icm20948_whoami[0]));
        printk("_______________________________\n");
        if (icm20948_whoami[0] != 0xEA) {
            printk("WHO AM I?!?!?\n");
        } 
    }
    return;
}

// Reset IMU internal registers
void icm20948_reset(void) {
    icm20948_reg_bank_select(0);
    char buff[] = {ICM20948_POWER_MGMT_1 , ICM20948_POWER_MGMT_1_BIT_RESET_DEVICE};
    int ret = i2c_write_dt(&i2c_dev,buff,sizeof(buff));
    if(ret != 0){
        printk("Failed to write to I2C device address 0x%c at Reg. 0x%c\n",i2c_dev.addr,ICM20948_POWER_MGMT_1);
		return;
    }
	
    printk("ICM-20948 reset successful.\n");
    return;
}

// select user register bank (input 0 1 2 or 3)
static void icm20948_reg_bank_select(int bank) { 	
	int ret;

	if (bank == 0) {
		char buff[] = {ICM20948_REG_BANK_SEL , ICM20948_REG_BANK_SEL_BIT_SEL_BANK_0};
    	ret = i2c_write_dt(&i2c_dev,buff,sizeof(buff));
        k_msleep(20);
    	if(ret != 0){
            printk("Failed to write to I2C device address 0x%c at Reg. 0x%c\n",i2c_dev.addr,ICM20948_REG_BANK_SEL);
			return;
    	}
		//else {printk("Reg bank %d selected.\n", bank);}
	}	
	else if (bank == 1) {
		char buff[] = {ICM20948_REG_BANK_SEL , ICM20948_REG_BANK_SEL_BIT_SEL_BANK_1};
    	ret = i2c_write_dt(&i2c_dev,buff,sizeof(buff));
        k_msleep(20);
    	if(ret != 0){
            printk("Failed to write to I2C device address 0x%c at Reg. 0x%c\n",i2c_dev.addr,ICM20948_REG_BANK_SEL);
			return;
    	}
		//else {printk("Reg bank %d selected.\n", bank);}
	}	
	else if (bank == 2) {
		char buff[] = {ICM20948_REG_BANK_SEL , ICM20948_REG_BANK_SEL_BIT_SEL_BANK_2};
    	ret = i2c_write_dt(&i2c_dev,buff,sizeof(buff));
        k_msleep(20);
    	if(ret != 0){
            printk("Failed to write to I2C device address 0x%c at Reg. 0x%c\n",i2c_dev.addr,ICM20948_REG_BANK_SEL);
			return;
    	}
		//else {printk("Reg bank %d selected.\n", bank);} 
	}	
	else if (bank == 3) {
		char buff[] = {ICM20948_REG_BANK_SEL , ICM20948_REG_BANK_SEL_BIT_SEL_BANK_3};
    	ret = i2c_write_dt(&i2c_dev,buff,sizeof(buff));
        k_msleep(20);
    	if(ret != 0){
            printk("Failed to write to I2C device address 0x%c at Reg. 0x%c\n",i2c_dev.addr,ICM20948_REG_BANK_SEL);
			return;
    	}
		//else {printk("Reg bank %d selected.\n", bank);}   
	}		
	else { printk("Invalid user bank register selection: %d\n", bank);}


return;
}

// Wake icm20948 from sleep mode
void icm20948_wake(void) {
    // wake device from sleep mode by disabling "sleep mode enabled" bit
    //           = (reg to write to       , value to write to reg)
    char buff[] = {ICM20948_POWER_MGMT_1 , ICM20948_POWER_MGMT_1_BIT_WAKE};
    icm20948_reg_bank_select(0); //select user bank 0    
    int ret = i2c_write_dt(&i2c_dev,buff,sizeof(buff));
    if(ret != 0){
        printk("Failed to write to I2C device address 0x%c at Reg. 0x%c\n",i2c_dev.addr,ICM20948_POWER_MGMT_1);
    }
    return; 
}

// Put icm20948 in sleep mode
void icm20948_sleep(void) {
	char buff[] = {ICM20948_POWER_MGMT_1 , ICM20948_POWER_MGMT_1_BIT_SLEEP};
    icm20948_reg_bank_select(0); //select user bank 0    
    int ret = i2c_write_dt(&i2c_dev,buff,sizeof(buff));
    if(ret != 0){
        printk("Failed to write to I2C device address 0x%c at Reg. 0x%c\n",i2c_dev.addr,ICM20948_POWER_MGMT_1);
    }   
    return; 
}

// Configure icm20948 
void icm20948_configure(void) { 
        
    return;
}  

// Get and store accelerometer, gyro, and temperature measurements
void icm20948_get_data(void) {		
        icm20948_reg_bank_select(0); //select user bank 0
        //Do a burst read of 14 bytes
        int8_t icm20948_reading[14] = {0}; // 0x2D to 0x3A
        int ret = i2c_burst_read_dt(&i2c_dev, ICM20948_ACCEL_XOUT_H,icm20948_reading,sizeof(icm20948_reading));
        if(ret != 0){
            printk("Failed to read to I2C device address 0x%c at Reg. 0x%c\n",i2c_dev.addr,ICM20948_ACCEL_XOUT_H);
        }

        //Print reading to console  
        //printk("_______________________________\n");            //     High Byte      |     Low Byte
        //printk("x Accel Raw Value:\t %d\n",     (int16_t)((icm20948_reading[ 0] << 8) | icm20948_reading[ 1])  ); // 0x2D to 0x2E
        //printk("y Accel Raw Value:\t %d\n",     (int16_t)((icm20948_reading[ 2] << 8) | icm20948_reading[ 3])  ); // 0x2F to 0x30
        //printk("z Accel Raw Value:\t %d\n",     (int16_t)((icm20948_reading[ 4] << 8) | icm20948_reading[ 5])  ); // 0x31 to 0x32
        //printk("x Gyro Raw Value:\t %d\n",      (int16_t)((icm20948_reading[ 6] << 8) | icm20948_reading[ 7])  ); // 0x33 to 0x34
        //printk("y Gyro Raw Value:\t %d\n",      (int16_t)((icm20948_reading[ 8] << 8) | icm20948_reading[ 9])  ); // 0x35 to 0x36
        //printk("z Gyro Raw Value:\t %d\n",      (int16_t)((icm20948_reading[10] << 8) | icm20948_reading[11])  ); // 0x37 to 0x38
        //printk("Temp Raw Value:\t\t %d\n",      (int16_t)((icm20948_reading[12] << 8) | icm20948_reading[13])  ); // 0x39 to 0x3A
        printk("_______________________________\n");
        printk("x Accel (g):\t %f\n",         (float)((icm20948_reading[ 0] << 8) | icm20948_reading[ 1])/16384); // 0x2D to 0x2E
        printk("y Accel (g):\t %f\n",         (float)((icm20948_reading[ 2] << 8) | icm20948_reading[ 3])/16384); // 0x2F to 0x30
        printk("z Accel (g):\t %f\n",         (float)((icm20948_reading[ 4] << 8) | icm20948_reading[ 5])/16384); // 0x31 to 0x32
        printk("x Gyro  (dps):\t %f\n",         (float)((icm20948_reading[ 6] << 8) | icm20948_reading[ 7])/131); // 0x33 to 0x34
        printk("y Gyro  (dps):\t %f\n",         (float)((icm20948_reading[ 8] << 8) | icm20948_reading[ 9])/131); // 0x35 to 0x36
        printk("z Gyro  (dps):\t %f\n",         (float)((icm20948_reading[10] << 8) | icm20948_reading[11])/131); // 0x37 to 0x38
        printk("Temp    (degC):\t %f\n",        (float)(((icm20948_reading[12] << 8) | icm20948_reading[13])/333.87 + 21)); // 0x39 to 0x3A
        printk("_______________________________\n");
        //printk("x Accel m/s^2:\t %f\n",         (float)((icm20948_reading[ 0] << 8) | icm20948_reading[ 1])*9.80665/16384); // 0x2D to 0x2E
        //printk("y Accel m/s^2:\t %f\n",         (float)((icm20948_reading[ 2] << 8) | icm20948_reading[ 3])*9.80665/16384); // 0x2F to 0x30
        //printk("z Accel m/s^2:\t %f\n",         (float)((icm20948_reading[ 4] << 8) | icm20948_reading[ 5])*9.80665/16384); // 0x31 to 0x32
        //printk("x Gyro  dps:\t %f\n",         (float)((icm20948_reading[ 6] << 8) | icm20948_reading[ 7])/131); // 0x33 to 0x34
        //printk("y Gyro  dps:\t %f\n",         (float)((icm20948_reading[ 8] << 8) | icm20948_reading[ 9])/131); // 0x35 to 0x36
        //printk("z Gyro  dps:\t %f\n",         (float)((icm20948_reading[10] << 8) | icm20948_reading[11])/131); // 0x37 to 0x38
        //printk("Temp       *C:\t %f\n",        (float)(((icm20948_reading[12] << 8) | icm20948_reading[13])/333.87 + 21)); // 0x39 to 0x3A
        //printk("_______________________________\n");
	return;
}

// function to read single byte from IMU registers
void icm20948_read_single_reg(int bank, uint8_t start_addr) {
    icm20948_reg_bank_select(bank);
    uint8_t icm20948_reading[1] = {0}; 
    int ret = i2c_burst_read_dt(&i2c_dev, start_addr, icm20948_reading, sizeof(icm20948_reading));
    if(ret != 0){
        printk("Failed to read Magnetometer readings.\n");
        return;
    }
    printk("ICM-20948 bank %d register 0x%.2X value :\t 0x%.2X\n", bank, start_addr, (uint8_t)(icm20948_reading[0]));
    icm20948_reg_bank_select(0);
    return;
    
}



/*************************************************************************************/
// Magnetometer user functions:
/*************************************************************************************/

// function to write to magnetometer internal registers
//static void icm20948_mag_write_reg(uint8_t reg, uint8_t data) {       
//    int ret;
//    icm20948_reg_bank_select(3);
//
//
//    //specifying address of slave device we want to write to (0x0C for magnetometer)
//    uint8_t buff1[] = {ICM20948_I2C_SLV0_ADDR , ICM20948_MAG_I2C_ADDR};
//    ret = i2c_write_dt(&i2c_dev,buff1,sizeof(buff1));
//    if(ret != 0){
//        printk("Failed to write to I2C device address 0x%c at Reg. 0x%c\n",i2c_dev.addr,ICM20948_I2C_SLV0_ADDR);
//    }   
//    // We then write "reg" to i2c_slv0_reg ("reg" is internal magnetometer register want to write to)
//    uint8_t buff2[] = {ICM20948_I2C_SLV0_REG , reg};
//    ret = i2c_write_dt(&i2c_dev,buff2,sizeof(buff2));
//    if(ret != 0){
//        printk("Failed to write to I2C device address 0x%c at Reg. 0x%c\n",i2c_dev.addr,ICM20948_I2C_SLV0_REG);
//    }
//    // We then write "data" to i2c_slv0_do (data is the 8-bit value we want to write to "reg")
//    uint8_t buff3[] = {ICM20948_I2C_SLV0_DO , data}; 
//    ret = i2c_write_dt(&i2c_dev,buff3,sizeof(buff3));
//    if(ret != 0){
//        printk("Failed to write to I2C device address 0x%c at Reg. 0x%c\n",i2c_dev.addr,ICM20948_I2C_SLV0_DO);
//    }
//
//    // Then we write 0x80 to i2c_slv0_ctrl to initiate write command
//    uint8_t buff4[] = {ICM20948_I2C_SLV0_CTRL , 0x80|0x01};
//    ret = i2c_write_dt(&i2c_dev,buff4,sizeof(buff4));
//    if(ret != 0){
//        printk("Failed to write to Magnetometer register.\n");
//    }
//    
////    printk("_______________________________\n");   
////    icm20948_read_single_reg(3,ICM20948_I2C_SLV0_ADDR);printk("ICM20948_I2C_SLV0_ADDR (exp \n");
////    icm20948_read_single_reg(3,ICM20948_I2C_SLV0_REG);printk("ICM20948_I2C_SLV0_REG (exp 0x%.2X)\n", reg);
////    icm20948_read_single_reg(3,ICM20948_I2C_SLV0_CTRL);printk("ICM20948_I2C_SLV0_CTRL (exp 0x81)\n"); //enable reading from device of length 1 byte
////    icm20948_read_single_reg(3,ICM20948_I2C_SLV0_DO);printk("ICM20948_I2C_SLV0_DO (exp 0x%.2X)\n", data);
////    printk("_______________________________\n");   
//    k_msleep(100);
//    icm20948_reg_bank_select(0);
//    return;
//}

// function to read magnetometer internal registers
//static void icm20948_mag_read_reg(uint8_t start_addr, uint8_t len) {    
//    int ret;
//
//    icm20948_reg_bank_select(3);
//    
//    k_msleep(20);
//    //specifying address of slave device we want to read from (0x8C for magnetometer)
//    uint8_t buff1[] = {ICM20948_I2C_SLV0_ADDR , 0x80|ICM20948_MAG_I2C_ADDR};
//    ret = i2c_write_dt(&i2c_dev,buff1,sizeof(buff1));
//    if(ret != 0){
//        printk("Failed to write to I2C device address 0x%c at Reg. 0x%c\n",i2c_dev.addr,ICM20948_I2C_SLV0_ADDR);
//    }
//    // We then write "start_addr" to i2c_slv0_reg (start_addr is register we want to initiate read from)
//    uint8_t buff2[] = {ICM20948_I2C_SLV0_REG , start_addr};
//    ret = i2c_write_dt(&i2c_dev,buff2,sizeof(buff2));
//    if(ret != 0){
//        printk("Failed to write to I2C device address 0x%c at Reg. 0x%c\n",i2c_dev.addr,ICM20948_I2C_SLV0_REG);
//    }
//    // Then we write 0x80 to i2c_slv0_ctrl to initiate read command (len is number of registers (BYTES) to read)
//    uint8_t buff3[] = {ICM20948_I2C_SLV0_CTRL , 0x80|len};
//    ret = i2c_write_dt(&i2c_dev,buff3,sizeof(buff3));
//    if(ret != 0){
//        printk("Failed to read from Magnetometer.\n");
//    }
//    
//    k_msleep(100);
//
//    icm20948_reg_bank_select(0);
//    return;
//}

// initialises magnetometer
void icm20948_mag_init(void) {  
    int ret;   
    icm20948_reg_bank_select(0);
    // resetting IMU i2c master module
    uint8_t buff0[] = {ICM20948_USER_CTRL , ICM20948_USER_CTRL_BIT_I2C_MST_RESET};
    ret = i2c_write_dt(&i2c_dev,buff0,sizeof(buff0));
    if(ret != 0){
        printk("Failed to reset magnetometer I2C master module!\n");
    } else {
        printk("Reset magnetometer I2C master module!\n");
    }
    // enabling IMU as a i2c master
    uint8_t buff1[] = {ICM20948_USER_CTRL , ICM20948_USER_CTRL_BIT_I2C_MST_EN};
    ret = i2c_write_dt(&i2c_dev,buff1,sizeof(buff1));
    if(ret != 0){
        printk("Failed to enable magnetometer!\n");
    }
    uint8_t buff2[1] = {0};
    ret = i2c_burst_read_dt(&i2c_dev, ICM20948_USER_CTRL, buff2  ,sizeof(buff2));
    if(ret != 0){
        printk("Failed to read to I2C device address 0x%c at Reg. 0x%c\n",i2c_dev.addr,ICM20948_USER_CTRL);
    }
    if(buff2[0] == 0x20) {
        printk("IMU enabled as I2C master.\n");
    }
    else {
        printk("Failed to enable IMU as I2C master. ICM20948_USER_CTRL set to 0x%.2X (expect 0x20).\n", buff2[0]);
    }
    k_msleep(50);

    return;
}


icm20948.h:

////////////////////////////////////////////////////////////
// Header file for ICM-20948 9-axis IMU ////////////////////
////////////////////////////////////////////////////////////

#include <stdio.h>
#include <stdint.h>

////////////////////////////////////////////////////////////
// ICM-20948 Functions:  ///////////////////////////////////
//in "icm20948.c"
void icm20948_init(void);  // initialise and verify icm20948 "who am i"
static void icm20948_reg_bank_select(int bank);  	// select register bank
void icm20948_reset(void);  //resets IMU
void icm20948_configure(void);  // configure icm20948 
void icm20948_get_data(void); 	// gets accelerometer, gyro, and temperature measurments, prints to terminal
void icm20948_wake(void);   // wakes from sleep mode
void icm20948_sleep(void);  // put IMU in sleep mode
void icm20948_read_single_reg(int bank, uint8_t start_addr); // read value of single register

//static void icm20948_mag_write_reg(uint8_t reg, uint8_t data);      // writes to magnetometer internal registers
//static void icm20948_mag_read_reg(uint8_t start_addr, uint8_t len);       // reads magnetometer internal registers
void icm20948_mag_init(void);    // initialises magnetometer


////////////////////////////////////////////////////////////
// Registers and associated bit definitions:  //////////////

// Magnetometer I2C address:
#define ICM20948_MAG_I2C_ADDR                                   (0x0C)
    #define ICM20948_MAG_I2C_ID                                     0x09 // expected who am I reg value

// Reg bank select (common to all user banks):
#define ICM20948_REG_BANK_SEL                                   (0x7F)
    // Bit definitions:
    #define ICM20948_REG_BANK_SEL_BIT_SEL_BANK_0                    0x00    /**< Register bank 0 */
    #define ICM20948_REG_BANK_SEL_BIT_SEL_BANK_1                    0x10    /**< Register bank 1 */
    #define ICM20948_REG_BANK_SEL_BIT_SEL_BANK_2                    0x20    /**< Register bank 2 */
    #define ICM20948_REG_BANK_SEL_BIT_SEL_BANK_3                    0x40    /**< Register bank 3 */

/*********************************************/
/* Bank 0 register map with bit definitions: */
/*********************************************/
#define ICM20948_WHO_AM_I                                       (0x00)    // Device ID register expected 0xEA 
#define ICM20948_USER_CTRL                                      (0x03)
    #define ICM20948_USER_CTRL_RESET                                0x00
    #define ICM20948_USER_CTRL_BIT_I2C_MST_EN                       0x20    // enable icm20948 as master I2C controller
    #define ICM20948_USER_CTRL_BIT_I2C_MST_RESET                    0x02    // reset icm20948 master I2C controller

#define ICM20948_POWER_MGMT_1                                   (0x06) // DEVICE IS IN SLEEP MODE BY DEFAULT
    #define ICM20948_POWER_MGMT_1_RESET                             0x41
    #define ICM20948_POWER_MGMT_1_BIT_WAKE                          0x01    // enable accelerometer and gyro   
    #define ICM20948_POWER_MGMT_1_BIT_RESET_DEVICE                  0x80    // RESET ALL REGISTERS -> bit autoclears when set high
    #define ICM20948_POWER_MGMT_1_BIT_SLEEP                         0x41    // enable sleep mode(BIT(6))      
#define ICM20948_POWER_MGMT_2                                   (0x07)
    #define ICM20948_POWER_MGMT_2_RESET                             0x00
    #define ICM20948_POWER_MGMT_2_BIT_DISABLE_                      0x80    // disables accelerometer and gyro

#define ICM20948_ACCEL_XOUT_H                                   (0x2D)  
#define ICM20948_ACCEL_XOUT_L                                   (0x2E)  
#define ICM20948_ACCEL_YOUT_H                                   (0x2F)  
#define ICM20948_ACCEL_YOUT_L                                   (0x30)  
#define ICM20948_ACCEL_ZOUT_H                                   (0x31)  
#define ICM20948_ACCEL_ZOUT_L                                   (0x32)  
#define ICM20948_GYRO_XOUT_H                                    (0x33)  
#define ICM20948_GYRO_XOUT_L                                    (0x34)  
#define ICM20948_GYRO_YOUT_H                                    (0x35)  
#define ICM20948_GYRO_YOUT_L                                    (0x36)  
#define ICM20948_GYRO_ZOUT_H                                    (0x37)  
#define ICM20948_GYRO_ZOUT_L                                    (0x38)  
#define ICM20948_TEMP_OUT_H                                     (0x39)  
#define ICM20948_TEMP_OUT_L                                     (0x3A)  

#define ICM20948_MAG_REG_1                                      (0x3B)    //// Magnetometer readings ////
#define ICM20948_MAG_REG_2                                      (0x3E)    /////////////////////////////// 
#define ICM20948_MAG_REG_3                                      (0x3F)    // 
#define ICM20948_MAG_REG_4                                      (0x40)    // 
#define ICM20948_MAG_REG_5                                      (0x41)    // 
#define ICM20948_MAG_REG_6                                      (0x42)    // 
#define ICM20948_MAG_REG_7                                      (0x43)    // 
#define ICM20948_MAG_REG_8                                      (0x44)    //  
#define ICM20948_MAG_REG_9                                      (0x45)    // 
#define ICM20948_MAG_REG_10                                     (0x46)    // 
#define ICM20948_MAG_REG_11                                     (0x47)    // 
#define ICM20948_MAG_REG_12                                     (0x48)    // these will all be signed
#define ICM20948_MAG_REG_13                                     (0x49)    // 
#define ICM20948_MAG_REG_14                                     (0x4A)    // 
#define ICM20948_MAG_REG_15                                     (0x4B)    // 
#define ICM20948_MAG_REG_16                                     (0x4C)    // 
#define ICM20948_MAG_REG_17                                     (0x4D)    // 
#define ICM20948_MAG_REG_18                                     (0x4E)    // 
#define ICM20948_MAG_REG_19                                     (0x4F)    // 
#define ICM20948_MAG_REG_20                                     (0x50)    // 
#define ICM20948_MAG_REG_21                                     (0x51)    /////////////////////////////// 
#define ICM20948_MAG_REG_22                                     (0x52)    ///////////////////////////////


// banks 1 & 2 currently not used


/*********************************************/
/* Bank 3 register map with bit definitions: */
/*********************************************/
#define ICM20948_I2C_MST_CTRL                                   (0x01)  
#define ICM20948_I2C_SLV0_ADDR                                  (0x03)  
#define ICM20948_I2C_SLV0_REG                                   (0x04)  
#define ICM20948_I2C_SLV0_CTRL                                  (0x05)
    #define ICM20948_I2C_SLV0_CTRL_BIT_ENABLE_MAG                   0x80 
#define ICM20948_I2C_SLV0_DO                                    (0x06)  


/*********************************************/
/* Magnetometer internal register map:       */
/*********************************************/
// these are not directly accessible 
#define ICM20948_MAG_INTERNAL_REG_WHO_AM_I                      (0x01)        // expected 0x09

#define ICM20948_MAG_INTERNAL_REG_STATUS_1                      (0x10)
#define ICM20948_MAG_INTERNAL_REG_X_DATA_L                      (0x11)
#define ICM20948_MAG_INTERNAL_REG_X_DATA_H                      (0x12)
#define ICM20948_MAG_INTERNAL_REG_Y_DATA_L                      (0x13)
#define ICM20948_MAG_INTERNAL_REG_Y_DATA_H                      (0x14)
#define ICM20948_MAG_INTERNAL_REG_Z_DATA_L                      (0x15)
#define ICM20948_MAG_INTERNAL_REG_Z_DATA_H                      (0x16)
#define ICM20948_MAG_INTERNAL_REG_STATUS_2                      (0x18)

#define ICM20948_MAG_INTERNAL_REG_CTRL_2                        (0x31)
    #define ICM20948_MAG_INTERNAL_REG_CTRL_2_RESET                  0x00    // power down mode
    #define ICM20948_MAG_INTERNAL_REG_CTRL_2_BIT_SINGLE_MEASURE     0x01    // single measurement mode
    #define ICM20948_MAG_INTERNAL_REG_CTRL_2_BIT_100KHZ             0x08    // 100kHz continuous measurement mode

#define ICM20948_MAG_INTERNAL_REG_CTRL_3                        (0x32)
    #define ICM20948_MAG_INTERNAL_REG_CTRL_3_MAG_SOFT_RESET         0x01 // delay 100 ms after RESET MAGNETOMETER INTERNAL REGISTERS 

prj.conf:

CONFIG_I2C=y

CONFIG_SENSOR=y
CONFIG_CBPRINTF_FP_SUPPORT=y
CONFIG_LOG=y

CONFIG_RESET_ON_FATAL_ERROR=n 

DT overlay:

&pinctrl { // pinctrl node is used to specify I/O pin assignment and properties

	i2c0_default: i2c0_default {
		group1 {
			psels = <NRF_PSEL(TWIM_SDA, 0, 26)>,
				    <NRF_PSEL(TWIM_SCL, 0, 27)>;
			bias-pull-up;
		};
	};

	i2c0_sleep: i2c0_sleep {
		group1 {
			psels = <NRF_PSEL(TWIM_SDA, 0, 26)>,
				    <NRF_PSEL(TWIM_SCL, 0, 27)>;
			low-power-enable;
		};
	};
};


&i2c0 { 
    compatible = "nordic,nrf-twim";
    status = "okay";
    clock-frequency = <(I2C_BITRATE_STANDARD)>;
    pinctrl-0 = <&i2c0_default>;
    pinctrl-1 = <&i2c0_sleep>;
    pinctrl-names = "default", "sleep";

    icm20948: icm20948@69 {
        reg = < 0x69 >;
        sda-pin = < 26 (GPIO_PULL_UP) >;
        scl-pin = < 27 (GPIO_PULL_UP) >;
    };
};

VCOM output:

_______________________________
*** Booting nRF Connect SDK v2.5.0 ***
_______________________________
I2C bus i2c@40003000 is ready!
Who am I: 0xEA
_______________________________
ICM-20948 reset successful.
_______________________________
I2C bus i2c@40003000 is ready!
Who am I: 0xEA
_______________________________
Reset magnetometer I2C master module!
IMU enabled as I2C master.

start of mag whoami
post zeroes checks:
ICM-20948 bank 3 register 0x03 value : 0x00
ADDR expect 0x00

ICM-20948 bank 3 register 0x04 value : 0x00
REG expect 0x00

ICM-20948 bank 3 register 0x06 value : 0x00
DO expect 0x00

ICM-20948 bank 3 register 0x05 value : 0x00
CTRL expect 0x00

post setup checks:
ICM-20948 bank 3 register 0x03 value : 0x00
ADDR expect 0x0C

ICM-20948 bank 3 register 0x04 value : 0x00
REG expect 0x31

ICM-20948 bank 3 register 0x06 value : 0x08
DO expect 0x08

ICM-20948 bank 3 register 0x05 value : 0x01
CTRL expect 0x81

start of read whoami:
ICM-20948 bank 3 register 0x03 value : 0x80
ADDR expect 0x8C

ICM-20948 bank 3 register 0x04 value : 0x00
REG expect 0x01

WHO AM I?!?!?! (mag): 0x00
...(expected ID 0x09)
end of mag whoami

Entered main loop.
End of program.

----------------------------------------

Parents
  • Hello,

    Sorry, dont' know. Have in mind that it may not only be the data on the i2c bus, but also timing requirements involved (e.g. between write and read, and sequence of commands). So possible having a logic analyzer to look at the actual data, timing, sequence and compare with the datasheet may be a good idea.

    Searching on google for the ICM20948 I could find some possible relevant discussions and source code that may be helpful. Though I recommend reaching out to the manufacturer directly for guideance and possible their drivers.

    Kenneth

Reply
  • Hello,

    Sorry, dont' know. Have in mind that it may not only be the data on the i2c bus, but also timing requirements involved (e.g. between write and read, and sequence of commands). So possible having a logic analyzer to look at the actual data, timing, sequence and compare with the datasheet may be a good idea.

    Searching on google for the ICM20948 I could find some possible relevant discussions and source code that may be helpful. Though I recommend reaching out to the manufacturer directly for guideance and possible their drivers.

    Kenneth

Children
Related