kopia lustrzana https://github.com/micropython/micropython
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.pull/6579/head
rodzic
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
|
||||
|
|
Ładowanie…
Reference in New Issue