首页 > 其他分享 >bootloader学习笔记-从零开发bootloader(4)

bootloader学习笔记-从零开发bootloader(4)

时间:2024-10-16 10:18:26浏览次数:9  
标签:addr app flash 笔记 学习 flag program printf bootloader

概要

Flash区域划分,从不同的区域启动用户程序,实现覆写代码的功能。

Flash区域划分

我们的Flash是从0x08000000开始的,具体能用的大小需要查看芯片手册,例如,我的GD32F303RC芯片,flash可用的区域为256KB,内存可用大小为48KB。

256KB也就是262144字节的大小,转换成16进制为0x40000,也就是说,在设置烧录位置时是不能超过这个大小的。

还有一个问题需要注意,M4处理器是32位架构,在设置烧录地址时一定要满足4字节对齐,也就是烧录地址必须是4的倍数。

我的分区如下:

每个区域中间留有0x1000大小的空间

从不同区域启动用户程序

对我们的逻辑处理函数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

相关文章

  • CF1672F 做题笔记
    CF1672F1CF1672F2考虑给定\(b\)算它的最小操作次数,我们将\(a_i\)向\(b_i\)连一条边,每个环需要大小减\(1\)次操作次数,所以求这张图的最大简单环划分,显然每个环中不会有相同元素,否则可以分裂成\(2\)个小环更优。F1需要构造使最小次数最大的\(b\),那么就是要最小化最大......
  • 如果你的PyTorch优化器效果欠佳,试试这4种深度学习中的高级优化技术吧
    在深度学习领域,优化器的选择对模型性能至关重要。虽然PyTorch中的标准优化器如SGD、Adam和AdamW被广泛应用,但它们并非在所有情况下都是最优选择。本文将介绍四种高级优化技术,这些技术在某些任务中可能优于传统方法,特别是在面对复杂优化问题时。我们将探讨以下算法:......
  • 机器学习—— 机器学习运维(MLOps)
    机器学习——机器学习运维(MLOps)机器学习运维(MLOps)——提高模型管理和部署效率的必备技能什么是MLOps?为什么MLOps很重要?MLOps示例:构建一个简单的ML流水线MLOps的关键工具总结机器学习运维(MLOps)——高效管理和部署AI模型的工具MLOps的优势MLOps实践的关键工具示例代码......
  • 从零开始学机器学习——分类器详解
    首先给大家介绍一个很好用的学习地址:https://cloudstudio.net/columns今天我们将结合第一章节中清洗得到的菜品数据,利用多种分类器对这些数据进行训练,以构建有效的模型。在这个过程中,我会详细讲解每一种分类器的原理及其重要性。尽管这些知识点对于实践来说并不是必须掌握的,因为......
  • 【开题报告】基于Springboot+vue英语四六级单词学习系统(程序+源码+论文) 计算机毕业设
    本系统(程序+源码)带文档lw万字以上文末可获取一份本项目的java源码和数据库参考。系统程序文件列表开题报告内容研究背景在当今全球化的教育环境中,英语作为国际交流的主要语言,其重要性日益凸显。对于我国大学生而言,英语四六级考试不仅是衡量英语水平的重要标尺,也是未来求......
  • 软件测试笔记|数据库基础|创建索引的原则
    创建数据库索引有以下原则:一、选择合适的列创建索引1.选择经常用于查询条件的列:如果某一列经常在WHERE子句中作为条件出现,那么为该列创建索引可以大大提高查询速度。例如,在一个员工表中,如果经常根据员工的姓名进行查询,那么为“姓名”列创建索引是一个不错的选择。2.选择......
  • sql手工注入获取库、表名(union联合注入)(个人学习笔记)
    1,发现存在sql漏洞的网站当我们发现一个网站存在sql注入的漏洞时,可以使用sql手工注入来获取信息,如下列的网站sql注入的原理是前端给后端发送指令的时候由于设计者未考虑安全,导致用户可以加上自己的指令进去,从而让服务器完成一些列危险的操作2,确定列表数名首先,我们要先知道......
  • React 基础阶段学习计划
    React基础阶段学习计划目标能够创建和使用React组件。理解并使用State和Props。掌握事件处理和表单处理。学习内容环境搭建安装Node.js和npm访问Node.js官网下载并安装最新版本的Node.js。打开终端或命令行工具,输入node-v和npm-v检查是否安装成功。使用Crea......
  • 基于深度学习的智能传感与监控
    基于深度学习的智能传感与监控技术通过利用传感器收集数据,并使用深度学习算法对这些数据进行分析,以实现智能化的监控和管理。这些系统广泛应用于智慧城市、工业自动化、医疗健康、农业、安防等领域,提升了数据分析的精确度、实时性和自动化水平。1.智能传感概述智能传感技术......
  • 基于深度学习的基于视觉的机器人导航
    基于深度学习的视觉机器人导航是一种通过深度学习算法结合视觉感知系统(如摄像头、LiDAR等)实现机器人在复杂环境中的自主导航的技术。这种方法使机器人能够像人类一样使用视觉信息感知环境、规划路径,并避开障碍物。与传统的导航方法相比,深度学习模型能够在动态环境中表现出更强......