原理分析
首先,我们从程序镜像说起,一个完整的程序镜像可以划分成2个部分:“中断向量表”和“逻辑程序”(姑且这么命名,仅为了方便说明)。
其中“中断向量表”占用前512个字节,也就是一页Flash,其余的部分则是“逻辑程序”。
正如前一篇中所指出的,CH579将flash划分为2个区域,每个区域占用125K Flash(0x1F400),分别用来存放ImageA和ImageB。
现在我们会发现一个问题,因为芯片选择的启动地址是固定的(Flash的起始地址),如果按照以上的Flash排布,芯片永远只会运行ImageA而不会运行ImageB。
所以在升级ImageB的时候,程序会将ImageB的“中断向量表”搬运到Flash的前512字节的地方,如下图。
这样的话ImageB就可以运行了。不过这里有个地方和我上一篇说的“就算升级失败,旧的镜像亦然可用”有点冲突,那就是当升级ImageB的时候,ImageA的“中断向量表”被覆盖而永久丢失,如果这时发现ImageB存在缺陷而无法运行的话,设备就“变砖”了。我想应该是官方给的例程仅做演示,所以并没有给出完整的解决方案。
源码分析
设备创建了一个OTA的SERVICE,其下有一个UUID为0xFEE1的CHARACTER,这就是OTA通信所使用的蓝牙Profile。我们此处没有升级APP的源码,不过通过分析CH579的代码,我们可以推断出APP和CH579的通信过程:
首先,APP向0xFEE1的CHARACTER写入相关命令(擦除、编程、获取信息等),然后CH579解析该命令后,再向0xFEE1的CHARACTER的数据缓存中写入返回结果,接着APP通过轮询读取0xFEE1的数据获取返回结果,如此便构成一次往返通信。
我们再来看CH579的通信解析代码:Rec_OTA_IAP_DataDeal。
整个OTA过程主要分为擦除、编程和校验3步。
首先是擦除:
case CMD_IAP_ERASE:
{
OpAdd = (UINT32)(iap_rec_data.erase.addr[0]);
OpAdd |= ((UINT32)(iap_rec_data.erase.addr[1]) << 8);
OpAdd = OpAdd * 4;
EraseBlockNum = (UINT32)(iap_rec_data.erase.block_num[0]);
EraseBlockNum |= ((UINT32)(iap_rec_data.erase.block_num[1]) << 8);
EraseAdd = OpAdd;
EraseBlockCnt = 0;
/* 检验就放在擦除里清0 */
VerifyStatus = 0;
PRINT("IAP_ERASE start:%08x num:%d\r\n",(int)OpAdd,(int)EraseBlockNum);
/* 当前是ImageB的话需要保留ImageA地址第一块 */
if( CurrImageFlag == IMAGE_B_FLAG )
{
EraseBlockNum -= 1;
EraseAdd += FLASH_BLOCK_SIZE;
}
/* 启动擦除 */
tmos_set_event( Peripheral_TaskID, OTA_FLASH_ERASE_EVT );
break;
}
该步骤主要是记录擦除地址(EraseAdd)和擦除大小(EraseBlockNum),然后启动擦除事件(OTA_FLASH_ERASE_EVT )。
然后是编程:
case CMD_IAP_PROM:
{
UINT32 i;
UINT8 status;
OpParaDataLen = iap_rec_data.program.len;
OpAdd = (UINT32)(iap_rec_data.program.addr[0]);
OpAdd |= ((UINT32)(iap_rec_data.program.addr[1]) << 8);
OpAdd = OpAdd * 4;
PRINT("IAP_PROM: %08x len:%d \r\n",(int)OpAdd,(int)OpParaDataLen);
for(i=0; i<(OpParaDataLen/4); i++)
{
OpParaData[i] = (UINT32)(iap_rec_data.program.buf[0+4*i]);
OpParaData[i] |= ((UINT32)(iap_rec_data.program.buf[1+4*i]) << 8);
OpParaData[i] |= ((UINT32)(iap_rec_data.program.buf[2+4*i]) << 16);
OpParaData[i] |= ((UINT32)(iap_rec_data.program.buf[3+4*i]) << 24);
}
/* 当前是ImageA,直接编程 */
if(CurrImageFlag == IMAGE_A_FLAG)
{
status = FlashWriteBuf(OpAdd, OpParaData, (UINT16) OpParaDataLen);
}
/* 当前是ImageB,除了第一块直接编程 */
else
{
/* 第一块内容 */
if((OpAdd + OpParaDataLen) <= FLASH_BLOCK_SIZE)
{
for(i = 0; i < OpParaDataLen; i++)
{
vectors_block_buf[OpAdd + i] = iap_rec_data.program.buf[i];
}
status = SUCCESS;
OTA_IAP_SendCMDDealSta(status);
}
/* 其他块 */
else
{
status = FlashWriteBuf(OpAdd, (PUINT32)OpParaData, (UINT16) OpParaDataLen);
OTA_IAP_SendCMDDealSta(status);
}
}
break;
}
该过程就是将APP发送过来的程序直接写入Flash,值得注意的是,如果当前执行的是ImageB,换言之新升级的程序是ImageA,那么我们需要先将其第一块内容(512字节的中断向量表)先暂存起来(vectors_block_buf),而不是直接写入Flash的前512字节,否则在没有关闭中断的情况下,当前程序会直接跑飞。
最后是校验:
case CMD_IAP_VERIFY:
{
UINT32 i;
UINT8 *p_flash;
UINT8 status = 0;
OpParaDataLen = iap_rec_data.verify.len;
OpAdd = (UINT32)(iap_rec_data.verify.addr[0]);
OpAdd |= ((UINT32)(iap_rec_data.verify.addr[1]) << 8);
OpAdd = OpAdd * 4;
PRINT("IAP_VERIFY: %08x len:%d \r\n",(int)OpAdd,(int)OpParaDataLen);
p_flash = (UINT8 *)OpAdd;
/* 当前是ImageA,直接读取ImageB校验 */
if(CurrImageFlag == IMAGE_A_FLAG)
{
for(i=0; i<OpParaDataLen; i++)
{
if(p_flash[i] != iap_rec_data.verify.buf[i]) break;
}
if(i == OpParaDataLen) status = SUCCESS;
else status = 0xff;
VerifyStatus |= status;
OTA_IAP_SendCMDDealSta(VerifyStatus);
}
/* 当前是ImageB,第一块和buf里对比,其他直接和ImageA校验 */
else
{
/* 第一块和RAM保存的参数对比 */
if( (OpAdd + OpParaDataLen) <= FLASH_BLOCK_SIZE )
{
for(i=0; i<OpParaDataLen; i++)
{
if(vectors_block_buf[OpAdd+i] != iap_rec_data.verify.buf[i])break;
}
if(i == OpParaDataLen) status = SUCCESS;
else status = 0xff;
}
/* 其他和Flash里对比 */
else
{
for(i=0; i<OpParaDataLen; i++)
{
if(p_flash[i] != iap_rec_data.verify.buf[i]) break;
}
if(i == OpParaDataLen) status = SUCCESS;
else status = 0xff;
}
VerifyStatus |= status;
OTA_IAP_SendCMDDealSta(VerifyStatus);
}
break;
}
大致分析差不多就是这些了。
繁絮至此,但求明细。
不错不错,感谢分享!
您好,请教一下,假如在ImageA里运行升级程序,升级ImageB,那如果升级的时候,第一件事把ImageA前的中断向量表用ImageB的中断向量表覆盖掉,那当前ImageA还能正常运行吗??这个有点没搞懂啊!
引用: 水上的浮尘 发表于 2021-3-25 16:08 您好,请教一下,假如在ImageA里运行升级程序,升级ImageB,那如果升级的时候,第一件事把ImageA前的中断向 ...
你思考的没错,确实不能正常运行了。
引用: Ansersion 发表于 2021-3-25 22:06 你思考的没错,确实不能正常运行了。
谢谢