Flashing STM32L15X EEPROM with STLink under Linux

Tags: Embedded, Linux, Programming

For a while now I've been evaluating some 32-bit micro controllers for a future product. One of them was the STM32L15x series. There are some handy development boards available such as the Nucleo boards. Since we need to have the ability to program processors from Linux for our small production line, tool support is one of the checkboxes that need to be ticked.

For the STM32 series, flashing the microcontroller can be done through GDB, OpenOCD, and the STLink tool. One issue that arose however was the need to program the EEPROM available on the STM32L series. This requirement comes from need to generate and program different EEPROM content on a per board basis at the production line. Doing that requires a few tweaks that are documented below...

Solutions

 

The EEPROM is located at address 0x08080000 while the regular flash sits at 0x08000000. The first thing we have to do is to allow us to differentiate between the two when writing C code. For this we can define a new attribute, which will allow us to relocate the EEPROM variables to another section (I'm assuming GCC here).

  #define    EEMEM     __attribute__ ((section(".eeprom")))

Once we have that, we can modify the linker script to a) define a new memory region and b) create an eeprom section to match it. The memory sections will now look like this:

MEMORY
{
  FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 256K
  EEPROM (rw): ORIGIN = 0x08080000, LENGTH = 8K
  RAM  (xrw) : ORIGIN = 0x20000000, LENGTH = 32K
}

 

With a .eeprom section like this after the existing flash section:

    .eeprom :
    {
        . = ALIGN(4);
        *(.eeprom)
        . = ALIGN(4);

    } >EEPROM

 

We can now define variables that reside in EEPROM:

uint8_t EEMEM somevar;​

That gets us half way there.

For the test purpose the STLink tool was used to program the device. The reason for this is that it is easy to script and can be integrated easily into the workflow of our production process. 

Using the STLink tool to write the resulting binary file causes some issues though. First, the tool will not allow to write beyond 0x08000000 + FLASH SIZE since it doesn't know there is EEPROM at 0x08080000. We can fix this by changing the STM32_CHIPID_L152_RE​ definition in stlink-common.h​ and change the .flash_size_reg​. Adding a large enough value to the size will stop STLink from complaining. 

The second issue is that the resulting linked binary is very large due to padding between the Flash and EEPROM section. This causes very long programming times and probably messes up a bunch of other stuff. Several times the programming would fail etc., which is understandable. To work around this, and this would be something we wanted anyway, we will split the Flash and EEPROM sections into separate files and also program them separately. This is similar to the way AVR micro controllers often get programmed.

To do this, we first build two hex files from .elf file generated. Let's assume this .elf file is called main.elf and we want to generate a file called main.hex for the Flash section, and another file called main.eep for the EEPROM file. We do this as follows:

For the Flash:
arm-none-eabi-objcopy -O ihex -R .eeprom main.elf program.hex

For the EEPROM:
arm-none-eabi-objcopy -j .eeprom --set-section-flags=.eeprom="alloc,load" --change-section-lma .eeprom=0 -O ihex main.elf program.eep​

These two files will be in Intel HEX format; the EEPROM file can now be processed to change certain aspects at the production line for each device. Once done, we have to convert them back to binary format since STLink doesn't take Intel HEX. This is done as follows:

arm-none-eabi-objcopy -I ihex -O binary program.hex program.hex.bin
arm-none-eabi-objcopy -I ihex -O binary program.eep program.eep.bin

Now we can finally program the STM32 with STLink. The Flash at address 0x08000000:

./st-flash --reset write program.hex.bin 0x08000000

​and the EEPROM at 0x08080000:

./st-flash --reset write program.eep.bin 0x08080000

This way, both can be programmed and modified independently.

One remaining issue is that this only works when the EEPROM has been cleared, which the STLink tool cannot do. While it is the intention for us to add this to the STLink tool in the near future, for now we use OpenOCD to do the clearing (OpenOCD also can not write the EEPROM, otherwise we'd just use it). The mass erase functionality for the STM32L is available, and we use it on the command line as follows:

openocd -f interface/stlink-v2-1.cfg -f target/stm32l1.cfg -f command.conf -c "erase_stm32l"

The "erase_stm32l" command is located in the command.conf file and looks like this:

  proc erase_stm32l { } {
      init
      halt
      stm32lx mass_erase 0
      exit
  }

Since this erases the chip completely, we can remove the erase section in STLink for now as to not erase the chip twice. And that's it. The programming tool we use at the assembly line will thus first use OpenOCD to clear the chip, and then use STLink to write the main binary and the EEPROM binary to the STM32. 

 

EDIT: one important thing I forgot to mention. The OpenOCD mass_erase command also erases the option bytes. Only the one at 0x1FF80000 gets put back. This will cause all kinds of side effects, such as not being able to write to EEPROM from within your code. To work around this, one can reset the option bytes by editing the stm32lx.c file in OpenOCD and add the following in stm32lx_mass_erase() before the read/write operation to FLASH_PECR near the end of the function:

/* unlock the oprions bytes*/
​stm32lx_unlock_options_bytes(bank);

 /* Reset the Option Bytes to default */
target_write_u32(target, 0x1FF80004, 0xff870078);
target_write_u32(target, 0x1FF80008, 0xffff0000);
target_write_u32(target, 0x1FF8000C, 0xffff0000);
target_write_u32(target, 0x1FF80010, 0xffff0000);
target_write_u32(target, 0x1FF80014, 0xffff0000);
target_write_u32(target, 0x1FF80018, 0xffff0000);
target_write_u32(target, 0x1FF8001C, 0xffff0000);
target_write_u32(target, 0x1FF80080, 0xffff0000);
target_write_u32(target, 0x1FF80084, 0xffff0000);

Needless to say, having all this properly integrated in OpenOCD and/or STLink will make life much easier. Hopefully we'll be able to get to doing that soon...

EDIT 2: Preliminary support for OpenOCD is added, you can find it in the 'playground' branch of my OpenOCD fork. This code is by no means intended to be production ready or to be sent upstream. Needs more work and cleanup...  However, you can use the following commands:

Resetting the option bytes:
stm32lx reset_option_bytes 0

Read the option bytes and report their values:
stm32lx read_option_bytes 0

Writing the EEPROM:
flash write_image program.eep.bin 0x08080000

This latter one is identical to writing the flash image, except that passing the address switches the behavior of the stm32lx_write() function because stm32lx_probe() changes the base address. For this to work, a new 'flash bank' command is added to the 'target/stm32l1.cfg' TCL script.