使用 C++ 将半字写入闪存时的 ARM Cortex-M HardFault 异常

Nix*_*xmd 5 c c++ gcc arm stm32

我已经使用 C++ 编写了一个在 ARM Cortex-M (STM32F0) 上运行的项目,但是我在将定义的缓冲区作为类成员访问时遇到了一些问题,尽管我通过将它们定义为全局变量来解决这个问题。

但是现在我完全被这个新问题困住了,我不知道该怎么办。

我有一个代码可以解锁闪存并向其中写入内容并关闭它。如果我在 C 文件中实现它并通过 C 自然运行它(从 main.c 调用)它工作完美。但是通过 C++ 文件(无论是写在 C 还是 C++ 源文件中)调用它会抛出一个 HardFault 异常。

static uint32_t waitForLastOperation(uint32_t msDelay)
{
  while (READ_BIT(FLASH->SR, FLASH_SR_BSY) && msDelay)
  {
    LL_mDelay(1);
    msDelay--;
  }

  /* Check FLASH End of Operation flag  */
  if (READ_BIT((FLASH->SR), (FLASH_SR_EOP)))
  {
    /* Clear FLASH End of Operation pending bit */
    (FLASH->SR) = (FLASH_SR_EOP);
  }

  if (READ_BIT((FLASH->SR),
      (FLASH_SR_WRPERR)) || READ_BIT((FLASH->SR), (FLASH_SR_PGERR)))
  {
    FLASH->SR = 0U;
    return 0;
  }

  /* There is no error flag set */
  return 1;
}

uint32_t programHalfWord(uint16_t data, uint32_t address)
{
  uint32_t status;

  /* Proceed to program the new data */
  SET_BIT(FLASH->CR, FLASH_CR_PG);

  /* Write data in the address */
  *(__IO uint16_t*) address = data;

  /* Wait for last operation to be completed */
  status = waitForLastOperation(FLASH_TIMEOUT);

  if (READ_BIT(FLASH->SR, FLASH_SR_EOP))
    FLASH->SR = FLASH_SR_EOP;

  /* If the program operation is completed, disable the PG Bit */
  CLEAR_BIT(FLASH->CR, FLASH_CR_PG);

  return status;
}

uint32_t flash_unlock()
{
  if (READ_BIT(FLASH->CR, FLASH_CR_LOCK) == RESET)
    return 1;

  /* Authorize the FLASH Registers access */
  WRITE_REG(FLASH->KEYR, FLASH_KEY1);
  WRITE_REG(FLASH->KEYR, FLASH_KEY2);

  /* Verify Flash is unlocked */
  if (READ_BIT(FLASH->CR, FLASH_CR_LOCK) != RESET)
    return 0;

  return 1;
}
Run Code Online (Sandbox Code Playgroud)

这就是我使用它的方式:

if(flash_unlock())
{
   programHalfWord(0x11, 0x8007C00);
}
Run Code Online (Sandbox Code Playgroud)

它在执行后立即抛出异常*(__IO uint16_t*) address = data;

Flash 在这个地址被擦除,地址对齐(它实际上是一个扇区的开始)。我已经检查了所有内容以确保闪存已解锁,但似乎用 C++ 编译的代码有些问题。

我正在使用 arm-none-eabi-gcc 和 arm-none-eabi-g++ 来编译我的代码。

提前致谢

更新:

以下是与 g++ 编译器一起使用的标志列表:

-mcpu=cortex-m0 -std=gnu++14 -g3 -DSTM32F030x6 -DHSE_STARTUP_TIMEOUT=100 -DLSE_STARTUP_TIMEOUT=5000 -DDEBUG -DLSE_VALUE=32768 -DDATA_CACHE_ENABLE=0 -DINSTRUCTION_CACHE_ENABLE=0 -DVDD_VALUE=3300 -DLSI_VALUE=40000 -DHSI_VALUE=8000000 -DUSE_FULL_LL_DRIVER -DPREFETCH_ENABLE=1 -DHSE_VALUE=2000000 -c -I../app/Inc -I../Inc -I../Drivers/STM32F0xx_HAL_Driver/Inc -I../Drivers/CMSIS/Include -I../Drivers/CMSIS/Device/ST/STM32F0xx/Include -I../app/Driver -Og -ffunction-sections -fdata-sections -fno-exceptions -fno-rtti -fno-threadsafe-statics -fno-use-cxa-atexit -Wall -fno-short-enums -fstack-usage --specs=nano.specs -mfloat-abi=soft -mthumb
Run Code Online (Sandbox Code Playgroud)

这是针对 gcc 的:

-mcpu=cortex-m0 -std=gnu11 -g3 -DSTM32F030x6 -DHSE_STARTUP_TIMEOUT=100 -DLSE_STARTUP_TIMEOUT=5000 -DDEBUG -DLSE_VALUE=32768 -DDATA_CACHE_ENABLE=0 -DINSTRUCTION_CACHE_ENABLE=0 -DVDD_VALUE=3300 -DLSI_VALUE=40000 -DHSI_VALUE=8000000 -DUSE_FULL_LL_DRIVER -DPREFETCH_ENABLE=1 -DHSE_VALUE=2000000 -c -I../app/Inc -I../Inc -I../Drivers/STM32F0xx_HAL_Driver/Inc -I../Drivers/CMSIS/Include -I../Drivers/CMSIS/Device/ST/STM32F0xx/Include -I../app/Driver -Og -ffunction-sections -fdata-sections -Wall -fno-short-enums -fstack-usage --specs=nano.specs -mfloat-abi=soft -mthumb
Run Code Online (Sandbox Code Playgroud)

和 g++ 链接器:

-mcpu=cortex-m0 -T"./STM32F030K6TX_FLASH.ld" -Wl,-Map="${ProjName}.map" -Wl,--gc-sections -static --specs=nano.specs -mfloat-abi=soft -mthumb -Wl,--start-group -lc -lm -lstdc++ -lsupc++ -Wl,--end-group
Run Code Online (Sandbox Code Playgroud)

rel*_*rel 4

由于在无法访问您的硬件/软件设置的情况下很难分析问题,因此在最近在 STM32 闪存编程上遇到一些麻烦之后(在不同的 STM32 型号(STM32F215RET6)上),我只能做出疯狂的猜测并提供一些提示。- 但我根本不是这个领域的专家,到目前为止我只使用供应商提供的 HAL 驱动程序来访问内部闪存。

\n

该错误可能是由内存总线错误引起的。

\n

使用调试器验证是否属于这种情况会很有趣(例如,在错误发生后立即读取闪存状态寄存器(FLASH_SR))。

\n

问题是:为什么你的 C 代码在使用 gcc 编译时可以工作,而在使用 g++ 编译时却不能工作?我想,这可能与技术细节有关,编译器“不知道”架构/内存模型的底层限制。

\n

STM32F030K6T参考手册 (RM0360)在“3.2.2 Flash 编程和擦除操作,主 Flash 存储器编程”部分中说道:

\n
\n

主闪存一次可以编程 16 位。当 CPU 将半字写入主 Flash 存储器地址且 FLASH_CR 寄存器的 PG 位置位时,编程操作开始。任何写入非半字长数据的尝试都会导致总线错误,从而产生硬故障中断。

\n
\n

因此,对内部闪存的 32 位写访问将导致硬故障中断。

\n

当您在启用程序集列表生成的情况下编译项目时,您可以分析 C++ 变体中到底发生了什么,并将其与 C 变体生成的机器代码进行比较。

\n

由于我最近也一直在研究与 STM32 闪存相关的问题,因此我在我的案例中查找了供应商提供的闪存代码(stm32f2xx_hal_flash.c)中发生的情况,事实证明,对闪存的主要写入操作 ( *(__IO uint16_t*)Address = Data;) 被转换为匹配的 ARM 半字存储指令strh,如预期的那样:

\n
strh r1, [r0] \n
Run Code Online (Sandbox Code Playgroud)\n

这可以通过查看 stm32f2xx_hal_flash.c 中 ST 提供的 FLASH_Program_HalfWord() 函数自动生成的汇编列表来验证。看起来像这样(用 GCC 编译,没有优化和调试信息-Og):

\n
 662:Drivers/STM32F2xx_HAL_Driver/Src/stm32f2xx_hal_flash.c **** static void FLASH_Program_HalfWord(uint32_t Address, uint16_t Data)\n 663:Drivers/STM32F2xx_HAL_Driver/Src/stm32f2xx_hal_flash.c **** {\n 140                    .loc 1 663 1 is_stmt 1 view -0\n 141                    .cfi_startproc\n 142                    @ args = 0, pretend = 0, frame = 0\n 143                    @ frame_needed = 0, uses_anonymous_args = 0\n 144                    @ link register save eliminated.\n 664:Drivers/STM32F2xx_HAL_Driver/Src/stm32f2xx_hal_flash.c ****   /* Check the parameters */\n 665:Drivers/STM32F2xx_HAL_Driver/Src/stm32f2xx_hal_flash.c ****   assert_param(IS_FLASH_ADDRESS(Address));\n 145                    .loc 1 665 3 view .LVU27\n 666:Drivers/STM32F2xx_HAL_Driver/Src/stm32f2xx_hal_flash.c ****   \n 667:Drivers/STM32F2xx_HAL_Driver/Src/stm32f2xx_hal_flash.c ****   /* If the previous operation is completed, proceed to program the new data */\n 668:Drivers/STM32F2xx_HAL_Driver/Src/stm32f2xx_hal_flash.c ****   CLEAR_BIT(FLASH->CR, FLASH_CR_PSIZE);\n 146                    .loc 1 668 3 view .LVU28\n 147 0000 074B          ldr r3, .L9\n 148 0002 1A69          ldr r2, [r3, #16]\n 149 0004 22F44072      bic r2, r2, #768\n 150 0008 1A61          str r2, [r3, #16]\n 669:Drivers/STM32F2xx_HAL_Driver/Src/stm32f2xx_hal_flash.c ****   FLASH->CR |= FLASH_PSIZE_HALF_WORD;\n 151                    .loc 1 669 3 view .LVU29\n 152                    .loc 1 669 13 is_stmt 0 view .LVU30\n 153 000a 1A69          ldr r2, [r3, #16]\n 154 000c 42F48072      orr r2, r2, #256\n 155 0010 1A61          str r2, [r3, #16]\n 670:Drivers/STM32F2xx_HAL_Driver/Src/stm32f2xx_hal_flash.c ****   FLASH->CR |= FLASH_CR_PG;\n 156                    .loc 1 670 3 is_stmt 1 view .LVU31\n 157                    .loc 1 670 13 is_stmt 0 view .LVU32\n 158 0012 1A69          ldr r2, [r3, #16]\n 159 0014 42F00102      orr r2, r2, #1\n 160 0018 1A61          str r2, [r3, #16]\n 671:Drivers/STM32F2xx_HAL_Driver/Src/stm32f2xx_hal_flash.c **** \n 672:Drivers/STM32F2xx_HAL_Driver/Src/stm32f2xx_hal_flash.c ****   *(__IO uint16_t*)Address = Data;\n 161                    .loc 1 672 3 is_stmt 1 view .LVU33\n 162                    .loc 1 672 28 is_stmt 0 view .LVU34\n 163 001a 0180          strh    r1, [r0]    @ movhi\n 673:Drivers/STM32F2xx_HAL_Driver/Src/stm32f2xx_hal_flash.c **** }\n 164                    .loc 1 673 1 view .LVU35\n 165 001c 7047          bx  lr\n 166                .L10:\n 167 001e 00BF          .align  2\n 168                .L9:\n 169 0020 003C0240      .word   1073888256\n 170                    .cfi_endproc\n
Run Code Online (Sandbox Code Playgroud)\n

生成的机器代码可以使用 进行反汇编和检查objdump,无需所有注释,如下所示:

\n
$ arm-none-eabi-objdump -d -j .text.FLASH_Program_HalfWord build/stm32f2xx_hal_flash.o\n\nbuild/stm32f2xx_hal_flash.o:     file format elf32-littlearm\n\n\nDisassembly of section .text.FLASH_Program_HalfWord:\n\n00000000 <FLASH_Program_HalfWord>:\n   0:   4b07        ldr r3, [pc, #28]   ; (20 <FLASH_Program_HalfWord+0x20>)\n   2:   691a        ldr r2, [r3, #16]\n   4:   f422 7240   bic.w   r2, r2, #768    ; 0x300\n   8:   611a        str r2, [r3, #16]\n   a:   691a        ldr r2, [r3, #16]\n   c:   f442 7280   orr.w   r2, r2, #256    ; 0x100\n  10:   611a        str r2, [r3, #16]\n  12:   691a        ldr r2, [r3, #16]\n  14:   f042 0201   orr.w   r2, r2, #1\n  18:   611a        str r2, [r3, #16]\n  1a:   8001        strh    r1, [r0, #0]\n  1c:   4770        bx  lr\n  1e:   bf00        nop\n  20:   40023c00    .word   0x40023c00\n
Run Code Online (Sandbox Code Playgroud)\n

如果您能在编译为 C++ 的目标文件中找到它的样子,那将会很有趣。也是使用strh指令吗?

\n

顺便说一句,所有 ARM 指令也以 ST 形式记录在STM32F0xxx Cortex-M0 编程手册 (PM0215)中:

\n
\n

Cortex-M0处理器实现ARMv6-M架构,该架构基于16位Thumb\xc2\xae指令集并包含Thumb-2技术。

\n
\n
\n

STRHRt, [Rn, <Rm|#imm>] 将寄存器存储为半字

\n
\n

作为参考,当然也在ARM\xc2\xaev6-M Architecture Reference Manual中。

\n
\n

旁注1:

\n

参考手册说地址0x8007C00位于闪存第 31 页的开头,在闪存扇区 7 中,假设使用 STM32F030K6Tx 芯片:\n表 5 来自 STM32 参考手册 RM0360

\n

如果该扇区通过闪存选项字节进行写保护,则忘记这一点可能会导致问题(但显然情况并非如此,因为它在 C 变体中工作正常)。只是为了完整起见(您已经对此发表了评论),引用参考手册“4.1.3 写保护选项字节”:

\n
\n

这组寄存器用于对 Flash 存储器进行写保护。\n清除 WRPx 字段中的一位(同时设置 nWRPx 字段中的相应位)将对给定的存储器\n扇区进行写保护。对于 STM32F030x4、STM32F030x6、STM32F070x6、STM32F030x8 和 STM32F070xB 器件,从 0 到 31 的 WRP 位按 4 kB 扇区保护闪存。

\n
\n

(可能不相关,但也值得一提:当心读保护 (RDP) 级别 2 或级别 3 处于活动状态时出现的不同条件。RDP 是一种不同的保护机制,通过闪存选项字节或锁定状态与扇区保护分开。当使用 RDP 级别 2 或 3 时,从调试器读取闪存或从 RAM 中执行时会导致硬故障。记录在参考手册的“3.3.1 读保护”部分中。)

\n
\n

旁注2:

\n

您可以尝试将官方的 HAL C 驱动程序代码或您自己测试过的 flash 相关 C 代码与项目的新 C++ 部分混合在一起,然后检查问题是否仍然出现。

\n

extern "C" { ... }(混合 C 和 C++ 时要小心,并始终使用相关帖子来处理命名管理: /sf/answers/72931631/

\n
\n

旁注3:

\n

正如已经提到的,我最近也遇到了与闪存编程无关的问题。并看到奇怪的总线错误(在硬故障后​​的状态寄存器中)。我还确保闪存已解锁,并且没有写保护。如果我没记错的话,我必须将其添加到擦除/写入操作之前(但我记不太清楚,现在找不到它)。这是一个必要但奇怪的修复,因为除了常规程序执行(来自闪存)之外,没有任何正在进行的操作。

\n
    while (FLASH_WaitForLastOperation(100) != HAL_OK) {\n        HAL_IWDG_Refresh(&hiwdg);\n    }\n
Run Code Online (Sandbox Code Playgroud)\n

这个问题可能与 STM32 使用带有预取缓冲区/等待状态/指令缓存和数据缓存的闪存的方式有关,如参考手册中所述(另请参阅:FLASH_ACR寄存器)。我没有进一步调查这个问题。只需确保启动写入/擦除访问时没有挂起/活动的闪存操作即可。

\n

另外值得注意的是,编程/擦除操作将阻止对总线(闪存)的任何读访问,但它们不会导致错误,如参考手册“3.2.2 闪存编程和擦除操作”部分中所述:

\n
\n

只要 CPU 不访问闪存,正在进行的闪存操作就不会阻塞 CPU。

\n

相反,在对闪存进行编程/擦除操作期间,任何读取闪存的尝试都会导致总线停止。编程/擦除操作完成后,读取操作将正确进行。这意味着在编程/擦除操作正在进行时无法进行代码或数据提取。

\n

对于闪存上的编程和擦除操作(写入/擦除),内部 RC 振荡器 (HSI) 必须打开。

\n
\n
\n

编辑:

\n

为了检查是否确实有足够的闪存可供写入,以及运行的二进制文件本身是否确实未使用该区域,这些命令可能会派上用场,作为将来的参考(使用我的测试二进制文件作为STM32F215RET 在这里):

\n
$ arm-none-eabi-strip build/prj.elf \n$ arm-none-eabi-objdump -h build/prj.elf \n\nbuild/prj.elf:     file format elf32-littlearm\n\nSections:\nIdx Name          Size      VMA       LMA       File off  Algn\n  0 .isr_vector   00000184  08000000  08000000  00010000  2**0\n                  CONTENTS, ALLOC, LOAD, READONLY, DATA\n  1 .text         000134a0  08000188  08000188  00010188  2**3\n                  CONTENTS, ALLOC, LOAD, READONLY, CODE\n  2 .rodata       00002968  08013628  08013628  00023628  2**3\n                  CONTENTS, ALLOC, LOAD, READONLY, DATA\n  3 .ARM          00000008  08015f90  08015f90  00025f90  2**2\n                  CONTENTS, ALLOC, LOAD, READONLY, DATA\n  4 .init_array   00000004  08015f98  08015f98  00025f98  2**2\n                  CONTENTS, ALLOC, LOAD, DATA\n  5 .fini_array   00000004  08015f9c  08015f9c  00025f9c  2**2\n                  CONTENTS, ALLOC, LOAD, DATA\n  6 .data         000002c0  20000000  08015fa0  00030000  2**3\n                  CONTENTS, ALLOC, LOAD, DATA\n  7 .bss          0000149c  200002c0  08016260  000302c0  2**3\n                  ALLOC\n  8 ._user_heap_stack 00000604  2000175c  08016260  0003175c  2**0\n                  ALLOC\n  9 .ARM.attributes 00000029  00000000  00000000  000302c0  2**0\n                  CONTENTS, READONLY\n 10 .comment      0000001e  00000000  00000000  000302e9  2**0\n                  CONTENTS, READONLY\n
Run Code Online (Sandbox Code Playgroud)\n

0x08016260通过二进制标记所用闪存的结尾。

\n

可以通过以下方式验证arm-none-eabi-size

\n
$ arm-none-eabi-size build/prj.elf \n   text    data     bss     dec     hex filename\n  90004     712    6816   97532   17cfc build/prj.elf\n$ echo $((90004 + 712))\n90716\n$ echo $((0x08016260 - 0x08000000 - (90004 + 712)))\n4\n
Run Code Online (Sandbox Code Playgroud)\n

因此,通过 2**3 -> 8 字节对齐和闪存基地址0x08000000,这意味着二进制文件实际上使用了 90720 字节的闪存。

\n

要找出哪些闪存扇区未使用,现在可以轻松地直接在参考手册中的“闪存组织”表中查找地址。

\n

就我而言,修改了链接器脚本以确保仅使用一半的闪存,如下所示:

\n
$ cat STM32F215RETx_FLASH.ld\n(...)\nMEMORY\n{\nRAM (xrw)      : ORIGIN = 0x20000000, LENGTH = 128K\nFLASH (rx)      : ORIGIN = 0x8000000, LENGTH = 256K /* keep 256K free at the end */\n/* FLASH (rx)      : ORIGIN = 0x8000000, LENGTH = 512K */\n}\n(...)\n
Run Code Online (Sandbox Code Playgroud)\n

这样,如果二进制文件太大,您将收到链接器错误。

\n