examples: Add example code for user C modules, both C and C++.
Add working example code to provide a starting point for users with files that they can just copy, and include the modules in the coverage test to verify the complete user C module build functionality. The cexample module uses the code originally found in cmodules.rst, which has been updated to reflect this and partially rewritten with more complete information.bound-method-equality
parent
fad4079778
commit
25c4563f26
|
@ -49,8 +49,9 @@ A MicroPython user C module is a directory with the following files:
|
||||||
expanded copy of ``$(USERMOD_DIR)`` to ``SRC_USERMOD``, eg
|
expanded copy of ``$(USERMOD_DIR)`` to ``SRC_USERMOD``, eg
|
||||||
``SRC_USERMOD += $(EXAMPLE_MOD_DIR)/example.c``
|
``SRC_USERMOD += $(EXAMPLE_MOD_DIR)/example.c``
|
||||||
|
|
||||||
If you have custom ``CFLAGS`` settings or include folders to define, these
|
If you have custom compiler options (like ``-I`` to add directories to search
|
||||||
should be added to ``CFLAGS_USERMOD``, or ``CXXFLAGS_USERMOD``.
|
for header files), these should be added to ``CFLAGS_USERMOD`` for C code
|
||||||
|
and to ``CXXFLAGS_USERMOD`` for C++ code.
|
||||||
|
|
||||||
See below for full usage example.
|
See below for full usage example.
|
||||||
|
|
||||||
|
@ -58,124 +59,113 @@ A MicroPython user C module is a directory with the following files:
|
||||||
Basic example
|
Basic example
|
||||||
-------------
|
-------------
|
||||||
|
|
||||||
This simple module named ``example`` provides a single function
|
This simple module named ``cexample`` provides a single function
|
||||||
``example.add_ints(a, b)`` which adds the two integer args together and returns
|
``cexample.add_ints(a, b)`` which adds the two integer args together and returns
|
||||||
the result.
|
the result. It can be found in the MicroPython source tree and has
|
||||||
|
a source file and a Makefile fragment with content as descibed above::
|
||||||
|
|
||||||
Directory::
|
micropython/
|
||||||
|
└──examples/
|
||||||
example/
|
└──usercmodule/
|
||||||
├── example.c
|
└──cexample/
|
||||||
|
├── examplemodule.c
|
||||||
└── micropython.mk
|
└── micropython.mk
|
||||||
|
|
||||||
|
Refer to the comments in these 2 files for additional explanation.
|
||||||
``example.c``
|
Next to the ``cexample`` module there's also ``cppexample`` which
|
||||||
|
works in the same way but shows one way of mixing C and C++ code
|
||||||
.. code-block:: c
|
in MicroPython.
|
||||||
|
|
||||||
// Include required definitions first.
|
|
||||||
#include "py/obj.h"
|
|
||||||
#include "py/runtime.h"
|
|
||||||
#include "py/builtin.h"
|
|
||||||
|
|
||||||
// This is the function which will be called from Python as example.add_ints(a, b).
|
|
||||||
STATIC mp_obj_t example_add_ints(mp_obj_t a_obj, mp_obj_t b_obj) {
|
|
||||||
// Extract the ints from the micropython input objects
|
|
||||||
int a = mp_obj_get_int(a_obj);
|
|
||||||
int b = mp_obj_get_int(b_obj);
|
|
||||||
|
|
||||||
// Calculate the addition and convert to MicroPython object.
|
|
||||||
return mp_obj_new_int(a + b);
|
|
||||||
}
|
|
||||||
// Define a Python reference to the function above
|
|
||||||
STATIC MP_DEFINE_CONST_FUN_OBJ_2(example_add_ints_obj, example_add_ints);
|
|
||||||
|
|
||||||
// Define all properties of the example module.
|
|
||||||
// Table entries are key/value pairs of the attribute name (a string)
|
|
||||||
// and the MicroPython object reference.
|
|
||||||
// All identifiers and strings are written as MP_QSTR_xxx and will be
|
|
||||||
// optimized to word-sized integers by the build system (interned strings).
|
|
||||||
STATIC const mp_rom_map_elem_t example_module_globals_table[] = {
|
|
||||||
{ MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_example) },
|
|
||||||
{ MP_ROM_QSTR(MP_QSTR_add_ints), MP_ROM_PTR(&example_add_ints_obj) },
|
|
||||||
};
|
|
||||||
STATIC MP_DEFINE_CONST_DICT(example_module_globals, example_module_globals_table);
|
|
||||||
|
|
||||||
// Define module object.
|
|
||||||
const mp_obj_module_t example_user_cmodule = {
|
|
||||||
.base = { &mp_type_module },
|
|
||||||
.globals = (mp_obj_dict_t*)&example_module_globals,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Register the module to make it available in Python
|
|
||||||
MP_REGISTER_MODULE(MP_QSTR_example, example_user_cmodule, MODULE_EXAMPLE_ENABLED);
|
|
||||||
|
|
||||||
|
|
||||||
``micropython.mk``
|
|
||||||
|
|
||||||
.. code-block:: make
|
|
||||||
|
|
||||||
EXAMPLE_MOD_DIR := $(USERMOD_DIR)
|
|
||||||
|
|
||||||
# Add all C files to SRC_USERMOD.
|
|
||||||
SRC_USERMOD += $(EXAMPLE_MOD_DIR)/example.c
|
|
||||||
|
|
||||||
# We can add our module folder to include paths if needed
|
|
||||||
# This is not actually needed in this example.
|
|
||||||
CFLAGS_USERMOD += -I$(EXAMPLE_MOD_DIR)
|
|
||||||
|
|
||||||
Finally you will need to define ``MODULE_EXAMPLE_ENABLED`` to 1. This
|
|
||||||
can be done by adding ``CFLAGS_EXTRA=-DMODULE_EXAMPLE_ENABLED=1`` to
|
|
||||||
the ``make`` command, or editing ``mpconfigport.h`` or
|
|
||||||
``mpconfigboard.h`` to add
|
|
||||||
|
|
||||||
.. code-block:: c
|
|
||||||
|
|
||||||
#define MODULE_EXAMPLE_ENABLED (1)
|
|
||||||
|
|
||||||
Note that the exact method depends on the port as they have different
|
|
||||||
structures. If not done correctly it will compile but importing will
|
|
||||||
fail to find the module.
|
|
||||||
|
|
||||||
|
|
||||||
Compiling the cmodule into MicroPython
|
Compiling the cmodule into MicroPython
|
||||||
--------------------------------------
|
--------------------------------------
|
||||||
|
|
||||||
To build such a module, compile MicroPython (see `getting started
|
To build such a module, compile MicroPython (see `getting started
|
||||||
<https://github.com/micropython/micropython/wiki/Getting-Started>`_) with an
|
<https://github.com/micropython/micropython/wiki/Getting-Started>`_),
|
||||||
extra ``make`` flag named ``USER_C_MODULES`` set to the directory containing
|
applying 2 modifications:
|
||||||
all modules you want included (not to the module itself). For example:
|
|
||||||
|
|
||||||
|
- an extra ``make`` flag named ``USER_C_MODULES`` set to the directory
|
||||||
Directory::
|
containing all modules you want included (not to the module itself).
|
||||||
|
For building the example modules which come with MicroPython,
|
||||||
|
set ``USER_C_MODULES`` to the ``examples/usercmodule`` directory.
|
||||||
|
For your own projects it's more convenient to keep custom code out of
|
||||||
|
the main source tree so a typical project directory structure will look
|
||||||
|
like this::
|
||||||
|
|
||||||
my_project/
|
my_project/
|
||||||
├── modules/
|
├── modules/
|
||||||
│ └──example/
|
│ └──example1/
|
||||||
│ ├──example.c
|
│ ├──example1.c
|
||||||
|
│ └──micropython.mk
|
||||||
|
│ └──example2/
|
||||||
|
│ ├──example2.c
|
||||||
│ └──micropython.mk
|
│ └──micropython.mk
|
||||||
└── micropython/
|
└── micropython/
|
||||||
├──ports/
|
├──ports/
|
||||||
... ├──stm32/
|
... ├──stm32/
|
||||||
...
|
...
|
||||||
|
|
||||||
Building for stm32 port:
|
|
||||||
|
with ``USER_C_MODULES`` set to the ``my_project/modules`` directory.
|
||||||
|
|
||||||
|
- all modules found in this directory will be compiled, but only those
|
||||||
|
which are explicitly enabled will be availabe for importing. Enabling a
|
||||||
|
module is done by setting the preprocessor define from its module
|
||||||
|
registration to 1. For example if the source code defines the module with
|
||||||
|
|
||||||
|
.. code-block:: c
|
||||||
|
|
||||||
|
MP_REGISTER_MODULE(MP_QSTR_cexample, example_user_cmodule, MODULE_CEXAMPLE_ENABLED);
|
||||||
|
|
||||||
|
|
||||||
|
then ``MODULE_CEXAMPLE_ENABLED`` has to be set to 1 to make the module available.
|
||||||
|
This can be done by adding ``CFLAGS_EXTRA=-DMODULE_CEXAMPLE_ENABLED=1`` to
|
||||||
|
the ``make`` command, or editing ``mpconfigport.h`` or ``mpconfigboard.h``
|
||||||
|
to add
|
||||||
|
|
||||||
|
.. code-block:: c
|
||||||
|
|
||||||
|
#define MODULE_CEXAMPLE_ENABLED (1)
|
||||||
|
|
||||||
|
|
||||||
|
Note that the exact method depends on the port as they have different
|
||||||
|
structures. If not done correctly it will compile but importing will
|
||||||
|
fail to find the module.
|
||||||
|
|
||||||
|
To sum up, here's how the ``cexample`` module from the ``examples/usercmodule``
|
||||||
|
directory can be built for the unix port:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
cd micropython/ports/unix
|
||||||
|
make USER_C_MODULES=../../examples/usercmodule CFLAGS_EXTRA=-DMODULE_CEXAMPLE_ENABLED=1 all
|
||||||
|
|
||||||
|
The build output will show the modules found::
|
||||||
|
|
||||||
|
...
|
||||||
|
Including User C Module from ../../examples/usercmodule/cexample
|
||||||
|
Including User C Module from ../../examples/usercmodule/cppexample
|
||||||
|
...
|
||||||
|
|
||||||
|
|
||||||
|
Or for your own project with a directory structure as shown above,
|
||||||
|
including both modules and building the stm32 port for example:
|
||||||
|
|
||||||
.. code-block:: bash
|
.. code-block:: bash
|
||||||
|
|
||||||
cd my_project/micropython/ports/stm32
|
cd my_project/micropython/ports/stm32
|
||||||
make USER_C_MODULES=../../../modules CFLAGS_EXTRA=-DMODULE_EXAMPLE_ENABLED=1 all
|
make USER_C_MODULES=../../../modules \
|
||||||
|
CFLAGS_EXTRA="-DMODULE_EXAMPLE1_ENABLED=1 -DMODULE_EXAMPLE2_ENABLED=1" all
|
||||||
|
|
||||||
|
|
||||||
Module usage in MicroPython
|
Module usage in MicroPython
|
||||||
---------------------------
|
---------------------------
|
||||||
|
|
||||||
Once built into your copy of MicroPython, the module implemented
|
Once built into your copy of MicroPython, the module
|
||||||
in ``example.c`` above can now be accessed in Python just
|
can now be accessed in Python just like any other builtin module, e.g.
|
||||||
like any other builtin module, eg
|
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
import example
|
import cexample
|
||||||
print(example.add_ints(1, 3))
|
print(cexample.add_ints(1, 3))
|
||||||
# should display 4
|
# should display 4
|
||||||
|
|
|
@ -0,0 +1,34 @@
|
||||||
|
// Include MicroPython API.
|
||||||
|
#include "py/runtime.h"
|
||||||
|
|
||||||
|
// This is the function which will be called from Python as cexample.add_ints(a, b).
|
||||||
|
STATIC mp_obj_t example_add_ints(mp_obj_t a_obj, mp_obj_t b_obj) {
|
||||||
|
// Extract the ints from the micropython input objects.
|
||||||
|
int a = mp_obj_get_int(a_obj);
|
||||||
|
int b = mp_obj_get_int(b_obj);
|
||||||
|
|
||||||
|
// Calculate the addition and convert to MicroPython object.
|
||||||
|
return mp_obj_new_int(a + b);
|
||||||
|
}
|
||||||
|
// Define a Python reference to the function above.
|
||||||
|
STATIC MP_DEFINE_CONST_FUN_OBJ_2(example_add_ints_obj, example_add_ints);
|
||||||
|
|
||||||
|
// Define all properties of the module.
|
||||||
|
// Table entries are key/value pairs of the attribute name (a string)
|
||||||
|
// and the MicroPython object reference.
|
||||||
|
// All identifiers and strings are written as MP_QSTR_xxx and will be
|
||||||
|
// optimized to word-sized integers by the build system (interned strings).
|
||||||
|
STATIC const mp_rom_map_elem_t example_module_globals_table[] = {
|
||||||
|
{ MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_cexample) },
|
||||||
|
{ MP_ROM_QSTR(MP_QSTR_add_ints), MP_ROM_PTR(&example_add_ints_obj) },
|
||||||
|
};
|
||||||
|
STATIC MP_DEFINE_CONST_DICT(example_module_globals, example_module_globals_table);
|
||||||
|
|
||||||
|
// Define module object.
|
||||||
|
const mp_obj_module_t example_user_cmodule = {
|
||||||
|
.base = { &mp_type_module },
|
||||||
|
.globals = (mp_obj_dict_t *)&example_module_globals,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Register the module to make it available in Python.
|
||||||
|
MP_REGISTER_MODULE(MP_QSTR_cexample, example_user_cmodule, MODULE_CEXAMPLE_ENABLED);
|
|
@ -0,0 +1,9 @@
|
||||||
|
EXAMPLE_MOD_DIR := $(USERMOD_DIR)
|
||||||
|
|
||||||
|
# Add all C files to SRC_USERMOD.
|
||||||
|
SRC_USERMOD += $(EXAMPLE_MOD_DIR)/examplemodule.c
|
||||||
|
|
||||||
|
# We can add our module folder to include paths if needed
|
||||||
|
# This is not actually needed in this example.
|
||||||
|
CFLAGS_USERMOD += -I$(EXAMPLE_MOD_DIR)
|
||||||
|
CEXAMPLE_MOD_DIR := $(USERMOD_DIR)
|
|
@ -0,0 +1,17 @@
|
||||||
|
extern "C" {
|
||||||
|
#include <examplemodule.h>
|
||||||
|
|
||||||
|
// Here we implement the function using C++ code, but since it's
|
||||||
|
// declaration has to be compatible with C everything goes in extern "C" scope.
|
||||||
|
mp_obj_t cppfunc(mp_obj_t a_obj, mp_obj_t b_obj) {
|
||||||
|
// Prove we have (at least) C++11 features.
|
||||||
|
const auto a = mp_obj_get_int(a_obj);
|
||||||
|
const auto b = mp_obj_get_int(b_obj);
|
||||||
|
const auto sum = [&]() {
|
||||||
|
return mp_obj_new_int(a + b);
|
||||||
|
} ();
|
||||||
|
// Prove we're being scanned for QSTRs.
|
||||||
|
mp_obj_t tup[] = {sum, MP_ROM_QSTR(MP_QSTR_hellocpp)};
|
||||||
|
return mp_obj_new_tuple(2, tup);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,25 @@
|
||||||
|
#include <examplemodule.h>
|
||||||
|
|
||||||
|
// Define a Python reference to the function we'll make available.
|
||||||
|
// See example.cpp for the definition.
|
||||||
|
STATIC MP_DEFINE_CONST_FUN_OBJ_2(cppfunc_obj, cppfunc);
|
||||||
|
|
||||||
|
// Define all properties of the module.
|
||||||
|
// Table entries are key/value pairs of the attribute name (a string)
|
||||||
|
// and the MicroPython object reference.
|
||||||
|
// All identifiers and strings are written as MP_QSTR_xxx and will be
|
||||||
|
// optimized to word-sized integers by the build system (interned strings).
|
||||||
|
STATIC const mp_rom_map_elem_t cppexample_module_globals_table[] = {
|
||||||
|
{ MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_cppexample) },
|
||||||
|
{ MP_ROM_QSTR(MP_QSTR_cppfunc), MP_ROM_PTR(&cppfunc_obj) },
|
||||||
|
};
|
||||||
|
STATIC MP_DEFINE_CONST_DICT(cppexample_module_globals, cppexample_module_globals_table);
|
||||||
|
|
||||||
|
// Define module object.
|
||||||
|
const mp_obj_module_t cppexample_user_cmodule = {
|
||||||
|
.base = { &mp_type_module },
|
||||||
|
.globals = (mp_obj_dict_t *)&cppexample_module_globals,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Register the module to make it available in Python.
|
||||||
|
MP_REGISTER_MODULE(MP_QSTR_cppexample, cppexample_user_cmodule, MODULE_CPPEXAMPLE_ENABLED);
|
|
@ -0,0 +1,5 @@
|
||||||
|
// Include MicroPython API.
|
||||||
|
#include "py/runtime.h"
|
||||||
|
|
||||||
|
// Declare the function we'll make available in Python as cppexample.cppfunc().
|
||||||
|
extern mp_obj_t cppfunc(mp_obj_t a_obj, mp_obj_t b_obj);
|
|
@ -0,0 +1,12 @@
|
||||||
|
CPPEXAMPLE_MOD_DIR := $(USERMOD_DIR)
|
||||||
|
|
||||||
|
# Add our source files to the respective variables.
|
||||||
|
SRC_USERMOD += $(CPPEXAMPLE_MOD_DIR)/examplemodule.c
|
||||||
|
SRC_USERMOD_CXX += $(CPPEXAMPLE_MOD_DIR)/example.cpp
|
||||||
|
|
||||||
|
# Add our module directory to the include path.
|
||||||
|
CFLAGS_USERMOD += -I$(CPPEXAMPLE_MOD_DIR)
|
||||||
|
CXXFLAGS_USERMOD += -I$(CPPEXAMPLE_MOD_DIR)
|
||||||
|
|
||||||
|
# We use C++ features so have to link against the standard library.
|
||||||
|
LDFLAGS_USERMOD += -lstdc++
|
|
@ -7,11 +7,13 @@ CFLAGS += \
|
||||||
-fprofile-arcs -ftest-coverage \
|
-fprofile-arcs -ftest-coverage \
|
||||||
-Wformat -Wmissing-declarations -Wmissing-prototypes \
|
-Wformat -Wmissing-declarations -Wmissing-prototypes \
|
||||||
-Wold-style-definition -Wpointer-arith -Wshadow -Wuninitialized -Wunused-parameter \
|
-Wold-style-definition -Wpointer-arith -Wshadow -Wuninitialized -Wunused-parameter \
|
||||||
-DMICROPY_UNIX_COVERAGE
|
-DMICROPY_UNIX_COVERAGE \
|
||||||
|
-DMODULE_CEXAMPLE_ENABLED=1 -DMODULE_CPPEXAMPLE_ENABLED=1
|
||||||
|
|
||||||
LDFLAGS += -fprofile-arcs -ftest-coverage
|
LDFLAGS += -fprofile-arcs -ftest-coverage
|
||||||
|
|
||||||
FROZEN_MANIFEST ?= $(VARIANT_DIR)/manifest.py
|
FROZEN_MANIFEST ?= $(VARIANT_DIR)/manifest.py
|
||||||
|
USER_C_MODULES = $(TOP)/examples/usercmodule
|
||||||
|
|
||||||
MICROPY_ROM_TEXT_COMPRESSION = 1
|
MICROPY_ROM_TEXT_COMPRESSION = 1
|
||||||
MICROPY_VFS_FAT = 1
|
MICROPY_VFS_FAT = 1
|
||||||
|
|
|
@ -49,6 +49,16 @@ print(buf.write(bytearray(16)))
|
||||||
# function defined in C++ code
|
# function defined in C++ code
|
||||||
print("cpp", extra_cpp_coverage())
|
print("cpp", extra_cpp_coverage())
|
||||||
|
|
||||||
|
# test user C module
|
||||||
|
import cexample
|
||||||
|
|
||||||
|
print(cexample.add_ints(3, 2))
|
||||||
|
|
||||||
|
# test user C module mixed with C++ code
|
||||||
|
import cppexample
|
||||||
|
|
||||||
|
print(cppexample.cppfunc(1, 2))
|
||||||
|
|
||||||
# test basic import of frozen scripts
|
# test basic import of frozen scripts
|
||||||
import frzstr1
|
import frzstr1
|
||||||
|
|
||||||
|
|
|
@ -145,6 +145,8 @@ OSError
|
||||||
None
|
None
|
||||||
None
|
None
|
||||||
cpp None
|
cpp None
|
||||||
|
5
|
||||||
|
(3, 'hellocpp')
|
||||||
frzstr1
|
frzstr1
|
||||||
frzstr1.py
|
frzstr1.py
|
||||||
frzmpy1
|
frzmpy1
|
||||||
|
|
Loading…
Reference in New Issue