GD32F4xx IAP 升级思路及调试遇到的问题记录

1、什么是IAP ?

  我们生活中使用的手机、智能手表等,更新软件时都是通过在设备上进行软件版本的检测和升级触发就可以升级软件。在目前这个网络时代产品的这项功能是必不可少的。但用单片机开发的产品由于芯片资源等的限制往往缺失这部分功能。其实资源足够的MCU是可以通过IAP的方式实现上述的升级功能。

  IAP( In Application Programming) , 指用户应用程序在运行过程中向Flash中烧写程序的功能。主要目的是在产品正式发布之后可以方便的通过预留的通信端口对产品进行固件程序升级。

2、GD32F4xx MCU IAP思路

  IAP的实现主要和内核相关,本文中提到的 GD32F4xx系列器件是基于ARM® Cortex™-M4处理器的32位通用微控制器。 GD32D 内部Flash起始地址为 0x08000000,固件程序一般就此地址开始写入。Cortex™-M4内核的MCU,其内部通过一张中断向量表来响应中断,程序启动后,首先从中断向量表中取出复位中断向量,执行复位中断程序完成启动进入Main() 函数运行。这张中断向量表的起始地址为0x08000004。当中断来临时,GD32 MCU的内部硬件机制会自动将PC指针定位到中断向量表位置,再根据中断源取出对应的中断向量执行中断服务程序,完成后再回到Main()函数中。
  根据以上描述,要实现IAP的功能需要将GD32的固件程序分为2个部分,一部分用于向Flash中烧写程序(命名为user_boot),另一部分为用户的应用程序。(命名为user_app)。GD32上电后首先执行user_boot, 将新的固件程序烧写到Flash中。烧写完成后从user_boot程序中跳转到烧写的程序(user_app)中执行,这样就实现了IAP功能。

以上简单描述了实现IAP的原理,有疑问的读者可自行百度,网上有很多详细讲解的资源。

2.1 GD32F4xx Flash

本文中使用的GD32F4xx芯片,Flash大小为1024KB,共分为12个扇区。

扇区每个扇区大小地址范围
0~316KB0x08000000 ~ 0x0800FFFF
464KB0x08010000~0x0801FFFF
5~ 11128KB0x08020000~0x080FFFFF

注意:
(1)在Flash的前256 KB区, CPU执行指令0等待,在此范围外,CPU读取指令存在较长延时。故尽量将固件程序设计烧写在Flash靠前部分。
(2)支持32位整字或16位半字、字节编程,扇区擦除和整片擦除操作。

2.2 GD32F4xx IAP设计

区域扇区大小地址范围
user_boot区0~132KB0x08000000~0x08007FFF
信息存储区216KB0x08008000~0x0800BFFF
user_app区3~8592KB0x0800C000~0x0809FFFF
user_app备份区9~11384KB0x080A0000~0x080FFFFF
  1. user_boot区:存储user_boot代码,用于烧写新固件程序及跳转到user_app。
  2. 信息存储区:用于存储升级标志、软件版本号
  3. user_app区:用户应用程序。
  4. user_app备份区: user_boot烧写新的固件程序时,首先烧写到user_app备份区,若新固件程序全部接收成功,则将user_app备份区代码copy到user_app区。若新固件程序下载失败,则不进行copy。保存新固件程序下载失败时,仍能运行原来的user_app程序。

2.3 固件下载方式

  实现IAP的固件下载方式有很多种,如串口、以太网、无线网络等等。IAP的处理框架都是一样,只是通信方式不同。本设计中采用串口通信的方式来下载固件程序。

2.4 升级协议

  为了保证下载固件程序的准确性,有必要制定一个通信协议。通信流程设计如下:

在这里插入图片描述

此部分设计为2类协议,一种是用于确认是否需要升级的交互协议,另一种是用于传输固件程序代码的协议。分开设计便于后续 不同通信端口的扩展。可参考代码

以上是GD32实现IAP的思路,读者可参考实现。

3、程序跳转及复位

3.1 GD32F4xx 程序跳转代码(亲测有效)

user_boot中

typedef  void (*iapfun)(void);

iapfun jump2app; 

//跳转到应用程序段
//appxaddr:用户代码起始地址.
void iap_load_app(uint32_t appxaddr)
{
	printf("[BOOT] iap_load_app !\r\n");
	if(((*(uint32_t*)appxaddr)&0x2FFE0000)==0x20000000)	//检查栈顶地址是否合法.
	{ 
		printf("[BOOT] jump !\r\n");
		__disable_irq();	//关闭所有中断
		jump2app=(iapfun)*(uint32_t*)(appxaddr+4);		//user_app区第二个字为程序开始地址(复位地址),即 Resethandler	

		//reset handler中会设置栈顶指针,此处不再重复设置
		//MSR_MSP(*(uint32_t*)appxaddr);				//初始化APP堆栈指针(user_app区的第一个字用于存放栈顶地址)
		jump2app();										//跳转到APP.
	}
}		

// addr: user_app的起始地址 , 如:按上面描述的设计思路 为 0x0800C000
void JummToUserApp(uint32_t addr)
{
	printf("[JummToUserApp] 0x%08x\r\n",addr);
	if(((*(uint32_t*)(addr+4))&0xFF000000)==FLASH_BASE)//判断是否为0X08XXXXXX.
	{	 
		iap_load_app(addr);//执行FLASH APP代码
	}
}

说明:
1、if((((uint32_t)appxaddr)&0x2FFE0000)==0x20000000) //检查栈顶地址是否合法.
appxaddr为user_app的起始地址,其中存储的是栈顶的地址。GD32F4xx MCU 的SRAM地址范围 0x20000000~0x200xxxxx(不同大小的SRAM 结束地址不同)。若存储的栈顶地址正确则,则必定在 SRAM地址的范围内。

user_app中,在main()中调用

nvic_vector_table_set(FLASH_BASE,0xC000);//设置偏移量
	__enable_irq();	//解除中断屏蔽

3.2 GD32F4xx 软件复位函数(亲测有效)

// 软件复位
void SoftReset(void)
{ 
	__set_FAULTMASK(1); // 关闭所有中断
	NVIC_SystemReset(); // 复位
}

4、MDK设置生成bin文件(亲测有效)

在这里插入图片描述

C:\Keil_v5\ARM\ARMCC\bin\fromelf.exe --bin -o .\Output\user_app.bin .\Output\iap_test.axf
设置后重新编译,文件会生成到工程Output文件夹下

5、GD32F4xx 串口升级示例

5.1 示例说明

以下提供一个串口升级的示例,以供需要的同学进行参考:
1、首先对GD32F4xx芯片的Flash区域进行划分, 我的划分就是按照2.2方式进行的分区。
2、流程说明:
(1)在GD32F4xx MCU内首先是烧录了2个程序,user_boot 和 user_app。他们的烧写到Flash中的位置如下:
user_boot:
在这里插入图片描述
user_app:
在这里插入图片描述
(2)程序启动后,首先执行user_boot程序从Flash的sector2中读取存储的版本信息。g_GtIapInfo 为版本信息结构体,包含当前设备上的软件版本号及版本升级标志IapUpgradeFlag 。如果需要升级则启动iap_task线程,接收新的软件。新软件接收完成后,直接软件复位SoftReset() 重启设备。如果不需要升级时,直接跳转到user_app执行。部分参考代码如下:

	
	UserIapInfo_read(&g_GtIapInfo);
	if(g_GtIapInfo.IapUpgradeFlag == 0xFFFFFFFF)	
	{
		Log_printf("IapInfo read fail!\r\n");
		UserIapInfoInit();		
	
		UserIapInfo_write(&g_GtIapInfo);
	}
	
	Log_printf("IapUpgradeFlag = %d,SoftVersion = %s\r\n",g_GtIapInfo.IapUpgradeFlag,g_GtIapInfo.CurVersion);

	// 需要升级则启动iap task
	if(g_GtIapInfo.IapUpgradeFlag) 
	{

		user_boot_init();
	
	//【启动RTX5系统】
	 	osKernelInitialize();                 // Initialize CMSIS-RTOS

		
	//【创建线程】
		osThreadNew(iap_task, NULL, &IAP_thread_attr);

	 	osKernelStart();                      // Start thread execution
		for (;;) {}
	}
	else
	{
		JummToUserApp(USER_APP_BASE_ADDR);
	}

(3)user_app中通过串口与升级工具连接,串口工具向user_app发送新软件的版本号信息,user_app根据版本号信息判断是否需要升级。若需要升级则将升级标志 IapUpgradeFlag 置位,置位后软件复位MCU,则程序进入user_boot的升级线程。不需要升级则继续执行user_app程序。部分参考代码如下:

ret = iap_check_upgrade(g_GtIapInfo.NewVersion, g_GtIapInfo.CurVersion);
if(ret)	// 需要升级
{
	Log_printf("Need Upgrade!\r\n");
	g_GtIapInfo.IapUpgradeFlag = 1;

	UserIapInfo_write(&g_GtIapInfo);

	// 复位
	SoftReset();
}
else
{
	Log_printf("Don't Need Upgrade!\r\n");
}

3、串口升级工具
串口升级工具使用visual studio C# 开发。界面如下:
在这里插入图片描述
使用说明:
(1)首先准备好要升级的新软件,如iap_app_V1.01.bin。用串口线连接GD32F4xx板子与电脑。
(2)运行串口升级工具后,首先点击①扫描,这时端口选择框内会出现你连接的串口,在②中选择正确的端口,设置串口参数:115200、8、1。
(3)然后点击③选择升级文件iap_app_V1.01.bin。打开成功可以看到右侧的Tx栏中有数据。
(4)然后点击④检测软件版本,若需要升级则升级按钮会由灰色不可操作状态变为可操作状态。
(5)点击⑤升级即可进行升级流程。

5.2 实测结果:软件升级成功

在这里插入图片描述
在这里插入图片描述
示例工程:
串口IAP升级例程:https://download.csdn.net/download/madao1234

6、GD32F4xx 以太网口升级示例

6.1 示例说明

1、本例实现的方式与串口示例的实现方式有所不同:在串口示例中,在user_app中接收到升级请求后,进入到user_boot中进行下载新版本程序;在本例中,是在user_app中接收升级请求,接收新版本程序。然后再跳转到user_boot中将新的app程序从app备份区拷贝到app区。另外,由于GD32F4xx的Flash只能扇区擦除,用于存储升级信息有些浪费,我这边是通过AT24C16 eeprom 来存储升级版本号等信息。
2、分区如下:

区域扇区大小地址范围
user_boot区016KB0x08000000~0x08003FFF
user_app区1~8624KB0x08004000~0x0809FFFF
user_app备份区9~11384KB0x080A0000~0x080FFFFF
3、流程说明
程序启动后,若需要升级则执行iap_upgrade();升级,否则直接跳转到user_app执行。
// 需要升级
	if(g_IapInfo.IapUpgradeFlag) 
	{
		iap_upgrade();
	}
	else
	{
		JummToUserApp(USER_APP_BASE_ADDR);
	}

user_app中,移植了uip协议栈,升级工具与控制板通过网口连接,user_app 通过TCP/IP协议接收升级请求及新版本程序。接收新版本程序完成后,执行软件复位操作,进入user_boot中将备份区的新版本程序拷贝到user_app区。

if(((*(uint8_t *)uip_appdata) == GT_PROTO_HEAD1)&&((*(uint8_t *)(uip_appdata+1)) == GT_PROTO_HEAD2)) // 升级协议
		{
			// iap升级协议处理
			iap_Protocol_Recv_Handle(uip_appdata, uip_len);
		}
		else if((*(uint8_t *)uip_appdata) == GT_UPGRADEFILE_REQ_HEAD)	// 升级文件下载
		{
			Recv_Queue_Write(uip_appdata,uip_len); 
			iap_scan_receive_handle();
		}
		else
		{
			// tcp数据处理
			
		}

		if(g_IapInfo.IapUpgradeFlag)
		{
			Log_printf("software reset!\r\n");
			SoftReset();
		}

4、以太网口升级工具
以太网口升级工具使用visual studio C# 开发。界面如下:
在这里插入图片描述
(1)首先准备好要升级的新软件,如iap_app_V1.02.bin。用网线连接GD32F4xx板子与电脑,需要将电脑IP设置成与板子的IP地址在同一个网段。
(2)运行升级工具后,首先产看①处IP地址及端口是否正确,IP地址及端口在工具的visual studio工程中可以进行修改:
在这里插入图片描述
在这里插入图片描述

(3)点击连接②按钮,进行tcp连接。连接成功日志栏会有提示。
(4)连接成功后,点击③打开bin文件按钮选择iap_app_V1.02.bin 文件。然后点击④检查软件版本按钮,工具发送升级请求,user_app确认需要升级后,会回复工具在日志栏有显示。
在这里插入图片描述
(5)点击⑤升级即可进行升级流程。

6.2 实测结果:成功升级

在这里插入图片描述
在这里插入图片描述

示例工程:
以太网IAP升级例程:https://download.csdn.net/download/madao1234

7、调试遇到的问题

7.1 IAP boot程序中在对Flash进行擦写时,PGSERR被置位导致hardfault?

现象:程序在调试时发现iap boot程序运行到write flash时就会卡死,经过单步调试发现运行到 fmc_state_get()时返回的状态一直是 PGSERR。

问题原因:经确认是由于堆栈越界导致,在IAP boot程序中使用了一个1024字节大小的buf数组,main函数的栈空间就分配了0x400(1024)。

解决:在startup_gd32f450_470.s中将栈空间调大,问题解决。

7.2 使用keil下载的程序可以正常跳转、但IAP烧写的程序在boot跳转app时会出现hardfault错误?

现象:当IAP 将接收到的新程序烧写到APP运行区后,执行跳转操作,出现hardfault,通过Peripherals-> Core Peripherals -> Fault Report 查看是 Usage Fault:UNDEFINSTR。 查看执行MSP地址内的数据是执行到了 App区。

原因:通过现象分析应该是IAP烧写的程序有问题。通过单步调试查看每一包的数据,最终确认是写入Flash函数有问题。写入结束地址错误导致,修改写入Flash函数后,问题解决。


To Be Continue …

  • 2
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

madao1024

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值