STM32GD32 hex格式、Bootloader、Bootloader升级APP研究以及编程实现

STM32GD32 hex格式、Bootloader、Bootloader升级APP研究以及编程实现,第1张


[本文发布地址https://blog.csdn.net/Stack_/article/details/124683892,未经允许不得转载。转载须注明出处]


一、Hex格式

【Intel英特尔Hex格式官方文档】
【什么是Hex文件格式?】


除每行开头的 ‘:’ 外,其它均是2字节表示一个十六进制数。例如02表示0x02,F2表示0xF2





行开头:每行以ASCII的 : 为开头
长度:标识数据区域的字节数
地址:相对于基地址的偏移地址
类型标识:有6种,如下表
数据区域
行校验: 冒号后、校验和前的值累加,并对和值按位取反后加1


数据标识定义指代本行数据类型
00数据
01文件结束
02扩展段地址。段地址始于8080CPU那个年代,现在段地址这个概念被弱化甚至尘封,忽略即可
03起始段地址
04扩展线性地址。 如上图中第1行标识了数据地址的高16位为0x0800即基地址为0x08000000,后面数据类型为数据的行的地址均为偏移地址,偏移地址都要加上基地址才能得出实际的地址,如出现下一个扩展线性地址,则表示指定新的基地址
05起始线性地址。 告诉单片机从哪个地址开始运行程序。目前看来这个也可以忽略



在Keil MDK中更改程序起始地址 0x8000000 -> 0x8005000 后, hex文件的地址变化如下:




二、Hex数据域
for (i = 0; i < 10; i++)
{
       printf("%x, ", *((uint32_t *)0x8000000 + i));
}
printf("\r\n");
for (i = 0; i < 20; i++)
{
	printf("%x, ", *((uint16_t *)0x8000000 + i));
}
printf("\r\n");
for (i = 0; i < 40; i++)
{
	printf("%x, ", *((uint8_t *)0x8000000 + i));
}

加上以上代码以输出Flash地址的值,烧写程序之后,对比hex文件和打印结果




  • Flash支持16位半字写入和32位整字写入,且为小端格式。
  • 选择半字写入时,向地址0x08000000写入0x5B88,向地址0x08000002写入0x2000,向地址0x08000004写入0x0145,…
  • 选择整字写入时,向地址0x08000000写入0x20005B88,向地址0x08000004写入0x08000145,向地址0x08000008写入0x0800014D,…



三、烧写地址设置

在Keil MDK中,对Cortex-M3/4已经默认了程序烧录起始地址是0x08000000,并且可以选择在烧写程序前进行全片擦除或是按需擦除 。那么就可以划分出两块区域分别放Bootloader程序和APP程序。




例如如下划分

StartSize
Bootloader0x80000000x5000 (20K)
APP0x80050000x78000 (492K)
或者
APP0x80000000x78000 (492K)
Bootloader0x80780000x5000 (20K)

Bootloader在前:单片机上电时先运行Bootloader,等待升级指令。无指令时计时,超时进入APP;有升级指令,则开始升级APP即接收升级数据写入Flash,写入完成后跳转APP。
Bootloader在后:单片机上电时直接进入APP,运行过程中接收到升级指令后跳转Bootloader进行APP升级。升级完成后跳转APP。
同样的,在APP中也可升级Bootloader,但一般不需要。
Bootloader的功能尽量单一,大小尽量小,且占用的地址不能与APP的重叠。


需要特别注意的是,如果不勾选“Use Memory Layout from Target Dialog”,则上面选项卡中设置的地址是不起作用的,需要自行修改分散加载文件(未研究)。





四、Bootloader跳转APP

跳转前需要检查APP的栈顶指针是否合法、关闭所有中断、重设栈顶指针、偏移中断向量表(可以在APP中设置)。

定义如下数据

#define APP_LOADED_ADDR						0x5000		//APP加载地址
typedef  void  (*pAppFunction) (void);
pAppFunction application;
__IO uint32_t JumpAddress = NULL;

升级APP完成后,调用如下代码

/* APP栈顶指针合法 */
if (0x20000000 == ((*(__IO uint32_t*)APP_LOADED_ADDR) & 0x2FFE0000))
{
	__disable_irq();	//关闭所有中断
	JumpAddress = *(__IO uint32_t*)(APP_LOADED_ADDR + 4);	//Reset_Handler 入口地址
	application = (pAppFunction) JumpAddress;
	__set_MSP(*(__IO uint32_t*) APP_LOADED_ADDR);	//APP程序堆栈指针起始
	printf("APP地址%#x, \r\n", APP_LOADED_ADDR);
	application();	//跳转到Reset_Handler即APP
}
  • 宏 APP_LOADED_ADDR 是APP的起始地址,而APP程序的起始是栈顶指针。
  • 为什么加载地址不是0x8005000?
    因为选择了从Flash启动,所以Flash被映射到了0x00000000。所以访问0x080050000和0x5000效果是一样的。
  • 所以如果正确写入了APP,则 地址 (__IO uint32_t *)APP_LOADED_ADDR 处的栈顶指针必然在该型号单片机的SRAM大小范围内。96K Sram地址范围为0x2000 0000 - 0x2001 7FFF,64K Sram地址范围为0x2000 0000 - 0x2000 FFFF。
  • (0x20000000 == (((__IO uint32_t)APP_LOADED_ADDR) & 0x2FFE0000)) 表示的是从地址0x5000取出栈顶指针,通过运算检查栈顶地址是否落在0x2000 0000 - 0x2001 7FFF。64K的检查为(0x20000000 == (((__IO uint32_t)APP_LOADED_ADDR) & 0x2FFF0000))。相当于读出的值直接与Sram范围值比较。
  • 跳转到APP不是跳转栈顶指针,而是跳转Reset_Handler,而Reset_Handler在栈顶指针后,所以需要偏移32位。
  • application为一个void类型的函数指针,并将其指向Reset_Handler的地址。application()表示执行指针指向的函数,即Reset_Handler函数。
  • 跳转前关闭中断,防止 1)已经跳转到APP了但来了一个中断并落在了Bootloader, 2)中断向量表已偏移但来了中断,就会落在错误的地方

需要特别注意的是,如果使能了Systick等内核级别的中断,用__disable_irq(); 之类的屏蔽中断的语句是无法关闭的,需要去停止Systick的运行。最优的是在bootloader跳转前关闭所有用到的资源以及中断,在APP中重新配置所需要的,以免在Bootloader中用到的中断在APP中没有用到,而缺少对应的中断处理导致中断死循环或HardFault。APP跳转Bootloader亦应如此。



五、Bootloader升级APP

从串口接收到hex文件的一行,校验后调用以下函数写入FLASH。以下是简单的一个写入流程

void UpgradeAPP(xxx)
{
	uint16_t i = 0;
    uint16_t line_data_len = xxx;	//数据域长度
    static uint32_t base_addr = 0;	
    uint32_t offset_addr = 0x0000ffff & xxx;	//偏移地址
    uint32_t write_addr = 0;
    static uint32_t has_erase_page = 0;
    uint32_t need_erase_page = 0;
    static bool APP_StartAddr_OK = false;
	uint8_t line_type = xxx;
	
    switch (line_type)	//行数据类型
    {
    case 4: //指定基地址
        base_addr = xxx;
        base_addr = (base_addr << 16) & 0xffff0000;
        【解锁FLASH】
        break;
    case 0: //写入Flash的数据
        write_addr = base_addr | offset_addr;

        if (APP_StartAddr_OK == false && ((APP_LOADED_ADDR + 0x08000000) == write_addr))
        {
            APP_StartAddr_OK = true;    //APP Flash首地址正确
        }
        if (APP_StartAddr_OK == true)
        {
            for (i = 0; i < line_data_len; i += 4)
            {
                need_erase_page = ((write_addr + i) - 0x08000000) / 2048;	//算出当前地址所在页,2K每页

                if (need_erase_page != has_erase_page)
                {
                    has_erase_page = need_erase_page;
                    【擦除该页】
                }
                
                【向指定地址以整字方式写入FLASH,或者接收到一页的量后采用页编程方式写入】
                【读出对比】
                printf("%x, ", *((volatile uint32_t *)(write_addr + i)));
            }
            printf("\r\n");
        }
        break;
        case 1: //文件结束
            【FLASH上锁】
        break;
    default:
        break;
    }
}



六、跳转APP后的处理
/* 基地址 */
#define NVIC_VECTTAB_RAM            ((uint32_t)0x20000000) /*!< RAM */
#define NVIC_VECTTAB_FLASH          ((uint32_t)0x08000000) /*!< Flash */

/* NVIC中断向量表偏移掩码 */
#define NVIC_VECTTAB_OFFSET_MASK    ((uint32_t)0x1FFFFF80)

#define USER_BOOT_EXIST						1			//是否有bootloader
#define VECT_TAB_OFFSET						0x5000		//

#if (USER_BOOT_EXIST == 1)
	/* APP中断向量表地址偏移 需要在Linker选项卡勾选“USE Memory Layout from Target Dialog”,并修改Target选项卡中的ROM的Start地址 */
	nvic_vector_table_set(NVIC_VECTTAB_FLASH, VECT_TAB_OFFSET);
	__enable_irq();	//解除中断屏蔽
#endif

void nvic_vector_table_set(uint32_t nvic_vict_tab, uint32_t offset)
{
    SCB->VTOR = nvic_vict_tab | (offset & NVIC_VECTTAB_OFFSET_MASK);
}
  • 如果在Bootloader跳转APP前就设置了中断向量偏移,则略过。
  • NVIC_VECTTAB_OFFSET_MASK掩码为0x1FFFFF80是因为VTOR寄存器的格式
  • APP地址偏移不能随意,CortexM3 M4权威指南中有如下描述。且还要关注该型号单片机的Flash每页大小,因为写Flash时需要整页擦除,须避免APP和Bootloader代码在同一页。

欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/langs/914733.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-16
下一篇 2022-05-16

发表评论

登录后才能评论

评论列表(0条)

保存