One thing you can say about embedded microprocessors: Running out of stack, heap, and flash memory is actually possible, and a difference of 8 kilobytes can actually make or break your code.
In these cases of memory pressure, you need to wring out all of the most dubious code in your codebase. Usually, this takes the form of removing parts of the C library or other transitively-included libraries that add bulk but are not strictly necessary.
Here’s a an example project that overran the runtime boundaries on a Nordic nRF51 Development Kit. Note that the total flash size is approaching the maximum-available user program storage space. Looking at the linker script, it’s apparent that there’s a total of 148KB available for programs and 20744 bytes of available for RAM:
1
2
3
4
5
|
MEMORY
{
FLASH (rx) : ORIGIN = 0x0001B000, LENGTH = 0x25000
RAM (rwx) : ORIGIN = 0x20002ef8, LENGTH = 0x5108
}
|
On the particular chip I’m using, there’s a total of 256KB flash and 32KB of RAM. The first ~16KB are chewed up by a bootloader, the next ~110KB are chewed up by the SoftDevice code. So that leaves around 148KB free for user code. But this may not be a hard rule. The minute I crossed the ~120KB code-size mark, I started getting hard faults at runtime:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
~/Test/code$ mbed compile
Building project Test (NRF51_DK, GCC_ARM)
Scan: ..
Scan: FEATURE_COMMON_PAL
Scan: FEATURE_BLE
Scan: FEATURE_STORAGE
Scan: mbed
Scan: env
Compile: Test.cpp
Link: Test
Elf2Bin: Test
+----------------------+--------+-------+------+
| Module | .text | .data | .bss |
+----------------------+--------+-------+------+
| Fill | 202 | 7 | 42 |
| Misc | 76620 | 2284 | 507 |
| features/FEATURE_BLE | 15512 | 9 | 532 |
| hal/common | 5385 | 4 | 345 |
| hal/targets | 14954 | 0 | 1423 |
| rtos/rtos | 215 | 4 | 0 |
| rtos/rtx | 6497 | 20 | 4227 |
| Subtotals | 119385 | 2328 | 7076 |
+----------------------+--------+-------+------+
Allocated Heap: 9272 bytes
Allocated Stack: 2048 bytes
Total Static RAM memory (data + bss): 9404 bytes
Total RAM memory (data + bss + heap + stack): 20724 bytes
Total Flash memory (text + data + misc): 121713 bytes
Image: ../.build/NRF51_DK/GCC_ARM/Test.hex
|
When the chip jumps into a situation of hard error, the kit will start flashing its LEDs in a criss-cross repeating pattern, like so:
If you’re watching the serial port, the error string Operator new out of memory
may appear.
Remove printf()
Turns out that printf() on an ARM Cortex-M0 adds 8 kilobytes to the program size, which is about 1/16th of the usable flash memory on the chip.
Removing all instances of printf() can help:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
~/Test/code$ mbed compile
Building project Test (NRF51_DK, GCC_ARM)
Scan: ..
Scan: FEATURE_COMMON_PAL
Scan: FEATURE_BLE
Scan: FEATURE_STORAGE
Scan: mbed
Scan: env
Compile: Test.cpp
Link: Test
Elf2Bin: Test
+----------------------+--------+-------+------+
| Module | .text | .data | .bss |
+----------------------+--------+-------+------+
| Fill | 208 | 7 | 42 |
| Misc | 68647 | 2280 | 507 |
| features/FEATURE_BLE | 15512 | 9 | 532 |
| hal/common | 5385 | 4 | 345 |
| hal/targets | 14954 | 0 | 1423 |
| rtos/rtos | 215 | 4 | 0 |
| rtos/rtx | 6497 | 20 | 4227 |
| Subtotals | 111418 | 2324 | 7076 |
+----------------------+--------+-------+------+
Allocated Heap: 9272 bytes
Allocated Stack: 2048 bytes
Total Static RAM memory (data + bss): 9400 bytes
Total RAM memory (data + bss + heap + stack): 20720 bytes
Total Flash memory (text + data + misc): 113742 bytes
Image: ../.build/NRF51_DK/GCC_ARM/Test.hex
|
Out of Memory
The bigger problem appears to be the fact that the program runs out of memory. Examining the compiler output, it’s clear that the Total RAM memory in use is, in both of the above cases, dangerously close to the total RAM limit stated in the Linker script.
And in fact, in a different program that I’ve been working on, even though the total Flash memory is even smaller than the above, the total RAM memory in use is enough to throw the whole program over the edge:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
+----------------------+--------+-------+------+
| Module | .text | .data | .bss |
+----------------------+--------+-------+------+
| Fill | 162 | 7 | 38 |
| Misc | 53207 | 2240 | 792 |
| features/FEATURE_BLE | 16032 | 9 | 532 |
| features/frameworks | 3454 | 84 | 400 |
| features/mbedtls | 9220 | 0 | 0 |
| hal/common | 3629 | 4 | 333 |
| hal/targets | 14833 | 0 | 1423 |
| rtos/rtos | 201 | 4 | 0 |
| rtos/rtx | 6255 | 20 | 4226 |
| Subtotals | 106993 | 2368 | 7744 |
+----------------------+--------+-------+------+
Allocated Heap: 8568 bytes
Allocated Stack: 2048 bytes
Total Static RAM memory (data + bss): 10112 bytes
Total RAM memory (data + bss + heap + stack): 20728 bytes
Total Flash memory (text + data + misc): 109361 bytes
|
What else can be thrown out? Stay tuned.
Turning on the verbose compile options, one can see a ton of compiler defines, of which some might be removed to disable certain pieces of code in the underlying architecture:
1 |
[DEBUG] Macros: -DDEVICE_ERROR_PATTERN=1 -DNRF51 -D__MBED__=1 -DTARGET_LIKE_MBED -DTARGET_NRF51822 -DDEVICE_PORTINOUT=1 -D__MBED_CMSIS_RTOS_CM -DDEVICE_LOWPOWERTIMER=1 -DDEVICE_RTC=1 -DTOOLCHAIN_object -DDEVICE_SERIAL_ASYNCH=1 -D__CMSIS_RTOS -DTARGET_MCU_NRF51822_UNIFIED -DTOOLCHAIN_GCC -DDEVICE_I2C_ASYNCH=1 -DTARGET_CORTEX_M -DARM_MATH_CM0 -DTARGET_UVISOR_UNSUPPORTED -DTARGET_NRF5 -DDEVICE_ANALOGIN=1 -DTARGET_M0 -DTARGET_MCU_NRF51 -DDEVICE_SERIAL=1 -DDEVICE_INTERRUPTIN=1 -D__CORTEX_M0 -DDEVICE_I2C=1 -DDEVICE_PORTOUT=1 -DDEVICE_SPI_ASYNCH=1 -DTARGET_FF_ARDUINO -DDEVICE_PORTIN=1 -DTARGET_RELEASE -DTARGET_NORDIC -DFEATURE_BLE=1 -DBLE_STACK_SUPPORT_REQD -DTARGET_NRF51_DK -DDEVICE_SLEEP=1 -DTOOLCHAIN_GCC_ARM -DTARGET_MCU_NRF51822 -DTARGET_MCU_NORDIC_32K -DSOFTDEVICE_PRESENT -DDEVICE_SPI=1 -DDEVICE_SPISLAVE=1 -DMBED_BUILD_TIMESTAMP=1472564212.93 -DDEVICE_PWMOUT=1 -DS130 -DTARGET_LIKE_CORTEX_M0 -DTARGET_MCU_NRF51_32K
|