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.
This commit is contained in:
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
|
||||
``SRC_USERMOD += $(EXAMPLE_MOD_DIR)/example.c``
|
||||
|
||||
If you have custom ``CFLAGS`` settings or include folders to define, these
|
||||
should be added to ``CFLAGS_USERMOD``, or ``CXXFLAGS_USERMOD``.
|
||||
If you have custom compiler options (like ``-I`` to add directories to search
|
||||
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.
|
||||
|
||||
|
@ -58,124 +59,113 @@ A MicroPython user C module is a directory with the following files:
|
|||
Basic example
|
||||
-------------
|
||||
|
||||
This simple module named ``example`` provides a single function
|
||||
``example.add_ints(a, b)`` which adds the two integer args together and returns
|
||||
the result.
|
||||
This simple module named ``cexample`` provides a single function
|
||||
``cexample.add_ints(a, b)`` which adds the two integer args together and returns
|
||||
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/
|
||||
└──usercmodule/
|
||||
└──cexample/
|
||||
├── examplemodule.c
|
||||
└── micropython.mk
|
||||
|
||||
example/
|
||||
├── example.c
|
||||
└── micropython.mk
|
||||
|
||||
|
||||
``example.c``
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
// 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.
|
||||
Refer to the comments in these 2 files for additional explanation.
|
||||
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
|
||||
in MicroPython.
|
||||
|
||||
|
||||
Compiling the cmodule into MicroPython
|
||||
--------------------------------------
|
||||
|
||||
To build such a module, compile MicroPython (see `getting started
|
||||
<https://github.com/micropython/micropython/wiki/Getting-Started>`_) with an
|
||||
extra ``make`` flag named ``USER_C_MODULES`` set to the directory containing
|
||||
all modules you want included (not to the module itself). For example:
|
||||
<https://github.com/micropython/micropython/wiki/Getting-Started>`_),
|
||||
applying 2 modifications:
|
||||
|
||||
- an extra ``make`` flag named ``USER_C_MODULES`` set to the 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/
|
||||
├── modules/
|
||||
│ └──example1/
|
||||
│ ├──example1.c
|
||||
│ └──micropython.mk
|
||||
│ └──example2/
|
||||
│ ├──example2.c
|
||||
│ └──micropython.mk
|
||||
└── micropython/
|
||||
├──ports/
|
||||
... ├──stm32/
|
||||
...
|
||||
|
||||
|
||||
Directory::
|
||||
with ``USER_C_MODULES`` set to the ``my_project/modules`` directory.
|
||||
|
||||
my_project/
|
||||
├── modules/
|
||||
│ └──example/
|
||||
│ ├──example.c
|
||||
│ └──micropython.mk
|
||||
└── micropython/
|
||||
├──ports/
|
||||
... ├──stm32/
|
||||
...
|
||||
- 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
|
||||
|
||||
Building for stm32 port:
|
||||
.. 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
|
||||
|
||||
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
|
||||
---------------------------
|
||||
|
||||
Once built into your copy of MicroPython, the module implemented
|
||||
in ``example.c`` above can now be accessed in Python just
|
||||
like any other builtin module, eg
|
||||
Once built into your copy of MicroPython, the module
|
||||
can now be accessed in Python just like any other builtin module, e.g.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import example
|
||||
print(example.add_ints(1, 3))
|
||||
import cexample
|
||||
print(cexample.add_ints(1, 3))
|
||||
# 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 \
|
||||
-Wformat -Wmissing-declarations -Wmissing-prototypes \
|
||||
-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
|
||||
|
||||
FROZEN_MANIFEST ?= $(VARIANT_DIR)/manifest.py
|
||||
USER_C_MODULES = $(TOP)/examples/usercmodule
|
||||
|
||||
MICROPY_ROM_TEXT_COMPRESSION = 1
|
||||
MICROPY_VFS_FAT = 1
|
||||
|
|
|
@ -49,6 +49,16 @@ print(buf.write(bytearray(16)))
|
|||
# function defined in C++ code
|
||||
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
|
||||
import frzstr1
|
||||
|
||||
|
|
|
@ -145,6 +145,8 @@ OSError
|
|||
None
|
||||
None
|
||||
cpp None
|
||||
5
|
||||
(3, 'hellocpp')
|
||||
frzstr1
|
||||
frzstr1.py
|
||||
frzmpy1
|
||||
|
|
Loading…
Reference in New Issue