Python has been used by embedded software developers for many years on the PC side to interact with embedded systems. Over the past several years, Micro Python, a Python port that can run on a microcontroller, has been gaining in popularity. Python developers may be tempted to architect and write their Micro Python code similar to how they write Python on a PC. Unfortunately, this would be a mistake. Micro Python while very powerful still runs on a resource constrained device which means developers need to be careful how they write their Python code. The biggest potential issues are related to the heap, memory fragmentation and running out of memory. Below are several tips that developers can follow to optimize their heap usage and get the most from their Micro Python application.
Tip #1 – Use mem_info to Monitor the Heap
Micro Python has a memory monitor included in the micropython library called mem_info. Calling mem_info will provide developers with a heap status so that they can determine how much free space is available along with basic diagnostics that can give a hint at how bad the heap is fragmented. The best thing a developer can do from the start of development is to monitor the heap using mem_info to understand how their software is impacting the heap. The basic mem_info output can be seen below:
When the basic information is not enough, a developer can request a verbose listing which provides a detailed map for how the heap is being used. An example can be seen below:
Tip #2 – Compile Modules into Frozen Bytecode
When a developer creates a Micro Python application they are creating scripts that are executed by the Python interpreter at run-time. Processing the script at run-time will not only use space on the heap but can also cause the heap to become fragmented. Developers can take modules within their application code and cross compile it into bytecode that is stored in flash with the kernel code. Creating the bytecode will cause the modules code to execute from flash rather than executing from the heap. The result is that less heap space is used and there is decreased heap fragmentation. Constant values and strings can also be precompiled into frozen bytecode which will prevent them from taking up unnecessary RAM.
Tip #3 – Use const()
RAM associated with generating a lookup for a variable that might be imported by other modules can be saved in a couple ways. First, a developer can use const() which is located in the micropython library. A developer can use const() in the following manner:
A = const(0x100)
Second, adding an underscore to as a prefix to the variable name, _A, will prevent the variable from being added to the dictionary and save a few bytes of RAM as well. While the savings may seem minimal at first, in large applications where there may be hundreds of variables, the savings can quickly add up.
Tip #4 – Allocate Communication Buffers Upfront
The nice thing about using Python is that buffers can be dynamically created in the code as needed. For example, when reading from a USB device, a developer might write code similar to the following:
data = usb.read(64)
The problem with this application is that every time data is read, a new 64-byte buffer is created on the heap which opens the possibility for the heap to become fragmented. Instead, developers can allocate a buffer upfront by doing the following:
buffer = bytearray(64)
This application code will use the same buffer over and over again and not continuously create and destroy buffer space on the heap.
Tip #5 – Periodically Call gc.collect
Micro Python has a garbage collector that will periodically run to return objects that are out scope to the heap. While the garbage collection is automatic, sometimes it can be useful for developers to periodically call gc.collect manually or to call it after they have initialized their application code. It is always useful to make a call to mem_info before a gc.collect call and then immediately after to see how much memory was returned to the heap.
Micro Python can dramatically help developers speed up their rapid prototyping or even production code development. Just because the software is in Python doesn’t mean that developers can now ignore real-time constraints that they were used to in C. Using Micro Python still requires developers to closely monitor their heap and track how they are using it. Ignoring the heap in a Micro Python application is a sure way to end up with “Out of Memory” errors and a crashing application.