概要
Flash区域划分,从不同的区域启动用户程序,实现覆写代码的功能。
Flash区域划分
我们的Flash是从0x08000000开始的,具体能用的大小需要查看芯片手册,例如,我的GD32F303RC芯片,flash可用的区域为256KB,内存可用大小为48KB。
256KB也就是262144字节的大小,转换成16进制为0x40000,也就是说,在设置烧录位置时是不能超过这个大小的。
还有一个问题需要注意,M4处理器是32位架构,在设置烧录地址时一定要满足4字节对齐,也就是烧录地址必须是4的倍数。
我的分区如下:
从不同区域启动用户程序
对我们的逻辑处理函数logical_processing()做修改,内容如下:
void logical_processing()
{
uint8_t flag = 1;
while(1)
{
//接收指令赋值给flag
flag = 1;//给flag赋值1、4、6,分别从三个区域启动代码
//处理指令
switch(flag)
{
//1.正常启动app
case 1:
printf("flag = 1 --Normally start the program\r\n");
addr = 0x08009000;
break;
//2.更新app
case 2:
printf("flag = 2 --Upgrade the program\r\n");
break;
//3.保存app到备份
case 3:
printf("flag = 3 --Save the program to the backup area\r\n");
break;
//4.从备份启动app
case 4:
printf("flag = 4 --Start the program from the backup area\r\n");
addr = 0x08012000;
break;
//5.将备份app覆盖正常启动区
case 5:
printf("flag = 5 --Overwrite the program from backup area\r\n");
break;
//6.从更新区启动app
case 6:
printf("flag = 6 --Start the program from the update area\r\n");
addr = 0x0801B000;
break;
//7.将更新区app覆盖正常启动区
case 7:
printf("flag = 7 --Overwrite the program from update area\r\n");
break;
default:
printf("command error\r\n");
break;
}
if(flag == 1||flag == 4||flag == 6) break;
if(flag == 2||flag == 3||flag == 5||flag == 7) show_menu();
}
unsigned int *p = (unsigned int *)addr;
unsigned int sp = *p;
unsigned int app_entry = *(p+1);
SCB->VTOR = addr;
app_start(sp,app_entry);
}
稍微修改一下用户代码,并烧录到三个不同的区域,修改flag的值1、4、6,三次烧录bootloader,现象如下:
覆写代码功能的实现
在工程中创建文件flash.c和flash.h,在flash.c中添加拷贝函数,内容如下:
数据传递三要素:源,目的,数据长度。源地址和目的地址根据我们对flash的安排,范围不能小于0x08000000、不能大于0x08040000。然后将地址转为指向一个字节大小的指针并进行数据拷贝。数据拷贝结束后,将指针转为指向4字节大小的指针,判断前4个字节是否相同,如果不相同就说明数据拷贝失败。
在逻辑处理函数中添加代码如下:
给flag赋值为3,代表此时要保存用户程序到备份区,由于我们前面在三个区域烧写过大差不差的用户程序可能会产生错误的打印结果,所以我这里选择拷贝bootloader代码到备份区,第一个参数源地址给0x08000000,第二个参数给备份区起始地址0x08012000,第三个参数拷贝数据的长度暂时定为0x8000。
编译烧录,运行结果如图:
可以看到,程序打印了“copy error”,说明在拷贝函数里,最后一步判断数据前四个字节是否相同出了问题,我们这里再修改一下代码,在拷贝函数最后一步判断的时候打印一下前四个字节。
编译烧录查看结果:
可以看到,源和目的的前四个字节不相同,大家也可以使用keil的调试功能直接查看memory去对比。如果此时给flag赋值为4,让bootloader从备份区启动代码,会发现,备份区代码依旧是我们之前烧录的那个,拷贝函数完全没有发挥作用。
首先,如果这段代码操作的是内存区的数据,那么这段代码可以实现数据拷贝功能,但是现在我们操作的是flash的数据,flash存储的主要是代码以及一些比较重要的数据,默认是上锁状态的。所以我们操作flash之前要按照以下步骤:解锁flash、擦除相应的区域,写入数据,上锁。
GD32芯片提供了FMC闪存控制器模块,函数被封装在了库文件gd32fxxx_fmc.c中,我们要使用库函数来完成flash的擦写,不同的芯片对flash操作的封装不同,但基本都差不多。关于FMC具体的内容和其他芯片flash模块的内容自行上网搜索。
在flash.c中添加代码如下:
/*按字(4字节)写入*/
void fmc_program(uint32_t data,uint32_t address)
{
fmc_unlock();//解锁flash
fmc_flag_clear(FMC_FLAG_BANK0_END|FMC_FLAG_BANK0_WPERR|FMC_FLAG_BANK0_PGERR);//清除标志位
fmc_word_program(address, data);//在指定地址写入4字节数据
fmc_flag_clear(FMC_FLAG_BANK0_END|FMC_FLAG_BANK0_WPERR|FMC_FLAG_BANK0_PGERR);
fmc_lock();//上锁flash
}
/*按页(2k字节,大小为0x800)擦除*/
void fmc_erase_program(uint32_t address)
{
fmc_unlock();//解锁flash
fmc_flag_clear(FMC_FLAG_BANK0_END|FMC_FLAG_BANK0_WPERR|FMC_FLAG_BANK0_PGERR);//清除标志位
fmc_page_erase(address);//按页擦除
fmc_flag_clear(FMC_FLAG_BANK0_END|FMC_FLAG_BANK0_WPERR|FMC_FLAG_BANK0_PGERR);
fmc_lock();//上锁flash
}
对拷贝函数做修改:
uint8_t copy_app_to(uint32_t yuan,uint32_t mudi,uint32_t len)
{
//判断地址范围是否超出边界
if(yuan<0x08000000||mudi<0x08000000||yuan>0x08040000||mudi>0x08040000)
{
printf("copy addr error\r\n");
return 0;
}
uint32_t addr_mudi = mudi;//目的地址参数传递
uint32_t addr_yuan = yuan;//源地址参数传递
uint32_t length = len+(len%4);//数据长度如果不是4的倍数就补足
uint32_t data = 0;//4字节数据
uint8_t flag = 1;//作用后续介绍
//flash页擦除,addr_mudi每擦除一页就会累加0x800
while(addr_mudi < mudi+len-1)
{
fmc_erase_program(addr_mudi);
addr_mudi += 0x800;
printf("temp = %x\r\n",addr_mudi);//打印当前页起始地址(调试用)
}
printf("flash erase success......\r\n");
addr_mudi = mudi;//目的地址参数传递
//开始数据拷贝
printf("start copy......\r\n");
while(length)//每轮循环length都会减4,前面我们对length做过补齐操作,所以最后length会减为0,也不会发生下溢出
{
//每轮循环取4字节数据放到目的地址
data = *((uint32_t *)addr_yuan);
addr_yuan += 4;
fmc_program(data,addr_mudi);
addr_mudi += 4;
//flag用于单次操作,因为数据前8个字节中,第一个4字节代表栈地址(我们的bootloader是跳
//转后永不返回的所以4个区域公用同一个栈地址不会出问题),但是第二个4字节代表的是程序
//入口地址(第(2)篇讲过),每个区域的入口地址是不同的,所以我们要单独对每次拷贝的第
//二个4字节做处理
if(flag)
{
flag = 0;//flag赋值为0后,后续循环不再进入该代码块
data = (*(uint32_t *)addr_yuan)-yuan+mudi;//程序入口地址改为当前区域入口地址
//例如:0x08009000区域程序入口地址为0x080090FF,拷贝到备份区,入口地址需要
//改为0x080120FF
addr_yuan += 4;
fmc_program(data,addr_mudi);
addr_mudi += 4;
}
length = length - 4;
}
if(*(uint32_t *)yuan!=*(uint32_t *)mudi)
{
printf("yuan:%x != mudi: %x \r\n",*(uint32_t *)yuan,*(uint32_t *)mudi);
printf("copy error\r\n");
return 0;
}
printf("copy success\r\n");//拷贝成功后打印
return 1;
}
然后依旧是之前的逻辑处理函数:
编译烧录,串口助手结果如下:
可以看到拷贝数据成功了,为了后续方便,我们将每个区域的起始地址以及页大小在flash.h中定义一下:
最后我们再完善一下逻辑处理函数,并给flag赋值为7,代表将更新区程序覆写到正常启动区,并从正常启动区启动:
void logical_processing()
{
uint8_t flag = 1;
while(1)
{
//接收指令赋值给flag
flag = 7;
//处理指令
switch(flag)
{
//1.正常启动app
case 1:
printf("flag = 1 --Normally start the program\r\n");
addr = APP_NOMAL_ADDR;
break;
//2.更新app
case 2:
printf("flag = 2 --Upgrade the program\r\n");
break;
//3.保存app到备份
case 3:
printf("flag = 3 --Save the program to the backup area\r\n");
copy_app_to(APP_NOMAL_ADDR,APP_BACKUP_ADDR,0x8000);
break;
//4.从备份启动app
case 4:
printf("flag = 4 --Start the program from the backup area\r\n");
addr = APP_BACKUP_ADDR;
break;
//5.将备份app覆盖正常启动区
case 5:
printf("flag = 5 --Overwrite the program from backup area\r\n");
copy_app_to(APP_BACKUP_ADDR,APP_NOMAL_ADDR,0x8000);
break;
//6.从更新区启动app
case 6:
printf("flag = 6 --Start the program from the update area\r\n");
addr = APP_UPGRADE_ADDR;
break;
//7.将更新区app覆盖正常启动区
case 7:
printf("flag = 7 --Overwrite the program from update area\r\n");
copy_app_to(APP_UPGRADE_ADDR,APP_NOMAL_ADDR,0x8000);
addr = APP_NOMAL_ADDR;//调试用
break;
default:
printf("command error\r\n");
break;
}
break;//调试用
if(flag == 1||flag == 4||flag == 6) break;
if(flag == 2||flag == 3||flag == 5||flag == 7) show_menu();
}
unsigned int *p = (unsigned int *)addr;
unsigned int sp = *p;
unsigned int app_entry = *(p+1);
SCB->VTOR = addr;
app_start(sp,app_entry);
}
编译烧录,查看结果:
可以看到,覆写代码功能可以正常使用了。
小结
完成Flash区域划分,实现了从不同的区域启动用户程序和覆写代码的功能。
标签:addr,app,flash,笔记,学习,flag,program,printf,bootloader From: https://blog.csdn.net/coooooode/article/details/142873931