STM32 platform uploads ELF file, not BIN

I build a simple “Blink” application, which does not work. Upon inspection, I found that there’s ELF file in the flash memory of my STM32. Here’s build log which confirms this:

Building in release mode
Checking size .pio\build\bluepill_f103c8_128k\firmware.elf
Advanced Memory Usage is available via "PlatformIO Home > Project Inspect"
RAM:   [==        ]  21.1% (used 4312 bytes from 20480 bytes)
Flash: [=         ]  14.8% (used 19448 bytes from 131072 bytes)
Configuring upload protocol...
AVAILABLE: blackmagic, cmsis-dap, dfu, jlink, stlink
CURRENT: upload_protocol = stlink
Uploading .pio\build\bluepill_f103c8_128k\firmware.elf

I need to upload BIN file instead.

My platformio.ini file:

[env:bluepill_f103c8_128k]
platform = ststm32
board_build.core = maple
board = bluepill_f103c8_128k
framework = arduino
build_flags = -DBOARD_RCC_PLLMUL=RCC_PLLMUL_6 -DSTM32 -DCONFIG_MAPLE_MINI_NO_DISABLE_DEBUG
upload_protocol = stlink

Do you really see the ELF header in flash? If that was the case the devs should have catched tha a long time ago. With upload_protocol = stlink (and no offset_address) it passes the ELF file to OpenOCD, which it handles perfectly fine per doc. You can see the verbose flashing command with the project task Advanced → Verbose Upload.

It appears I have already opened a similar issue 3 years ago: OpenOCD loads ELF file into Blue Pill, not BIN (for framework-arduinoststm32-maple), firmware is linked incorrectly · Issue #263 · platformio/platform-ststm32 · GitHub

Here’s my verbose flashing command:

Processing bluepill_f103c8_128k (platform: ststm32; board_build.core: maple; board: bluepill_f103c8_128k; framework: arduino; build_flags: -DBOARD_RCC_PLLMUL=RCC_PLLMUL_6 -DSTM32 -DCONFIG_MAPLE_MINI_NO_DISABLE_DEBUG; upload_protocol: stlink)
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------CONFIGURATION: https://docs.platformio.org/page/boards/ststm32/bluepill_f103c8_128k.html
PLATFORM: ST STM32 (15.4.1) > BluePill F103C8 (128k)
HARDWARE: STM32F103C8T6 72MHz, 20KB RAM, 128KB Flash
DEBUG: Current (stlink) External (blackmagic, cmsis-dap, jlink, stlink)
PACKAGES:
 - framework-arduinoststm32-maple @ 3.10000.201129 (1.0.0)
 - tool-dfuutil @ 1.11.0
 - tool-openocd @ 2.1100.211028 (11.0)
 - tool-stm32duino @ 1.0.2
 - toolchain-gccarmnoneeabi @ 1.70201.0 (7.2.1)
LDF: Library Dependency Finder -> https://bit.ly/configure-pio-ldf
LDF Modes: Finder ~ chain, Compatibility ~ soft
Found 32 compatible libraries
Scanning dependencies...
No dependencies
Building in release mode
MethodWrapper(["checkprogsize"], [".pio\build\bluepill_f103c8_128k\firmware.elf"])
Advanced Memory Usage is available via "PlatformIO Home > Project Inspect"
RAM:   [==        ]  21.1% (used 4312 bytes from 20480 bytes)
Flash: [=         ]  14.8% (used 19448 bytes from 131072 bytes)
.pio\build\bluepill_f103c8_128k\firmware.elf  :

section            size        addr

.text             14176   134238208

.text.align           0   134252384

.ARM.exidx            8   134252384

.data              3240   536873984

.rodata            2024   134255632

.bss               1072   536877224

.debug_frame        960           0

.comment            126           0

.ARM.attributes      41           0

Total             21647
<lambda>(["upload"], [".pio\build\bluepill_f103c8_128k\firmware.elf"])
AVAILABLE: blackmagic, cmsis-dap, dfu, jlink, stlink
CURRENT: upload_protocol = stlink
openocd -d2 -s C:\Users\azarubkin\.platformio\packages\tool-openocd/scripts -f interface/stlink.cfg -c "transport select hla_swd" -f target/stm32f1x.cfg -c "reset_config none" -c "program {.pio\build\bluepill_f103c8_128k\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

hla_swd
Info : The selected transport took over low-level target control. The results might differ compared to plain JTAG/SWD
Info : DEPRECATED target event trace-config; use TPIU events {pre,post}-{enable,disable}
none separate

Info : clock speed 1000 kHz
Info : STLINK V2J37S7 (API v2) VID:PID 0483:3748
Info : Target voltage: 3.192675
Info : stm32f1x.cpu: Cortex-M3 r1p1 processor detected
Info : stm32f1x.cpu: target has 6 breakpoints, 4 watchpoints
Info : starting gdb server for stm32f1x.cpu on 3333
Info : Listening on port 3333 for gdb connections
target halted due to debug-request, current mode: Thread 
xPSR: 0x01000000 pc: 0x00010100 msp: 0x464c457c
** Programming Started **
Info : device id = 0x20036410
Info : flash size = 128kbytes
** Programming Finished **
** Verify Started **
** Verified OK **
** Resetting Target **
shutdown command invoked

If I set the board to genericSTM32F103CB instead of bluepill_f103c8_128k, it works.

For some reason, openocd writes additional headers to the beginning of the flash memory when an application has an offset in its linker script

That’s weird, with your config above there should be no offset. Did you locally modify a linker file?

Try adding

board_upload.offset_address = 0x0

if you don’t have an offset but want to upload the .bin file.

Now it says

Uploading .pio\build\bluepill_f103c8_128k\firmware.bin
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: 1

hla_swd
none separate

target halted due to debug-request, current mode: Thread 
xPSR: 0x01000000 pc: 0xfffffffe msp: 0xfffffffc
** Programming Started **
Warn : no flash bank found for address 0x00000000
** Programming Finished **
** Verify Started **
Error: checksum mismatch - attempting binary compare
embedded:startup.tcl:1070: Error: ** Verify Failed **
in procedure 'program' 
in procedure 'program_error' called at file "embedded:startup.tcl", line 1131
at file "embedded:startup.tcl", line 1070
*** [upload] Error 1

and the flash memory contents are unchanged (I performed full chip erase before programming).

With board_upload.offset_address = 0x8000000 it programs but firmware does not start.

Why is this at 0x8005000 and not 0x8000000? Is there any bootloader active or did you change linker addresses at some point?

If you have local changes that are save-worthy, I’d recommend removing C:\Users\.platformio\packages\framework-arduinoststm32-maple and ``C:\Users.platformio.cacheand rebuilding and uploading without anyoffset_address. Double check that .text is at 0x8000000 then.

I don’t know. Based on my platformio.ini, I presume there’s no bootloader.

Did that, but no, .text is at 0x8005000. Are you able to replicate my case?

PLATFORM: ST STM32 (15.4.1) > BluePill F103C8 (128k)
HARDWARE: STM32F103C8T6 72MHz, 20KB RAM, 128KB Flash
DEBUG: Current (stlink) External (blackmagic, cmsis-dap, jlink, stlink)
PACKAGES:
 - framework-arduinoststm32-maple @ 3.10000.201129 (1.0.0)
 - tool-dfuutil @ 1.11.0
 - tool-openocd @ 2.1100.211028 (11.0)
 - tool-stm32duino @ 1.0.2
 - toolchain-gccarmnoneeabi @ 1.70201.0 (7.2.1)

The linker script in verbose build output is set to flash.ld.
Its contents:

INCLUDE mem-flash.inc

/* Provide memory region aliases for common.inc */
REGION_ALIAS("REGION_TEXT", rom);
REGION_ALIAS("REGION_DATA", ram);
REGION_ALIAS("REGION_BSS", ram);
REGION_ALIAS("REGION_RODATA", rom);

/* Let common.inc handle the real work. */
INCLUDE common.inc

The contents of mem-flash.inc:

MEMORY
{
  ram (rwx) : ORIGIN = 0x20000C00, LENGTH = 17K
  rom (rx)  : ORIGIN = 0x08005000, LENGTH = 108K
}

So, the wrong linker script is used.

o_O okay there’s the hardcoded 0x5000 offset, but why… Is that verbiatim from the framework?

I’ve looked at Arduino_STM32/STM32F1/variants/generic_stm32f103c/ld at bab4fda0fa31ba2d1f91f45329843d2df77da74b · rogerclarkmelbourne/Arduino_STM32 · GitHub but really don’t understand why it links stuff a certain way. For flash.ld, it links through mem-flash.inc inclusion with 0x5000 offset, but when using flash_c8.ld, it links with 0 offset (but only 44K available user memory. It really really likes its bootloader it seems…

Anything changes when you set

board_build.ldscript = jtag_c8.ld

?

Yes, now .text is at 0x8000000. The linker script selection algorithm should be adjusted.

I think the Maple framework wants all the boards to have its Maple DFU bootloader, hence all the offsets, except in the JTAG linker scripts.

I find the Maple core to be very outdated anyway. Is the regular core (GitHub - stm32duino/Arduino_Core_STM32: STM32 core support for Arduino) no good for this project?

I just got used to that core. In the regular core I’m not sure how to set custom RCC PLL multiplier. In Maple core, it’s achieved by adding a single #define: -DBOARD_RCC_PLLMUL=RCC_PLLMUL_6.

For STM32Duino, you override the whole _weak clock init method with your own and have your settings directly in there, i.e. in your regular firmware code somewhere you add

extern "C" void SystemClock_Config(void)
{
  RCC_OscInitTypeDef RCC_OscInitStruct = {};
  RCC_ClkInitTypeDef RCC_ClkInitStruct = {};
  RCC_PeriphCLKInitTypeDef PeriphClkInit = {};

  /* Initializes the CPU, AHB and APB busses clocks */
  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
  RCC_OscInitStruct.HSEState = RCC_HSE_ON;
  RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
  RCC_OscInitStruct.HSIState = RCC_HSI_ON;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
  RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL6;  // was: RCC_PLL_MUL9
  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK) {
    Error_Handler();
  }

  /* Initializes the CPU, AHB and APB busses clocks */
  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_SYSCLK
                                | RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2;
  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
  RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;

  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK) {
    Error_Handler();
  }

  PeriphClkInit.PeriphClockSelection = RCC_PERIPHCLK_ADC | RCC_PERIPHCLK_USB;
  PeriphClkInit.AdcClockSelection = RCC_ADCPCLK2_DIV6;
 // this likely needs adjusting too to hit 48MHz when USB is needed!!
  PeriphClkInit.UsbClockSelection = RCC_USBCLKSOURCE_PLL_DIV1_5;
  if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInit) != HAL_OK) {
    Error_Handler();
  }
}