Bootstrap

ICM20948 DMP代码详解(40)

接前一篇文章:ICM20948 DMP代码详解(39)

 

上一回继续解析inv_icm20948_set_slave_compass_id函数,解析到第5段代码inv_icm20948_setup_compass_akm函数,本回解析接下来的代码。为了便于理解和回顾,再次贴出该函数源码,在EMD-Core\sources\Invn\Devices\Drivers\ICM20948\Icm20948DataBaseDriver.c中,如下:

int inv_icm20948_set_slave_compass_id(struct inv_icm20948 *s, int id)
{
	int result = 0;
	(void)id;
 
	//result = inv_icm20948_wakeup_mems(s);
	//if (result)
	//	return result;
		
	inv_icm20948_prevent_lpen_control(s);
	activate_compass(s);
	
	inv_icm20948_init_secondary(s);
 
	// Set up the secondary I2C bus on 20630.
	inv_icm20948_set_secondary(s);
 
	//Setup Compass
	result = inv_icm20948_setup_compass_akm(s);
 
	//Setup Compass mounting matrix into DMP
	result |= inv_icm20948_compass_dmp_cal(s, s->mounting_matrix, s->mounting_matrix_secondary_compass);
	
	if (result)
		desactivate_compass(s);
 
	//result = inv_icm20948_sleep_mems(s);
	inv_icm20948_allow_lpen_control(s);
	return result;
}

5)inv_icm20948_setup_compass_akm函数

再次贴出inv_icm20948_setup_compass_akm函数源码,在EMD-Core\sources\Invn\Devices\Drivers\ICM20948\Icm20948AuxCompassAkm.c中,如下:

/*
 *  inv_icm20948_setup_compass_akm() - Configure akm series compass.
 */
int inv_icm20948_setup_compass_akm(struct inv_icm20948 *s)
{
	int result;
	unsigned char data[4];
#if (MEMS_CHIP != HW_ICM20948)
	uint8_t sens, cmd;
#endif
	//reset variable to initial values
	memset(s->secondary_state.final_matrix, 0, sizeof(s->secondary_state.final_matrix));
	memset(s->secondary_state.compass_sens, 0, sizeof(s->secondary_state.compass_sens));
	s->secondary_state.scale = 0;
	s->secondary_state.dmp_on = 1;
	s->secondary_state.secondary_resume_compass_state = 0;
 
	/* Read WHOAMI through I2C SLV for compass */
	result = inv_icm20948_execute_read_secondary(s, COMPASS_I2C_SLV_READ, s->secondary_state.compass_chip_addr, REG_AKM_ID, 1, data);
	if (result) {
        // inv_log("Read secondary error: Compass.\r\n");
		return result;
    }
	if (data[0] != DATA_AKM_ID) {
        // inv_log("Compass not found!!\r\n");
		return -1;
    }
    // inv_log("Compass found.\r\n");
 
	/* setup upper and lower limit of self-test */
#if (MEMS_CHIP == HW_ICM20948)
	s->secondary_state.st_upper = AK09916_ST_Upper;
	s->secondary_state.st_lower = AK09916_ST_Lower;
#else
	if (HW_AK8975 == s->secondary_state.compass_slave_id) {
		s->secondary_state.st_upper = AKM8975_ST_Upper;
		s->secondary_state.st_lower = AKM8975_ST_Lower;
	} else if (HW_AK8972 == s->secondary_state.compass_slave_id) {
		s->secondary_state.st_upper = AKM8972_ST_Upper;
		s->secondary_state.st_lower = AKM8972_ST_Lower;
	} else if (HW_AK8963 == s->secondary_state.compass_slave_id) {
		s->secondary_state.st_upper = AKM8963_ST_Upper;
		s->secondary_state.st_lower = AKM8963_ST_Lower;
	} else if (HW_AK09911 == s->secondary_state.compass_slave_id) {
		s->secondary_state.st_upper = AK09911_ST_Upper;
		s->secondary_state.st_lower = AK09911_ST_Lower;
	} else if (HW_AK09912 == s->secondary_state.compass_slave_id) {
		s->secondary_state.st_upper = AK09912_ST_Upper;
		s->secondary_state.st_lower = AK09912_ST_Lower;
	} else if (HW_AK09916 == s->secondary_state.compass_slave_id) {
		s->secondary_state.st_upper = AK09916_ST_Upper;
		s->secondary_state.st_lower = AK09916_ST_Lower;
	} else {
		return -1;
	}
#endif
 
 
#if (MEMS_CHIP == HW_ICM20948)
	/* Read conf and configure compass through I2C SLV for compass and subsequent channel */
	s->secondary_state.mode_reg_addr = REG_AK09916_CNTL2;
	// no sensitivity adjustment value
	s->secondary_state.compass_sens[0] = 128;
	s->secondary_state.compass_sens[1] = 128;
	s->secondary_state.compass_sens[2] = 128;
#else
	/* Read conf and configure compass through I2C SLV for compass and subsequent channel */
	if (HW_AK09916 == s->secondary_state.compass_slave_id) {
		s->secondary_state.mode_reg_addr = REG_AK09916_CNTL2;
		// no sensitivity adjustment value
		s->secondary_state.compass_sens[0] = 128;
		s->secondary_state.compass_sens[1] = 128;
		s->secondary_state.compass_sens[2] = 128;
	}
	else {
		// Fuse ROM access not possible for ak9916
		/* set AKM to Fuse ROM access mode */
		if (HW_AK09911 == s->secondary_state.compass_slave_id) {
			s->secondary_state.mode_reg_addr = REG_AK09911_CNTL2;
			sens = REG_AK09911_SENSITIVITY;
			cmd = DATA_AK09911_MODE_FR;
		} else if (HW_AK09912 == s->secondary_state.compass_slave_id) {
			s->secondary_state.mode_reg_addr = REG_AK09912_CNTL2;
			sens = REG_AK09912_SENSITIVITY;
			cmd = DATA_AK09912_MODE_FR;
		} else {
			s->secondary_state.mode_reg_addr = REG_AKM_MODE;
			sens = REG_AKM_SENSITIVITY;
			cmd = DATA_AKM_MODE_FR;
		}
 
		result = inv_icm20948_read_secondary(s, COMPASS_I2C_SLV_READ, s->secondary_state.compass_chip_addr, sens, THREE_AXES);
		if (result)
			return result;
		// activate FUSE_ROM mode to CNTL2
		result = inv_icm20948_execute_write_secondary(s, COMPASS_I2C_SLV_WRITE, s->secondary_state.compass_chip_addr,
				s->secondary_state.mode_reg_addr, cmd);
 
		if (result)
			return result;
		// read sensitivity
		result = inv_icm20948_read_mems_reg(s, REG_EXT_SLV_SENS_DATA_00, THREE_AXES, s->secondary_state.compass_sens);
		if (result)
			return result;
	}
	//aply noise suppression filter (only available for 9912)
	if (HW_AK09912 == s->secondary_state.compass_slave_id) {
		result = inv_icm20948_execute_write_secondary(s, COMPASS_I2C_SLV_WRITE, s->secondary_state.compass_chip_addr, REG_AK09912_CNTL1,
                                     DATA_AK9912_NSF << DATA_AK9912_NSF_SHIFT);
		if (result)
			return result;
	}
#endif
	/* Set compass in power down through I2C SLV for compass */
	result = inv_icm20948_execute_write_secondary(s, COMPASS_I2C_SLV_WRITE, s->secondary_state.compass_chip_addr, s->secondary_state.mode_reg_addr, DATA_AKM_MODE_PD);
	if (result)
		return result;
 
	s->secondary_state.secondary_resume_compass_state = 1;
	s->secondary_state.compass_state = INV_ICM20948_COMPASS_SETUP;
	return inv_icm20948_suspend_akm(s);
}

本回开始解析inv_icm20948_execute_read_secondary函数源码。

inv_icm20948_execute_read_secondary函数在EMD-Core\sources\Invn\Devices\Drivers\ICM20948\Icm20948AuxTransport.c中,代码如下:

int inv_icm20948_execute_read_secondary(struct inv_icm20948 *s, int index, unsigned char addr, int reg, int len, uint8_t *d)
{
	int result = 0;

	result |= inv_icm20948_read_secondary(s, index, addr, reg, len);
	
	result |= inv_icm20948_secondary_enable_i2c(s);
    
	inv_icm20948_sleep_us(SECONDARY_INIT_WAIT*1000);
    
	result |= inv_icm20948_secondary_disable_i2c(s);

    result |= inv_icm20948_read_mems_reg(s, REG_EXT_SLV_SENS_DATA_00, len, d); 

	result |= inv_icm20948_secondary_stop_channel(s, index);

	return result;
}

inv_icm20948_execute_read_secondary函数中又调用了不少函数,一个一个来看。

1)inv_icm20948_read_secondary函数

代码片段如下:

	result |= inv_icm20948_read_secondary(s, index, addr, reg, len);

inv_icm20948_read_secondary函数在EMD-Core\sources\Invn\Devices\Drivers\ICM20948\Icm20948AuxTransport.c中,代码如下:

/*
* inv_configure_secondary_read(): set secondary registers for reading.
The chip must be set as bank 3 before calling.
* This is derived from inv_icm20948_read_secondary in linux...
* for now, uses a very simple data struct for the registers
* 
* index gives the mapping to the particular SLVx registers
* addr is the physical address of the device to be accessed
* reg is the device register we wish to access
* len is the number of bytes to be read
* 
*/
int inv_icm20948_read_secondary(struct inv_icm20948 *s, int index, unsigned char addr, unsigned char reg, char len)
{
	int result = 0;
    unsigned char data;

    data = INV_MPU_BIT_I2C_READ | addr;
	result |= inv_icm20948_write_mems_reg(s, s->secondary_state.slv_reg[index].addr, 1, &data);

    data = reg;
	result |= inv_icm20948_write_mems_reg(s, s->secondary_state.slv_reg[index].reg, 1, &data);
    
    data = INV_MPU_BIT_SLV_EN | len;
	result |= inv_icm20948_write_mems_reg(s, s->secondary_state.slv_reg[index].ctrl, 1, &data);
    
	return result;
}

仔细读一下函数注释:

inv_icm20948_read_secondary函数的功能是设置用于读取的辅助寄存器。在调用之前,必须将芯片设置为bank 3。这是从linux中的inv_icm20948_read_secondary派生出来的……目前,对寄存器使用一个非常简单的数据结构。

参数index给出了到特定SLVx寄存器的映射;

参数addr是要访问的设备的物理地址;

参数reg是我们要访问的设备寄存器;

参数len是要读取的字节数。

4894ecce0d6048c3abe69e6d59c5afcd.png

7baf817243a14d6dbf220b6faaa08b1e.png

对于函数功能有了一个大致的了解后,来看具体的函数代码。

第1段代码如下:

    data = INV_MPU_BIT_I2C_READ | addr;
	result |= inv_icm20948_write_mems_reg(s, s->secondary_state.slv_reg[index].addr, 1, &data);

INV_MPU_BIT_I2C_READ宏在EMD-Core\sources\Invn\Devices\Drivers\ICM20948\Icm20948Defs.h中,定义如下:

#define INV_MPU_BIT_I2C_READ    0x80

而addr是inv_icm20948_read_secondary函数的参数,对应的实参是在inv_icm20948_setup_compass_akm函数中传入的s->secondary_state.compass_chip_addr,由上一回可知,该地址为AK0991x_DEFAULT_I2C_ADDR即0x0C。这就与函数注释中所讲的参数addr是要访问的设备的物理地址相对应了。

4376df5092564a7789db3b7683028c0f.png

那么,此时的data就是0x80 | 0x0C = 0x8C。

再来看第2行代码中的s->secondary_state.slv_reg[index].addr。它是在前文书解析的inv_icm20948_init_secondary函数中初始化的(参见https://phmatthaus.blog.csdn.net/article/details/142306413)。

d7de791f173f4566b6fd1f154ab78020.png

而此处的index对应的实参也是在inv_icm20948_setup_compass_akm函数中,为COMPASS_I2C_SLV_READ。该宏前文书讲到过,在EMD-Core\sources\Invn\Devices\Drivers\ICM20948\Icm20948AuxTransport.h中,定义如下:

/** @brief I2C from secondary device can stand on up to 4 channels. To perform automatic read and feed DMP :
- channel 0 is reserved for compass reading data
- channel 1 is reserved for compass writing one-shot acquisition register
- channel 2 is reserved for als reading data */
#define COMPASS_I2C_SLV_READ		0
#define COMPASS_I2C_SLV_WRITE		1
#define ALS_I2C_SLV					2

也就是此处的index为0,表示通道0,其用于读取指南针的寄存器。

这样,s->secondary_state.slv_reg[index].addr就是s->secondary_state.slv_reg[0].addr,在inv_icm20948_init_secondary函数中赋的值是REG_I2C_SLV0_ADDR。REG_I2C_SLV0_ADDR宏在EMD-Core\sources\Invn\Devices\Drivers\ICM20948\Icm20948Defs.h中,定义如下:

#define REG_I2C_SLV0_ADDR       (BANK_3 | 0x03)
#define REG_I2C_SLV0_REG        (BANK_3 | 0x04)
#define REG_I2C_SLV0_CTRL       (BANK_3 | 0x05)
#define REG_I2C_SLV0_DO         (BANK_3 | 0x06)

#define REG_I2C_SLV1_ADDR       (BANK_3 | 0x07)
#define REG_I2C_SLV1_REG        (BANK_3 | 0x08)
#define REG_I2C_SLV1_CTRL       (BANK_3 | 0x09)
#define REG_I2C_SLV1_DO         (BANK_3 | 0x0A)

#define REG_I2C_SLV2_ADDR       (BANK_3 | 0x0B)
#define REG_I2C_SLV2_REG        (BANK_3 | 0x0C)
#define REG_I2C_SLV2_CTRL       (BANK_3 | 0x0D)
#define REG_I2C_SLV2_DO         (BANK_3 | 0x0E)

#define REG_I2C_SLV3_ADDR       (BANK_3 | 0x0F)
#define REG_I2C_SLV3_REG        (BANK_3 | 0x10)
#define REG_I2C_SLV3_CTRL       (BANK_3 | 0x11)
#define REG_I2C_SLV3_DO         (BANK_3 | 0x12)

#define REG_I2C_SLV4_CTRL       (BANK_3 | 0x15)

也就是BANK_3 | 0x03 = (3 << 7) | 0x03。

对应于ICM20948芯片手册中的以下内容:

bff48cb9dfbc4bceb0b9b59bbd1d3fcf.png

ef3f71b3a2e645df84c56ae2e86ca54b.png

这就与前边data为0x80 | 0x0C = 0x8C对应上了。

综上,inv_icm20948_read_secondary函数第1段代码的意思是:向I2C _SLV0_ADDR寄存器写入1个字节数据,该字节内容为0x8C,由于I2C_SLV0_RN位(bit 7)为高,因此代表准备读取数据。实际上就是选通AK09916磁力计芯片,准备读取数据。

    data = INV_MPU_BIT_I2C_READ | addr;
	result |= inv_icm20948_write_mems_reg(s, s->secondary_state.slv_reg[index].addr, 1, &data);

这样,inv_icm20948_read_secondary函数的第1段代码就解析完了。其作用就是通过ICM20948的I2C_SLV0_ADDR寄存器,设置要读取的i2c从设备的地址。此处是AK09916的设备地址0x0C。

inv_icm20948_read_secondary函数余下两段代码的解析,请看下回。

 

;