GD32 IAP跳转全解析:从向量表重定位到实战排坑指南
2026/7/29 4:33:16 网站建设 项目流程

1. 从一次“变砖”事故说起:为什么IAP跳转是道坎

那天下午,测试同事拿着刚焊好的几块板子来找我,脸色不太好看。“这几块板子,用上位机升级完固件后,重启就再也不动了,串口没任何打印,跟砖头一样。”我接过板子,连上J-Link,发现芯片确实跑飞了,PC指针指在一个奇怪的地址上。问题很明确:Bootloader通过串口接收完新的APP固件,写入Flash,然后执行跳转,但这一跳,就再也没回来。

这不是我第一次遇到GD32的IAP跳转问题,相信也不会是最后一次。IAP,全称In-Application Programming,即在应用编程,是嵌入式设备实现固件远程升级、功能迭代的核心技术。其核心流程可以概括为:设备上电先运行Bootloader程序,Bootloader检查是否有升级需求(如通过串口接收新固件),如果没有,则跳转到存储在Flash另一区域的APP应用程序并执行。听起来逻辑清晰,但魔鬼藏在细节里。从Bootloader跳转到APP这个动作,涉及中断向量表重映射、栈指针初始化、编译器链接脚本配置、芯片启动模式等多个环节,任何一个环节配置不当,都会导致跳转后程序跑飞、死机,也就是我们常说的“变砖”。

网上关于STM32的IAP教程汗牛充栋,但GD32虽然硬件兼容,在启动流程、Flash操作等底层细节上仍有自己的“脾气”。直接套用STM32的经验,很可能就会踩坑。结合热搜词里高频出现的“gd32 bootloader跳转应用后不执行”、“gd32 bor导致死机”、“应用程序app里面的程序基地址要怎样设置呢”,可以看出这是GD32开发者普遍面临的痛点。本文将结合我多次踩坑和填坑的经验,为你拆解GD32 IAP跳转的全流程,不仅告诉你“怎么做”,更重点剖析“为什么这么做”,以及那些数据手册和官方例程里不会写的“坑点”。

2. 理解跳转的本质:不仅仅是修改PC指针

很多人认为,从Bootloader跳转到APP,无非就是用一个函数指针,指向APP的入口地址然后调用。这种理解过于简单,是导致后续一系列问题的根源。真正的跳转,是一次完整的“软重启”过程,需要为APP创造一个干净的、符合其预期的运行时环境。

2.1 中断向量表的重定位:第一个拦路虎

这是跳转操作中最关键的一步。Cortex-M内核(包括GD32使用的ARM Cortex-M3/M4/M23等)芯片,上电或复位后,硬件会从内存地址0x0000 0000(称为向量表起始地址)处读取前两个32位数据:第一个是初始栈顶指针(MSP)的值,第二个就是复位向量,即程序开始执行的第一条指令地址。之后,所有中断服务程序的入口地址也依次排列在这个向量表中。

在只有单一APP的传统工程中,链接脚本通常将向量表放在Flash起始地址(例如0x0800 0000),芯片的启动配置(Boot0引脚)会让硬件从该地址映射到0x0000 0000,一切顺理成章。

但在IAP方案中,Flash被划分为两个区域:

  • Bootloader区:通常从0x0800 0000开始,占用一定大小(如16KB)。
  • APP区:从Bootloader区之后开始,例如0x0800 4000(假设Bootloader占16KB)。

此时,APP的编译链接地址(VMA)是0x0800 4000,它的中断向量表自然也编译在了这个地址。但是,芯片上电启动后,硬件依然从0x0000 0000(映射到Bootloader的0x0800 0000)去取向量表。如果直接跳转到APP的代码,当中断发生时,硬件还是会去0x0800 0000附近找中断服务函数,但那里是Bootloader的代码,必然导致程序崩溃。

解决方案:在APP的启动代码(通常是startup_gd32xxxx.s汇编文件或system_gd32xxxx.c中的SystemInit函数)的最开始,必须重新设置向量表偏移寄存器(VTOR)。对于Cortex-M3/M4/M33内核,VTOR寄存器是可写的。

在APP的初始化阶段,需要添加如下代码:

// 在APP的main函数之前,例如在SystemInit函数中 #ifdef VECT_TAB_SRAM // 如果向量表在SRAM,则设置SRAM地址 SCB->VTOR = SRAM_BASE | VECT_TAB_OFFSET; #else // 默认在Flash,设置APP的起始地址 SCB->VTOR = FLASH_BASE | VECT_TAB_OFFSET; // FLASH_BASE 应为 0x08000000 #endif

关键在于,这里的VECT_TAB_OFFSET必须是APP区相对于Flash基址的偏移量。如果APP起始于0x0800 4000,那么VECT_TAB_OFFSET就是0x4000。很多GD32的官方BSP包中,system_gd32xxxx.c文件里已经预留了VECT_TAB_OFFSET的定义,你只需要在编译器预定义宏或选项里修改它即可,例如-DVECT_TAB_OFFSET=0x4000

踩坑点1:忘记在APP中设置VTOR。这是导致跳转后,一旦发生中断(特别是SysTick定时器中断)就立即死机的最常见原因。Bootloader跳转前关闭所有中断只是治标,APP运行后中断总会被打开。

2.2 栈指针的初始化:为APP铺好第一条路

在跳转的那一刻,CPU的当前栈指针(SP)指向的是Bootloader运行时使用的栈空间。直接跳转到APP的main函数是危险的,因为APP的启动代码期望SP是从自己的向量表第一项加载的初始值。

因此,规范的跳转函数应该模拟一次复位后的硬件行为:

  1. 获取APP起始地址(即APP的向量表起始地址)。
  2. 从该地址读取第一个字,作为新的栈顶指针(MSP)值。
  3. 从该地址读取第二个字,即APP的复位中断服务程序入口地址。
  4. 将步骤1中读取的栈指针值赋给当前CPU的MSP寄存器。
  5. 将步骤2中读取的复位地址转换为一个函数指针。
  6. 调用这个函数指针(对于ARM Cortex-M,通常使用汇编指令BXBLX)。

下面是一个经过实践验证的跳转函数:

typedef void (*pFunction)(void); pFunction JumpToApplication; uint32_t JumpAddress; /** * @brief 执行跳转到应用程序 * @param app_addr: 应用程序的起始地址(即APP中断向量表地址) * @retval 无 */ void iap_jump_to_app(uint32_t app_addr) { // 1. 检查app_addr是否是一个合法的可执行地址(对齐检查、范围检查) if(((*(__IO uint32_t*)app_addr) & 0x2FFE0000) == 0x20000000) // 栈顶值应在SRAM范围内 { // 2. 关闭所有中断,防止在跳转过程中发生中断 __disable_irq(); // 3. 设置主栈指针(MSP)为APP向量表的第一个条目 __set_MSP(*(__IO uint32_t*)app_addr); // 4. 获取APP的复位处理函数地址(向量表第二个条目) JumpAddress = *(__IO uint32_t*)(app_addr + 4); JumpToApplication = (pFunction)JumpAddress; // 5. 初始化APP的数据段(.data)和清零BSS段(.bss)的工作,应由APP自己的启动代码完成。 // Bootloader无需也不应该代劳。 // 6. 执行跳转 JumpToApplication(); } else { // 地址非法,跳转失败,可以在此处触发软件复位或进入错误处理 NVIC_SystemReset(); } }

踩坑点2:跳转前没有禁用全局中断。如果在跳转过程中发生中断,而CPU已经跳转到APP区域但VTOR还未被APP初始化,中断向量获取错误,直接死机。务必使用__disable_irq()踩坑点3:跳转函数使用__set_CONTROL(0)吗?对于从Bootloader(可能使用了PSP进程栈)跳转到APP的场景,确保回到使用MSP的状态是好的,但更关键的是MSP本身的值必须来自APP的向量表。上述代码中的__set_MSP是关键。

3. 工程配置的双向奔赴:Bootloader与APP的编译设置

跳转逻辑写对了,但如果Bootloader和APP的工程配置“各想各的”,依然无法成功牵手。这需要双方在编译和链接阶段就达成一致。

3.1 链接脚本(.ld / .sct)的修改:地址空间的约定

链接脚本告诉链接器,程序的各个段(代码.text、只读数据.rodata、已初始化数据.data、未初始化数据.bss等)应该放在存储器的什么位置。

对于Bootloader工程:

  • ROM(FLASH)起始地址:通常是0x0800 0000,大小根据实际需求设定,例如16K (0x4000)。
  • RAM起始地址和大小:按芯片实际SRAM配置,如0x2000 0000,大小64K。
  • 重点:Bootloader的代码和向量表必须从0x0800 0000开始编译链接,因为芯片上电就从这里启动。

对于APP工程:

  • ROM(FLASH)起始地址:必须是Bootloader区之后的地址。例如Bootloader占0x4000,则APP起始于0x0800 4000。大小则为总Flash减去Bootloader和可能预留的其他区域(如参数存储区)。
  • RAM起始地址:可以和Bootloader相同(0x2000 0000)。但这里有一个大坑:Bootloader和APP的栈、堆以及全局变量都使用同一块物理RAM。如果Bootloader在跳转前没有妥善清理,或者APP的启动代码错误地初始化了RAM内容,可能会互相影响。
    • 推荐做法:在链接脚本中,为APP的栈预留空间时,可以考虑留有一定的余量,或者确保Bootloader在跳转前将其用到的全局变量区清零(但这并非必须,因为APP的启动代码会初始化自己的.data.bss段)。更关键的是避免双方使用固定的RAM地址进行通信时发生重叠,如果需要在Bootloader和APP间传递参数,应使用Flash的特定扇区或带ECC的SRAM区域(如果芯片支持)。

以使用ARM GCC编译链和.ld脚本为例,APP的链接脚本内存部分应类似:

MEMORY { RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 64K FLASH (rx) : ORIGIN = 0x8004000, LENGTH = 256K-16K /* 假设总Flash 256K,Bootloader用16K */ }

在Keil MDK中,则需要通过Options for Target -> Target选项卡,修改IROM1的起始地址和大小。

3.2 中断向量表的偏移量设置:告诉APP“你在哪”

如前所述,APP需要知道自己的向量表在Flash中的偏移。这个配置通常在工程预编译宏中完成。

  • 在Keil MDK中Options for Target -> C/C++ -> Preprocessor Symbols,添加或修改VECT_TAB_OFFSET=0x4000
  • 在IAR Embedded Workbench中Options -> C/C++ Compiler -> Preprocessor -> Defined symbols,添加VECT_TAB_OFFSET=0x4000
  • 在GCC Makefile或CMakeLists.txt中:在编译参数中添加-DVECT_TAB_OFFSET=0x4000

确保这个值与你APP的Flash起始地址计算出的偏移量完全一致。例如APP起始于0x0800 8000,则偏移量为0x8000。

3.3 系统初始化代码的检查:确保VTOR被正确写入

光定义了宏还不够,必须确保这个宏被启动代码使用。打开APP工程中的system_gd32xxxx.c文件,找到SystemInit函数,查看其中关于向量表设置的部分。它应该类似下面这样:

void SystemInit(void) { /* ... 其他系统时钟初始化 ... */ #ifdef VECT_TAB_SRAM SCB->VTOR = SRAM_BASE | VECT_TAB_OFFSET; #else SCB->VTOR = FLASH_BASE | VECT_TAB_OFFSET; #endif }

确认FLASH_BASE通常是0x08000000,VECT_TAB_OFFSET就是你上面定义的宏。编译后,你可以通过反汇编查看SystemInit函数,确认是否有对SCB->VTOR的赋值指令。

踩坑点4:不同的GD32系列或固件库版本,SystemInit函数可能位于不同的文件,或者向量表设置的方式略有不同。务必以你实际使用的BSP包代码为准。有些旧版本库可能需要手动修改system_xxx.c文件中的VECT_TAB_OFFSET常量。

4. 跳转前的临门一脚:Bootloader的善后工作

在Bootloader决定跳转并调用iap_jump_to_app函数之前,它需要为APP的启动创造一个“无菌”环境。

4.1 外设与时钟的复位

Bootloader可能初始化并使用了某些外设,如GPIO、USART、DMA、定时器等。跳转前,应该将这些外设反初始化(DeInit)或恢复到复位状态。否则,APP重新初始化这些外设时可能会遇到硬件状态冲突。

  • 关闭外设时钟:在GD32中,外设时钟默认是关闭的。如果Bootloader打开了某些外设时钟(如USART0),跳转前最好将其关闭(rcu_periph_clock_disable(RCU_USART0))。这不是必须的,因为APP通常会重新开启它需要的时钟,但这是一个好习惯。
  • 复位外设寄存器:更彻底的做法是调用外设的deinit函数,例如usart_deinit(USART0)。这会将外设的所有寄存器恢复到复位默认值。
  • 特别注意DMA和中断:如果Bootloader使用了DMA进行数据传输(例如串口DMA接收固件包),务必在传输完成后、跳转前,停止DMA、清除所有相关标志位和中断使能。DMA控制器可能还在访问内存,强行跳转会引发总线错误。

4.2 全局中断与SysTick的处理

  • 禁用全局中断:如前所述,在跳转函数中第一时间__disable_irq()
  • SysTick定时器:SysTick通常被用作操作系统(如FreeRTOS)的时基,或者简单的延时函数(delay_ms)。如果Bootloader使用了SysTick,跳转前需要将其禁用。
    SysTick->CTRL = 0; // 禁用SysTick定时器和中断
    因为APP的启动代码或操作系统可能会重新配置SysTick,如果旧的SysTick中断在跳转后触发,而中断向量指向错误,会导致死机。

4.3 Flash解锁与上锁状态的考量

Bootloader在升级过程中需要擦写Flash,因此会调用fmc_unlock()解锁Flash操作控制器。在跳转到APP之前,建议重新上锁Flashfmc_lock())。虽然APP在需要操作Flash时(如存储参数)可以自己解锁,但保持一个已知的、安全的状态(已上锁)跳转过去,是一个更稳妥的做法。避免APP一开始运行就处在Flash可写的异常状态下。

4.4 栈指针的显式设置

尽管我们的跳转函数里已经通过__set_MSP设置了主栈指针,但在跳转函数执行前,确保Bootloader本身的栈没有溢出或处于异常状态。一个简单的做法是,在跳转判断逻辑的最后阶段,尽量减少局部变量的使用,避免深层的函数调用。

5. 实战排坑:那些让你抓狂的“玄学”问题

即使你严格遵循了上述步骤,仍然可能遇到一些诡异的问题。下面分享几个我亲身经历过的坑。

5.1 跳转后“偶尔”死机,尤其是开启优化后

现象:跳转大部分时间成功,但偶尔(比如十次有一两次)APP启动失败。开启编译器优化(如-O2)后,失败率显著增加。

排查

  1. 检查栈对齐:ARM Cortex-M内核要求栈指针(SP)在访问内存时必须8字节对齐(对于支持双精度浮点单元的设备)。如果APP向量表第一个字(栈顶初值)不是8字节对齐的,在某些严格的内存访问指令下会导致UsageFault。在APP的链接脚本中,确保栈顶地址是8字节对齐的。例如,在.ld脚本中:
    _estack = ORIGIN(RAM) + LENGTH(RAM) - 8; /* 假设RAM结束地址是0x20010000,这里就是0x2000FFF8 */ .stack : { . = ALIGN(8); _sstack = .; . = . + _Min_Stack_Size; . = ALIGN(8); _estack = .; } >RAM
    确保_estack(栈顶)是8字节对齐的。
  2. 检查跳转函数的属性:如果跳转函数iap_jump_to_app被编译器内联(inline)了,可能会影响栈帧和寄存器状态。可以尝试将该函数标记为__attribute__((noinline, noclone)),强制编译器不优化它。
    __attribute__((noinline, noclone)) void iap_jump_to_app(uint32_t app_addr)
  3. 关闭跳转前所有中断后,延迟一小段时间:有些外设中断标志位清除需要时间,或者DMA传输完成中断的清理需要等待。在__disable_irq()后,插入一个简单的空循环延时(几个微秒),再执行栈指针设置和跳转。

5.2 APP中使用FreeRTOS等RTOS时跳转失败

现象:裸机APP跳转正常,但一旦APP工程中加入了FreeRTOS,跳转后大概率死在启动阶段(可能在xPortStartScheduler中或之前)。

排查

  1. VTOR设置时机:确保在RTOS内核启动之前,VTOR已经被正确设置。通常RTOS的启动代码(如vTaskStartScheduler)会配置PendSV、SysTick等系统中断。如果VTOR指向的是Bootloader的向量表,这些中断的安装会出错。最好的位置是在main函数一开始、任何硬件和RTOS初始化之前调用一个设置VTOR的函数,或者确认SystemInit函数在main之前已被执行且正确设置了VTOR。
  2. SysTick冲突:Bootloader如果使用了SysTick,必须在跳转前彻底关闭(SysTick->CTRL = 0)。因为FreeRTOS会重新配置SysTick作为系统时基,旧的SysTick配置会干扰新的。
  3. 栈指针与任务栈:FreeRTOS使用PSP(进程栈指针)来运行任务,而MSP用于内核和异常。我们的跳转函数设置的是MSP。这通常是没问题的,因为CPU从复位向量开始执行时,使用的是MSP。FreeRTOS的启动代码会正确初始化PSP。但要确保跳转时,我们没有错误地切换到了PSP模式(通常不会)。

5.3 关于“GD32 BOR导致死机”的猜测

热搜词中出现了“gd32 bor导致死机”。BOR(Brown-out Reset,欠压复位)是芯片在电源电压低于某个阈值时触发的复位,用于防止电压不足时程序跑飞。

一种可能的踩坑场景是:在Bootloader进行Flash擦写操作时,功耗可能瞬时增大,如果电源设计余量不足,可能引发电压跌落,触发BOR复位。BOR复位后,芯片重新从Bootloader启动,但此时Flash操作可能被中断,导致Flash内容处于不一致或损坏的状态(例如扇区擦除到一半)。后续再跳转或执行APP时就会失败。

对策

  1. 硬件上:确保供电电源的电流能力和动态响应足够,在Flash编程期间,电源电压稳定。在芯片的VDD引脚附近放置足够容量的去耦电容(如10uF钽电容+100nF陶瓷电容)。
  2. 软件上:在Bootloader的Flash编程逻辑中,加入更强的健壮性检查。例如,在开始擦写一个扇区前,先读取其内容备份;在写入完成后,进行校验(CRC或字节对比)。如果校验失败,则标记该次升级失败,并尝试恢复或等待下一次升级。对于关键数据,可以考虑使用备份扇区等机制。

5.4 跳转后,APP中的局部变量或静态变量值异常

现象:APP运行后,某些函数的局部变量初值不是0,或者静态变量没有按预期初始化。

排查: 这很可能不是跳转问题,而是APP的启动代码没有正确初始化.data段和清零.bss段。在IAP方案中,这部分工作必须由APP自己的启动代码完成,Bootloader不应该也不能代劳。

  • .data:存放已初始化的全局变量和静态变量(如int g_var = 100;)。它的初始值存储在Flash中,启动时需要从Flash拷贝到RAM的对应位置。
  • .bss:存放未初始化的全局变量和静态变量(如int g_var2;),启动时需要将这片RAM区域清零。

在标准的ARM GCC启动文件(如startup_gd32xxxx.s)中,会有Reset_Handler汇编函数,其中会调用__libc_init_array等函数,这些函数最终会调用_startup之类的C函数来完成.data.bss的初始化。你必须确保APP工程使用的是完整的、标准的启动文件,并且链接脚本正确定义了_sidata,_sdata,_edata,_sbss,_ebss这些符号供启动代码使用。

一个常见的错误是,开发者为了节省空间,在APP工程中使用了一个“简化版”的启动文件,或者自己写的启动代码遗漏了数据段初始化,导致变量行为异常。

6. 调试技巧与验证手段

当跳转失败时,盲目的猜测效率很低。以下是一些有效的调试和验证手段:

  1. 利用硬件调试器(J-Link/ST-Link)

    • 在跳转函数处设断点:单步执行,观察app_addr的值是否正确(应该是APP的起始地址,如0x08004000)。
    • 观察栈指针(SP):在执行__set_MSP前后,观察MSP寄存器的值。执行后,MSP的值应该等于*(uint32_t*)app_addr),也就是APP向量表第一个字。这个值应该是一个合理的RAM地址(如0x2000xxxx)。
    • 观察程序计数器(PC):单步执行到JumpToApplication()这一句,然后Step Into。PC应该跳转到APP的复位向量地址(即*(uint32_t*)(app_addr+4))。继续单步,应该进入APP的Reset_Handler汇编代码。
    • 如果跳转后直接跑飞:在APP的Reset_Handler入口处设断点。如果断点能触发,说明跳转逻辑基本正确,问题可能出在APP的初始化(如VTOR设置、时钟配置)。如果断点无法触发,说明跳转的目标地址或执行流程根本不对,回头检查Bootloader的跳转代码和APP的起始地址/中断向量表内容。
  2. 查看内存内容

    • 验证APP向量表:在调试器的Memory窗口,查看APP起始地址(如0x0800 4000)。第一个字应该是栈顶初值(如0x20001000),第二个字应该是Reset_Handler的函数地址(通常指向0x0800xxxx范围内的一个地址)。如果第二个字是0xFFFFFFFF,说明这个Flash地址没有被正确编程,APP固件可能没有烧录或烧录失败。
    • 验证VTOR寄存器:在APP开始运行后(例如在main函数第一行设断点),查看SCB->VTOR寄存器的值。它应该等于0x08000000 + VECT_TAB_OFFSET
  3. 软件指示灯和日志

    • 在Bootloader的关键阶段(开始跳转、跳转前、跳转后)和APP的初始阶段(Reset_HandlerSystemInitmain开头),通过翻转一个GPIO引脚(接LED)或发送特定的字符串到串口(如果Bootloader和APP共用串口,需注意协议错开),可以直观地看到程序执行流。这是一种非常原始但有效的调试方法,特别是在没有调试器的情况下。
  4. 校验APP固件完整性

    • 在Bootloader跳转前,可以对APP区域的Flash内容计算CRC32校验和,与预先存储在固定位置(如APP固件末尾或单独的参数区)的CRC值进行比对。只有校验通过才执行跳转。这可以防止因传输错误、Flash写入不完整等原因导致的跳转后崩溃。

7. 一个完整的跳转流程 checklist

最后,我将整个流程浓缩成一个检查清单,供你在实现和调试时逐项核对:

Bootloader侧:

  • [ ]链接脚本:ROM起始地址为0x0800 0000,大小正确。
  • [ ]跳转地址:跳转函数接收的app_addr参数是否正确(通常是APP的起始地址,如0x08004000)?这个地址是否通过可靠的机制获取(如从固定Flash扇区读取)?
  • [ ]跳转函数
    • [ ] 是否检查了目标地址的合法性(栈顶值在RAM范围内)?
    • [ ] 是否在跳转前__disable_irq()禁用了所有中断?
    • [ ] 是否使用__set_MSP(*(uint32_t*)app_addr)正确设置了主栈指针?
    • [ ] 是否将复位向量地址转换为函数指针并跳转?
  • [ ]环境清理
    • [ ] 是否关闭了已使用的硬件外设时钟或进行DeInit?
    • [ ] 是否禁用了SysTick(SysTick->CTRL = 0)?
    • [ ] 是否停止了所有活动的DMA并清除了标志?
    • [ ] Flash操作控制器是否已重新上锁(fmc_lock())?
  • [ ]共享资源:如果Bootloader和APP通过共享内存(某块SRAM)通信,通信机制是否在跳转前已完成?APP是否知道如何读取这些数据?

APP侧:

  • [ ]链接脚本:ROM起始地址是否设置为Bootloader之后的正确地址(如0x08004000)?大小是否正确?
  • [ ]预编译宏:是否正确定义了VECT_TAB_OFFSET宏(如-DVECT_TAB_OFFSET=0x4000)?
  • [ ]启动代码SystemInit函数中是否包含根据VECT_TAB_OFFSET设置SCB->VTOR的代码?编译后反汇编确认该指令存在。
  • [ ]向量表内容:编译生成的APP二进制文件,其开头8个字节是否是有效的栈顶地址和复位向量地址?可以通过arm-none-eabi-objdump -s工具查看。
  • [ ]初始化依赖:APP是否没有依赖Bootloader留下的任何硬件状态(除了约定的共享数据)?APP的启动代码是否完整地初始化了自己的.data.bss段?

烧录与调试:

  • [ ]烧录APP:你是通过Bootloader的IAP功能烧录APP,还是直接使用编程器烧录?如果直接烧录,需要将APP二进制文件烧录到正确的偏移地址(如0x08004000),并且不能覆盖Bootloader区域
  • [ ]调试配置:在调试APP工程时,你的调试器(如J-Link)配置中的下载地址和复位地址是否也相应修改为APP的起始地址?否则调试器可能会错误地擦写Bootloader区域。

我个人的体会是,GD32的IAP跳转问题,十之八九出在向量表偏移(VTOR)栈指针初始化这两件事上。剩下的问题,往往与编译器优化外设状态残留以及电源完整性相关。按照本文梳理的流程和检查清单,耐心地一步步验证,大部分“玄学”问题都能找到根因。最后记住,在修改跳转相关代码后,务必完整地重新编译Bootloader和APP两个工程,并确保将正确的固件烧录到正确的位置。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询