接前一篇文章:ICM20948 DMP代码详解(24)
上一回讲到了inv_icm20948_load_firmware函数,对于大体功能进行了介绍,本回深入其具体实现代码细节。为了便于理解和回顾,再次贴出相关代码:
//Setup Ivory DMP.
result |= inv_icm20948_load_firmware(s, dmp3_image, dmp3_image_size);
if(result)
return result;
else
s->base_state.firmware_loaded = 1;
/** Loads the dmp firmware for the icm20948 part.
* @param[in] dmp_image_sram Load DMP3 image from SRAM.
*/
int inv_icm20948_load_firmware(struct inv_icm20948 *s, const unsigned char *dmp3_image, unsigned int dmp3_image_size)
{
return inv_icm20948_firmware_load(s, dmp3_image, dmp3_image_size, DMP_LOAD_START);
}
int inv_icm20948_firmware_load(struct inv_icm20948 *s, const unsigned char *data_start, unsigned short size_start, unsigned short load_addr)
{
int write_size;
int result;
unsigned short memaddr;
const unsigned char *data;
unsigned short size;
unsigned char data_cmp[INV_MAX_SERIAL_READ];
int flag = 0;
if(s->base_state.firmware_loaded)
return 0;
// Write DMP memory
data = data_start;
size = size_start;
memaddr = load_addr;
while (size > 0) {
write_size = min(size, INV_MAX_SERIAL_WRITE);
if ((memaddr & 0xff) + write_size > 0x100) {
// Moved across a bank
write_size = (memaddr & 0xff) + write_size - 0x100;
}
result = inv_icm20948_write_mems(s, memaddr, write_size, (unsigned char *)data);
if (result)
return result;
data += write_size;
size -= write_size;
memaddr += write_size;
}
// Verify DMP memory
data = data_start;
size = size_start;
memaddr = load_addr;
while (size > 0) {
write_size = min(size, INV_MAX_SERIAL_READ);
if ((memaddr & 0xff) + write_size > 0x100) {
// Moved across a bank
write_size = (memaddr & 0xff) + write_size - 0x100;
}
result = inv_icm20948_read_mems(s, memaddr, write_size, data_cmp);
if (result)
flag++; // Error, DMP not written correctly
if (memcmp(data_cmp, data, write_size))
return -1;
data += write_size;
size -= write_size;
memaddr += write_size;
}
#if defined(WIN32)
//if(!flag)
// inv_log("DMP Firmware was updated successfully..\r\n");
#endif
return 0;
}
先看以下代码片段:
// Write DMP memory
data = data_start;
size = size_start;
memaddr = load_addr;
while (size > 0) {
write_size = min(size, INV_MAX_SERIAL_WRITE);
if ((memaddr & 0xff) + write_size > 0x100) {
// Moved across a bank
write_size = (memaddr & 0xff) + write_size - 0x100;
}
result = inv_icm20948_write_mems(s, memaddr, write_size, (unsigned char *)data);
if (result)
return result;
data += write_size;
size -= write_size;
memaddr += write_size;
}
data = data_start,对应的实际是dmp3_image数组的地址;size = size_start,对应的实际是dmp3_image数组的长度;memaddr = load_addr,对应的实际是DMP_LOAD_START。DMP_LOAD_START宏在EMD-Core\sources\Invn\Devices\Drivers\ICM20948\Icm20948Dmp3Driver.c和EMD-Core\sources\Invn\Devices\Drivers\ICM20948\Icm20948Defs.h中都有定义,不过值都是一样的。
#define DMP_LOAD_START 0x90
接下来进入while循环。
一上来先确定write_size即本次写入的数据长度。
write_size = min(size, INV_MAX_SERIAL_WRITE);
write_size取size和INV_MAX_SERIAL_WRITE的较小者。一开始的时候,size为size_start,也就是sizeof(dmp3_image),非常大(#define DMP_CODE_SIZE 14301);而INV_MAX_SERIAL_WRITE前文书提到过,只有16,因此write_size为16。
这里实际上就能知道,这个写入是每次最多写入16个字节,最后一次写入的很有可能小于16个字节。
接下来要做的是调整write_size:
if ((memaddr & 0xff) + write_size > 0x100) {
// Moved across a bank
write_size = (memaddr & 0xff) + write_size - 0x100;
}
memaddr当前是0x90,write_size当前是0,两者加在一起为0x90,小于0x100即256,因此不会进入判断体,无需调整。
那么什么时候需要调整呢?每次写16个字节,当某一次memaddr加上write_size大于0x100(256)的时候,就不再写write_size字节了,而是写(memaddr & 0xff) + write_size - 0x100个字节。这样实际上就保证了memaddr按照以bank为模进行写入,也就是注释中的“Moved across a bank”。
为何要这样做?笔者理解是因为dmp3_image数组中是以bank为单位的,一个bank是256个字节。
bank0的长度为0x100-0x90=256-144=112(0x70),正好是7行。从bank1开始,就是16行了。
接下来来到以下代码片段:
result = inv_icm20948_write_mems(s, memaddr, write_size, (unsigned char *)data);
if (result)
return result;
data += write_size;
size -= write_size;
memaddr += write_size;
从memaddr开始写入write_size个字节的数据。memaddr一开始是0x90,第一次写入16个字节后,变为0x90+0x10=0xA0(160),size减小16,data即dmp3_image数组的指针也向后移16个字节。第二次重复这一过程……这样依次循环,直到size<=0(实际上只能是==0)为止。
写完之后就来到以下代码片段:
// Verify DMP memory
data = data_start;
size = size_start;
memaddr = load_addr;
while (size > 0) {
write_size = min(size, INV_MAX_SERIAL_READ);
if ((memaddr & 0xff) + write_size > 0x100) {
// Moved across a bank
write_size = (memaddr & 0xff) + write_size - 0x100;
}
result = inv_icm20948_read_mems(s, memaddr, write_size, data_cmp);
if (result)
flag++; // Error, DMP not written correctly
if (memcmp(data_cmp, data, write_size))
return -1;
data += write_size;
size -= write_size;
memaddr += write_size;
}
这段很好理解。就是写入之后,再从DMP_LOAD_START(0x90)开始读取sizeof(dmp3_image)大小的数据(当然,还是每次最多16个字节的机制),并与dmp3_image原始数据进行比较,最终完全一致,则证明写入正确,返回0;中间有任何一次不正确,则返回-1,表示DMP固件加载失败。
这里提一下DMP_LOAD_START(0x90)这个寄存器,也可以说是内存。你如果在芯片手册中找,是找不到的。感觉这个DMP像个后门似的,在手册中不体现,一般不让用户知道。
至此,inv_icm20948_firmware_load函数就解析完了,inv_icm20948_load_firmware函数也就解析完了。回到inv_icm20948_initialize_lower_driver函数中的代码片段:
//Setup Ivory DMP.
result |= inv_icm20948_load_firmware(s, dmp3_image, dmp3_image_size);
if(result)
return result;
else
s->base_state.firmware_loaded = 1;
如果正常没有问题即返回了0,则走else分支,将s->base_state.firmware_loaded设置为1。
下一回继续解析inv_icm20948_initialize_lower_driver函数中的后续代码。