diff --git a/docs/develop/cmodules.rst b/docs/develop/cmodules.rst new file mode 100644 index 0000000000000..961ec1b86ea90 --- /dev/null +++ b/docs/develop/cmodules.rst @@ -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 `_, these +modules are (currently) embedded directly in the program image instead of being +dynamically loaded. This requires a `custom build of MicroPython +`_. + + +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_USERMOD``, which will include +these C files in the build: + +.. highlight:: make +.. code:: + + # Add all C files to SRC_USERMOD. + SRC_USERMOD += 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 +`_) 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 diff --git a/docs/develop/index.rst b/docs/develop/index.rst new file mode 100644 index 0000000000000..dcfef29d2927a --- /dev/null +++ b/docs/develop/index.rst @@ -0,0 +1,12 @@ +Developing and building MicroPython +=================================== + +This chapter contains some documentation for how to extend MicroPython. Note +that it doesn't aim to be a complete guide for developing with MicroPython, see +`this getting started guide +`_ for that. + +.. toctree:: + :maxdepth: 1 + + cmodules.rst diff --git a/docs/esp8266_index.rst b/docs/esp8266_index.rst index 519acecda5407..499cee93a0ba3 100644 --- a/docs/esp8266_index.rst +++ b/docs/esp8266_index.rst @@ -9,4 +9,5 @@ MicroPython documentation and references library/index.rst reference/index.rst genrst/index.rst + develop/index.rst license.rst diff --git a/docs/pyboard_index.rst b/docs/pyboard_index.rst index 2255a75607a14..a91e72a96c95a 100644 --- a/docs/pyboard_index.rst +++ b/docs/pyboard_index.rst @@ -9,4 +9,5 @@ MicroPython documentation and references library/index.rst reference/index.rst genrst/index.rst + develop/index.rst license.rst diff --git a/docs/reference/speed_python.rst b/docs/reference/speed_python.rst index 4db60ec14d68f..c5aa80c6e1841 100644 --- a/docs/reference/speed_python.rst +++ b/docs/reference/speed_python.rst @@ -1,3 +1,5 @@ +.. _speed_python: + Maximising MicroPython Speed ============================ diff --git a/docs/unix_index.rst b/docs/unix_index.rst index 1bfeb0bdac390..d9df32acefa1a 100644 --- a/docs/unix_index.rst +++ b/docs/unix_index.rst @@ -6,4 +6,5 @@ MicroPython documentation and references library/index.rst reference/index.rst genrst/index.rst + develop/index.rst license.rst diff --git a/docs/wipy_index.rst b/docs/wipy_index.rst index 15c04c0fba8ec..83e0428818937 100644 --- a/docs/wipy_index.rst +++ b/docs/wipy_index.rst @@ -9,4 +9,5 @@ MicroPython documentation and references library/index.rst reference/index.rst genrst/index.rst + develop/index.rst license.rst diff --git a/py/mkenv.mk b/py/mkenv.mk index 2c9c86a7ae291..49697d4068df2 100644 --- a/py/mkenv.mk +++ b/py/mkenv.mk @@ -41,6 +41,7 @@ RM = rm ECHO = @echo CP = cp MKDIR = mkdir +LN = ln SED = sed PYTHON = python @@ -61,6 +62,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 diff --git a/py/mkrules.mk b/py/mkrules.mk index 30ac520aa1cd3..0a5b0085690dd 100644 --- a/py/mkrules.mk +++ b/py/mkrules.mk @@ -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,15 @@ $(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. +$(BUILD)/genhdr/cmodules.h: | $(HEADER_BUILD)/mpversion.h + @$(ECHO) "GEN $@" + $(Q)$(GEN_CMODULES) $(USER_C_MODULES) > $@ + +# make sure C modules are put in the build directory +$(BUILD)/cmodules: + @$(LN) -sf "$(realpath $(USER_C_MODULES))" $(BUILD)/cmodules + ifneq ($(PROG),) # Build a standalone executable (unix does this) diff --git a/py/objmodule.c b/py/objmodule.c index d2a67ffb83130..e3f0971c32491 100644 --- a/py/objmodule.c +++ b/py/objmodule.c @@ -27,6 +27,10 @@ #include #include +#ifdef MICROPY_CMODULES_INCLUDE_H +#include MICROPY_CMODULES_INCLUDE_H +#endif + #include "py/objmodule.h" #include "py/runtime.h" #include "py/builtin.h" @@ -220,6 +224,11 @@ STATIC const mp_rom_map_elem_t mp_builtin_module_table[] = { // extra builtin modules as defined by a port MICROPY_PORT_BUILTIN_MODULES + +#ifdef MICROPY_EXTRA_BUILTIN_MODULES + // extra builtin modules from USER_C_MODULES + MICROPY_EXTRA_BUILTIN_MODULES +#endif }; MP_DEFINE_CONST_MAP(mp_builtin_module_map, mp_builtin_module_table); diff --git a/py/py.mk b/py/py.mk index 19ce55a473130..3b2bc587a5bcc 100644 --- a/py/py.mk +++ b/py/py.mk @@ -103,6 +103,15 @@ $(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_MOD += $(addprefix $(BUILD)/cmodules/, $(SRC_USERMOD)) +$(SRC_MOD) : | $(BUILD)/cmodules +SRC_QSTR += $(BUILD)/genhdr/cmodules.h +endif + # py object files PY_CORE_O_BASENAME = $(addprefix py/,\ mpstate.o \ @@ -274,7 +283,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: diff --git a/tools/gen-cmodules.py b/tools/gen-cmodules.py new file mode 100755 index 0000000000000..524e3c03d3335 --- /dev/null +++ b/tools/gen-cmodules.py @@ -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])