Lora-net/LoRaMac-node

EEPROM emulation library problems with STM32L4xx single-bank flash configuration

Closed this issue · 3 comments

When used on STM32L4xx platforms (and possibly others), with flash configured for single-bank mode, the EEPROM emulation library miscalculates flash sectors which causes silent erase failures.

The stack uses the ST EEPROM emulation library for persistence on ST targets; this uses flash on the target to emulate EEPROM. When porting the stack to a new ST target, the emulated EEPROM must be given a place in the flash memory map; this is done by setting START_PAGE_ADDRESS in boards/myBoard/eeprom_emul_conf.h. In my case, I am using a 512K flash processor (STM32L471) with the flash memory configured as a single contiguous bank so I set the emulated EEPROM start page address to to 0x0806E000 (flash page 220).

This results in a subtle, silent failure. I think this is a bug in the ST EEPROM emulation library that first manifests when the emulated EEPROM is initialized (EEPROM_Emul/Core/eeprom_emul.c: EE_Init(). This calls EEPROM_Emul/Porting/STM32L4/flash_interface.c: Page_Erase() which calls GetBankNumber(). That function should consider whether flash is configured in single or dual bank mode, but simply assumes dual-bank mode resulting in the wrong sector(s) being erased.

The EEPROM emulation library: src/boards/mcu/stm32/EEPROM_Emul/Core/eeprom_emul.h defines:

  • #define PAGE(__ADDRESS__) (uint32_t)((((__ADDRESS__) - FLASH_BASE) % BANK_SIZE) / FLASH_PAGE_SIZE)
  • BANK_SIZE is defined as FLASH_BANK_SIZE from HAL which is defined as (FLASH_SIZE >> 1) so it works out to 256K on my 512K processor
  • Note that this limits the calculated page numbers to the first half of flash.
  • The reverse macro tries to fix this by adding BANK_SIZE if the START_PAGE_ADDRESS is higher than BANK_SIZE:
    #define PAGE_ADDRESS(__PAGE__) (uint32_t)(FLASH_BASE + (__PAGE__) * PAGE_SIZE + ((START_PAGE_ADDRESS - FLASH_BASE) / BANK_SIZE) * BANK_SIZE)
    Although that works for most cases, it doesn't fix a subtle problem when Core/eeprom_emul.c calls PageErase(page, 1U) in Porting/STM32L4/flash_interface.c and passes in the page number to erase. PageErase() uses the reversing macro to calculate the address. This gets back to the correct address, but then GetBankNumber() is called to determine which bank that address is in and determines that it is in Bank 2. The actual flash erase operation then tries to erase sector 92 in bank 2 instead of sector 220 in bank 1. This works out to a page in the second 512K of flash...which doesn't exist on this processor - at least not officially (see below) and as a result, the erase sort of "works", but not on the appropriate sector.

The real issue (at least for the EEPROM emulation library) is that BANK_SIZE (which evaluates to FLASH_BANK_SIZE defined in HAL) needs to evaluate to the size of flash when in single-bank mode and half of flash when in dual-bank mode. I think the fix is to:

  1. Modify the definition of BANK_SIZE in src/boards/mcu/stm32/EEPROM_Emul/Porting/STM32L4/flash_interface.h to:
    #define BANK_SIZE ((FLASH->OPTR & FLASH_OPTR_DUALBANK)?FLASH_BANK_SIZE:FLASH_SIZE)
  2. Modify function GetBankNumber() in src/boards/mcu/stm32/EEPROM_Emul/Porting/STM32L4/flash_interface.c to use BANK_SIZE instead of FLASH_BANK_SIZE

I realize this is really a bug in the ST EEPROM emulation library, but because it is included in this code base, I thought it important to report the issue here so others are aware. This may affect more dual-bank-capable ST processors than the one mentioned here.

Why the erase works (but on the wrong sector): ST has a huge number of processor variants and almost certainly doesn't produce that many different types of wafer. So the STM32L471 is likely an STM32L476 with some fuses burnt to identify it as the smaller flash variant and packaging marked accordingly. The STM32L471 parts correctly report their flash size as 512K, but seem to contain 1MB of flash: when you write data to the upper 512K, it will be there after a power cycle and hasn't overwritten anything in the lower 512K. Erasing pages in the upper 512K also seems to work and returns an OK status. However, you can't count on a free 512K...ST may later make wafers that really only have 512K flash or otherwise disable access so you have to code assuming flash matches what is reported by the FLASH_SIZE register.

Another issue is that NvmDataMgmtStore() doesn't handle failures of NvmmWrite().
NvmmWrite() calls EepromMcuWriteBuffer(); if an error such as no active page occurs, returns LMN_STATUS_ERROR which effectively is not handled.

Thanks for reporting it here (we use real EEPROM chips), helpful for the LoRaWAN community.

In this project we just use the libraries provided by ST with a specific version.
The currently used version of the ST library functionality is enough for this project supported platforms.

Maybe in the mean time ST has produced newer versions of the library which is able to support newer MCU variants.
I would recommend to approach ST support.

In our examples we use the EEPROM emulation just as an example. The reason being that the produced firmware binary will work out of the box for the given evaluation kit. No need to have external components.

As @pieterdemil pointed out one should use external real NVM devices (EEPROM, FRAM, etc). The biggest advantage being the maximum number of MCU FLASH erase/write cycles which is much lower than for a real NVM memory.

In the future it would be nice if you could post this kind of questions on the project Discussions tab. It is a better place to engage discussions and then we can agree if it is an issue or not.
If you don't mind I move this issue to the Discussions tab.