Using newlib with libopencm3 on STM32F103 - linker settings?

Famework: libopencm3
Board: bluepill (STM32F103)
freeRTOS CDC example: stm32f103c8t6/rtos/usbcdcdemo at master · ve3wwg/stm32f103c8t6 · GitHub
Authors makefile: stm32f103c8t6/Makefile.incl at master · ve3wwg/stm32f103c8t6 · GitHub

I am trying to continue with freeRTOS examples from the STM32 Beginners book. I am trying to make the CDCserial example work compiled from platformio. Currently if I compile with the authors make file, the file easily fits in the 20k bluePill RAM, but when compiled in platformio, the ram is 21kb - 688 bytes too much. It appears the solution for this is the use of newlib.

It seems like maybe I need a separate target.json file, or some way to specify more linker config? As usual, any help is greatly appreciated!

My current platformio.ini :

;PlatformIO Project Configuration File


platform = ststm32

board = genericSTM32F103CB

framework = libopencm3

lib_deps = 




; Find dependencies

;lib_ldf_mode = chain

build_unflags = -lc -lgcc

;******** ST Link V2 Flags ********

debug_tool = stlink 

;******** Flag to fix weak types in freeRTOS libopencm3 Implementation ********

lib_archive = no

;******** DFU Upload Flags ********

; Must be in DFU USB bootloader mode.


;upload_command = dfu\dfu-util.exe -D .pio\build\genericSTM32F103CB\firmware.bin

;******** Custom Linker File ********

;build_flags =-Wl,-T'"$PROJECT_DIR/src/bluepill_custom.ld"',-v

;******** RS232 Debug Monitor Settings ********


monitor_speed = 115200

;monitor_port = 

;monitor_flags= --raw

;******** Knock-off STM32F103 Chip ID Hack ********


; Legit ID: 0x1ba01477

; Clone ID: 0x2ba01477

upload_flags = -c set CPUTAPID 0x2ba01477

build_flags = 

;-Wextra -Wshadow -Wimplicit-function-declaration

;-Wredundant-decls -Wmissing-prototypes -Wstrict-prototypes

;-fno-common -ffunction-sections -fdata-sections

 -Wextra -Wshadow -Wimplicit-function-declaration

 -fno-common -ffunction-sections -fdata-sections


The following is my compiler output:

    Archiving .pio\build\genericSTM32F103CB\libFrameworkLibOpenCM3.a
    Indexing .pio\build\genericSTM32F103CB\libFrameworkLibOpenCM3.a
    Linking .pio\build\genericSTM32F103CB\firmware.elf
    .pio\build\genericSTM32F103CB\lib2e3\newlib\newlib\libc\string\strcasecmp.o: In function `strcasecmp':
    strcasecmp.c:(.text.strcasecmp+0xa): undefined reference to `__locale_ctype_ptr'
    strcasecmp.c:(.text.strcasecmp+0x20): undefined reference to `__locale_ctype_ptr'
    .pio\build\genericSTM32F103CB\lib898\rtos\tasks.o: In function `vTaskSwitchContext':
    tasks.c:(.text.vTaskSwitchContext+0x26): undefined reference to `vApplicationStackOverflowHook'
    .pio\build\genericSTM32F103CB\libef7\getline\getline.o: In function `getline':
    getline.c:(.text.getline+0x28): undefined reference to `getc'
    getline.c:(.text.getline+0x44): undefined reference to `__assert_func'
    getline.c:(.text.getline+0x4c): undefined reference to `malloc'
    getline.c:(.text.getline+0x6a): undefined reference to `realloc'
    .pio\build\genericSTM32F103CB\src\adventure.o: In function `adventure':
    adventure.c:(.text.adventure+0xee): undefined reference to `__locale_ctype_ptr'
    adventure.c:(.text.adventure+0x116): undefined reference to `__locale_ctype_ptr'
    adventure.c:(.text.adventure+0x178): undefined reference to `__locale_ctype_ptr'
    adventure.c:(.text.adventure+0x21e): undefined reference to `__locale_ctype_ptr'
    adventure.c:(.text.adventure+0x37a): undefined reference to `__locale_ctype_ptr'
    .pio\build\genericSTM32F103CB\src\adventure.o:adventure.c:(.text.adventure+0x416): more undefined references to `__locale_ctype_ptr' follow
    .pio\build\genericSTM32F103CB\src\main.o: In function `main':
    main.c:(.text.startup.main+0x48): undefined reference to `xQueueCreateMutex'
    collect2.exe: error: ld returned 1 exit status
    *** [.pio\build\genericSTM32F103CB\firmware.elf] Error 1

This article and the linked project code seem to solve the issue I am facing, but I am not sure how the target.json file is composed in relation to platformio.ini.

The following link also deals with this specific question, but I am not experienced enough with the toolchain to really make actionable decisions yet.

Exchanging the C library is a thing I haven’t done myself yet, but I guess it would need modifications in the linker flags and general GCC flags in the build scripts (platform-ststm32/ at develop · platformio/platform-ststm32 · GitHub, builder-framework-libopencm3/ at 39e2eed656463acef1cf70a34fc9db5f73a5540c · platformio/builder-framework-libopencm3 · GitHub)

Thanks. I have actually discovered there is a bunch of variables that effect final file size. However my main confusion was that FreeRTOS pre-requisitions all of the RAM you make available to it’s stack - so I was seeing 99.7% RAM usage and that was a normal thing. Interesting to learn ARM-NONE-EABI 9.x has a different compiled file size from 7.x, but not surprising given what compilers do.

I will dig through your links and see if they help.

Thanks a lot for your continued help Max, people like you make DIY fun instead of a rabbit hole. Happy New Years!

Unfortunately changing the number of tasks stacks/size in freeRTOSconfig did not make my out of RAM problem go away. I have not yet figured out how to reduce RAM usage, but also have not dug too deep.

Do you have the PlatformIO for reproduction on github or something?

@maxgerhardt i’ve posted examples about similar problem in this thread: Why `impure_data` appears on custom build and eats 1K memory? Need fix

  1. Succeeded to patch envs after via post script, but lost 1K of RAM.
  2. Problem is that i passed ldscript path via -Wl,T build option. If i set it in post script, as in, size is ok.

Could you take a look? I’d like to understand 2 things:

  1. Why result depends on method to pass ldsript path? I carefully compared linker command in verbose mode - this option is the only difference.
  2. I don’t like to hardcode path in post-script. Can it be transfered somehow from platform.ini (for example via shell env var)?

Hm… if i pass path via -T instead of -Wl,T, result seems ok. But -T is not documented anywhere. Is that correct at all?

1 Like


The code is here:

All I have done is copy it into platformio and used the platformio.ini I have pasted in the initial comment.

I have bigger problems than this at the moment though, which I will mention briefly now in case anyone reading has any advice:

  1. When I try to use libopencm3 USART alongside libopencm3 USB HID or use USB HID in FreeRTOS, I get errorless crashes. Neither works. I am assuming it is interrupt priorities or something similar. I am not skilled enough to write USB HID software without a debug output console, and that means without USART being viable along side USB HID, I simply am stuck. If there is a way to use the PIO universal debugger to actually debug these things, it is failing for me. The debugger simply runs forever when my software crashes. I do not know how to figure out where it has crashed. I need print statements and to see if they are spit out or not.

About to give up on libopencm3 because it has no dedicated forum and everywhere I’ve asked for help with it, people just get mad that you are asking for help with someone elses software. There is zero community for libopencm3.

I am going to start another, more general thread here asking for advice I think, maybe some experienced devs will find it in their heart to comment and advise! I am struggling to understand how to use USB HID while I have interrupts going off from buttons and encoders. I can move everything to polling\loops, but I really am reluctant to continue changing my working ‘main program code’ until I have a platform figured out that will let me have a Composite HID device of Joystick\Keyboard, alongside a standard USART output for debugging! It really is sad the libopencm3 HID library does not seem to play alongside USART!

If it will help I can put up my whole platformio project attempting to compile the CDC example here. I really dont want to abandon platformio, it is a very nice user experience and I’ve now spent a month learning some of it’s basic-intermediate ins and outs! Moving to attolic in particular is not appealing. Same with spending a ton of $ on IAR or Keil.

Thanks again for your ongoing help. If I seem exasperated it isn’t with you! I know this is all my own shortcomings. :blush:

Hm… if i pass path via -T instead of -Wl,T , result seems ok. But -T is not documented anywhere. Is that correct at all?

I will try this next time I load the code, and report back.

Thank you!

Tested, -T in build_flags shows right stat, but causes wrong elf and binary size. I currently know 2 working solutions for ldscript path:

  1. In platform.ini use -Wl,-t<path>
    • Easy, works, but adds 1K .data.impure_data (RAM loss)
  2. Add path via post-script
    • Works, size ok, but need improvment to pass value via platform.ini

Hear is current state of my script for configuration without framework (relies on

Import("env", "projenv")

for e in [ env, projenv ]:



Final version:

Import("env", "projenv")

for e in [ env, projenv ]:
    # Fix options after ""
    e.Replace(LINKFLAGS = [i for i in e['LINKFLAGS'] if i not in [ '-nostartfiles', '-nostdlib' ]])
    e.Append(LINKFLAGS = [ "--specs=nano.specs", "--specs=nosys.specs" ])
    e.Replace(AS = '$CC', ASCOM = '$ASPPCOM')

    # .ld script should be passed to linker directly as "-T<path>"
    # instead of "-Wl,-T<path>", to avoid 1K RAM loss for ".data.impure_data"
    e.Replace(BUILD_FLAGS = [i for i in e['BUILD_FLAGS'] if not i.startswith('-Wl,-T')])
    e.Replace(LINKFLAGS = [(i[4:] if i.startswith('-Wl,-T') else i) for i in e['LINKFLAGS']])

Nothing hardcoded, everything configurable from .ini


This script causes the compile to happen without losing the 1k of RAM?

Yes. - that’s an appropriate part of platformio.ini. It uses local HAL sources instead of provided by PIO. Resource usage is the same as with stm32cube “framework”.

Just so you know, FreeRTOS and newlib have some issues. Solvable, but not simple.