接前一篇文章:ICM20948 DMP代码详解(8)
上一回解析完了EMP-App中的入口函数main()中重点关注的第2段代码,本回继续往下解析。为了便于理解和回顾,再次贴出main函数代码,如下:
int main (void)
{
int rc = 0;
/* Hardware initialization */
sysclk_init();
board_init();
sysclk_enable_peripheral_clock(ID_TC0);
/* Configure Device - Host Interface */
configure_console();
#ifdef INV_MSG_ENABLE
/* Setup message logging */
INV_MSG_SETUP(INV_MSG_ENABLE, msg_printer);
#endif
INV_MSG(INV_MSG_LEVEL_INFO, "##########################");
INV_MSG(INV_MSG_LEVEL_INFO, " ICM20948 example ");
INV_MSG(INV_MSG_LEVEL_INFO, " Ver: %s", EMD_RELEASE_VERSION_STRING);
INV_MSG(INV_MSG_LEVEL_INFO, "##########################");
/* Initialize External Sensor Interrupt */
ext_int_initialize(&ext_interrupt_handler);
interface_initialize();
/* Configure sysTick Timer */
SysTick_Config(sysclk_get_cpu_hz() / MILLISECONDS_PER_SECOND);
/*
* Initialize icm20948 serif structure
*/
struct inv_icm20948_serif icm20948_serif;
icm20948_serif.context = 0; /* no need */
icm20948_serif.read_reg = idd_io_hal_read_reg;
icm20948_serif.write_reg = idd_io_hal_write_reg;
icm20948_serif.max_read = 1024*16; /* maximum number of bytes allowed per serial read */
icm20948_serif.max_write = 1024*16; /* maximum number of bytes allowed per serial write */
icm20948_serif.is_spi = interface_is_SPI();
/*
* Reset icm20948 driver states
*/
inv_icm20948_reset_states(&icm_device, &icm20948_serif);
inv_icm20948_register_aux_compass(&icm_device, INV_ICM20948_COMPASS_ID_AK09916, AK0991x_DEFAULT_I2C_ADDR);
/*
* Setup the icm20948 device
*/
rc = icm20948_sensor_setup();
/*
* Now that Icm20948 device was initialized, we can proceed with DMP image loading
* This step is mandatory as DMP image are not store in non volatile memory
*/
rc += load_dmp3();
check_rc(rc, "Error sensor_setup/DMP loading.");
/*
* Initialize Dynamic protocol stuff
*/
DynProTransportUart_init(&transport, iddwrapper_transport_event_cb, 0);
DynProtocol_init(&protocol, iddwrapper_protocol_event_cb, 0);
InvScheduler_init(&scheduler);
InvScheduler_initTask(&scheduler, &commandHandlerTask, "commandHandlerTask", CommandHandlerTaskMain, 0, INVSCHEDULER_TASK_PRIO_MIN, 1);
InvScheduler_initTask(&scheduler, &blinkerLedTask, "blinkerLedTask", BlinkerLedTaskMain, 0, INVSCHEDULER_TASK_PRIO_MIN+1, 1000000/SCHEDULER_PERIOD);
InvScheduler_startTask(&blinkerLedTask, 0);
InvScheduler_startTask(&commandHandlerTask, 0);
hw_timer_start(20); // Start the timestamp timer at 20 Hz.
while (1)
{
InvScheduler_dispatchTasks(&scheduler);
if (irq_from_device == 1) {
inv_icm20948_poll_sensor(&icm_device, (void *)0, build_sensor_event_data);
__disable_irq();
irq_from_device = 0;
__enable_irq();
}
}
return 0;
}
重点关注的第3段代码如下:
/*
* Setup the icm20948 device
*/
rc = icm20948_sensor_setup();
注意,从icm20948_sensor_setup函数开始,带返回值了,之前的函数都不带返回值。
icm20948_sensor_setup函数在EMD-App\src\ICM20948\sensor.c中,代码如下:
int icm20948_sensor_setup(void)
{
int rc;
uint8_t i, whoami = 0xff;
/*
* Just get the whoami
*/
rc = inv_icm20948_get_whoami(&icm_device, &whoami);
if (interface_is_SPI() == 0) { // If we're using I2C
if (whoami == 0xff) { // if whoami fails try the other I2C Address
switch_I2C_to_revA();
rc = inv_icm20948_get_whoami(&icm_device, &whoami);
}
}
INV_MSG(INV_MSG_LEVEL_INFO, "ICM20948 WHOAMI value=0x%02x", whoami);
/*
* Check if WHOAMI value corresponds to any value from EXPECTED_WHOAMI array
*/
for(i = 0; i < sizeof(EXPECTED_WHOAMI)/sizeof(EXPECTED_WHOAMI[0]); ++i) {
if(whoami == EXPECTED_WHOAMI[i]) {
break;
}
}
if(i == sizeof(EXPECTED_WHOAMI)/sizeof(EXPECTED_WHOAMI[0])) {
INV_MSG(INV_MSG_LEVEL_ERROR, "Bad WHOAMI value. Got 0x%02x.", whoami);
return rc;
}
/* Setup accel and gyro mounting matrix and associated angle for current board */
inv_icm20948_init_matrix(&icm_device);
/* set default power mode */
INV_MSG(INV_MSG_LEVEL_VERBOSE, "Putting Icm20948 in sleep mode...");
rc = inv_icm20948_initialize(&icm_device, dmp3_image, sizeof(dmp3_image));
if (rc != 0) {
INV_MSG(INV_MSG_LEVEL_ERROR, "Initialization failed. Error loading DMP3...");
return rc;
}
/*
* Configure and initialize the ICM20948 for normal use
*/
INV_MSG(INV_MSG_LEVEL_INFO, "Booting up icm20948...");
/* Initialize auxiliary sensors */
inv_icm20948_register_aux_compass( &icm_device, INV_ICM20948_COMPASS_ID_AK09916, AK0991x_DEFAULT_I2C_ADDR);
rc = inv_icm20948_initialize_auxiliary(&icm_device);
if (rc == -1) {
INV_MSG(INV_MSG_LEVEL_ERROR, "Compass not detected...");
}
icm20948_apply_mounting_matrix();
icm20948_set_fsr();
/* re-initialize base state structure */
inv_icm20948_init_structure(&icm_device);
/* we should be good to go ! */
INV_MSG(INV_MSG_LEVEL_VERBOSE, "We're good to go !");
return 0;
}
icm20948_sensor_setup函数包含了很多段以及子函数,还是一段一段、一个函数一个函数来看。
先来看第1段:
/*
* Just get the whoami
*/
rc = inv_icm20948_get_whoami(&icm_device, &whoami);
if (interface_is_SPI() == 0) { // If we're using I2C
if (whoami == 0xff) { // if whoami fails try the other I2C Address
switch_I2C_to_revA();
rc = inv_icm20948_get_whoami(&icm_device, &whoami);
}
}
INV_MSG(INV_MSG_LEVEL_INFO, "ICM20948 WHOAMI value=0x%02x", whoami);
/*
* Check if WHOAMI value corresponds to any value from EXPECTED_WHOAMI array
*/
for(i = 0; i < sizeof(EXPECTED_WHOAMI)/sizeof(EXPECTED_WHOAMI[0]); ++i) {
if(whoami == EXPECTED_WHOAMI[i]) {
break;
}
}
if(i == sizeof(EXPECTED_WHOAMI)/sizeof(EXPECTED_WHOAMI[0])) {
INV_MSG(INV_MSG_LEVEL_ERROR, "Bad WHOAMI value. Got 0x%02x.", whoami);
return rc;
}
inv_icm20948_get_whoami函数在EMD-Core\sources\Invn\Devices\Drivers\ICM20948\Icm20948Setup.c中,代码如下:
/* Identification related functions */
int inv_icm20948_get_whoami(struct inv_icm20948 * s, uint8_t * whoami)
{
return inv_icm20948_read_reg_one(s, REG_WHO_AM_I, whoami);
}
函数本身很好理解,就是调用inv_icm20948_read_reg_one函数,读取REG_WHO_AM_I寄存器的值,并返回给第2个参数uint8_t whoami。
REG_WHO_AM_I宏在EMD-Core\sources\Invn\Devices\Drivers\ICM20948\Icm20948Defs.h中定义,如下:
/*register and associated bit definition*/
/* bank 0 register map */
#define REG_WHO_AM_I (BANK_0 | 0x00)
对应的就是ICM20948芯片手册中的WHO_AM_I寄存器,如下所示:
这里,要对inv_icm20948_read_reg_one函数做详细分析和介绍,因为后边读各个寄存器也会用到该函数。inv_icm20948_read_reg_one函数在EMD-Core\sources\Invn\Devices\Drivers\ICM20948\Icm20948Transport.h中,代码如下:
static inline int inv_icm20948_read_reg_one(struct inv_icm20948 * s, uint8_t reg, uint8_t * reg_value)
{
return inv_icm20948_read_reg(s, reg, reg_value, 1);
}
inv_icm20948_read_reg_one函数实际调用的是inv_icm20948_read_reg函数,只是最后一个参数传入的值是1,代表读取1个寄存器的内容。可以看到,inv_icm20948_read_reg_one函数实际上是inv_icm20948_read_reg函数的一个特例。
inv_icm20948_read_reg函数在EMD-Core\sources\Invn\Devices\Drivers\ICM20948\Icm20948Transport.c中,代码如下 :
int inv_icm20948_read_reg(struct inv_icm20948 * s, uint8_t reg, uint8_t * buf, uint32_t len)
{
return inv_icm20948_serif_read_reg(&s->serif, reg, buf, len);
}
这里重点说一下调用inv_icm20948_serif_read_reg函数时的第个参数&s->serif。前文书(https://phmatthaus.blog.youkuaiyun.com/article/details/141951849)已讲过,s->serif是在inv_icm20948_reset_states函数中赋值的,值取自于main函数中创建并初始化的struct inv_icm20948_serif icm20948_serif。
/** @brief Reset and initialize driver states
* @param[in] s handle to driver states structure
*/
static inline void inv_icm20948_reset_states(struct inv_icm20948 * s,
const struct inv_icm20948_serif * serif)
{
assert(icm20948_instance == 0);
memset(s, 0, sizeof(*s));
s->serif = *serif;
icm20948_instance = s;
}
当然,此处前边还有&符,代表实际上传入的是变量的地址。
inv_icm20948_serif_read_reg函数在EMD-Core\sources\Invn\Devices\Drivers\ICM20948\Icm20948Serif.h中,代码如下:
static inline int inv_icm20948_serif_read_reg(struct inv_icm20948_serif * s,
uint8_t reg, uint8_t * buf, uint32_t len)
{
assert(s);
if(len > s->max_read)
return INV_ERROR_SIZE;
if(s->read_reg(s->context, reg, buf, len) != 0)
return INV_ERROR_TRANSPORT;
return 0;
}
inv_icm20948_serif_read_reg函数中会用到两个在main函数中赋值的变量:
max_read用于对inv_icm20948_serif_read_reg函数的参数uint32_t len进行检查,确保一次最多只能读取16K字节。
read_reg指向了实际的读取函数idd_io_hal_read_reg。该函数在EMD-App\src\ICM20948\system.c中,代码如下:
int idd_io_hal_read_reg(void * context, uint8_t reg, uint8_t * rbuffer, uint32_t rlen){
(void)context;
#if SERIF_TYPE_SPI
return spi_master_transfer_rx(NULL, reg, rbuffer, rlen);
#else /* SERIF_TYPE_I2C */
return i2c_master_read_register(I2C_Address, reg, rlen, rbuffer);
#endif
}
这才调用了真正的i2c寄存器读取函数。
之所以弄得分了这么多层,应该是为了便于移植。本例中,调用的最终函数为i2c_master_read_register。这个函数是适配TDK SAMG55 Dev Kit的。如果是自己的板子(比如笔者是乐鑫ESP32系列模组),那么在此替换掉这个i2c_master_read_register函数,改为适合自己板子的i2c读取函数就好(当然,上下文和函数参数也可能会有一定微调)。
i2c_master_read_register函数是平台相关的了,这里就不再深入,只贴出代码(EMD-App\src\ICM20948\system.c中),了解一下就好。
static unsigned long i2c_master_read_register(unsigned char Address, unsigned char RegisterAddr, unsigned short RegisterLen, unsigned char *RegisterValue){
twi_packet_t packet_read;
if(Address == 0){
Address = I2C_Address; // Slave Address is 0x69 for on-board sensors, 0x68 for external sensors
}
packet_read.chip = Address;
packet_read.addr[0] = RegisterAddr;
packet_read.addr_length = 1;
packet_read.buffer = RegisterValue;
packet_read.length = RegisterLen;
if(twi_master_read((Twi*)BOARD_BASE_TWI, &packet_read) == TWI_SUCCESS){
return TWI_SUCCESS;
}
return TWI_BUSY;
}
至此,icm20948_sensor_setup函数第1段代码中的inv_icm20948_get_whoami函数就解析完了。下一回继续讲解第1段代码余下的部分。