PlatformIO Community

Dual Bank STM32L082

Hello,

I am using a custom PCB with an STM32L082 (192kByte flash). The PCB works great (sw is stm32duino based).

I found out it has dual bank support. My idea is to have 2 firmware versions and to be able to select which firmware to boot. Which bank is used to boot from can be selected by using the BFB2 bit (Boot From Bank 2).

My assumption is that I can “just” flash the BIN file generated by platformio to bank2 (0x0801 8000) (using CubeProgrammer or similar). However that seems not to work.

Am I missing something or do I need to make alterations during the compilation of the BIN file in platformio?

Thanks,
deBuffel

I think you need to at least tell the firmware that its base address is now different, since the code is not position-independent (-fPIC). That should be achievable via manipulating certain build flags and upload options in the platformio.ini. Specifcially, for uploading, board_upload.offset_address should be set to the absolute address where the binary shall be flashed. (Then PIO is also perfectly capable of flashing the binary directly).

Further, in the STM32Duino, sadly it only chooses from a handfull of offsets depending on the upload protocol, which won’t be correct for your case. So you would have to manually define -DVECT_TAB_OFFSET=<offset from 0x8000000> and the -Wl,--defsym=LD_FLASH_OFFSET flag.

In general, the Application note AN4767 by STMicroelectronics would also be an interesting read for you.

In any case, if I create an example project like

[env:nucleo_l073rz]
platform = ststm32
board = nucleo_l073rz
framework = arduino

with functionally empty src/main.cpp of

#include <Arduino.h>

void setup(){}
void loop(){}

and compile the project, I can inspect the start address of the ELF file by force-disassembling the first 20 ‘instructions’ in the ELF file by doing

>arm-none-eabi-objdump -D .pio\build\nucleo_l073rz\firmware.elf | head -n20

.pio\build\nucleo_l073rz\firmware.elf:     file format elf32-littlearm


Disassembly of section .isr_vector:

08000000 <g_pfnVectors>:
 8000000:       20005000        andcs   r5, r0, r0
 8000004:       08001c05        stmdaeq r0, {r0, r2, sl, fp, ip}

and so the the firmware starts at the regular 0x08000000. Now when I rewrite the platformio.ini as in…

; global build settings
[env]
platform = ststm32
board = nucleo_l073rz
framework = arduino

[env:nucleo_l073rz_bank1]
; no special options

[env:nucleo_l073rz_bank2]
board_upload.offset_address = 0x08018000
extra_scripts = set_bank2.py

with set_bank2.py as

Import("env")
offset = 0x18000 # from 0x8000000
env.Append(
    CPPDEFINES=[("VECT_TAB_OFFSET", "%s" % hex(offset))],
)
# remove old 0-offset, inject new one
linkflags = env["LINKFLAGS"]
linkflags = [x for x in linkflags if not str(x).startswith("-Wl,--defsym=LD_FLASH_OFFSET=")]
linkflags.append("-Wl,--defsym=LD_FLASH_OFFSET=%s" % hex(offset))
env["LINKFLAGS"] = linkflags

And try to compile, most annoyingly, there is a compile error caused by the provided Arduino-STM32Duino…

C:\Users\Max\.platformio\packages\framework-arduinoststm32\system\STM32L0xx/system_stm32l0xx.c:71:1: error: unknown type name 'define'
   71 | define USER_VECT_TAB_ADDRESS
      | ^~~~~~

This line has to be #define USER_VECT_TAB_ADDRESS to be fixed. Doing that, one gets a Bank2 firmware which…

>arm-none-eabi-objdump -D .pio\build\nucleo_l073rz_bank2\firmware.elf | head -n20

.pio\build\nucleo_l073rz_bank2\firmware.elf:     file format elf32-littlearm


Disassembly of section .isr_vector:

08018000 <g_pfnVectors>:
 8018000:       20005000        andcs   r5, r0, r0
 8018004:       08019c11        stmdaeq r1, {r0, r4, sl, fp, ip, pc}

starts at the right address ^-^. And as a side-effect, it also builds the firmware made for Bank1.

Now that firmware has a better chance of working. I’m not sure about the bank boot selection process, but if you’ve already figured that out so that it immediately starts executing at 0x08018000, that should also be taken care of.

1 Like

Hi Maximilian,

thanks a lot for sharing this and the great explanation. I think this is a very good starting point for me (it saves me a lot of time and frustrations :-)).

I always assumed that a generated BIN file is independent of the NVM addresses and one can “just” flash it anywhere.

Will dig into it and post my results later …

Chrs

Most ARM/Thumb code is actually position independent because the calls e.g. usually have relative addressing (e.g., +200 bytes ahead), but the vector table contains absolute addresses of the handler functions, and that includes the Reset_Handler, which is the address of the first executed code. Also, when code uses function pointers, they are usually implemented as absolute jumps (load absolute address into register and then branch/jump to that register’s value). So without those internally used firmware addresses agreeing with where the binary is actually flashed, that will crash and burn.

I have been compiling 2 banks simultaneously and it is nice to be able to compile and generate 2 bins.
After I followed your steps it generated 2 bins and elf.

Bank1 and bank2 both start at 0x08000000

>arm-none-eabi-objdump -D .pio\build\STM32L082_bank2\firmware.elf | more

.pio\build\STM32L082_bank2\firmware.elf:     file format elf32-littlearm


Disassembly of section .isr_vector:

08000000 <g_pfnVectors>:
 8000000:       20005000        andcs   r5, r0, r0

Bank1 similar

>arm-none-eabi-objdump -D .pio\build\STM32L082_bank1\firmware.elf | more

.pio\build\STM32L082_bank1\firmware.elf:     file format elf32-littlearm


Disassembly of section .isr_vector:

08000000 <g_pfnVectors>:
 8000000:       20005000        andcs   r5, r0, r0

The flashing of the code works fine but it does not run (simple print). When not generating the bank2 files (uncomment in platform.ini) and only flash bank1 the code does run.

Then your board might use a different Arduino core implementations that does not respect these macros and linker symbols. What’s the full platformio.ini, and possibly custom JSON files and linker file?

Below the files. Not aware of a different arduino core implementation. However such low level things are pretty new for me (steep learning curve)

platform.ini

[env]
platform = ststm32
board = JENG_STM32L082KZ
framework = arduino
monitor_speed = 115200                      
; monitor_filters = default, time, log2file

[env:STM32L082_bank1]
; no special options

[env:STM32L082_bank2]
board_upload.offset_address = 0x08018000
extra_scripts = set_bank2.py

JSON board file

{
  "build": {
    "cpu": "cortex-m0plus",
    "extra_flags": "-DSTM32L082xx",
    "f_cpu": "32000000L",
    "framework_extra_flags": {
      "arduino": "-D__CORTEX_SC=0"
    },
    "mcu": "stm32l082kz",
    "product_line": "STM32L082xx",
    "variant": "STM32L0xx/L082KZ_JENG"
  },
  "debug": {
    "default_tools": [
      "stlink"
    ],
    "jlink_device": "STM32L082KZ",
    "onboard_tools": [
      "stlink"
    ],
    "openocd_board": "JENG_STM32L082KZ",
    "openocd_target": "stm32l0",
    "svd_path": "STM32L0x2"
  },
  "frameworks": [
    "arduino"
  ],
  "name": "JENG_STM32L082KZ",
  "upload": {
    "maximum_ram_size": 20480,
    "maximum_size": 196608,
    "protocol": "mbed",
    "protocols": [
      "stlink",
      "mbed"
    ]
  },
  "url": "https://www.st.com/en/microcontrollers-microprocessors/stm32l082kz.html",
  "vendor": "ST"
}

Linkerscript (ldscript.ld)

/**
 ******************************************************************************
 * @file      LinkerScript.ld
 * @author    Auto-generated by STM32CubeIDE
 * @brief     Linker script for STM32L072KZTx Device from STM32L0 series
 *                      192Kbytes FLASH
 *                      20Kbytes RAM
 *
 *            Set heap size, stack size and stack location according
 *            to application requirements.
 *
 *            Set memory bank area and size if external memory is used
 ******************************************************************************
 * @attention
 *
 *
 * This software component is licensed by ST under BSD 3-Clause license,
 * the "License"; You may not use this file except in compliance with the
 * License. You may obtain a copy of the License at:
 *                        opensource.org/licenses/BSD-3-Clause
 *
 ******************************************************************************
 */
/* Entry Point */
ENTRY(Reset_Handler)

/* Highest address of the user mode stack */
_estack = ORIGIN(RAM) + LENGTH(RAM);	/* end of "RAM" Ram type memory */

_Min_Heap_Size = 0x200;	/* required amount of heap  */
_Min_Stack_Size = 0x400;	/* required amount of stack */

/* Memories definition */
MEMORY
{
  RAM    (xrw)    : ORIGIN = 0x20000000,   LENGTH = 20K
  FLASH    (rx)    : ORIGIN = 0x8000000,   LENGTH = 192K
}

/* Sections */
SECTIONS
{
  /* The startup code into "FLASH" Rom type memory */
  .isr_vector :
  {
    . = ALIGN(4);
    KEEP(*(.isr_vector)) /* Startup code */
    . = ALIGN(4);
  } >FLASH

  /* The program code and other data into "FLASH" Rom type memory */
  .text :
  {
    . = ALIGN(4);
    *(.text)           /* .text sections (code) */
    *(.text*)          /* .text* sections (code) */
    *(.glue_7)         /* glue arm to thumb code */
    *(.glue_7t)        /* glue thumb to arm code */
    *(.eh_frame)

    KEEP (*(.init))
    KEEP (*(.fini))

    . = ALIGN(4);
    _etext = .;        /* define a global symbols at end of code */
  } >FLASH

  /* Constant data into "FLASH" Rom type memory */
  .rodata :
  {
    . = ALIGN(4);
    *(.rodata)         /* .rodata sections (constants, strings, etc.) */
    *(.rodata*)        /* .rodata* sections (constants, strings, etc.) */
    . = ALIGN(4);
  } >FLASH

  .ARM.extab   : {
    . = ALIGN(4);
    *(.ARM.extab* .gnu.linkonce.armextab.*)
    . = ALIGN(4);
  } >FLASH

  .ARM : {
    . = ALIGN(4);
    __exidx_start = .;
    *(.ARM.exidx*)
    __exidx_end = .;
    . = ALIGN(4);
  } >FLASH

  .preinit_array     :
  {
    . = ALIGN(4);
    PROVIDE_HIDDEN (__preinit_array_start = .);
    KEEP (*(.preinit_array*))
    PROVIDE_HIDDEN (__preinit_array_end = .);
    . = ALIGN(4);
  } >FLASH

  .init_array :
  {
    . = ALIGN(4);
    PROVIDE_HIDDEN (__init_array_start = .);
    KEEP (*(SORT(.init_array.*)))
    KEEP (*(.init_array*))
    PROVIDE_HIDDEN (__init_array_end = .);
    . = ALIGN(4);
  } >FLASH

  .fini_array :
  {
    . = ALIGN(4);
    PROVIDE_HIDDEN (__fini_array_start = .);
    KEEP (*(SORT(.fini_array.*)))
    KEEP (*(.fini_array*))
    PROVIDE_HIDDEN (__fini_array_end = .);
    . = ALIGN(4);
  } >FLASH

  /* Used by the startup to initialize data */
  _sidata = LOADADDR(.data);

  /* Initialized data sections into "RAM" Ram type memory */
  .data :
  {
    . = ALIGN(4);
    _sdata = .;        /* create a global symbol at data start */
    *(.data)           /* .data sections */
    *(.data*)          /* .data* sections */
    *(.RamFunc)        /* .RamFunc sections */
    *(.RamFunc*)       /* .RamFunc* sections */

    . = ALIGN(4);
    _edata = .;        /* define a global symbol at data end */

  } >RAM AT> FLASH

  /* Uninitialized data section into "RAM" Ram type memory */
  . = ALIGN(4);
  .bss :
  {
    /* This is used by the startup in order to initialize the .bss section */
    _sbss = .;         /* define a global symbol at bss start */
    __bss_start__ = _sbss;
    *(.bss)
    *(.bss*)
    *(COMMON)

    . = ALIGN(4);
    _ebss = .;         /* define a global symbol at bss end */
    __bss_end__ = _ebss;
  } >RAM

  /* User_heap_stack section, used to check that there is enough "RAM" Ram  type memory left */
  ._user_heap_stack :
  {
    . = ALIGN(8);
    PROVIDE ( end = . );
    PROVIDE ( _end = . );
    . = . + _Min_Heap_Size;
    . = . + _Min_Stack_Size;
    . = ALIGN(8);
  } >RAM

  /* Remove information from the compiler libraries */
  /DISCARD/ :
  {
    libc.a ( * )
    libm.a ( * )
    libgcc.a ( * )
  }

  .ARM.attributes 0 : { *(.ARM.attributes) }
}

This is not how the linker script is supposed to be, it is missing the variable flash size and offset fields…

  RAM    (xrw)    : ORIGIN = 0x20000000,   LENGTH = LD_MAX_DATA_SIZE
  FLASH    (rx)    : ORIGIN = 0x8000000 + LD_FLASH_OFFSET, LENGTH = LD_MAX_SIZE - LD_FLASH_OFFSET

from https://github.com/stm32duino/Arduino_Core_STM32/blob/main/variants/STM32F1xx/F103C8T_F103CB(T-U)/ldscript.ld#L37-L41

Use these exact two lines in your linkerscript and retry.

That seems to generate correct elf files for bank2

>arm-none-eabi-objdump -D .pio\build\STM32L082_bank2\firmware.elf | more

.pio\build\STM32L082_bank2\firmware.elf:     file format elf32-littlearm

Disassembly of section .isr_vector:

08018000 <g_pfnVectors>:
 8018000:       20005000        andcs   r5, r0, r0

The next challenge is the upload. It seems that the bank2 is not flashed at the right address.
The 0x08018000 appears to be empty (see below). Plus the code is not running.

image

When I reverse platformio.ini (bank2 first and bank1 last - see below) the code works. So I seems to me that with the platformio.ini below the bank1 is overwritten by bank2 code (at 0x0800 0000) - board_upload.offset_address = 0x08018000 not working

[env]
platform = ststm32
board = JENG_STM32L082KZ
framework = arduino
monitor_speed = 115200                      
; monitor_filters = default, time, log2file

[env:STM32L082_bank2]
board_upload.offset_address = 0x08018000
extra_scripts = set_bank2.py

[env:STM32L082_bank1]
; no special options

When you use the project environment switcher or just the project tasks panel to just execute a Advanced → Verbose Upload for the bank2 environments, what’s the OpenOCD command?

> Executing task in folder DualBank: C:\Users\Jeroen\.platformio\penv\Scripts\platformio.exe run --verbose --target upload --environment STM32L082_bank2 <

Processing STM32L082_bank2 (board_upload.offset_address: 0x08018000; extra_scripts: set_bank2.py; platform: ststm32; board: JENG_STM32L082KZ; framework: arduino; monitor_speed: 115200)
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
CONFIGURATION: https://docs.platformio.org/page/boards/ststm32/JENG_STM32L082KZ.html
PLATFORM: ST STM32 (15.2.0) > JENG_STM32L082KZ
HARDWARE: STM32L082KZ 32MHz, 20KB RAM, 192KB Flash
DEBUG: Current (stlink) On-board (stlink)
PACKAGES:
 - framework-arduinoststm32 4.20100.211028 (2.1.0)
 - framework-cmsis 2.50700.210515 (5.7.0)
 - tool-dfuutil 1.9.200310
 - tool-openocd 2.1100.211028 (11.0)
 - tool-stm32duino 1.0.2
 - toolchain-gccarmnoneeabi 1.90201.191206 (9.2.1)
LDF: Library Dependency Finder -> https://bit.ly/configure-pio-ldf
LDF Modes: Finder ~ chain, Compatibility ~ soft
Found 11 compatible libraries
Scanning dependencies...
No dependencies
Building in release mode
MethodWrapper(["checkprogsize"], [".pio\build\STM32L082_bank2\firmware.elf"])
Advanced Memory Usage is available via "PlatformIO Home > Project Inspect"
RAM:   [          ]   4.1% (used 844 bytes from 20480 bytes)
Flash: [=         ]   6.7% (used 13140 bytes from 196608 bytes)
.pio\build\STM32L082_bank2\firmware.elf  :

section              size        addr
.isr_vector           192   134316032
.text               11900   134316224
.rodata              1128   134328124
.ARM.extab              0   134329252
.ARM                    8   134329252
.preinit_array          0   134329260
.init_array            16   134329260
.fini_array             8   134329276
.data                 112   536870912
.bss                  732   536871024
.noinit                 0   536871756
._user_heap_stack    1540   536871756
.ARM.attributes        40           0
.comment              102           0
.debug_frame         1032           0

Total               16810
<lambda>(["upload"], [".pio\build\STM32L082_bank2\firmware.bin"])
AVAILABLE: mbed, stlink
CURRENT: upload_protocol = mbed
MethodWrapper(["upload"], [".pio\build\STM32L082_bank2\firmware.bin"])
Auto-detected: E:\
MethodWrapper(["upload"], [".pio\build\STM32L082_bank2\firmware.bin"])
Firmware has been successfully uploaded.
(Some boards may require manual hard reset)

That’s not good, when it uses mbed, it has no control over the firmware binary’s target address. It just accepts the plain file with no offset info. You need to make stlink the default upload method. (I hope one is fitted?)

Set upload_protocol = stlink in the [env] section of the platformio.ini, or, exchange

for "protocol": "stlink" in the board’s JSON file.

When switching to stlink as upload_protocol I see the following openocd command (in verbose upload):

AVAILABLE: mbed, stlink
CURRENT: upload_protocol = stlink
openocd -d2 -s C:\Users\Jeroen\.platformio\packages\tool-openocd/scripts -f board/JENG_STM32L082KZ.cfg -c "program {.pio\build\STM32L082_bank2\firmware.bin} 0x08018000 verify reset; shutdown;"

So that looks good. I used mbed as STLink often has ‘Error: target voltage may be too low for reliable debugging’ / as I see now again. Will have to fix it.

Thanks a lot so far / be back l8r

Maybe the VTARGET pin of the STLink isn’t properly connected to the system’s running voltage, like 3.3V. Or it has problems with lower voltages like 1.8V.

Vtarget I measure 2.5(V) (but cubeprogrammer says 0.01(V).
Maybe increase it to 3.3(V) tomorrow. Enough for today.

1 Like

When using STLINKv2 I have Vtarget now at good level and able to connect in CubeProgrammer.
Platformio upload struggling to get it working

For the variabels from the linker file - see below - does I need to add extra build_flags in the platformio.ini ?

  • LD_MAX_DATA_SIZE
  • LD_FLASH_OFFSET
  • LD_MAX_SIZE

I could imagine something like this to add?
build_flags = -Wl,--defsym=LD_MAX_DATA_SIZE=0x5000 -Wl,--defsym=LD_MAX_SIZE=0x30000 -Wl,--defsym=LD_FLASH_OFFSET=0x18000

Meaning it fails to upload (with what error message?) or fails to run the code in bank2?

Apologise :roll_eyes:. my Dual Bank experiment was burried due to other projects. Will pick it up in the coming weeks again.

The status quo was that upload is not working when using platformio (CubeProgrammer is able to connect).

AVAILABLE: mbed, stlink
CURRENT: upload_protocol = stlink
openocd -d2 -s C:\Users\jeroen\.platformio\packages\tool-openocd/scripts -f board/JENG_STM32L082KZ.cfg -c "program {.pio\build\STM32L082_bank1\firmware.elf}  verify reset; shutdown;"
xPack OpenOCD x86_64 Open On-Chip Debugger 0.11.0+dev (2021-10-16-21:19)
Licensed under GNU GPL v2
For bug reports, read
        http://openocd.org/doc/doxygen/bugs.html
debug_level: 2

Info : The selected transport took over low-level target control. The results might differ compared to plain JTAG/SWD
Info : clock speed 300 kHz
Info : STLINK V2J34S7 (API v2) VID:PID 0483:3748
Info : Target voltage: 1.767589
Error: init mode failed (unable to connect to the target)
in procedure 'program'
** OpenOCD init failed **
shutdown command invoked

*** [upload] Error 1

Can you open an issue about this in Issues · platformio/platform-ststm32 · GitHub? If it works with other tools, it should work with ours.

Just issued it: https://github.com/platformio/platform-ststm32/issues/641

1 Like