• 【STM32】IAP升级01 bootloader实现以及APP配置(主要)


    APP程序以及中断向量表的偏移设置

    前言

    通过之前的了解 之前的了解,我们知道实现IAP升级需要两个条件:
    1.APP程序必须在 IAP 程序之后的某个偏移量为 x 的地址开始;
    2.APP程序的中断向量表相应的移动,移动的偏移量为 x;

    1.APP 程序起始地址设置

    默认条件下的起始地址

    默认的条件下,图中 IROM1 的起始地址(Start)一般为 0x08000000,大小(Size)为 0x100000,即从 0x08000000 开始的 1024K 空间为我们的程序存储区。
    在这里插入图片描述

    设置APP起始地址

    存储在flash上的APP起始地址设置方法

    设置起始地址(Start)为 0x08010000,偏移量为 0x10000(64K 字节,即留给 BootLoader 的空间),因而,留给 APP 用的 FLASH 空间(Size)为 0x80000-0x10000=0x70000(448KB)。设置好 Start 和 Size,就完成 APP 程序的起始地址设置。IRAM 是内存的地址,APP 可以独占这些内存,我们不需要修改。

    需要确保 APP 起始地址在 Bootloader 程序结束位置之后,并且偏移量为 0x200 的倍数即可
    在这里插入图片描述

    存储在SRAM上的APP起始地址设置方法

    将 IROM1 的起始地址(Start)定义为:0x20001000,大小为 0x19000(100K 字节),即从地址 0x20000000 偏移0x1000 开始,存放 SRAM APP 代码。
    STM32F407ZGT6 只有一个 128K(不算 CCM)的片内 SRAM,存放程序的位置与变量的加载位置不能重复,所以我们需要设置 IRAM1 中的地址(SRAM)的起始地址变为 0x2001A000,分配大小只有 0x6000(24K 字节)。整个 STM32F407ZGT6 的 SRAM(不含 CCM)分配情况为:最开始的 4K 给 Bootloader 程序使用,随后的 100K 存放 APP 程序,最后 24K,用作 APP 程序的内存。
    在这里插入图片描述

    2. 重新设置中断向量表的地址

    SCB->VTOR寄存器存放的是中断向量表的起始地址。默认的情况它由 BOOT 的启动模式决定。重定向这个位置,这样就可以从 Flash 区域的任意位置启动代码。

    是你跳转到app程序后,你后续得跑app的中断向量表。要实现这个就必须在刚进入app的时候重定向中断向量表

    /* 设置 NVIC 的向量表偏移寄存器,VTOR 低 9 位保留,即[8:0]保留 */
     SCB->VTOR = baseaddr | (offset & (uint32_t)0xFFFFFE00);
    
    • 1
    • 2

    baseaddr 为基地址(即 APP 程序首地址),Offset 为偏移量

    比如 FLASH APP 设置中断向量表偏移量为 0x10000

    SCB->VTOR = FLASH_BASE | ( 0x10000 & (uint32_t)0xFFFFFE00);
    
    • 1

    SRAM APP 的情况可以参考正点原子探索者开发指南V1.1。

    3.设置keil生成bin文件

    MDK 默认生成的文件是.hex 文件,并不方便用作 IAP更新,我们希望生成的文件是.bin 文件.

    hex和bin文件的区别

    简单来说:HEX文档是ascii码的文档。 是不能直接烧到单片机中的。 中间要有转换程序。 但是现在很多编程器都设计成直接可以导入hex文件烧录的,其实这是做了设计的。 bin文件是二进制文件,是可以直接烧到芯片中,中间不用转换的。

    设置keil生成bin文件

    在 MDK 点击 Options for Target→User 选项卡,在 After Build/Rebuild一栏中,勾选 Run #1,我们推荐使用相对地址,在勾选的同一行后的输入框并写入命令行:fromelf --bin -o …\Output@L.bin …\Output%L
    在这里插入图片描述
    D:\Keil_v5\ARM\ARMCC\bin\fromelf.exe是MDK 自带的格式转换工具 fromelf.exe的路径。

    通过这一步设置,我们就可以在 MDK 编译成功之后,调用 fromelf.exe,
    ..\..\Output\%L 表示当前编译的链接文件(…\是相对路径,表示上级目录,编译器默认从工程文件*.uvprojx 开始查找,根据我的工程文件 Output 的位置就能明白路径的含义)
    指令–bin –o …\Output@L.bin表示在 Output 目录下生成一个.bin 文件,
    @L 在 Keil 的下表示 Output 选项卡下的 Name of Executable 后面的字符串,即在 Output 文件夹下生成一个 atk_f407.bin 文件。

    在得到.bin 文件之后,我们只需要将这个 bin 文件传送给单片机,即可执行 IAP 升级。

    Bootloader程序的实现

    1、APP文件的编写与通常编写一致,只需要设定好偏移地址
    2、新的中断向量表的偏移设置好
    这两点在前文已经写过,接下来就是bootloader的实现了。

    Bootloader 和 app 属于两个独立的工程,不是一个工程!!!
    相关资料:
    1.写 STM32 内部 Flash 的功能用到 STM32 的 Flash操作。STM32片上Flash操作
    2.设置bootloader的起始地址为复位之后第一个启动的工程。
    这个例子中,bootloader从0x800 0000开始,APP从0x800 8000开始,执行完bootloader之后再执行APP。

    BootLoader 的编译器设置
    在这里插入图片描述
    App 的编译器设置
    在这里插入图片描述

    1.bootloader程序也是一个app程序

    是我们专门用来搬运APP程序的工具,所以也和是和普通程序一样的执行流程。
    IAP升级程序(bootloader)启动流程
    在这里插入图片描述
    简单来说
    初始化好时钟,GPIO,外设(依照升级方法来确定初始化对应的外设),那么这块MCU就有了可以和外部通信的能力。
    在这里插入图片描述

    例如,使用串口升级,那么步骤就是

    bootloader的main之前

    1.单片机复位,从0x800 0000开始执行。
    2.执行0x0800 0004处的Rest_Handler复位中断向量函数
    3. 复位中断向量函数执行
    在这里插入图片描述
    调用SystemInit函数。这个函数里面开启了外部晶振,设置了PLL,除能了所有中断,设置了时钟

    _main 标号表示 C/C++标准实时库函数里的一个初始化子程序 _main的入口地址。该程序的一个主要作用是初始化堆栈(跳转_user_initial_stackheap标号进行初始化堆栈),并初始化映像文件,最后跳转到C程序中的main函数。这也正解释了为什么所有的C程序必须有一个main函数作为程序的起点,因为这是由C/C++标准实时库所规定的。
    4.跳转到IAP升级程序(bootloader)的main函数。

    bootloader的main之后

    1.初始化

    初始化和正常没有bootloader 的程序一下,该咋样就咋样,为了避免无效功耗损失,一般是需要什么就初始化什么。时钟初始化,串口初始化,定时器初始化等(如果有用到定时轮询或者定时器延时等可以初始化)

    2.检查APP 程序是否完整

    若出现某些情况导致APP程序不全,有可能导致程序跑飞,进入硬件错误。

    初始化完成后,首先检测 APP 分区中最后地址的标志位是否符合(如是否等于0x5a5a,一般APP程序的末尾加入特殊字符,以检测完整性),如果符合,启动倒计时(时间大约几百毫秒就行,根据实际情况决定),等待进入 APP 程序,在此期间,也会等待接收升级的指令;否则,则一直等待接收升级的指令。
    check收到的估计bin的最后四个字节是否为0x5A5A

    int CheckIfAPPCanJump(void)
    {
        uint32_t *pFlag = (uint32_t *)(APP_FLASH_CODE_BASE + APP_FLASH_CODE_SIZE - 4);
        if (*pFlag == 0x5A5A)
        { return 1; }
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    3.升级

    接收到升级指令后,则启动升级流程,升级期间不允许进入 APP 程序。

    4.跳转APP程序(IAP 如何实现跳转到用户程序)

    升级完成后,需要跳转到 APP 程序(可以自动跳转,也可以增加跳转指令手动跳转),跳转前的准备:
    1.检测 APP 程序是否存在,若存在,则继续往下执行APP升级
    2.关闭全局中断(可选,放置升级中被打断)
    3.清除所有中断标志位(可选,放置升级中被打断)

    void Disable_irq(void)
    {
        uint8_t i;
     
    	 __disable_irq(); // 关闭总中断
        //__enable_irq();
     
        /* 清除所有中断标志 */
        for(i= 0; i < CRS_IRQn; i++)
        {
            NVIC_ClearPendingIRQ((IRQn_Type)i);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    4.跳转 APP 启动程序地址(当然,也能直接跳转到 APP 程序的 main 函数,但在跳转前需要重新初始化堆栈等内容,所以最好还是交给启动文件处理,跳转到resethandle)

    4.1 设定跳转地址

    typedef  void (*pFunction)(void);       //定义一个pFunction类型 这是个函数指针
    pFunction Jump_To_Application;          //Jump_To_Application设置为pFunction类型
    
    /*设定跳转地址,FLASH_USER_START_ADDR是APP程序的起始地址*/
    JumpAddress = *(__IO uint32_t*) (FLASH_USER_START_ADDR + 4);
    Jump_To_Application = (pFunction) JumpAddress;//将APP程序的中断向量表的Rest_Handler地址赋值给Jump_To_Application
    __set_MSP(*(__IO uint32_t*) FLASH_USER_START_ADDR); //这里是将把应用程序起始地址设为栈顶指针
    Jump_To_Application(); //设置PC指针为复位地址,你可以理解为跳转到应用程序的函数
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    设定跳转地址,FLASH_USER_START_ADDR是APP程序的起始地址,+4是因为中断向量表每四个字节代表一个中断函数的地址。所以JumpAddress指向APP中断向量表的Rest_Handler.

    将跳转地址JumpAddress强制转换pFunction类型,可以理解为编译器将其编译成一个函数

    2.官方的demo,以stm32f10x为例

    IAP/src/main.c 
     
    int main(void)
    {
      /* Flash unlock */
      /*flash解锁,因为需要操作flash*/
      FLASH_Unlock();
     
      /* Initialize Key Button mounted on STM3210X-EVAL board */
      /*初始化按键,demo中的升级触发条件为按键按下*/         
      STM_EVAL_PBInit(BUTTON_KEY, BUTTON_MODE_GPIO);   
     
      /* Test if Key push-button on STM3210X-EVAL Board is pressed */
      if (STM_EVAL_PBGetState(BUTTON_KEY)  == 0x00)
      { 
        /* If Key is pressed */
        /*如果按键按下,即触发升级,进行升级*/
        /* Execute the IAP driver in order to re-program the Flash */
        /*初始化串口,demo里面使用的是usart1 + Y-MODe协议*/
        IAP_Init();
        SerialPutString("\r\n================================================================");
        SerialPutString("\r\n=          (C) COPYRIGHT 2010 STMicroelectronics           =");
        SerialPutString("\r\n=                                                          =");
        SerialPutString("\r\n=  In-Application Programming Application  (Version 3.3.0) =");
        SerialPutString("\r\n=                                                          =");
        SerialPutString("\r\n=                       By MCD Application Team            =");
        SerialPutString("\r\n============================================================");
        SerialPutString("\r\n\r\n");
        /*升级菜单,用户自己实现*/
         /*升级中可选关闭所有中断,防止升级被打断,但是在跳转到APP程序后要第一时间打开总中断*/
        Main_Menu ();
      }
      /* Keep the user application running */
      else//不升级 进入APP
      {
        /* Test if user code is programmed starting from address "ApplicationAddress" */
        /*升级条件不满足,跳转到用户程序处执行用户程序*/
        if (((*(__IO uint32_t*)ApplicationAddress) & 0x2FFE0000 ) == 0x20000000)//检测栈顶指针
        { 
          /* Jump to user application */
          /*ApplicationAddress为用户程序的栈地址,+4便为用户程序的复位中断向量地址*/  
          JumpAddress = *(__IO uint32_t*) (ApplicationAddress + 4);
          Jump_To_Application = (pFunction) JumpAddress;
          /* Initialize user application's Stack Pointer */
          __set_MSP(*(__IO uint32_t*) ApplicationAddress);
          /*执行用户空间的复位中断向量函数,里面主要是进行系统时钟配置,执行用户空间的main函数数*/  
          Jump_To_Application();
        }
      }
     
      while (1)
      {}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53

    检测栈顶指针

    升级菜单demo,更具需求裁剪

    void Main_Menu(void)
    {
      uint8_t key = 0;
      
      /* Get the number of block (4 or 2 pages) from where the user program will be loaded */
      /*计算IAP占用的flash页数*/  
      BlockNbr = (FlashDestination - 0x08000000) >> 12;
     
      /* Compute the mask to test if the Flash memory, where the user program will be
         loaded, is write protected */
    #if defined (STM32F10X_MD) || defined (STM32F10X_MD_VL)
      UserMemoryMask = ((uint32_t)~((1 << BlockNbr) - 1));
    #else /* USE_STM3210E_EVAL */
      if (BlockNbr < 62)
      {
        UserMemoryMask = ((uint32_t)~((1 << BlockNbr) - 1));
      }
      else
      {
        UserMemoryMask = ((uint32_t)0x80000000);
      }
    #endif /* (STM32F10X_MD) || (STM32F10X_MD_VL) */
     
     
      /* Test if any page of Flash memory where program user will be loaded is write protected */
      /*检测flash中用户空间的写保护锁是否开启*/ 
      if ((FLASH_GetWriteProtectionOptionByte() & UserMemoryMask) != UserMemoryMask)
      {
        FlashProtection = 1;
      }
      else
      {
        FlashProtection = 0;
      }
     
      while (1)
      {
        SerialPutString("\r\n================== Main Menu ============================\r\n\n");
        SerialPutString("  Download Image To the STM32F10x Internal Flash ------- 1\r\n\n");
        SerialPutString("  Upload Image From the STM32F10x Internal Flash ------- 2\r\n\n");
        SerialPutString("  Execute The New Program ------------------------------ 3\r\n\n");
        
        if(FlashProtection != 0)
        {
          SerialPutString("  Disable the write protection ------------------------- 4\r\n\n");
        }
        
        SerialPutString("==========================================================\r\n\n");
        
        key = GetKey();
     
        if (key == 0x31)
        {
          /* Download user application in the Flash */
          /*下载程序*/  
          SerialDownload();
        }
        else if (key == 0x32)
        {
          /* Upload user application from the Flash */
          SerialUpload();
        }
        else if (key == 0x33)
        {
          JumpAddress = *(__IO uint32_t*) (ApplicationAddress + 4);
     
          /* Jump to user application */
          Jump_To_Application = (pFunction) JumpAddress;
          /* Initialize user application's Stack Pointer */
          __set_MSP(*(__IO uint32_t*) ApplicationAddress);
          Jump_To_Application();
        }
        else if ((key == 0x34) && (FlashProtection == 1))
        {
          /* Disable the write protection of desired pages */
          FLASH_DisableWriteProtectionPages();
        }
        else
        {
          if (FlashProtection == 0)
          {
            SerialPutString("Invalid Number ! ==> The number should be either 1, 2 or 3\r");
          }
          else
          {
            SerialPutString("Invalid Number ! ==> The number should be either 1, 2, 3 or 4\r");
          } 
        }
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90

    进入APP

    APP 代码起始设置见前文
    APP中断向量表需要偏移是你跳转到app程序后,你后续得跑app的中断向量表。要实现这个就必须在刚进入app的时候重定向中断向量表

    注意在 mian 函数起始处重新设置中断向量表(寄存器 SCB->VTOR)的偏移量,否则 APP 无法正常运行

    void SetVectorTable(void)
    {
        uint8_t i;
     
        SCB->VTOR = APP_FLASH_CODE_BASE;
     
        /* 清除所有中断标志 */
        for(i= 0; i < CRS_IRQn; i++)
        {
            NVIC_ClearPendingIRQ((IRQn_Type)i);
        }
        
        /* 在BOOT中跳转之前若关闭了全局中断, 此需要重新打开 */
        __enable_irq();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
  • 相关阅读:
    多目标优化算法matlab代码大合集
    多媒体ffmpeg学习教程
    鼠标滚轮滚动切换内容
    高薪程序员&面试题精讲系列129之你熟悉哪些版本控制工具?你知道哪些Git命令?fetch与pull有哪些区别?
    深入理解完美哈希
    【概率论基础进阶】随机变量及其分布-随机变量及其分布函数
    Tomcat Java内存马 listener型
    【C++编程语言】之STL常用算法之 拷贝和替换算法 copy()函数 replace()函数 replace_if()函数 swap()函数
    Java:爬虫htmlunit抓取a标签
    Qt翻译(本地化)坑总结
  • 原文地址:https://blog.csdn.net/apythonlearner/article/details/133176744