1. Blog>
  2. STM32 Blue Pill — Analyse and Optimise Your RAM and ROM

STM32 Blue Pill — Analyse and Optimise Your RAM and ROM

by: Sep 01,2020 8356 Views 0 Comments Posted in Technology

STM32 Blue Pill programming Stm32 Embedded Systems

Someday our Blue Pill development tools will get so smart… And automatically flag out seemingly innocuous changes we made (like adding sprintf()) that caused our Blue Pill program to bloat beyond Blue Pill’s built-in 64 KB of ROM and 20 KB of RAM.

But until that day comes, we have to stay vigilant. And learn the tips and tools in this article to prevent Blue Pill Bloat…

1.We’ll create a sample Blue Pill program with Visual Studio Code and PlatformIO

2.Study the Linker Script that was used to create the Blue Pill executable, and the Memory Layout that it enforces

3.Learn what’s inside the Text, Data and BSS Sections of a Blue Pill executable

4.Understand how the Stack and Heap are organised

5.Analyse RAM and ROM usage with Google Sheets and the Linker Map

6.Peek at the Assembly Code generated by the compiler, as well as the Vector Map and reset_handler() used during Blue Pill startup

7.Lastly, some tips I have learnt from optimising a huge Blue Pill program

So if you’re giving up on Blue Pill because you thought 64 KB of ROM and 20 KB of RAM can’t do much… this article will amaze you!

When you see sections marked by a diamond… If you’re new to STM32 Blue Pill programming, skip these sections because they will be hard to understand. In these sections I’ll explain some advanced Blue Pill features that we’ll be seeing in future articles, as we stack up more functions and optimise them.


Create A Sample Blue Pill Program

We’ll use Visual Studio Code and the PlatformIO Extension to create a simple LED blink program for analysing the RAM and ROM usage.

Follow the steps in the video below to install the PlatformIO Extension and create the sample program. Please copy the updated content from my GitHub repository for the following files:

platformio.ini: https://github.com/lupyuen/stm32bluepill-blink/blob/master/platformio.ini

main.c: https://github.com/lupyuen/stm32bluepill-blink/blob/master/src/main.c

If you don’t have a Blue Pill and ST Link, you may skip the Build and Upload steps in the video. This article doesn’t require a Blue Pill to run the demos. Click “CC” to view the instructions in the video…


Blue Pill Project Build

What happens when you click the Build button to build a Blue Pill executable?

1. The C compiler arm-none-eabi-gcc compiles main.c into main.o

2. The Linker arm-none-eabi-ld links main.o with some code libraries (Standard C Library, Math Library, libopencm3) to resolve the functions called by main.o

3. The Linker generates the Blue Pill executable firmware.elf and the Blue Pill ROM image firmware.bin, ready to be flashed into the Blue Pill

For our simple blink demo, the Linker generates a tiny Blue Pill executable file (744 bytes of ROM, 8 bytes of RAM)…

Linking .pioenvs/bluepill_f103c8/firmware.elf
Memory region     Used Size Region Size %age Used
rom:          744 B    64 KB   1.14%
ram:           8 B    20 KB   0.04%

How does the Linker know what to put into RAM and ROM? It uses a Linker Script that contains a list of rules about what functions and variables to put into RAM or ROM. Let’s look at the Linker Script…

Blue Pill Memory Layout


Linker Script

To understand the above memory layout, let’s look at the Linker Script that was used to generate the Blue Pill executable. Open the following file in Visual Studio Code…

For Windows: %userprofile%\.platformio\packages\framework-libopencm3\lib\stm32\f1\stm32f103x8.ld

For Mac and Linux: ~/.platformio/packages/framework-libopencm3/lib/stm32/f1/stm32f103x8.ld

Or open the web version of the Linker Script (split into 2 files):

https://github.com/libopencm3/libopencm3/blob/master/lib/stm32/f1/stm32f103x8.ld

https://github.com/libopencm3/libopencm3/blob/master/lib/cortex-m-generic.ld

MEMORY /* Define memory regions. */
{
 rom (rx) : ORIGIN = 0x08000000, LENGTH = 64K
 ram (rwx) : ORIGIN = 0x20000000, LENGTH = 20K
}

This defines the overall memory layout for Blue Pill: 64 KB ROM starting at address 0x0800 0000, 20 KB RAM starting at 0x2000 0000. So all addresses on the Blue Pill look like 0x08… or 0x20…

Each Memory Region (RAM, ROM) consists of multiple Sections, which are defined as follows…

SECTIONS /* Define sections. */
{
 .text : {
  ...   /* Vector table  */
  ...   /* Program code  */
  ...   /* Read-only data */
 } >rom
 .data : { /* Read-write initialised data   */
  ...
 } >ram AT >rom
 .bss : {  /* Read-write zero initialised data */
  ...
 } >ram


Text Section

Blue Pill ROM, containing the Text Section and the Data Section

The Text Section is stored in ROM (read only) and contains:

1.Vector Table: Defines the program start address and the interrupt routines, to be explained in a while

2.Executable Program Code: The compiled machine code for each function

3.Read-Only Data: Constant strings and other constant data that cannot be changed


Data and BSS Sections

Blue Pill RAM, containing the Data Section, BSS Section, Heap and Stack

Global Variables in the Blue Pill program will be allocated in the Data Section or BSS Section (both are in RAM). What’s the difference?

int bss_var;     // Will be allocated in BSS Section (RAM). 
int data_var = 123// Will be allocated in Data Section (RAM and ROM).

Let’s say we have the above two global variables defined. The first variable bss_var is automatically initialised to 0 (or NULL for pointers) according to the C specification. Global variables initialised to 0 or NULL are allocated in the BSS Section. (We’ll see later that the initialisation is done in reset_handler().)

As for data_var, it has an initial non-zero value, 123. data_var will have 2 copies in memory:

1.One read-only copy in the ROM, within the Data Section, that remembers the initial value 123 permanently, across restarts.

2.Another read-write copy in RAM, within the Data Section, that is set to 123 upon startup. But as the program runs, the value may change.

That’s why we see two copies of Data Section in the Memory Layout (think of it as a Reference Copy vs a Working Copy). Also we see this peculiar rule in the Linker Script…

.data : { /* Read-write initialised data   */
  ...
 } >ram AT >rom

The directive >ram AT >rom simply means “allocate in RAM and in ROM”. If you’re wondering why it’s called BSS… here’s the story.


Stack

PROVIDE(
  _stack = ORIGIN(ram) + LENGTH(ram)
);

The Stack keeps track of the local variables within each function call. It starts at the top of RAM (0x2000 4FFF) and grows downwards. So for this program…

void gpio_setup(void) { ... }
int main(void) {
  int stack_var = bss_var++ + data_var++;
  gpio_setup();
  ...

We will see in the Stack (from high address to low address)…

1.One Stack Frame for main(). It contains the value of stack_var.

2.One Stack Frame for gpio_setup(). It contains the values of the local variables in gpio_setup().

3.Plus Stack Frames for other functions called by gpio_setup().

What happens when the Stack hits the BSS Section? That’s when our program crashes with an “Out Of Stack Space” error. We should never allow this to happen — be careful with recursive functions.


Heap

If we use new in C++ and malloc() in C, the dynamic memory storage will be allocated from the Heap. The Heap lies between the BSS Section and the Stack. Yes it’s a tight squeeze in 20 KB of shared RAM and might cause problems with the Stack. That’s why I avoid using the Heap wherever possible.

Can we create additional Memory Regions and Sections? Yes we can! I created custom Memory Regions bootrom and bootram, each with its own Sections, in this Linker Script (this allows me to partition the Blue Pill RAM and ROM for the Bootloader and for the Application):

https://github.com/lupyuen/codal-libopencm3/blob/master/ld/stm32f103x8.ld


Linker Map

Now that we understand how RAM and ROM are used in Blue Pill, let’s learn to analyse the memory usage of a simple program — our blink demo.

After building the project, open the firmware.map file located at the top folder of the project. Or browse the web version.

firmware.map is the Linker Map that tells us all the RAM and ROM allocated for the functions and variables in our program. So if we run out of RAM or ROM, the Linker Map firmware.map is the right file to check. It was created when we specified this Linker Command-Line Option in platformio.ini: -Wl,-Map,firmware.map

firmware.map is very dense with lots of numbers. Later we’ll use Google Sheets to analyse the Linker Map. But meanwhile have a look at this to understand how the Linker Map is structured (skip to line 90 in firmware.map)…

Quick peek at the Linker Map firmware.map

Remember the variables bss_var, data_var, stack_var in main.c?

Let’s use the Linker Map to verify that bss_var is indeed allocated in the BSS Section and data_var is allocated in the Data Section. And stack_var should not appear in the Linker Map because it’s allocated on the Stack only when the main() function executes…

Verify that data_var and bss_var are allocated in the Data / BSS Sections according to the Linker Map firmware.map


Disassemble the Blue Pill Executable

We’ll get back to the Linker Map in a while. There are times when you’ll have to account for every single byte of code or data in your program. Like when you’re investigating why a function takes up so much code or data. For such situations, inspecting the Assembly Code generated by the Blue Pill compiler may be helpful.

Let’s set up a Task in Visual Studio Code to dump (or disassemble) the Assembly Code into firmware.dump

1.Click Terminal → Configure Tasks

2.In tasks.json file that appears, replace the contents by the contents of this file: https://github.com/lupyuen/stm32bluepill-blink/blob/master/.vscode/tasks.json

3.Click Terminal → Run Task → ?? Disassemble STM32 Blue Pill

This generates a firmware.dump file from the firmware.elf executable that was created by the PlatformIO build step. In case you’re curious, the command looks like this…

~/.platformio/packages/toolchain-gccarmnoneeabi/bin/arm-none-eabi-objdump --wide --syms --source --line-numbers .pioenvs/bluepill_f103c8/firmware.elf >firmware.dump

To see the Assembly Code, open the firmware.dump file located at the top folder of your project. Or open the web copy: https://github.com/lupyuen/stm32bluepill-blink/blob/master/firmware.dump

Here we see every single byte of Machine Code generated by the C compiler (the green column). To the right is the Assembly Code that corresponds to the Machine Code. The lines in yellow refer to the C Source Code that was used to generate the Assembly Code .

Fortunately the Blue Pill uses a RISC-based processor (Arm Cortex-M3) so the Assembly Code is easier to understand: ldr for Load Register, str for Store Register, … For details, check out the STM32 Cortex?-M3 programming manual.

Every Blue Pill program begins execution at the ROM start address 0x0800 0000… but don’t take me word for it, take a peek at firmware.dump at address 0x0800 0000

Disassembly of Blue Pill ROM at 0x0800 0000

That doesn’t look like executable Machine Code. But when we group the bytes into 32 bits (4 bytes each), familiar addresses begin to emerge (Hint: Blue Pill addresses all begin with 0x08 or 0x20)…

8000000: 0x2000 5000 → start of stack in RAM (grows downwards)
8000004: 0x0800 0255 → reset_handler() function in ROM
8000008: 0x0800 0251 → null_handler() function in ROM
800000C: 0x0800 024F → blocking_handler() function in ROM
8000010: 0x0800 024F → blocking_handler() function in ROM
8000014: 0x0800 024F → blocking_handler() function in ROM

If you cross-reference the 32-bit numbers with firmware.dump, you’ll realise that this block of numbers actually contains important info like…

  • 0x2000 5000: Start address of the stack (which grows downwards). It also marks the end of RAM.
  • 0x0800 0255: Address of the reset_handler() function, which is the entry point for the program and initialises the global variables.
  • 0x0800 0251: Address of the null_handler() function. This is an interrupt service routine that does nothing.
  • 0x0800 024F: Address of the blocking_handler() function. This is an interrupt service routine that loops forever (because the Blue Pill can’t recover from the exception that has occurred).

This list of addresses at 0x0800 0000 is known as the Vector Table.


Vector Table

Every Blue Pill program must have a Vector Table at the start of ROM, 0x0800 0000. Because without it, the Blue Pill won’t know where in RAM to allocate the Stack, and which function to call to execute the program (i.e. the reset_handler() function).

The Vector Table structure is defined here…

For Windows: %userprofile%\.platformio\packages\framework-libopencm3\lib\stm32\f1\vector_nvic.c

For Mac and Linux: ~/.platformio/packages/framework-libopencm3/lib/stm32/f1/vector_nvic.c

vector_table_t vector_table = {
 .initial_sp_value = &_stack,       // Stack starts here and grows downwards.
 .reset      = reset_handler,    // Entry point for the user program.
 .nmi       = nmi_handler,     // Default: null_handler
 .hard_fault    = hard_fault_handler,  // Default: blocking_handler

 .memory_manage_fault = mem_manage_handler,   // Default: blocking_handler
 .bus_fault      = bus_fault_handler,   // Default: blocking_handler
 .usage_fault     = usage_fault_handler,  // Default: blocking_handler
 .debug_monitor    = debug_monitor_handler, // Default: null_handler

 .sv_call = sv_call_handler,  // Default: null_handler
 .pend_sv = pend_sv_handler,  // Default: null_handler
 .systick = sys_tick_handler, // Default: null_handler
 .irq = {
  // IRQ_HANDLERS from ~/.platformio/packages/framework-libopencm3/lib/stm32/f1/vector_nvic.c
  // The "*_isr" Interrupt Service Routines below default to blocking_handler() unless overridden in the user program.
  [NVIC_WWDG_IRQ]      = wwdg_isr, 
  [NVIC_PVD_IRQ]      = pvd_isr, 
  [NVIC_TAMPER_IRQ]     = tamper_isr, 
  [NVIC_RTC_IRQ]      = rtc_isr, 
  [NVIC_FLASH_IRQ]     = flash_isr, 
  [NVIC_RCC_IRQ]      = rcc_isr, 
  [NVIC_EXTI0_IRQ]     = exti0_isr, 
  [NVIC_EXTI1_IRQ]     = exti1_isr, 
  [NVIC_EXTI2_IRQ]     = exti2_isr, 
  [NVIC_EXTI3_IRQ]     = exti3_isr, 
  [NVIC_EXTI4_IRQ]     = exti4_isr, 
  [NVIC_DMA1_CHANNEL1_IRQ] = dma1_channel1_isr, 
  [NVIC_DMA1_CHANNEL2_IRQ] = dma1_channel2_isr, 
  [NVIC_DMA1_CHANNEL3_IRQ] = dma1_channel3_isr, 
  [NVIC_DMA1_CHANNEL4_IRQ] = dma1_channel4_isr, 
  [NVIC_DMA1_CHANNEL5_IRQ] = dma1_channel5_isr, 
  [NVIC_DMA1_CHANNEL6_IRQ] = dma1_channel6_isr, 
  [NVIC_DMA1_CHANNEL7_IRQ] = dma1_channel7_isr, 
  [NVIC_ADC1_2_IRQ]     = adc1_2_isr, 
  [NVIC_USB_HP_CAN_TX_IRQ] = usb_hp_can_tx_isr, 
  [NVIC_USB_LP_CAN_RX0_IRQ] = usb_lp_can_rx0_isr, 
  [NVIC_CAN_RX1_IRQ]    = can_rx1_isr, 
  [NVIC_CAN_SCE_IRQ]    = can_sce_isr, 
  [NVIC_EXTI9_5_IRQ]    = exti9_5_isr, 
  [NVIC_TIM1_BRK_IRQ]    = tim1_brk_isr, 
  [NVIC_TIM1_UP_IRQ]    = tim1_up_isr, 
  [NVIC_TIM1_TRG_COM_IRQ]  = tim1_trg_com_isr, 
  [NVIC_TIM1_CC_IRQ]    = tim1_cc_isr, 
  [NVIC_TIM2_IRQ]      = tim2_isr, 
  [NVIC_TIM3_IRQ]      = tim3_isr, 
  [NVIC_TIM4_IRQ]      = tim4_isr, 
  [NVIC_I2C1_EV_IRQ]    = i2c1_ev_isr, 
  [NVIC_I2C1_ER_IRQ]    = i2c1_er_isr, 
  [NVIC_I2C2_EV_IRQ]    = i2c2_ev_isr, 
  [NVIC_I2C2_ER_IRQ]    = i2c2_er_isr, 
  [NVIC_SPI1_IRQ]      = spi1_isr, 
  [NVIC_SPI2_IRQ]      = spi2_isr, 
  [NVIC_USART1_IRQ]     = usart1_isr, 
  [NVIC_USART2_IRQ]     = usart2_isr, 
  [NVIC_USART3_IRQ]     = usart3_isr, 
  [NVIC_EXTI15_10_IRQ]   = exti15_10_isr, 
  [NVIC_RTC_ALARM_IRQ]   = rtc_alarm_isr, 
  [NVIC_USB_WAKEUP_IRQ]   = usb_wakeup_isr, 
  [NVIC_TIM8_BRK_IRQ]    = tim8_brk_isr, 
  [NVIC_TIM8_UP_IRQ]    = tim8_up_isr, 
  [NVIC_TIM8_TRG_COM_IRQ]  = tim8_trg_com_isr, 
  [NVIC_TIM8_CC_IRQ]    = tim8_cc_isr, 
  [NVIC_ADC3_IRQ]      = adc3_isr, 
  [NVIC_FSMC_IRQ]      = fsmc_isr, 
  [NVIC_SDIO_IRQ]      = sdio_isr, 
  [NVIC_TIM5_IRQ]      = tim5_isr, 
  [NVIC_SPI3_IRQ]      = spi3_isr, 
  [NVIC_UART4_IRQ]     = uart4_isr, 
  [NVIC_UART5_IRQ]     = uart5_isr, 
  [NVIC_TIM6_IRQ]      = tim6_isr, 
  [NVIC_TIM7_IRQ]      = tim7_isr, 
  [NVIC_DMA2_CHANNEL1_IRQ] = dma2_channel1_isr, 
  [NVIC_DMA2_CHANNEL2_IRQ] = dma2_channel2_isr, 
  [NVIC_DMA2_CHANNEL3_IRQ] = dma2_channel3_isr, 
  [NVIC_DMA2_CHANNEL4_5_IRQ]= dma2_channel4_5_isr, 
  [NVIC_DMA2_CHANNEL5_IRQ] = dma2_channel5_isr, 
  [NVIC_ETH_IRQ]      = eth_isr, 
  [NVIC_ETH_WKUP_IRQ]    = eth_wkup_isr, 
  [NVIC_CAN2_TX_IRQ]    = can2_tx_isr, 
  [NVIC_CAN2_RX0_IRQ]    = can2_rx0_isr, 
  [NVIC_CAN2_RX1_IRQ]    = can2_rx1_isr, 
  [NVIC_CAN2_SCE_IRQ]    = can2_sce_isr, 
  [NVIC_OTG_FS_IRQ]     = otg_fs_isr
 }
};

Arm Vector Table Definition

The Vector Table is defined by Arm (not STM). The Vector Table also includes a complete list of Interrupt Service Routines that will be called when an interrupt is triggered. For example, rtc_alarm_isr() is the interrupt service routine that will be called when the Real-Time Clock triggers an Alarm Interrupt on the Blue Pill.

What happens when you don’t define any interrupt service routines? libopencm3 will provide one of the following default interrupt service routines for the interrupt, depending on the nature of the interrupt…

  • null_handler():This is an interrupt service routine that does nothing. null_handler() is the default interrupt service routine for non-critical interrupts, like the Real-Time Clock Alarm Interrupt.
  • blocking_handler(): This is an interrupt service routine that loops forever and never returns. blocking_handler() is the default interrupt service routine for critical exceptions that will prevent Blue Pill from operating correctly. Hard Fault is a serious exception that uses the blocking_handler() by default.
void blocking_handler(void) {
	while (1);
}

void null_handler(void) {
	/* Do nothing. */
}

null_handler() and blocking_handler() from https://github.com/libopencm3/libopencm3/blob/master/lib/cm3/vector.c


void __attribute__ ((weak)) reset_handler(void) {
	volatile unsigned *src, *dest; funcp_t *fp;

	for (src = &_data_loadaddr, dest = &_data; /* _data marks the start of Data Section */
		dest < &_edata;
		src++, dest++) {
		*dest = *src;
	}

	while (dest < &_ebss) { /* _ebss marks the end of BSS Section (uninitialised vars) */
		*dest++ = 0;
	}

	/* Ensure 8-byte alignment of stack pointer on interrupts */
	/* Enabled by default on most Cortex-M parts, but not M3 r1 */
	SCB_CCR |= SCB_CCR_STKALIGN;

	/* might be provided by platform specific vector.c */
	pre_main();

	/* Constructors. */
	for (fp = &__preinit_array_start; fp < &__preinit_array_end; fp++) {
		(*fp)();
	}
	for (fp = &__init_array_start; fp < &__init_array_end; fp++) {
		(*fp)();
	}

	/* Call the application's entry point. */
	(void) main();

	/* Destructors. */
	for (fp = &__fini_array_start; fp < &__fini_array_end; fp++) {
		(*fp)();
	}
}

reset_handler() from https://github.com/libopencm3/libopencm3/blob/master/lib/cm3/vector.c


reset_handler() Function

reset_handler() is the function that’s executed when the Blue Pill starts up. libopencm3 provides a default reset_handler() function. According to the code above…

1.reset_handler() copies the Data Section from ROM to RAM. The Data Section contains variables that are initialised to non-zero values. (Remember that the Data Section exists in ROM and RAM?)

2.reset_handler() initialises variables in the BSS Section to 0 or NULL (for pointers).

3.reset_handler() calls our main() function after initialisation. So all variables in the Data and BSS Sections will be set to their proper initial values when our main() function runs.

4.For C++ programs, reset_handler() also calls the C++ Constructor Methods to create global C++ objects. It calls the C++ Destructor Methods to destroy global C++ objects when main() returns. But this rarely happens because for most Blue Pill programs, the main() function runs forever in a loop handling events.

So reset_handler() (like the Vector Table) is essential for Blue Pill operation. Don’t tamper with it, just make sure it’s always in ROM. Be careful when coding C++ Constructor Methods — they are called before main().



Use Google Sheets To Analyse The Linker Map

As you recall, the Linker Map file firmware.map can be hard to analyse because it’s full of details. What if we used a Google Sheets spreadsheet to crunch the file and show us only the details that we need, to guide us in trimming down our RAM and ROM usage?

Check out this Blue Pill Memory Map that was generated from our sample blink program…

The spreadsheet contains formulas to parse the lines from firmware.map, extract the columns that we need, and sort the functions and variables by size. Here are the highlights of the spreadsheet…

Sample Google Sheets spreadsheet that analyses the Linker Map file firmware.map

You can use the Google Sheets Template to analyse your own Linker Map files…

1.Click this link to open the Memory Map template…

2.Click File → Make A Copy to copy the file into your Google Drive storage

3.Paste the contents of your Linker Map file firmware.map into the Import sheet

4.Click the Symbols sheet

5.Click Data → Filter Views → All Objects By Size

This video shows you the steps. Click “CC” to view the instructions in the video…

Using Google Sheets to analyse the Linker Map firmware.map


What Happens When We Add sprintf()?

sprintf() is a common C function used for formatting numbers as strings. What happens to the RAM and ROM usage when we add it to our Blue Pill program? Try it out yourself…

1.Edit main.c. Insert the lines below marked // Added line

...
#include <stdio.h>   // Added line
...
int main(void)
{
 // Sample call to sprintf() to demo changes in the memory map.
 char stack_buf[64]; // Added line
 bss_var = sprintf(stack_buf, "%d %d", bss_var, data_var); // Added line

 // Lines below not changed.
 int stack_var = bss_var++ + data_var++;
 int i;
 gpio_setup();
 ...

2.Build the project in Visual Studio Code

3.Copy the contents of firmware.map into a new copy of the Memory Map Template

4.Click the Symbols sheet

5.Click Data → Filter Views → All Objects By Size

Before adding sprintf(), the build output show this as the RAM and ROM usage…

Linking .pioenvs/bluepill_f103c8/firmware.elf
Memory region     Used Size Region Size %age Used
rom:          744 B    64 KB   1.14%
ram:           8 B    20 KB   0.04%

But after adding sprintf(), the ROM usage has increased from 1% to 33%!

Linking .pioenvs/bluepill_f103c8/firmware.elf
Memory region     Used Size Region Size %age Used
rom:         22,132 B    64 KB   33.77%
ram:          2,576 B    20 KB   12.58%

What cause the ROM usage to jump so much? Take a look at the Memory Map you have created. Or check out my copy of the Memory Map…

Did you notice the changes in the Memory Map after adding sprintf()? Here’s a summary…

Memory Map after adding sprintf()

Because of the added baggage, I don’t use sprintf() and the stdio library in my Blue Pill programs. For logging, I wrote a library that performs simple formatting of numbers: https://github.com/lupyuen/codal-libopencm3/tree/master/stm32/logger

So now we have learnt how to use Google Sheets to analyse the Linker Map to find large functions and variables. This should help fix most of our Blue Pill memory problems… though sometimes we will need to use special tricks to cut down on memory usage. Read on for some tips…


Memory Map from the port of MakeCode to Blue Pill


Optimising A Huge Memory Map

The Memory Map above is from a complicated project that I’m doing in my spare time… Porting the MakeCode visual programming tool (used in the BBC micro:bit) to Blue Pill. I used this Memory Map to optimise the MakeCode runtime for Blue Pill. Here’s the file for the above Memory Map…

50 KB of code and read-only data - Why is it so huge? This happens when we port over code from a higher-capacity microcontroller (micro:bit) to the Blue Pill. (The same thing happened when I attempted to port MicroPython to Blue Pill.) Compare the BBC micro:bit specs to Blue Pill…

BBC micro:bit  256 KB ROM  16 KB RAM
Blue Pill    64 KB ROM  20 KB RAM

Can we really squeeze a micro:bit program into Blue Pill?

Memory Map from the port of MakeCode to Blue Pill, after optimisation

Yes we can! I used the Memory Map to identify the large objects to be fixed. After optimisation, the program is a lot smaller (shown above). Here is the optimised Memory Map file…

Using Google Sheets to analyse the Linker Map is perfect for such porting projects, it really helps to pinpoint the bloat so we can decide how to trim it down.


Work In Progress: Porting MakeCode to Blue Pill


What’s Next

Porting the MakeCode visual programming tool to Blue Pill is incredibly complicated, but I learnt a lot from the process. This is the first article that documents one of the complicated porting tasks: memory optimisation. Coming up: More articles from my Blue Pill porting experience…

1.Replacing the standard math functions by smaller qfplib library

2.Unit testing the math functions with the QEMU Blue Pill Emulator

3.Creating a WebUSB Bootloader for MakeCode

4.Updating the Bootloader with a tiny Baseloader

So don’t throw out the Blue Pill yet, there’s so much more you can do with it!

UPDATE: I have written a new series of articles on Blue Pill programming with Apache Mynewt realtime operating system, check this out…

I used the memory map spreadsheet to analyse the ROM usage of my Apache Mynewt application…


Note: The content and the pictures in this article are contributed by the author. The opinions expressed by contributors are their own and not those of PCBWay. If there is any infringement of content or pictures, please contact our editor (zoey@pcbway.com) for deleting.


Written by

Join us
Wanna be a dedicated PCBWay writer? We definately look forward to having you with us.
  • Comments(0)
You can only upload 1 files in total. Each file cannot exceed 2MB. Supports JPG, JPEG, GIF, PNG, BMP
0 / 10000
    Back to top