【前言】最近正在开发一个具有升级功能的项目,使用的是GD32的E230芯片。这里只阐述个人认知水平的观点,本人很菜,所以酌情观看本文。
关于IAP升级我还是第一次做,无非就是将内存分两份,一份用于Bootloader,一份用于APP。芯片共64K Flash空间,在做升级前,已经使用了公司的V3协议,所以就顺便也套用了升级的框架,升级的协议是在V3协议的基础上,利用数据段进行了处理,相当于协议套协议,具体内容下面再说。
一、分配内存:
将工程只保留最简单的通讯协议部分,以及状态指示灯,以及升级用的框架代码,发现用了22K,预留3K即共用25K做Bootloader。 然后1K作为bin文件的信息段(Info),剩下的64-25-1=38K作为APP程序空间。
二、程序整体运行
初始化--》判断能否跳APP--》Loop循环
Loop循环:{通讯协议、要运行的程序、......}
三、移植对应的升级框架:
Boot端的升级框架:定义{升级间隔、每包数据大小、Bin文件最大的大小、APP地址、跳转程序、Flash操作结构体}
Flash操作结构体{Info地址、擦除函数、写入函数、读取函数}
【准备升级】:通过通讯协议和上位机通讯交换信息,通过校验一些数据来确保固件OK。比如info起始地址、固件大小是否合适、APP起始地址是否......。验证通过后则将准备标记置位,并擦除固件对应大小的Flash为后面的升级做准备。返回包可以包括传输密钥等传输参数。
【接收升级包】:通过协议接受升级包,保存前要做验证,比如是否准备接受,包长是否合适、密钥是否正确等...。记录当前的包数并写入固件信息到对应的Flash。
【校验源码完整性】:通过校验传输密钥等操作后,进行包数校验是否足包。
【跳转APP】:如果上述步骤完成,则可以跳转APP。
对Info信息的说明:Info是跟着APP固件走的,里面包括版本号,CRC校验码,等等用于校验固件是否正确等
每次从Boot跳转到APP都是要通过Info判断,如果判断通过则跳转否则不跳转。在APP端使用的时候,点击升级的时候清除掉Info然后再跳到Boot,就可以不自动跳回来APP。
===============================分割线=================================
============================细节问题补充=================================
跳转APP程序介绍:
void BUpdataFirm_JumpAddress(uint32_t address)
{
/* 反向初始化用到的外设、中断等 */
MCUDriverMain_DeInit();
Delay(100);
uint32_t SpInitVal; //栈顶的地址存放
uint32_t JumpAddr; //跳转地址存放
void (*pFun)(void);
disable_irq(); //关闭中断(到APP中记得再打开!)
SpInitVal = *(uint32_t *)address; //地址中的数据拿出来,作为栈顶的地址
// JumpAddr = *(uint32_t *)(address + 4); //栈顶地址+4 就是程序运行的地址
__asm volatile ("MSR msp, %0" : : "r" (SpInitVal) : ); //设置栈顶
pFun = (void (*)(void))(*(uint32_t *)0x08006804); //设置程序执行地址
(*pFun)(); //执行程序
}
【注意】:
1、因为跳转前关闭了中断,所以APP中一定要先开启中断!!!否则会有不进中断等问题。
2、传参address是栈顶地址存放的地址,所以要对address强转(uint32_t*)然后取值。
3、本来JumpAddr这一句是个函数封装的,因为没在GD库中找到这句话,就直接改了一下,之前是inline内联函数,但是我把inline的标记给删了,然后就会出现一个问题:执行到这一句的时候,一些变量的值会改变,导致跳转的地方不对。是因为没有加inline的时候,前面刷新了栈顶指针,在栈中存放的数据会改变,汇编指令会重新读取数据,导致出错。所以这里直接把这句话放这里了。相当于内联函数的作用。
4、上位机的跳转可以取巧:直接调用复位函数就可以回到Boot中
/**
\brief Disable IRQ Interrupts
\details Disables IRQ interrupts by setting the I-bit in the CPSR.
Can only be executed in Privileged modes.
*/
void disable_irq(void)
{
__asm volatile ("cpsid i" : : : "memory");
}
/**
\brief Enable IRQ Interrupts
\details Enables IRQ interrupts by setting the I-bit in the CPSR.
Can only be executed in Privileged modes.
*/
void enable_irq(void)
{
__asm volatile ("cpsie i" : : : "memory");
}
这是开启和关闭中断向量的函数(本来也是内联的)。
问题一、字节对齐的问题
/*************************************************************
** Function name: GD32Flash_FlashWrite
** Descriptions: 内存写入
** Input parameters: flashStart:内存起始地址
** *pData:数据指针 必须是4的倍数
** size:写入的数据个数 必须是4的倍数
** Output parameters: no
** Returned value: no
*************************************************************/
void GD32Flash_FlashWrite(
uint32_t flashStart,
uint8_t *pData,
uint16_t size)
{
uint32_t cir = 0;
uint32_t data;
fmc_unlock(); //内存解锁
for(cir = 0; cir < size; cir += 4) {
// data = *(uint32_t *)&pData[cir];
memcpy(&data,pData+cir,sizeof(uint32_t));
fmc_word_program(flashStart + cir, data);
fmc_flag_clear(FMC_FLAG_END | FMC_FLAG_WPERR | FMC_FLAG_PGERR);
}
fmc_lock();
}
这里的pData是一个数组进来的,每次到这里写入的时候都会卡死,不知道为什么。我在初始化的时候也测试了程序是没问题的,但是一到实际升级的时候就出问题。
问大佬:发现是这里data=*(uint32_t)&pData[cir]会卡死,通过单步调试发现此时pData[cir]的地址是E5即0101结尾,为奇数结尾。此时一下读四个字节就会出问题。具体的搜一下其他文章。
其实导致这个主要是因为协议进行了字节对齐,而且使用的过程中没有成双成对的使用,有的是一个字节有的两个,就会导致后面读取使用的时候出问题。但是用memcpy可以解决问题,暂时不知道还有没有其他办法,日后再搜。
问题二、串口无法接收0x11、0x13的问题
调试过程中发现协议数据包不对,仔细核对发现少了0x11这个数据,后来搜了一下,说是上位机的问题。果然换了个上位机就可以了。
上位机需要更改一个关于流控的参数(听大佬说 好像是这样就可以了)
===============================分割线=================================
============================MDK软件设置=================================
调试的过程中要Boot和APP两个程序相互跳转,所以配置MDK十分重要。
这是Boot的配置:
Target可以不变,但是程序烧录大小要更改,否则每次烧录全部地址会擦除APP的程序。
这是APP的配置:
Target中的起始地址要更改