Python is becoming ever more and more popular and now that real-time embedded software developers have access to Python on the microcontroller through Micro Python, its important now more than ever that developers understand the Micro Python heap. There are several heap related libraries and kernel options that developers need to be aware of.
First, not all Micro Python ports fully utilize the RAM space that is available in the microcontroller. Micro Python was developed first for the STM32F405 which has 128 KB RAM. Most ports start with the STM32F405 as the default template which means that the heap may still only be allocated to 0x1C000 bytes. This may seem like a lot of heap space but the Python interpreter uses the heap during run-time to allocate objects and process scripts so the more heap space that is available, the better!
Developers can look in their kernel linker file for the variable _heap_end. This variable should be sized to match the amount of RAM the microcontroller has. For example, the STM32F405 has 128 kB of RAM and has _heap_end = 0x2001C000. A develop using a part such as the STM32F429 which has 192 kB of RAM would want to make sure that _heap_end = 0x2002C000. Allocating an additional 0x10000 64 kB of RAM to the heap. Making this adjustment can prevent out of memory errors from cropping up in your scripts.
Even if a developer has a lot of heap space, they will want to periodically monitor their heap space usage to ensure that the heap is being used effectively. As the Python interpreter executes its script, objects will be created and destroyed. There is a garbage collector that will reclaim the discarded objects but sometimes it just doesn’t run fast enough. Over time, the heap can become very fragmented and nearly unusable. Developers can force garbage collection by importing the gc library and calling the collect method.
Micro Python memory usage can be closely monitored during execution using the mem_info method. Calling mem_info() will print out basic information such as the stack usage, total heap space, used heap space and even the available block counts. It really gets interesting when a developer calls mem_info using verbose mode (passing in a 1). When this happens, the basic information is still provided but a complete print out of how the heap is being used is also printed. A small sampling can be seen in the images below:
Each character represents how a heap memory location is being used. Unfortunately, there isn’t a lot of published information on what the characters mean but thankfully there was some documentation hidden aware in the gc kernel code that describes what most characters mean. To save you the trouble, you can find the secret decoder ring below:
. is free
= is tail
h is qstr-pool and qstr-data
A is array
A is byte array
S is string
D is dictionary
L is typelist
T is tuple
F is float
M is module
B is type_fun_bc
m is at mark
Developers can’t forget that even though Python is running on a microcontroller, there may be real-time and memory consequences for the way they are writing their code. From a memory perspective, using mem_info sprinkled through-out the program during development is a great way to monitor how the heap is being used and whether or not the heap is fragmenting to a point where attempting to allocate another object will eventually fail.