【解决方案】STM32单片机实现USB DFU IAP在线烧写程序的功能,但JumpToApplication跳转到用户程序时出现HardFault错误的解决办法

跳转代码如下:

/*
  DFU工具下载的子程序虽然起始地址不是0x08000000 (由项目属性Target选项卡中的IROM1配置)
  但是也可以在Keil中直接下载程序, 而且还能使用ST-Link进行程序调试
  只要在DFU主程序中禁用CRC校验就行
  请确保system_stm32xxxx.c中设置的SCB->VTOR刚好等于程序的起始地址
*/
#define CRCEN 1 // 若想要直接在Keil中下载并用ST-Link调试子程序, 则需要在主程序中禁用CRC校验
#define START_ADDR 0x08003a00 // 一定要保证这个地址不在DFU程序的地址范围中, 否则擦除Flash会直接Hard Error
#define RESERVED_SIZE 0x100 // 请参考system_xxx.c中描述的VECT_TAB_OFFSET的对齐方式

typedef void (*Runnable)(void);

static int jump_to_application(void)
{
#if CRCEN
  const uint8_t *crc = (const uint8_t *)(START_ADDR + 4);
  const uint32_t *size = (const uint32_t *)START_ADDR;
  uint32_t crc2;
#endif
  static Runnable run;
  uint32_t *addr = (uint32_t *)(START_ADDR + RESERVED_SIZE + 4);
  uint32_t *msp = (uint32_t *)(START_ADDR + RESERVED_SIZE);
  
#if CRCEN
  if (START_ADDR + RESERVED_SIZE + *size >= FLASH_BASE + FLASH_SIZE)
#else
  if ((*msp & 0xf0000000) != 0x20000000 || (*addr & 0xff000000) != 0x08000000)
#endif
  {
    printf("Program data error!\n");
    return -1;
  }
  
#if CRCEN
  crc2 = calc_crc8((const uint8_t *)START_ADDR + RESERVED_SIZE, *size);
  if (*crc == crc2)
  {
    printf("CRC passed! addr=0x%08x, msp=0x%08x\n", *addr, *msp);
#endif
    printf("Jump to application...\n");
    run = (Runnable)*addr;
    
    // 一旦修改了栈顶指针, 就不能再使用函数中的局部变量, 否则可能会出现Hard Error错误
    // 但可以使用static变量, 所以run变量必须声明为static
    __set_MSP(*msp); // 修改栈顶指针
    run();
    return 0;
#if CRCEN
  }
  else
  {
    printf("CRC failed! 0x%02x!=0x%02x\n", *crc, crc2);
    return -1;
  }
#endif
}

程序保存在(START_ADDR + RESERVED_SIZE)地址处。跳转的核心代码很简单,先__set_MSP设置栈顶指针,然后执行函数指针run指向的函数。

最开始,这个函数的run变量没有加static,是一个普通的局部变量,意味着这个变量是在栈上分配的。
而跳转时先执行了__set_MSP修改了栈顶指针,导致了run变量失效,所以执行run()函数就出错了。

所以,给run变量加上static修饰符,使其成为从全局空间分配的静态变量,这样即使修改了栈顶指针,run变量也不会失效。执行run指向的函数也能成功。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章