5

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
Nixmd
  • 775
  • 5
  • 11
  • 20
  • If the same code works with gcc and not g++ it's probably due to either a difference in how you compile (options passed ?) or to other code (RTOS being compiled in the project that's not C++ compatible ?). Do you use the STL ? C++ has high level mechanism in the STL (exceptions, allocator in constructor / destructor etc.) that may not be suited for microcontroller – Clonk Sep 03 '20 at 13:22
  • have you tried to debug the HardFault in assembly? usually those types of errors are triky to debug, you can read more about this here https://www.freertos.org/Debugging-Hard-Faults-On-Cortex-M-Microcontrollers.html, if you follow the steps you can see the exact line (in assebly and wich line of c called it) after you dissasemble the pc register, and also if the error is caused by an impresise fault you'll need to disable all impresice code with that your code will run slower but it'll help you to find the error. – Hector Manuel Sep 04 '20 at 02:49
  • @Clonk Checkout update I've included flags and Also I don't use freeRTOS in this project and every mechanism used are safe for MCUs, so there is no exceptions, alloc and const/destr is being used. – Nixmd Sep 04 '20 at 14:08
  • Do you have the MemManage Exception enabled? try to enable that and see what happens, maybe t gets escalated to an hardFault. Moreover, try to compile with the -O0 in order to remove any optimization – Andrea Nisticò Sep 04 '20 at 14:19
  • Do you use the Standard Peripherals Library? Or where are the macros like `_IO` and `READ_BIT` defined? – Codo Sep 04 '20 at 15:36
  • @AndreaNisticò I'll try it – Nixmd Sep 05 '20 at 09:34
  • @Codo These are from LL and also they are defined in HAL too and they are macros for volatile and (WORD & BIT_POSITION) – Nixmd Sep 05 '20 at 09:36
  • Have you also tried to use the vendor supplied HAL functions instead of your own? Like: HAL_FLASH_Unlock(), HAL_FLASH_Program(), HAL_FLASH_Lock()? - Beware that individual flash sectors could also be write-protected, see: "4.1.3 Write protection option byte" in the reference manual. - The address `0x8007C00` doesn't seem to be right at the beginning of a 4 KB sector BTW (it's on flash Page 31). - Check the linker script, and make sure that flash memory area you're writing to keeps unsed by the program, also check it with `arm-none-eabi-objdump -h prg.elf` – rel Sep 05 '20 at 16:07
  • Hello @rel These functions are all based on a copy of HAL flash drivers and they do work in pure C projects but not in C++. Write protection option bytes is not enabled. Size of each sector in this MCU is 1KB so that address is correct. It's possible to write to that address in C projects but not in C++. – Nixmd Sep 06 '20 at 10:39
  • Disabling optimization did not help – Nixmd Sep 06 '20 at 13:53

2 Answers2

4

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.

rel
  • 764
  • 5
  • 18
  • Hello @rel Thanks for all the time and effort you put into this article. Right now I don't have access to the hardware but I will check it tomorrow – Nixmd Sep 07 '20 at 18:09
  • Hi Nixmd, you're welcome, sorry for the very long post, I hope there's something useful in there to find out what's going on in your case. I've added some additional info on the linker script change I made and some checks that could be handy, meant as a future reference. I had troubles too with a similar STM32 flash problem recently, but I'm not sure if these findings are going to help with your specific problem. - It is possible that you need to change some of the compiler/linker flags, in the case of C++... I have no advice on that, because I've never worked with C++ on STM32 so far. – rel Sep 08 '20 at 17:24
  • here is disassembled instructions of attempting to write to flash written in C++ => ```131 *(__IO uint16_t*) LINE_NUMBER_FLASH_ADDRESS = lineNumber;``` ```08002098: uxth r2, r0``` ```0800209a: ldr r3, [pc, #68] ; (0x80020e0 )``` ```0800209c: strh r2, [r3, #0]``` – Nixmd Sep 09 '20 at 06:57
  • It's using `strh` instruction but still generates hardfault which makes it misterius :/ – Nixmd Sep 09 '20 at 07:00
  • In that case, and if all other mentioned error sources have been checked, you could try to isolate the problem further, by starting from the working C variant of the project. Maybe you could add new parts in small incremental steps, until this strange problem occurs... It would be interesting to step through the program with a debugger to see what's going on (status register? backtrace?). And you could catch the Hard Fault interrupt, in a usually already generated ISR called `HardFault_Handler()`, defined in `Src/stm32f0xx_it.c` (if STM32CubeMX was used) and use that for debugging as well. – rel Sep 09 '20 at 16:38
0

I had the same issue. My mistake was invalid alignment. I'm assured 90% of such a errors is alignment issues, especially on Cortex-M0. I've made proper alignment of my strucrure inside C++ class and it was enough. Note: functions executed in RAM, like write half page, should better be inside extern "C" {} expression.