接前一篇文章: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是要读取的字节数。
对于函数功能有了一个大致的了解后,来看具体的函数代码。
第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是要访问的设备的物理地址相对应了。
那么,此时的data就是0x80 | 0x0C = 0x8C。
再来看第2行代码中的s->secondary_state.slv_reg[index].addr。它是在前文书解析的inv_icm20948_init_secondary函数中初始化的(参见https://phmatthaus.blog.csdn.net/article/details/142306413)。
而此处的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芯片手册中的以下内容:
这就与前边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函数余下两段代码的解析,请看下回。