[原创] 【CH579M-R1】+OTA原理及源码分析

Ansersion   2021-1-14 14:02 楼主

原理分析

首先,我们从程序镜像说起,一个完整的程序镜像可以划分成2个部分:“中断向量表”和“逻辑程序”(姑且这么命名,仅为了方便说明)。

其中“中断向量表”占用前512个字节,也就是一页Flash,其余的部分则是“逻辑程序”。

11.png

正如前一篇中所指出的,CH579将flash划分为2个区域,每个区域占用125K Flash(0x1F400),分别用来存放ImageA和ImageB。

12.png

现在我们会发现一个问题,因为芯片选择的启动地址是固定的(Flash的起始地址),如果按照以上的Flash排布,芯片永远只会运行ImageA而不会运行ImageB。

所以在升级ImageB的时候,程序会将ImageB的“中断向量表”搬运到Flash的前512字节的地方,如下图。

13.png

这样的话ImageB就可以运行了。不过这里有个地方和我上一篇说的“就算升级失败,旧的镜像亦然可用”有点冲突,那就是当升级ImageB的时候,ImageA的“中断向量表”被覆盖而永久丢失,如果这时发现ImageB存在缺陷而无法运行的话,设备就“变砖”了。我想应该是官方给的例程仅做演示,所以并没有给出完整的解决方案。

 

源码分析

设备创建了一个OTA的SERVICE,其下有一个UUID为0xFEE1的CHARACTER,这就是OTA通信所使用的蓝牙Profile。我们此处没有升级APP的源码,不过通过分析CH579的代码,我们可以推断出APP和CH579的通信过程:

首先,APP向0xFEE1的CHARACTER写入相关命令(擦除、编程、获取信息等),然后CH579解析该命令后,再向0xFEE1的CHARACTER的数据缓存中写入返回结果,接着APP通过轮询读取0xFEE1的数据获取返回结果,如此便构成一次往返通信。

14.jpg

 

我们再来看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;
		}

 

大致分析差不多就是这些了。

 

繁絮至此,但求明细。

 

回复评论 (4)

不错不错,感谢分享!

点赞  2021-1-14 20:52

您好,请教一下,假如在ImageA里运行升级程序,升级ImageB,那如果升级的时候,第一件事把ImageA前的中断向量表用ImageB的中断向量表覆盖掉,那当前ImageA还能正常运行吗??这个有点没搞懂啊!

点赞  2021-3-25 16:08
引用: 水上的浮尘 发表于 2021-3-25 16:08 您好,请教一下,假如在ImageA里运行升级程序,升级ImageB,那如果升级的时候,第一件事把ImageA前的中断向 ...

xxx.png

你思考的没错,确实不能正常运行了。

 

点赞  2021-3-25 22:06
引用: Ansersion 发表于 2021-3-25 22:06 你思考的没错,确实不能正常运行了。  

谢谢

点赞  2021-3-28 15:04
电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 京公网安备 11010802033920号
    写回复