py: Implement a module system for external, user C modules.
This system makes it a lot easier to include external libraries as static, native modules in MicroPython. Simply pass USER_C_MODULES (like FROZEN_MPY_DIR) as a make parameter.
This commit is contained in:
parent
cf22f4793c
commit
2e516074da
|
@ -0,0 +1,86 @@
|
|||
Extending MicroPython with C
|
||||
============================
|
||||
|
||||
Some specialized code would be unacceptably slow or needs to access hardware in
|
||||
a way that cannot be done from MicroPython. Therefore, it supports a way of
|
||||
extending the language with custom modules written in C. But before you consider
|
||||
writing a module in C, please take a look at :ref:`speed_python`.
|
||||
|
||||
`Unlike CPython <https://docs.python.org/3/extending/building.html>`_, these
|
||||
modules are (currently) embedded directly in the program image instead of being
|
||||
dynamically loaded. This requires a `custom build of MicroPython
|
||||
<https://github.com/micropython/micropython/wiki/Getting-Started>`_.
|
||||
|
||||
|
||||
Writing a module
|
||||
----------------
|
||||
|
||||
A module is a directory with the following files:
|
||||
|
||||
* ``micropython.mk``, which contains the Makefile fragment for this module.
|
||||
* All C files you would like included.
|
||||
|
||||
Put the required build commands in ``micropython.mk``. For a simple module, you
|
||||
will only have to add the file paths to ``SRC_MOD``, which will include these C
|
||||
files in the build:
|
||||
|
||||
.. highlight:: make
|
||||
.. code::
|
||||
|
||||
# Add all C files to SRC_MOD.
|
||||
SRC_MOD += $(USER_C_MODULES)/example/example.c
|
||||
|
||||
This is a very bare bones module named ``example`` that provides
|
||||
``example.double(x)``. Note that the name of the module must be equal to the
|
||||
directory name and is also used in the name of the ``mp_obj_module_t`` object at
|
||||
the bottom.
|
||||
|
||||
.. highlight:: c
|
||||
.. code::
|
||||
|
||||
// Include required definitions first.
|
||||
#include "py/obj.h"
|
||||
#include "py/runtime.h"
|
||||
|
||||
// This is the function you will call using example.double(n).
|
||||
STATIC mp_obj_t example_double(mp_obj_t x_obj) {
|
||||
// Check input value and convert it to a C type.
|
||||
if (!MP_OBJ_IS_SMALL_INT(x_obj)) {
|
||||
mp_raise_ValueError("x is not a small int");
|
||||
}
|
||||
int x = mp_obj_int_get_truncated(x_obj);
|
||||
|
||||
// Calculate the double, and convert back to MicroPython object.
|
||||
return mp_obj_new_int(x + x);
|
||||
}
|
||||
STATIC MP_DEFINE_CONST_FUN_OBJ_1(example_double_obj, example_double);
|
||||
|
||||
// Define all properties of the example module, which currently are the name (a
|
||||
// string) and a function.
|
||||
// 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_double), MP_ROM_PTR(&example_double_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,
|
||||
};
|
||||
|
||||
|
||||
Using a module
|
||||
--------------
|
||||
|
||||
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:
|
||||
|
||||
.. highlight:: shell
|
||||
.. code::
|
||||
|
||||
$ make USER_C_MODULES=path-to-modules-folder all
|
|
@ -26,3 +26,11 @@ implementation and the best practices to use them.
|
|||
constrained.rst
|
||||
packages.rst
|
||||
asm_thumb2_index.rst
|
||||
cmodules.rst
|
||||
|
||||
.. only:: port_pyboard
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
asm_thumb2_index.rst
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
.. _speed_python:
|
||||
|
||||
Maximising MicroPython Speed
|
||||
============================
|
||||
|
||||
|
|
|
@ -61,6 +61,7 @@ endif
|
|||
MAKE_FROZEN = $(PYTHON) $(TOP)/tools/make-frozen.py
|
||||
MPY_CROSS = $(TOP)/mpy-cross/mpy-cross
|
||||
MPY_TOOL = $(PYTHON) $(TOP)/tools/mpy-tool.py
|
||||
GEN_CMODULES = $(PYTHON) $(TOP)/tools/gen-cmodules.py
|
||||
|
||||
all:
|
||||
.PHONY: all
|
||||
|
|
|
@ -105,7 +105,7 @@ endif
|
|||
ifneq ($(FROZEN_MPY_DIR),)
|
||||
# to build the MicroPython cross compiler
|
||||
$(TOP)/mpy-cross/mpy-cross: $(TOP)/py/*.[ch] $(TOP)/mpy-cross/*.[ch] $(TOP)/ports/windows/fmode.c
|
||||
$(Q)$(MAKE) -C $(TOP)/mpy-cross
|
||||
$(Q)$(MAKE) -C $(TOP)/mpy-cross USER_C_MODULES=
|
||||
|
||||
# make a list of all the .py files that need compiling and freezing
|
||||
FROZEN_MPY_PY_FILES := $(shell find -L $(FROZEN_MPY_DIR) -type f -name '*.py' | $(SED) -e 's=^$(FROZEN_MPY_DIR)/==')
|
||||
|
@ -123,6 +123,13 @@ $(BUILD)/frozen_mpy.c: $(FROZEN_MPY_MPY_FILES) $(BUILD)/genhdr/qstrdefs.generate
|
|||
$(Q)$(MPY_TOOL) -f -q $(BUILD)/genhdr/qstrdefs.preprocessed.h $(FROZEN_MPY_MPY_FILES) > $@
|
||||
endif
|
||||
|
||||
# to build a list of modules for py/objmodule.c.
|
||||
ifneq ($(USER_C_MODULES),)
|
||||
$(BUILD)/genhdr/cmodules.h: | $(HEADER_BUILD)/mpversion.h
|
||||
@$(ECHO) "GEN $@"
|
||||
$(Q)$(GEN_CMODULES) $(USER_C_MODULES) > $@
|
||||
endif
|
||||
|
||||
ifneq ($(PROG),)
|
||||
# Build a standalone executable (unix does this)
|
||||
|
||||
|
|
|
@ -31,8 +31,6 @@
|
|||
#include "py/runtime.h"
|
||||
#include "py/builtin.h"
|
||||
|
||||
#include "genhdr/moduledefs.h"
|
||||
|
||||
STATIC void module_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) {
|
||||
(void)kind;
|
||||
mp_obj_module_t *self = MP_OBJ_TO_PTR(self_in);
|
||||
|
|
9
py/py.mk
9
py/py.mk
|
@ -129,6 +129,13 @@ $(BUILD)/$(BTREE_DIR)/%.o: CFLAGS += -Wno-old-style-definition -Wno-sign-compare
|
|||
$(BUILD)/extmod/modbtree.o: CFLAGS += $(BTREE_DEFS)
|
||||
endif
|
||||
|
||||
# External modules written in C.
|
||||
ifneq ($(USER_C_MODULES),)
|
||||
CFLAGS_MOD += -DMICROPY_CMODULES_INCLUDE_H='"genhdr/cmodules.h"'
|
||||
include $(USER_C_MODULES)/*/micropython.mk
|
||||
SRC_QSTR += $(BUILD)/genhdr/cmodules.h
|
||||
endif
|
||||
|
||||
# py object files
|
||||
PY_CORE_O_BASENAME = $(addprefix py/,\
|
||||
mpstate.o \
|
||||
|
@ -300,7 +307,7 @@ endif
|
|||
|
||||
# Sources that may contain qstrings
|
||||
SRC_QSTR_IGNORE = py/nlr%
|
||||
SRC_QSTR = $(SRC_MOD) $(filter-out $(SRC_QSTR_IGNORE),$(PY_CORE_O_BASENAME:.o=.c)) $(PY_EXTMOD_O_BASENAME:.o=.c)
|
||||
SRC_QSTR += $(SRC_MOD) $(filter-out $(SRC_QSTR_IGNORE),$(PY_CORE_O_BASENAME:.o=.c)) $(PY_EXTMOD_O_BASENAME:.o=.c)
|
||||
|
||||
# Anything that depends on FORCE will be considered out-of-date
|
||||
FORCE:
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
# Generate genhdr/cmodules.h for inclusion in py/objmodule.c.
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
import sys
|
||||
import os
|
||||
from glob import glob
|
||||
|
||||
def update_modules(path):
|
||||
modules = []
|
||||
for module in sorted(os.listdir(path)):
|
||||
if not os.path.isfile('%s/%s/micropython.mk' % (path, module)):
|
||||
continue # not a module
|
||||
modules.append(module)
|
||||
|
||||
# Print header file for all external modules.
|
||||
print('// Automatically generated by genmodules.py.\n')
|
||||
for module in modules:
|
||||
print('extern const struct _mp_obj_module_t %s_user_cmodule;' % module)
|
||||
print('\n#define MICROPY_EXTRA_BUILTIN_MODULES \\')
|
||||
for module in modules:
|
||||
print(' { MP_ROM_QSTR(MP_QSTR_%s), MP_ROM_PTR(&%s_user_cmodule) }, \\' % (module, module))
|
||||
print()
|
||||
|
||||
if __name__ == '__main__':
|
||||
update_modules(sys.argv[1])
|
Loading…
Reference in New Issue