Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ARM Cortex-M HardFault exception on writting halfword to flash using C++

Tags:

c++

c

gcc

arm

stm32

I've written a project using C++ to run on ARM Cortex-M (STM32F0) but I had some problems with accessing defined buffers as class members though I resolved that by defining them as global vars.

But now I'm completely stuck with this new problem which I don't know what to do with it.

I've a code to unlock flash and write something into it and close it. If I implement it in C file and run it through C nature (call from main.c) it works perfect. but calling that through C++ files (whether written inside C or C++ source file) it will throw a HardFault Exception.

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;
}

and this is how I use it:

if(flash_unlock())
{
   programHalfWord(0x11, 0x8007C00);
}

It throws exception right after executing *(__IO uint16_t*) address = data;.

Flash is erased at this address, address is aligned (it's actually start of a sector). I've checked everything to make sure that flash is unlocked but it seems that there's something with the code compiled in C++.

I'm using arm-none-eabi-gcc and arm-none-eabi-g++ to compile my code.

Thanks in advance

Update:

Here's the list of flags being used with g++ compiler:

-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

And this is for 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

and g++ linker:

-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
like image 627
Nixmd Avatar asked Sep 03 '20 08:09

Nixmd


1 Answers

Since it is difficult to analyze the issue without having access to your hardware / software setup, I can only make wild guesses and provide some hints, after having some troubles with STM32 flash programming as well recently (on a different STM32 model (STM32F215RET6)). - But I'm not an expert in this area at all, and I've only used the vendor supplied HAL driver to access the internal flash so far.

The error might be caused by a memory bus error.

It would be interesting to verify if that's case with a debugger (e.g. by reading the flash status register (FLASH_SR), right after the error occurred).

The question is: Why does your C code work, when compiled with gcc and why not, when compiled with g++? I guess, it might have something to do with a technical detail, that the compiler "doesn't know" about the underlying restrictions of the architecture / memory model.

The STM32F030K6T reference manual (RM0360) says, in section "3.2.2 Flash program and erase operations, Main Flash memory programming":

The main Flash memory can be programmed 16 bits at a time. The program operation is started when the CPU writes a half-word into a main Flash memory address with the PG bit of the FLASH_CR register set. Any attempt to write data that are not half-word long will result in a bus error generating a Hard Fault interrupt.

So, 32-bit write access to the internal flash will cause a Hard Fault interrupt.

When you compile the project with assembly listing generation enabled, you could analyze what's exactly going in your C++ variant, and compare it to the generated machine code of the C variant.

Since I've been working on a STM32 flash related issue recently as well, I've looked up what's going on in the vendor supplied flash code in my case (stm32f2xx_hal_flash.c), and it turns out, that the main write operation to the flash (*(__IO uint16_t*)Address = Data;) is translated to the matching ARM half-word store instruction strh, like expected:

strh r1, [r0] 

This could be verified by looking at the auto-generated assembly listings for the ST supplied FLASH_Program_HalfWord() function in stm32f2xx_hal_flash.c. It looks like that (compiled with GCC with no optimization and debugging information -Og):

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

The generated machine code could be disassembled and inspected with objdump, without all the annotations, like that:

$ arm-none-eabi-objdump -d -j .text.FLASH_Program_HalfWord build/stm32f2xx_hal_flash.o

build/stm32f2xx_hal_flash.o:     file format elf32-littlearm


Disassembly of section .text.FLASH_Program_HalfWord:

00000000 <FLASH_Program_HalfWord>:
   0:   4b07        ldr r3, [pc, #28]   ; (20 <FLASH_Program_HalfWord+0x20>)
   2:   691a        ldr r2, [r3, #16]
   4:   f422 7240   bic.w   r2, r2, #768    ; 0x300
   8:   611a        str r2, [r3, #16]
   a:   691a        ldr r2, [r3, #16]
   c:   f442 7280   orr.w   r2, r2, #256    ; 0x100
  10:   611a        str r2, [r3, #16]
  12:   691a        ldr r2, [r3, #16]
  14:   f042 0201   orr.w   r2, r2, #1
  18:   611a        str r2, [r3, #16]
  1a:   8001        strh    r1, [r0, #0]
  1c:   4770        bx  lr
  1e:   bf00        nop
  20:   40023c00    .word   0x40023c00

It would be interesting, if you could find out how it looks like in your object file compiled as C++. Is it also using the strh instruction?

By the way, all the ARM instructions are documented also be ST in the STM32F0xxx Cortex-M0 programming manual (PM0215):

The Cortex-M0 processor implements the ARMv6-M architecture, which is based on the 16-bit Thumb® instruction set and includes Thumb-2 technology.

STRHRt, [Rn, <Rm|#imm>] Store register as halfword

And as a reference, also in the ARM®v6-M Architecture Reference Manual of course.


Side note 1:

The reference manual says that address 0x8007C00 is right at the beginning of flash page 31, in flash sector 7, assuming a STM32F030K6Tx chip is used: Table 5 from the STM32 reference manual RM0360

Forgetting about this could cause issues, if the sector is write protected via flash option bytes (but that obviously wasn't the case, since it works fine in the C variant). Just for the sake of completeness (you've already commented on that), a quote from the reference manual, "4.1.3 Write protection option byte":

This set of registers is used to write-protect the Flash memory. Clearing a bit in WRPx field (and at the same time setting a corresponding bit in nWRPx field) will write-protect the given memory sector. For STM32F030x4, STM32F030x6, STM32F070x6, STM32F030x8 and STM32F070xB devices, WRP bits from 0 to 31 are protecting the Flash memory by sector of 4 kB.

(Possibly unrelated, but also worth mentioning: beware of the different conditions present when Read Protection (RDP) Level 2 or Level 3 is active. RDP is a different protection mechanism, separate from the sector protection via flash option bytes, or lock state of the flash. Reading the flash from a debugger or when executing form RAM will cause a Hard Fault when RDP Level 2 or 3 is used. Documented in the reference manual, section "3.3.1 Read protection".)


Side note 2:

You could try to mix the official HAL C driver code or your own tested flash related C code, and the new C++ parts of the project, and check if the problem still occurs.

(Be careful when mixing C and C++, and always take care of naming mangeling by using extern "C" { ... }, related post: https://stackoverflow.com/a/1041880/5872574)


Side note 3:

Like already mentioned, I've recently had an unrelated issue with flash programming as well. And saw strange bus errors (in the status register after a Hard Fault). I also made sure that the flash was unlocked, and not write protected. If I remember correctly, I had to add this in front of my erase / write operations (but I do not remember exactly and can't find it right now). It was a necessary but strange fix, because there was no operation in progress, besides regular program execution (from flash).

    while (FLASH_WaitForLastOperation(100) != HAL_OK) {
        HAL_IWDG_Refresh(&hiwdg);
    }

This issue possibly had something to do with the way the STM32 uses the flash with a prefetch buffer / wait states / instruction cache and the data cache like described in the reference manual (see also: FLASH_ACR register). I didn't investigate the issue any further. Just make sure that there is no flash operation pending/active when a write/erase access is initiated.

Also interesting to note, program/erase operations will prevent any read access to the bus (flash memory), but they will not cause an error, like described in the reference manual, in section "3.2.2 Flash program and erase operations":

An ongoing Flash memory operation will not block the CPU as long as the CPU does not access the Flash memory.

On the contrary, during a program/erase operation to the Flash memory, any attempt to read the Flash memory will stall the bus. The read operation will proceed correctly once the program/erase operation has completed. This means that code or data fetches cannot be made while a program/erase operation is ongoing.

For program and erase operations on the Flash memory (write/erase), the internal RC oscillator (HSI) must be ON.


EDIT:

In order to check whether there's really enough flash memory left to write to, and that the area is really unused by the running binary itself, these commands could come in handy, meant as a future reference (using my test binary for an STM32F215RET here):

$ arm-none-eabi-strip build/prj.elf 
$ arm-none-eabi-objdump -h build/prj.elf 

build/prj.elf:     file format elf32-littlearm

Sections:
Idx Name          Size      VMA       LMA       File off  Algn
  0 .isr_vector   00000184  08000000  08000000  00010000  2**0
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  1 .text         000134a0  08000188  08000188  00010188  2**3
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
  2 .rodata       00002968  08013628  08013628  00023628  2**3
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  3 .ARM          00000008  08015f90  08015f90  00025f90  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  4 .init_array   00000004  08015f98  08015f98  00025f98  2**2
                  CONTENTS, ALLOC, LOAD, DATA
  5 .fini_array   00000004  08015f9c  08015f9c  00025f9c  2**2
                  CONTENTS, ALLOC, LOAD, DATA
  6 .data         000002c0  20000000  08015fa0  00030000  2**3
                  CONTENTS, ALLOC, LOAD, DATA
  7 .bss          0000149c  200002c0  08016260  000302c0  2**3
                  ALLOC
  8 ._user_heap_stack 00000604  2000175c  08016260  0003175c  2**0
                  ALLOC
  9 .ARM.attributes 00000029  00000000  00000000  000302c0  2**0
                  CONTENTS, READONLY
 10 .comment      0000001e  00000000  00000000  000302e9  2**0
                  CONTENTS, READONLY

0x08016260 marks the end of the used flash memory by the binary.

That can be verified with arm-none-eabi-size:

$ arm-none-eabi-size build/prj.elf 
   text    data     bss     dec     hex filename
  90004     712    6816   97532   17cfc build/prj.elf
$ echo $((90004 + 712))
90716
$ echo $((0x08016260 - 0x08000000 - (90004 + 712)))
4

So, with 2**3 -> 8 byte alignment and a flash base address of 0x08000000, that means that 90720 bytes of flash memory are actually used by the binary.

To find out which of the flash sectors are left unused, it is now easy to look the address up directly in the "Flash memory organization" table in the reference manual.

In my case, the linker script was modified to make sure that only half of the flash is used, like that:

$ cat STM32F215RETx_FLASH.ld
(...)
MEMORY
{
RAM (xrw)      : ORIGIN = 0x20000000, LENGTH = 128K
FLASH (rx)      : ORIGIN = 0x8000000, LENGTH = 256K /* keep 256K free at the end */
/* FLASH (rx)      : ORIGIN = 0x8000000, LENGTH = 512K */
}
(...)

That way you'll get a linker error if the binary gets too large.

like image 198
rel Avatar answered Oct 24 '22 08:10

rel