Habilelabs-Logo
Blog

Making a Micropython C extension module with CMake

August 12th, 2022 . 6 minutes read
Blog featured image

MicroPython is a lean and efficient implementation of the Python 3 programming language that includes a small subset of the Python standard library and is optimized to run on microcontrollers and in constrained environments.

The MicroPython pyboard is a compact electronic circuit board that runs MicroPython on the bare metal, giving you a low-level Python operating system that can be used to control all kinds of electronic projects.

Why MicroPython?

Python is one of the most widely used, simple and easy-to-learn programming languages around.

So, the emergence of MicroPython makes it extremely easy and simple to program digital electronics.

MicroPython’s goal is to make programming digital electronics as simple as possible, so it can be used by anyone.

Currently, MicroPython is used by hobbyists, researchers, teachers, educators, and even in commercial products.

The code for blinking an LED on a ESP32 or ESP8266 is as simple as follows:

Let’s get started!

from machine import Pin
from time import sleep
led = Pin(2, Pin.OUT)
while True:
led.value(not led.value())
sleep(0.5)

Requirements

The first thing you need is a board with an ESP32 chip. The MicroPython software supports the ESP32 chip itself and any board should work.

Powering the board

Your ESP32 board has a USB connector on it then most likely it is powered through this when connected to your PC.

Getting the firmware

The first thing you need to do is download the MicroPython firmware .bin file to load onto your ESP32 device. You can download it from the MicroPython downloads page. From here, you have 3 main choices:

  • Stable firmware builds
  • Daily firmware builds
  • Daily firmware builds with SPIRAM support

If you are just starting with MicroPython, the best bet is to go for the Stable firmware builds. If you are an advanced, experienced MicroPython ESP32 user who would like to follow development closely and help with testing new features, there are daily builds.

In this blog we’ll recommend you to use Esp32 Version 17 bin firmware file : Esp32-20210902-v1.17.bin

Deploying the firmware

Once you have the MicroPython firmware you need to load it onto your ESP32 device.

There are two main steps to do this:

  • You need to put your device in bootloader mode.
  • Need to copy across the firmware.

Fortunately, ESP32 boards have a USB connector, a USB-serial converter, and the DTR and RTS pins wired in a special way then deploying the firmware should be easy as all steps can be done automatically.

For best results it is recommended to first erase the entire flash of your device before putting on new MicroPython firmware.

Currently Micropython only supports “esptool.py” to copy across the firmware. You can find this tool here: https://github.com/espressif/esptool/, or install it using pip:

pip install esptool

Using esptool.py you can erase the flash with the command:

esptool.py --port /dev/ttyUSB0 erase_flash

And then deploy the new firmware using:

esptool.py --chip esp32 --port /dev/ttyUSB0 write_flash -z 0x1000 esp32-20210902-v1.17.bin

Writing a Mpy C extension

In Embedded while writing lengthy codes we sometimes find a quick solution, here the Micropython comes to the rescue, allowing us to implement an arbitrarily complex module directly in Microcontrollers.

When developing modules for use with MicroPython you may find you run into limitations with the Python environment, often due to an inability to access certain hardware resources or Python speed limitations.

Writing some or all of your modules in C (and/or C++ if implemented for your port) is a viable option.

This of course allows the implementation of components in C++ as well, as long as C linkage is used for the functions effectively called in the “.Mpy” module.

The focus here is on using C to build native modules, but in principle any language which can be compiled to stand-alone machine code can be put into a .mpy file.

A native .mpy module is built using the mpy_ld.py tool, which is found in the tools/ directory of the project.

This tool takes a set of object files (.o files) and links them together to create a native .mpy files. It requires CPython 3 and the library pyelftools v0.25 or greater.

Supported features and limitations

A .mpy file can contain MicroPython bytecode and/or native machine code. If it contains native machine code then the .mpy file has a specific architecture associated with it. Current supported architectures are :

  • x86 (32 bit)
  • x64 (64 bit x86)
  • armv6m (ARM Thumb, eg Cortex-M0)
  • armv7m (ARM Thumb 2, eg Cortex-M3)
  • armv7emsp (ARM Thumb 2, single precision float, eg Cortex-M4F, Cortex-M7)
  • armv7emdp (ARM Thumb 2, double precision float, eg Cortex-M7)
  • xtensa (non-windowed, eg ESP8266)
  • xtensawin (windowed with window size 8, eg ESP32)

For more details about .mpy files see MicroPython .mpy files.

Defining a native module

A native .mpy module is defined by a set of files that are used to build the .mpy. The filesystem layout consists of two main parts, the source files and the Makefile:

Let’s Start with example

This section provides a fully working example of a simple module named factorial.

This module provides a single function factorial.factorial(x) which computes the factorial of the input and returns the result.

Directory layout:

Make a Directory with Name “C_Mpy_Micropython” with & Create 3 Files in that folder :

mkdir C_Mpy_Micropython

C_Mpy_Micropython/

-- makefile

-- factorial.c

-- main.py

The file Makefile contains:

  • # Location of top-level MicroPython directory

MPY_DIR = ../../..

  • # Name of module

MOD = factorial

  • # Source files (.c or .py)

SRC = factorial.c

  • # Architecture to build for (x86, x64, armv6m, armv7m, xtensa, xtensawin)

ARCH = x64

  • # Include to get the rules for compiling and linking the module

include $(MPY_DIR)/py/dynruntime.mk

The file factorial.c contains:

  • // Include the header file to get access to the MicroPython API #include

"py/dynruntime.h"

  • // Helper function to compute factorial

STATIC mp_int_t factorial_helper(mp_int_t x)

{ if (x == 0)

{ return 1; }

return x * factorial_helper(x - 1); }

  • // This is the function which will be called from Python, as factorial(x) STATIC

mp_obj_t factorial(mp_obj_t x_obj) {

  • // Extract the integer from the MicroPython input object

mp_int_t x = mp_obj_get_int(x_obj);

  • // Calculate the factorial

mp_int_t result = factorial_helper(x);

  • // Convert the result to a MicroPython integer object and return it

return mp_obj_new_int(result);

}

  • // Define a Python reference to the function above

STATIC MP_DEFINE_CONST_FUN_OBJ_1(factorial_obj, factorial);

  • // This is the entry point and is called when the module is imported

mp_obj_t mpy_init(mp_obj_fun_bc_t *self, size_t n_args, size_t n_kw, mp_obj_t *args) {

  • // This must be first, it sets up the globals dict and other things

MP_DYNRUNTIME_INIT_ENTRY

  • // Make the function available in the module’s namespace

mp_store_global(MP_QSTR_factorial,MP_OBJ_FROM_PTR(&factorial_obj));

  • // This must be last, it restores the globals dict

MP_DYNRUNTIME_INIT_EXIT }

The file main.py contains:

import factorial

print(factorial.factorial(10))

  • Download & Extract “micropython-1.17.zip” from GitHub.
  • Export Path of xtensa-esp32-elf

export PATH="{Path location to file}:/xtensa-esp32-elf/bin:$PATH"

  • Change your “Path location to file” to your “location to file xtensa-esp32-elf” & run command.
  • Move “C_MPy_Python” folder into “micropython-1.17” folder with

mv C_Mpy_Micropython micropython-1.17

  • Go to “C_Mpy_Micropython”.

cd micropython-1.17/C_Mpy_Micropython/

  • Run Make Command

make

Now we have .mpy File in C_Mpy_Micropython folder. This ”.mpy” file is our native file for Micropyton.

  • In the same directory copy all files in ESP32 then run it into micropython.

rshell -p {your port number} cp * /pyboard

**Press reset/EN button on board**

  • #Your terminal should display 3628800

Because our ‘C’ Program was a factorial program and in main.py we called .mpy file function for factorial of ‘10’.

Now our C program is running with micropython in form of .mpy file

For More understanding of flow with micropython and Mpy file visit to GitHub.

Advantages and Disadvantages

Make is a relatively complex tool that can provide you a native file to handel cross programs like creating Micropython bindings for either C or C++.

But After commanding on it we can access multiple C files in micropython so if any library is not found in Python we can use C file and run any hardware with machine.

Conclusion:

Congrats! You’ve now had an overview of Micropython with .mpy creating C bindings.

This was everything about Micropython and calling a pattern to make mpy files to run it with your firmware in ESP32 and the things you need to consider when creating bindings.

Author: Abhimanyu Paliwal
Share: