你的浏览器版本过低,可能导致网站不能正常访问!
为了你能正常使用网站功能,请使用这些浏览器。

【经验分享】STM32F09x 不使用 BOOT 脚实现 System Bootloader 升级代码

[复制链接]
STMCU小助手 发布时间:2022-2-18 20:49
前言
众所周知,使用 STM32 时,当需要使用 System Memory 中的 Bootloader 进行代码升级的时候,需要将 BOOT0 脚拉高,复位后才能进入 Bootloader 程序,使用 Flash Loader Demonstrator 等工具进行串口烧写升级。这就需要在 BOOT0 这个引脚上留出按键或者是跳线脚。但是,STM32F09x 具有一项新的特性,使不必使用 BOOT 脚而进行串口升级成为可能。我们来共同探讨一下。
问题
某客户在其产品的设计中,使用了 STM32F091RCT6,产品在出厂后将来可能由于功能的升级需要升级代码。由于外观的需要,客户不希望留一个用于升级的按键或是跳线槽在外边。希望能够是通过接收串口命令来实现启动升级,又希望能够直接使用 System Memory 中的 Bootloader 进行代码升级。

调研
1.认识一下 STMF09x 和 STM32F04x 在 Boot Configuration 中的新特性
打开参考手册 RM0091,翻到 Boot Configuration 那一节,我们可以看到 Table 3 中对 Boot Mode 进行了描述,如下:

5UCW8UG)H7I~_ND~4FNXNIP.png

从表格中,我们先来看一下第 1 条注释:
“Grey options are available on STM32F04x and STM32F09x devices only.”
意思是说,灰色部分对 STM32F04x 和 STM32F09x 是有效的。也就是说,除了 BOOT1 是在 Option Byte 中的 nBOOT1 配置之外,STM32F04x 和 STM32F09x 甚至可以将 BOOT0 通过 Option Byte 中的 nBOOT0 来配置,只要将 Option Byte 中的BOOT_SEL 位配置为 0。这使得纯粹使用软件来实现进入 System Memory 中的 Bootloader 来进行串口升级成为可能。

2.方案设计
既然 STM32F04x 和 STM32F09x 甚至可以将 BOOT0 通过 Option Byte 中的 nBOOT0 来配置,那么基本的思路就是:

1) 在用户代码中,设置进入串口升级的条件(比如说使用复合按键,或者接收到串口发来的一串命令,等等);一旦条件成立,就将 Option Byte 中关于 BOOT 的配置设置为:BOOT_SEL=0(BOOT0 信号由 nBOOT0 选项位定义),nBOOT1=1 & nBOOT0=0。然后进行复位,重载 Option Byte,这个时候系统就会进入 System Memory 中去运行Bootloader。

2) 此时,打开 Flash Loader Demonstrator,可以进行下载,在下载之前我们要设置在烧写完程序后从 Bootloader 跳转到用户代码,因为我们需要在用户代码中去将 BOOT 的配置修改为从 Main Flash memory 启动。如果不这么做,那么每次复位后都将从 System memory 启动。

3) 从第二步可以看到,我们还需要在用户代码的最前面加入判断,如果 BOOT 的配置不是从 Main Flash memory 启动的话,必须对 Option Byte 进行改写,将 BOOT 的配置设置为从 Main Flash memory 启动:BOOT_SEL=0(BOOT0 信号由 nBOOT0 选项位定义),nBOOT1=1 & nBOOT0=1。
   从这个思路出发,我们可以得到需要设计的流程图。
   主程序的流程图如下:

7V4)BY3AM`848G63L6``BSD.png

   当进入串口升级的条件成立时,其流程如下:

WNECXS2LYH9([A$~$S00CCL.png

  实验
   1.设计目标
   使用 NUCLEO-F091RC 来实现此实验。空片进行第一次烧写时使用 SWD 进行烧写,之后所有的.hex 文件再次烧写都将使用Flash Loader Demonstrator 进行下载。不使用 BOOT0 脚。由于只是实验,这里用按下 USER 按键来代表进入串口烧写的条件,用户代码运行 LD2 闪烁的功能。

   2.程序设计
   main()主程序如下:
  1.    int main(void)
  2.    {
  3.    /*!APB2ENR |= RCC_APB2ENR_SYSCFGEN;
  4.    SYSCFG->CFGR1 = (uint32_t)0x00000000;
  5.    /* 此处调用子程序,用来判断 BOOT 的配置是否从 Main Flash memory 启动,如果不是,将
  6.    Option Bytes 中的 BOOT 配置设置为从 Main Flash memory 启动 */
  7.    BOOTCONF_User();
  8.    /* 配置 LED 灯 LD2 的初始化 */
  9.    STM_NUCLEO_LD2Init();
  10.    /* 配置 USER 按键的初始化,设置为中断口 */
  11.    STM_EVAL_PBInit(BUTTON_USER, BUTTON_MODE_EXTI);
  12.    /* 主循环进行闪灯,随时等待按键中断以启动串口烧写 */
  13.    while (1)
  14.    {
  15.    /* Toggle LD2 */
  16.    STM_NUCLEO_LD2Toggle();
  17.    /* Inset delay */
  18.    Delay(0xAFFFF);
  19.    }
  20.    }
复制代码

   BOOTCONF_User()就是我们思路中的第 3)步,也是我们所关心的,我们来仔细看代码:
  1.    void BOOTCONF_User(void)
  2.    {
  3.    FLASH_Status status = FLASH_COMPLETE;
  4.    /* 定义一个 8 个半字的数组用来存放 option bytes 的数据 */
  5.    uint16_t ob[8];
  6.    /* 读取 Option Bytes 的数据 */
  7.    ob[0] = *(uint16_t*)(0x1FFFF800); //RDP
  8.    ob[1] = *(uint16_t*)(0x1FFFF802); //USER
  9.    ob[2] = *(uint16_t*)(0x1FFFF804); //DATA0
  10.    ob[3] = *(uint16_t*)(0x1FFFF806); //DATA1
  11.    ob[4] = *(uint16_t*)(0x1FFFF808); //WRP0
  12.    ob[5] = *(uint16_t*)(0x1FFFF80A); //WRP1
  13.    ob[6] = *(uint16_t*)(0x1FFFF80C); //WRP2
  14.    ob[7] = *(uint16_t*)(0x1FFFF80E); //WPR3
  15.    /* 判断 BOOT 配置是否正确并更新 */
  16.    /* 当从串口烧写完代码之后,从 Bootloader 跳转到用户代码,我们需要将 BOOT 配置设置回来,
  17.    修改为从 Main Flash memory 启动;平时已经是此设置的时候,就不需要修改了 */
  18.    if((ob[1] & 0x0098) != 0x0018) //判断是否是“BOOT_SEL=0 且 nBOOT1=1 且 nBOOT0=1”, 否则更新
  19.    {
  20.    /* 先解锁 */
  21.    FLASH_Unlock();
  22.    FLASH_OB_Unlock();
  23.    /* 再擦除,擦除完成后对 option bytes 进行修改更新*/
  24.    /* Wait for last operation to be completed */
  25.    status = FLASH_WaitForLastOperation(FLASH_ER_PRG_TIMEOUT);
  26.    if(status == FLASH_COMPLETE)
  27.    {
  28.    /* 擦除 */
  29.    /* If the previous operation is completed, proceed to erase the option bytes */
  30.    FLASH->CR |= FLASH_CR_OPTER;
  31.    FLASH->CR |= FLASH_CR_STRT;
  32.    /* Wait for last operation to be completed */
  33.    status = FLASH_WaitForLastOperation(FLASH_ER_PRG_TIMEOUT);
  34.    /* If the erase operation is completed, disable the OPTER Bit */
  35.    FLASH->CR &= ~FLASH_CR_OPTER;
  36.    /* 更新 option Bytes */
  37.    /* ob[1]对应的是 USER,里边包含了 BOOT_SEL, nBOOT1, nBOOT0,所以我们对 ob[1]中的这三个位
  38.    进行修改 */
  39.    ob[1] = (ob[1] & 0x6767) | 0x8018;

  40. /* 写回 option bytes */
  41. /* Enable the Option Bytes Programming operation */
  42. FLASH->CR |= FLASH_CR_OPTPG;
  43. /* Restore the last read protection Option Byte value */
  44. OB->RDP = ob[0];
  45. status = FLASH_WaitForLastOperation(FLASH_ER_PRG_TIMEOUT);
  46. if(status == FLASH_TIMEOUT)
  47. while(1) ; //ERROR
  48. FLASH->CR &= ~FLASH_CR_OPTPG;
  49. FLASH->CR |= FLASH_CR_OPTPG;
  50. OB->USER = ob[1];
  51. status = FLASH_WaitForLastOperation(FLASH_ER_PRG_TIMEOUT);
  52. if(status == FLASH_TIMEOUT)
  53. while(1) ; //ERROR
  54. FLASH->CR &= ~FLASH_CR_OPTPG;
  55. FLASH->CR |= FLASH_CR_OPTPG;
  56. OB->DATA0 = ob[2];
  57. status = FLASH_WaitForLastOperation(FLASH_ER_PRG_TIMEOUT);
  58. if(status == FLASH_TIMEOUT)
  59. while(1) ; //ERROR
  60. FLASH->CR &= ~FLASH_CR_OPTPG;
  61. FLASH->CR |= FLASH_CR_OPTPG;
  62. OB->DATA1 = ob[3];
  63. status = FLASH_WaitForLastOperation(FLASH_ER_PRG_TIMEOUT);
  64. if(status == FLASH_TIMEOUT)
  65. while(1) ; //ERROR
  66. FLASH->CR &= ~FLASH_CR_OPTPG;
  67. FLASH->CR |= FLASH_CR_OPTPG;
  68. OB->WRP0 = ob[4];
  69. status = FLASH_WaitForLastOperation(FLASH_ER_PRG_TIMEOUT);
  70. if(status == FLASH_TIMEOUT)
  71. while(1) ; //ERROR
  72. FLASH->CR &= ~FLASH_CR_OPTPG;
  73. FLASH->CR |= FLASH_CR_OPTPG;
  74. OB->WRP1 = ob[5];
  75. status = FLASH_WaitForLastOperation(FLASH_ER_PRG_TIMEOUT);
  76. if(status == FLASH_TIMEOUT)
  77. while(1) ; //ERROR
  78. FLASH->CR &= ~FLASH_CR_OPTPG;
  79. FLASH->CR |= FLASH_CR_OPTPG;
  80. OB->WRP2 = ob[6];
  81. status = FLASH_WaitForLastOperation(FLASH_ER_PRG_TIMEOUT);
  82. if(status == FLASH_TIMEOUT)
  83. while(1) ; //ERROR
  84. FLASH->CR &= ~FLASH_CR_OPTPG;
  85. FLASH->CR |= FLASH_CR_OPTPG;
  86. OB->WRP3 = ob[7];
  87. status = FLASH_WaitForLastOperation(FLASH_ER_PRG_TIMEOUT);
  88. if(status == FLASH_TIMEOUT)
  89. while(1) ; //ERROR
  90. FLASH->CR &= ~FLASH_CR_OPTPG;
  91. }
  92. else
  93. {
  94. if (status != FLASH_TIMEOUT)
  95. {
  96. /* Disable the OPTPG Bit */
  97. FLASH->CR &= ~FLASH_CR_OPTPG;
  98. }
  99. }
  100. /* 写完,重新锁止 */
  101. FLASH_OB_Lock();
  102. FLASH_Lock();
  103. /* 验证 option bytes 数据被正确写入 */
  104. if((*(uint16_t*)(0x1FFFF800)) != ob[0])
  105. while(1) ; //ERROR
  106. if((*(uint16_t*)(0x1FFFF802)) != ob[1])
  107. while(1) ; //ERROR
  108. if((*(uint16_t*)(0x1FFFF804)) != ob[2])
  109. while(1) ; //ERROR
  110. if((*(uint16_t*)(0x1FFFF806)) != ob[3])
  111. while(1) ; //ERROR
  112. if((*(uint16_t*)(0x1FFFF808)) != ob[4])
  113. while(1) ; //ERROR
  114. if((*(uint16_t*)(0x1FFFF80A)) != ob[5])
  115. while(1) ; //ERROR
  116. if((*(uint16_t*)(0x1FFFF80C)) != ob[6])
  117. while(1) ; //ERROR
  118. if((*(uint16_t*)(0x1FFFF80E)) != ob[7])
  119. while(1) ; //ERROR

  120. /* 将 FLASH_CR 的 OBL_LAUNCH 位置 1,产生一个可以重新载入 option bytes 的复位
  121. 其目的在于避免此复位和 Power On 的复位以外的其他复位产生时不能正常工作。
  122. 例如,使用串口烧写完代码,从 Bootloader 跳入用户代码,此时如果按下 RESET 按键
  123. 产生复位,则又重新进入 System Memory 中。所以加此复位,以避免此现象产生。 */
  124. FLASH_OB_Launch();
  125. }
  126. }
复制代码

我们再来看按键中断的程序:
  1. void EXTI4_15_IRQHandler(void)
  2. {
  3. if(EXTI_GetITStatus(EXTI_Line13) != RESET)
  4. {
  5. EXTI_ClearITPendingBit(USER_BUTTON_EXTI_LINE);
  6. BOOTCONF_System();
  7. FLASH_OB_Launch();
  8. }
  9. }
复制代码

这个代码很简单,就是清除一下中断标志位,然后将 Option Bytes 中的 BOOT 配置设置为从 System memory 启动,然后再调用 FLASH_OB_Launch()来产生一个可以重新载入 option bytes 的复位,这个复位将导致复位后进入的是 System memory,而不是 Main Flash memory 了。
其中关键的代码是 BOOTCONF_System(),我们来看一下:
  1. void BOOTCONF_System(void)
  2. {
  3. FLASH_Status status = FLASH_COMPLETE;
  4. uint16_t ob[8];
  5. /* Get opiton bytes data */
  6. ob[0] = *(uint16_t*)(0x1FFFF800); //RDP
  7. ob[1] = *(uint16_t*)(0x1FFFF802); //USER
  8. ob[2] = *(uint16_t*)(0x1FFFF804); //DATA0
  9. ob[3] = *(uint16_t*)(0x1FFFF806); //DATA1
  10. ob[4] = *(uint16_t*)(0x1FFFF808); //WRP0
  11. ob[5] = *(uint16_t*)(0x1FFFF80A); //WRP1
  12. ob[6] = *(uint16_t*)(0x1FFFF80C); //WRP2
  13. ob[7] = *(uint16_t*)(0x1FFFF80E); //WPR3
  14. /* Unlock */
  15. FLASH_Unlock();
  16. FLASH_OB_Unlock();
  17. /* Erase and update Option bytes*/
  18. /* Wait for last operation to be completed */
  19. status = FLASH_WaitForLastOperation(FLASH_ER_PRG_TIMEOUT);
  20. if(status == FLASH_COMPLETE)
  21. {
  22. /* If the previous operation is completed, proceed to erase the option bytes */
  23. FLASH->CR |= FLASH_CR_OPTER;
  24. FLASH->CR |= FLASH_CR_STRT;
  25. /* Wait for last operation to be completed */
  26. status = FLASH_WaitForLastOperation(FLASH_ER_PRG_TIMEOUT);
  27. /* If the erase operation is completed, disable the OPTER Bit */
  28. FLASH->CR &= ~FLASH_CR_OPTER;
  29. /* Update option bytes */
  30. ob[1] = (ob[1] & 0xFFF7) | 0x0800;
  31. /* Enable the Option Bytes Programming operation */
  32. FLASH->CR |= FLASH_CR_OPTPG;
  33. /* Restore the last read protection Option Byte value */
  34. OB->RDP = ob[0];
  35. status = FLASH_WaitForLastOperation(FLASH_ER_PRG_TIMEOUT);
  36. if(status == FLASH_TIMEOUT)
  37. while(1) ; //ERROR
  38. FLASH->CR &= ~FLASH_CR_OPTPG;
  39. FLASH->CR |= FLASH_CR_OPTPG;
  40. OB->USER = ob[1];
  41. status = FLASH_WaitForLastOperation(FLASH_ER_PRG_TIMEOUT);
  42. if(status == FLASH_TIMEOUT)
  43. while(1) ; //ERROR
  44. FLASH->CR &= ~FLASH_CR_OPTPG;
  45. FLASH->CR |= FLASH_CR_OPTPG;
  46. OB->DATA0 = ob[2];
  47. status = FLASH_WaitForLastOperation(FLASH_ER_PRG_TIMEOUT);
  48. if(status == FLASH_TIMEOUT)
  49. while(1) ; //ERROR
  50. FLASH->CR &= ~FLASH_CR_OPTPG;
  51. FLASH->CR |= FLASH_CR_OPTPG;
  52. OB->DATA1 = ob[3];
  53. status = FLASH_WaitForLastOperation(FLASH_ER_PRG_TIMEOUT);
  54. if(status == FLASH_TIMEOUT)
  55. while(1) ; //ERROR
  56. FLASH->CR &= ~FLASH_CR_OPTPG;
  57. FLASH->CR |= FLASH_CR_OPTPG;
  58. OB->WRP0 = ob[4];
  59. status = FLASH_WaitForLastOperation(FLASH_ER_PRG_TIMEOUT);
  60. if(status == FLASH_TIMEOUT)
  61. while(1) ; //ERROR
  62. FLASH->CR &= ~FLASH_CR_OPTPG;
  63. FLASH->CR |= FLASH_CR_OPTPG;
  64. OB->WRP1 = ob[5];
  65. status = FLASH_WaitForLastOperation(FLASH_ER_PRG_TIMEOUT);
  66. if(status == FLASH_TIMEOUT)
  67. while(1) ; //ERROR
  68. FLASH->CR &= ~FLASH_CR_OPTPG;
  69. FLASH->CR |= FLASH_CR_OPTPG;
  70. OB->WRP2 = ob[6];
  71. status = FLASH_WaitForLastOperation(FLASH_ER_PRG_TIMEOUT);
  72. if(status == FLASH_TIMEOUT)
  73. while(1) ; //ERROR
  74. FLASH->CR &= ~FLASH_CR_OPTPG;
  75. FLASH->CR |= FLASH_CR_OPTPG;
  76. OB->WRP3 = ob[7];
  77. status = FLASH_WaitForLastOperation(FLASH_ER_PRG_TIMEOUT);
  78. if(status == FLASH_TIMEOUT)
  79. while(1) ; //ERROR
  80. FLASH->CR &= ~FLASH_CR_OPTPG;
  81. }
  82. else
  83. {
  84. if (status != FLASH_TIMEOUT)
  85. {
  86. /* Disable the OPTPG Bit */
  87. FLASH->CR &= ~FLASH_CR_OPTPG;
  88. }
  89. }
  90. /* Lock */
  91. FLASH_OB_Lock();
  92. FLASH_Lock();
  93. /* Verify */
  94. if((*(uint16_t*)(0x1FFFF800)) != ob[0])
  95. while(1) ; //ERROR
  96. if((*(uint16_t*)(0x1FFFF802)) != ob[1])
  97. while(1) ; //ERROR
  98. if((*(uint16_t*)(0x1FFFF804)) != ob[2])
  99. while(1) ; //ERROR
  100. if((*(uint16_t*)(0x1FFFF806)) != ob[3])
  101. while(1) ; //ERROR
  102. if((*(uint16_t*)(0x1FFFF808)) != ob[4])
  103. while(1) ; //ERROR
  104. if((*(uint16_t*)(0x1FFFF80A)) != ob[5])
  105. while(1) ; //ERROR
  106. if((*(uint16_t*)(0x1FFFF80C)) != ob[6])
  107. while(1) ; //ERROR
  108. if((*(uint16_t*)(0x1FFFF80E)) != ob[7])
  109. while(1) ; //ERROR
  110. }
复制代码

我们可以看到,其实其整个过程与 BOOTCONF_User()非常地像,唯独有两个区别:

1) 没有判断当前的 BOOT 配置,并且在更新 USER 时只是将 nBOOT0 清零而已。因为在用户代码的最开始,我们已经保证了,BOOT 的配置一定是从 Main Flash memory 启动,所以要切换到从 System memory 启动,仅需要将nBOOT0 清零。

2) Option Bytes 更新并验证后,并没有调用 FLASH_OB_Launch()。因为我们把它放在中断程序里头了。

程序要点到这里就已经很清楚了。此程序仅为实验用,所以对于出错处理和代码优化,可根据实际应用自行处理。

   3.实验步骤
   下面我们来进行实验:
1) 我们将程序编译生成的的.hex(我们将其命名为 1.hex)通过 SWD 接口,使用 STM32 ST-LINK Utility 烧写到
   NUCLEO_F091RC 后,可以看到 STM32F091RC 正在欢快地运行 1.hex,闪烁着 LD2。关闭 STM32 ST-LINK
   Utility。
   注:1.hex 是慢闪烁现象

J2GNGO[}}P2R3[IZIVA9W}L.png

2) 回到程序中,将 main()函数中主循环的“Delay(0xAFFFF);”修改为“Delay(0xFFFF);”,重新编译生成.hex 文件,我们将其命名为 2.hex。
   注:2.hex 是快闪烁现象
3) 现在我们要开始进行串口升级了。按下 USER 按键,发现 LD2 不再闪烁了,程序已经进入 System memory 运行Bootloader 了。
4) 打开 Flash Loader Demonstrator,做以下的配置:

({])KDG{@Z7`9Q{H)0}~K8M.png

5) 点“Next”进入下一个界面,再点“Next ”再进入下一个界面,再点“Next ”进入下一个界面,我们在这个页面中选择下载代码“Download to device”,导入刚才编译生成的 2.hex,选中“Jump to the user program ”,意思就是通过串口烧写完毕后,直接跳到用户代码去运行。如图:

]%Y0R~@{HF9]ASWVPWC4Q.png

6) 点击“Next”进行代码烧写。烧写成功后,我们就可以看到 LD2 更加欢快地闪烁了,证明我们的烧写是成功的。按下 RESET 按键,也能正常运行代码;断电后再上电,也正常运行。

~2K[VF~O@X(2CS%M{]{AM_K.png

   到这里,我们的实验成功了,我们可以重复 3)~6)步骤来反复烧写 1.hex 和 2.hex,体验这个串口升级的方便性,再也不用去拔跳线插跳线了。

   结论
   由于 STM32F04x 和 STM32F09x 可以将 BOOT0 通过 Option Byte 中的 nBOOT0 来配置,这个新特性决定了我们可以很方便地实现不使用 BOOT 脚就直接从 System memory 中的 Bootloader 进行代码升级。非常地方便。

   处理
   此实验证明了“STM32F04x 和 STM32F09x 不使用 BOOT 脚实现 System Bootloader 升级代码”的可行性。但只是实验,客户可自行处理错误情况和代码优化。此外,此实验用按下 USER 按键来激活串口烧写,客户可自行根据应用来修改激活条件,比如从串口接到到一串固定的数据作为激活条件。

   后续
   其实我们在看参考手册时,还看到这么一句话:
   “For STM32F04x and STM32F09x devices, see also Empty check description.”
   也就是说,空片也是可以通过串口进行烧写的,我们后续继续讨论。

收藏 评论0 发布时间:2022-2-18 20:49

举报

0个回答

所属标签

相似分享

官网相关资源

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
STM32N6 AI生态系统
STM32MCU,MPU高性能GUI
ST ACEPACK电源模块
意法半导体生物传感器
STM32Cube扩展软件包
关注我们
st-img 微信公众号
st-img 手机版