summaryrefslogtreecommitdiffstatshomepage
diff options
context:
space:
mode:
-rw-r--r--docs/develop/cmodules.rst160
-rw-r--r--examples/usercmodule/cexample/examplemodule.c34
-rw-r--r--examples/usercmodule/cexample/micropython.mk9
-rw-r--r--examples/usercmodule/cppexample/example.cpp17
-rw-r--r--examples/usercmodule/cppexample/examplemodule.c25
-rw-r--r--examples/usercmodule/cppexample/examplemodule.h5
-rw-r--r--examples/usercmodule/cppexample/micropython.mk12
-rw-r--r--ports/unix/variants/coverage/mpconfigvariant.mk4
-rw-r--r--tests/unix/extra_coverage.py10
-rw-r--r--tests/unix/extra_coverage.py.exp2
10 files changed, 192 insertions, 86 deletions
diff --git a/docs/develop/cmodules.rst b/docs/develop/cmodules.rst
index 849d0e60a2..2d08df8282 100644
--- a/docs/develop/cmodules.rst
+++ b/docs/develop/cmodules.rst
@@ -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
+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.
-``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);
+Compiling the cmodule into MicroPython
+--------------------------------------
- // 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);
+To build such a module, compile MicroPython (see `getting started
+<https://github.com/micropython/micropython/wiki/Getting-Started>`_),
+applying 2 modifications:
- // 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);
+- 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::
- // Define module object.
- const mp_obj_module_t example_user_cmodule = {
- .base = { &mp_type_module },
- .globals = (mp_obj_dict_t*)&example_module_globals,
- };
+ my_project/
+ ├── modules/
+ │ └──example1/
+ │ ├──example1.c
+ │ └──micropython.mk
+ │ └──example2/
+ │ ├──example2.c
+ │ └──micropython.mk
+ └── micropython/
+ ├──ports/
+ ... ├──stm32/
+ ...
- // Register the module to make it available in Python
- MP_REGISTER_MODULE(MP_QSTR_example, example_user_cmodule, MODULE_EXAMPLE_ENABLED);
+ with ``USER_C_MODULES`` set to the ``my_project/modules`` directory.
-``micropython.mk``
+- 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:: make
+ .. code-block:: c
- EXAMPLE_MOD_DIR := $(USERMOD_DIR)
+ MP_REGISTER_MODULE(MP_QSTR_cexample, example_user_cmodule, MODULE_CEXAMPLE_ENABLED);
- # 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)
+ 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
-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
-.. code-block:: c
+ #define MODULE_CEXAMPLE_ENABLED (1)
- #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.
+ 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:
-Compiling the cmodule into MicroPython
---------------------------------------
+.. code-block:: bash
-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:
+ 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::
-Directory::
+ ...
+ Including User C Module from ../../examples/usercmodule/cexample
+ Including User C Module from ../../examples/usercmodule/cppexample
+ ...
- my_project/
- ├── modules/
- │ └──example/
- │ ├──example.c
- │ └──micropython.mk
- └── micropython/
- ├──ports/
- ... ├──stm32/
- ...
-Building for stm32 port:
+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
diff --git a/examples/usercmodule/cexample/examplemodule.c b/examples/usercmodule/cexample/examplemodule.c
new file mode 100644
index 0000000000..f608823c9e
--- /dev/null
+++ b/examples/usercmodule/cexample/examplemodule.c
@@ -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);
diff --git a/examples/usercmodule/cexample/micropython.mk b/examples/usercmodule/cexample/micropython.mk
new file mode 100644
index 0000000000..dbfe3c5cbd
--- /dev/null
+++ b/examples/usercmodule/cexample/micropython.mk
@@ -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)
diff --git a/examples/usercmodule/cppexample/example.cpp b/examples/usercmodule/cppexample/example.cpp
new file mode 100644
index 0000000000..06809732a4
--- /dev/null
+++ b/examples/usercmodule/cppexample/example.cpp
@@ -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);
+}
+}
diff --git a/examples/usercmodule/cppexample/examplemodule.c b/examples/usercmodule/cppexample/examplemodule.c
new file mode 100644
index 0000000000..ceb588bef6
--- /dev/null
+++ b/examples/usercmodule/cppexample/examplemodule.c
@@ -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);
diff --git a/examples/usercmodule/cppexample/examplemodule.h b/examples/usercmodule/cppexample/examplemodule.h
new file mode 100644
index 0000000000..d89384a630
--- /dev/null
+++ b/examples/usercmodule/cppexample/examplemodule.h
@@ -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);
diff --git a/examples/usercmodule/cppexample/micropython.mk b/examples/usercmodule/cppexample/micropython.mk
new file mode 100644
index 0000000000..e10d965a00
--- /dev/null
+++ b/examples/usercmodule/cppexample/micropython.mk
@@ -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++
diff --git a/ports/unix/variants/coverage/mpconfigvariant.mk b/ports/unix/variants/coverage/mpconfigvariant.mk
index f11d0b0d28..55399831aa 100644
--- a/ports/unix/variants/coverage/mpconfigvariant.mk
+++ b/ports/unix/variants/coverage/mpconfigvariant.mk
@@ -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
diff --git a/tests/unix/extra_coverage.py b/tests/unix/extra_coverage.py
index 1c028506e3..b4808993a7 100644
--- a/tests/unix/extra_coverage.py
+++ b/tests/unix/extra_coverage.py
@@ -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
diff --git a/tests/unix/extra_coverage.py.exp b/tests/unix/extra_coverage.py.exp
index 514ff9437b..257224108a 100644
--- a/tests/unix/extra_coverage.py.exp
+++ b/tests/unix/extra_coverage.py.exp
@@ -145,6 +145,8 @@ OSError
None
None
cpp None
+5
+(3, 'hellocpp')
frzstr1
frzstr1.py
frzmpy1