Micro Python on the pyboard

This article brought to you by LWN subscribers Subscribers to LWN.net made this article — and everything that surrounds it — possible. If you appreciate our content, please buy a subscription and make the next set of articles possible.

A 2013 Kickstarter project brought us Micro Python, which is a version of Python 3 for microcontrollers, along with the pyboard to run it on. Micro Python is a complete rewrite of the interpreter that avoids some of the CPython (the canonical Python interpreter written in C) implementation details that don't work well on microcontrollers. I recently got my hands on a pyboard and decided to give it—and Micro Python—a try.

All of the core Python language has been implemented in Micro Python, as well as a number of the standard libraries. Some C-language Python standard modules have not been ported—at least yet. Beyond that, Micro Python developer Damien George has added a library that simplifies access to the pyboard and its peripherals (e.g. LEDs, buttons, accelerometer).

The key to running Python on a microcontroller (such as the STM32F405 used on the pyboard) is to keep memory usage low while still providing good performance. The footprint for Micro Python is 260KB (as detailed in the "official end" message for the Kickstarter back in April), though removing floating point support brings it down to 240KB. A stripped-down version of the interpreter can be as small as 75KB (and run in 8KB of RAM), but leaves out support for SD cards, FAT filesystems, USB serial ports, USB mass storage, and so on.

While Micro Python will run any Python 3.4 code, it is not byte-code compatible with CPython. In particular, it avoids heap allocations for integer operations and for calling methods. Heap allocations inevitably lead to garbage collection, which can cause problems for time-critical processing and for code running from interrupts (since garbage collection is not reentrant).

So Micro Python encodes "small" integers in the top 31 bits of "pointers" on the stack (with the low bit set to one to distinguish them from real pointers). It also adds a new byte code ( CALL_METHOD ) that retrieves the information it needs from the stack, rather than creating a new object (which requires a heap allocation) as CPython does. The latter is a technique adopted from PyPy. Information about all of this is, unfortunately, buried in the first FAQ entry on the Kickstarter page and is not separately linkable.

Micro Python has four different "emitters" that generate different kinds of code from the compiler. The default byte-code emitter does what its name implies: generates byte codes to run on the Micro Python virtual machine. The other emitters are accessed using Python decorators. The native emitter ( @micropython.native ) translates each byte code to ARM Thumb (the native code for the pyboard CPU) machine code. The viper emitter ( @micropython.viper ) also generates ARM Thumb code, but it further optimizes integer operations to bypass the normal Python binary operation runtime code, which speeds things up considerably. Viper does not support all of the Python language, however. The final emitter ( @micropython.asm_thumb ) allows for inline assembly code using a Python-like syntax. The emitters are detailed in some of the Kickstarter update entries: "The 3 different code emitters", "The 3 different code emitters, part 2", and "Inline assembler".

The emitters are targeted at situations where better performance might be needed for a time-critical section. Both the native and viper emitters generate faster code, but they also generate larger code. Viper actually generates somewhat smaller and faster (sometimes much faster) code than native, but the viper emitter does not support all of the language types and constructs (though it is still under development), so it may not be the best choice for all cases. Using the emitters is as simple as decorating a function:

@micropython.native def foo(bar): ...

The asm_thumb emitter allows Micro Python to easily interface with assembly code; it handles putting function arguments into registers according to the ARM embedded-application binary interface (EABI), having the function return the contents of register r0 , and so on. Python's syntax is used so that there are no changes needed to the parser to support asm_thumb . Here is a snippet from the example in the Kickstarter update posting:

@micropython.asm_thumb def delay(r0): b(loop_entry) # branch to 'loop_entry' label(loop1) # 'loop1' label movw(r1, 55999) # move 55999 to register 1 ...

Instead of the usual syntax, things like branches, labels, and opcodes are encoded as functions for the emitter.

The documentation for Micro Python is fairly extensive, covering the standard libraries that come with it, as well as certain libraries that have "micro-fied" in keeping with Micro Python's focus on small size. Beyond that, there are some additional libraries that are not part of the standard Micro Python distribution, but that can be downloaded and added as needed. The documentation also covers the pyboard hardware and the pyb library written to access various features of the device.

The board itself is perhaps two large postage stamps (or two large coins) in size. It is powered by USB, which also acts as its means of communication (as a serial port and a mass storage device). The device effectively has Python as its operating system; connecting to it with a terminal program leads to a Micro Python prompt. The board can also boot into a Python program by putting a suitable main.py in the boot filesystem.

There are two choices for the boot filesystem, either the small internal partition in the microcontroller's 1MB flash or a micro SD card that has been inserted into a slot on the board. If there is a micro SD card present, it takes precedence. There are also two safe modes that are accessible by holding down buttons at boot time: one that can bypass the startup scripts (both a boot.py and the main.py that get run by default) and another that resets the filesystem in the flash partition to its factory default state.

A third way to run code on the device is to use pyboard.py to transfer a file over the serial connection and run it on the board. So if you get tired of typing a program at the prompt, or uploading it to the flash or SD card and rebooting, you can edit it locally, then send and run it all at once.

The pyboard has two buttons (one reset, RST, and one user definable, USR), four LEDs, an accelerometer, a realtime clock, and 30 general-purpose I/O (GPIO) pins. The pyb library has made it easy to access this functionality. For example:

Micro Python v1.3.10 on 2015-02-13; PYBv1.0 with STM32F405RG >>> leds = [pyb.LED(n) for n in range(1,5)] # LEDs are numbered 1-4 >>> for i in range(10): ... for l in leds: ... l.on() ... pyb.delay(1000) ... l.off() ...

That program will blink each LED in turn for one second ( pyb.delay() is in milliseconds) ten times. A somewhat more complex example reads the USR button to stop the loop:

>>> sw = pyb.Switch() >>> flag = True >>> while flag: ... for l in leds: ... l.on() ... pyb.delay(200) ... l.off() ... if sw(): ... flag = False ...

Hitting the RST button instead of USR (as I did, twice, sadly) will only lead to having to type the code in again (or to using one of the other means for running the code). One final example:

>>> acc = pyb.Accel() >>> while True: ... print(acc.x(), acc.y(), acc.z()) ... pyb.delay(200) ... 2 0 22 -8 5 17 -11 4 18 -16 4 12 ...

The accelerometer returns signed values -30 to 30 for each of the three axes. The accelerometer tutorial page has some additional examples. In fact, the Micro Python tutorial has much of interest (e.g. turning the pyboard into a USB mouse).

Overall, the pyboard is an interesting device to play with. One can imagine various kinds of gadgets that could be built (or prototyped) using it. The recently completed WiPy Kickstarter envisions Micro Python as an entrant into the Internet of Things (IoT) sweepstakes. The Micro Python store also has devices of various sorts that can be hooked up to the pyboard.

Micro Python and the pyboard are in some ways reminiscent of the Python in the GRUB bootloader project (known as the BIOS Implementation Test Suite or BITS) that was presented at PyCon. One big difference is that Micro Python only implements Python 3; another is the size of the systems targeted. But getting a Python prompt when booting a system is a reminder of days gone by—when a BASIC (or DOS) prompt would greet computer users.

There are plenty of other things to investigate with the pyboard: updating the firmware to something more recent than February, for example, or messing around with interrupts from the switch rather than polling. For those looking for an easily programmed microcontroller to use in a project, the pyboard may fit the bill well. As mentioned, the tutorial has some interesting ideas, but others are also starting to use the board (e.g. to control a hexapod robot). The Raspberry Pi and other single-board computers also support Python, of course, but they are a far more heavyweight solution. For smaller tasks, especially those that need to be battery operated, the pyboard is definitely an attractive option.

