Skip to content

Commit 8449e41

Browse files
committed
docs/develop: Add documentation on how to build native .mpy modules.
1 parent e58c7ce commit 8449e41

File tree

3 files changed

+209
-0
lines changed

3 files changed

+209
-0
lines changed

docs/develop/cmodules.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
.. _cmodules:
2+
13
MicroPython external C modules
24
==============================
35

@@ -17,6 +19,10 @@ more sense to keep this external to the main MicroPython repository.
1719
This chapter describes how to compile such external modules into the
1820
MicroPython executable or firmware image.
1921

22+
An alternative approach is to use :ref:`natmod` which allows writing custom C
23+
code that is placed in a .mpy file, which can be imported dynamically in to
24+
a running MicroPython system without the need to recompile the main firmware.
25+
2026

2127
Structure of an external C module
2228
---------------------------------

docs/develop/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,4 @@ See the `getting started guide
1111

1212
cmodules.rst
1313
qstr.rst
14+
natmod.rst

docs/develop/natmod.rst

Lines changed: 202 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,202 @@
1+
.. _natmod:
2+
3+
Native machine code in .mpy files
4+
=================================
5+
6+
This section describes how to build and work with .mpy files that contain native
7+
machine code from a language other than Python. This allows you to
8+
write code in a language like C, compile and link it into a .mpy file, and then
9+
import this file like a normal Python module. This can be used for implementing
10+
functionality which is performance critical, or for including an existing
11+
library written in another language.
12+
13+
One of the main advantages of using native .mpy files is that native machine code
14+
can be imported by a script dynamically, without the need to rebuild the main
15+
MicroPython firmware. This is in contrast to :ref:`cmodules` which also allows
16+
defining custom modules in C but they must be compiled into the main firmware image.
17+
18+
The focus here is on using C to build native modules, but in principle any
19+
language which can be compiled to stand-alone machine code can be put into a
20+
.mpy file.
21+
22+
A native .mpy module is built using the ``mpy_ld.py`` tool, which is found in the
23+
``tools/`` directory of the project. This tool takes a set of object files
24+
(.o files) and links them together to create a native .mpy files.
25+
26+
Supported features and limitations
27+
----------------------------------
28+
29+
A .mpy file can contain MicroPython bytecode and/or native machine code. If it
30+
contains native machine code then the .mpy file has a specific architecture
31+
associated with it. Current supported architectures are (these are the valid
32+
options for the ``ARCH`` variable, see below):
33+
34+
* ``x86`` (32 bit)
35+
* ``x64`` (64 bit x86)
36+
* ``armv7m`` (ARM Thumb 2, eg Cortex-M3)
37+
* ``armv7emsp`` (ARM Thumb 2, single precision float, eg Cortex-M4F, Cortex-M7)
38+
* ``armv7emdp`` (ARM Thumb 2, double precision float, eg Cortex-M7)
39+
* ``xtensa`` (non-windowed, eg ESP8266)
40+
* ``xtensawin`` (windowed with window size 8, eg ESP32)
41+
42+
When compiling and linking the native .mpy file the architecture must be chosen
43+
and the corresponding file can only be imported on that architecture. For more
44+
details about .mpy files see :ref:`mpy_files`.
45+
46+
Native code must be compiled as position independent code (PIC) and use a global
47+
offset table (GOT), although the details of this varies from architecture to
48+
architecture. When importing .mpy files with native code the import machinery
49+
is able to do some basic relocation of the native code. This includes
50+
relocating text, rodata and BSS sections.
51+
52+
Supported features of the linker and dynamic loader are:
53+
54+
* executable code (text)
55+
* read-only data (rodata), including strings and constant data (arrays, structs, etc)
56+
* zeroed data (BSS)
57+
* pointers in text to text, rodata and BSS
58+
* pointers in rodata to text, rodata and BSS
59+
60+
The known limitations are:
61+
62+
* data sections are not supported; workaround: use BSS data and initialise the
63+
data values explicitly
64+
65+
* static BSS variables are not supported; workaround: use global BSS variables
66+
67+
So, if your C code has writable data, make sure the data is defined globally,
68+
without an initialiser, and only written to within functions.
69+
70+
Defining a native module
71+
------------------------
72+
73+
A native .mpy module is defined by a set of files that are used to build the .mpy.
74+
The filesystem layout consists of two main parts, the source files and the Makefile:
75+
76+
* In the simplest case only a single C source file is required, which contains all
77+
the code that will be compiled into the .mpy module. This C source code must
78+
include the ``py/dynruntime.h`` file to access the MicroPython dynamic API, and
79+
must at least define a function called ``mpy_init``. This function will be the
80+
entry point of the module, called when the module is imported.
81+
82+
The module can be split into multiple C source files if desired. Parts of the
83+
module can also be implemented in Python. All source files should be listed in
84+
the Makefile, by adding them to the ``SRC`` variable (see below). This includes
85+
both C source files as well as any Python files which will be included in the
86+
resulting .mpy file.
87+
88+
* The ``Makefile`` contains the build configuration for the module and list the
89+
source files used to build the .mpy module. It should define ``MPY_DIR`` as the
90+
location of the MicroPython repository (to find header files, the relevant Makefile
91+
fragment, and the ``mpy_ld.py`` tool), ``MOD`` as the name of the module, ``SRC``
92+
as the list of source files, optionally specify the machine architecture via ``ARCH``,
93+
and then include ``py/dynruntime.mk``.
94+
95+
Minimal example
96+
---------------
97+
98+
This section provides a fully working example of a simple module named ``factorial``.
99+
This module provides a single function ``factorial.factorial(x)`` which computes the
100+
factorial of the input and returns the result.
101+
102+
Directory layout::
103+
104+
factorial/
105+
├── factorial.c
106+
└── Makefile
107+
108+
The file ``factorial.c`` contains:
109+
110+
.. code-block:: c
111+
112+
// Include the header file to get access to the MicroPython API
113+
#include "py/dynruntime.h"
114+
115+
// Helper function to compute factorial
116+
STATIC mp_int_t factorial_helper(mp_int_t x) {
117+
if (x == 0) {
118+
return 1;
119+
}
120+
return x * factorial_helper(x - 1);
121+
}
122+
123+
// This is the function which will be called from Python, as factorial(x)
124+
STATIC mp_obj_t factorial(mp_obj_t x_obj) {
125+
// Extract the integer from the MicroPython input object
126+
mp_int_t x = mp_obj_get_int(x_obj);
127+
// Calculate the factorial
128+
mp_int_t result = factorial_helper(x);
129+
// Convert the result to a MicroPython integer object and return it
130+
return mp_obj_new_int(result);
131+
}
132+
// Define a Python reference to the function above
133+
STATIC MP_DEFINE_CONST_FUN_OBJ_1(factorial_obj, factorial);
134+
135+
// This is the entry point and is called when the module is imported
136+
mp_obj_t mpy_init(mp_obj_fun_bc_t *self, size_t n_args, size_t n_kw, mp_obj_t *args) {
137+
// This must be first, it sets up the globals dict and other things
138+
MP_DYNRUNTIME_INIT_ENTRY
139+
140+
// Make the function available in the module's namespace
141+
mp_store_global(MP_QSTR_factorial, MP_OBJ_FROM_PTR(&factorial_obj));
142+
143+
// This must be last, it restores the globals dict
144+
MP_DYNRUNTIME_INIT_EXIT
145+
}
146+
147+
The file ``Makefile`` contains:
148+
149+
.. code-block:: make
150+
151+
# Location of top-level MicroPython directory
152+
MPY_DIR = ../../..
153+
154+
# Name of module
155+
MOD = features0
156+
157+
# Source files (.c or .py)
158+
SRC = features0.c
159+
160+
# Architecture to build for (x86, x64, armv7m, xtensa, xtensawin)
161+
ARCH = x64
162+
163+
# Include to get the rules for compiling and linking the module
164+
include $(MPY_DIR)/py/dynruntime.mk
165+
166+
Compiling the module
167+
--------------------
168+
169+
Be sure to select the correct ``ARCH`` for the target you are going to run on.
170+
Then build with::
171+
172+
$ make
173+
174+
Without modifying the Makefile you can specify the target architecture via::
175+
176+
$ make ARCH=armv7m
177+
178+
Module usage in MicroPython
179+
---------------------------
180+
181+
Once the module is built there should be a file called ``factorial.mpy``. Copy
182+
this so it is accessible on the filesystem of your MicroPython system and can be
183+
found in the import path. The module con now be accessed in Python just like any
184+
other module, for example::
185+
186+
import factorial
187+
print(factorial.factorial(10))
188+
# should display 3628800
189+
190+
Further examples
191+
----------------
192+
193+
See ``examples/natmod/`` for further examples which show many of the available
194+
features of native .mpy modules. Such features include:
195+
196+
* using multiple C source files
197+
* including Python code alongside C code
198+
* rodata and BSS data
199+
* memory allocation
200+
* use of floating point
201+
* exception handling
202+
* including external C libraries

0 commit comments

Comments
 (0)