summaryrefslogtreecommitdiff
path: root/examples
diff options
context:
space:
mode:
authorJim Mussared <jim.mussared@gmail.com>2023-05-29 16:56:38 +1000
committerDamien George <damien@micropython.org>2023-09-02 00:16:16 +1000
commit607548f32d98d955775bc02d4fc2b33a79e860ee (patch)
tree4475bda4610f80b129014c01b2617b9223b2b278 /examples
parentf52a2cd55afb3f487801aaa62e15328134eb73a4 (diff)
examples/natmod: Add features4 as a class definition example.
Also provide a basic README.md for dynamic native modules. This work was funded through GitHub Sponsors. Signed-off-by: Jim Mussared <jim.mussared@gmail.com>
Diffstat (limited to 'examples')
-rw-r--r--examples/natmod/README.md74
-rw-r--r--examples/natmod/features4/Makefile14
-rw-r--r--examples/natmod/features4/features4.c73
3 files changed, 161 insertions, 0 deletions
diff --git a/examples/natmod/README.md b/examples/natmod/README.md
new file mode 100644
index 000000000..0cc4010ef
--- /dev/null
+++ b/examples/natmod/README.md
@@ -0,0 +1,74 @@
+# Dynamic Native Modules
+
+Dynamic Native Modules are .mpy files that contain native machine code from a
+language other than Python. For more info see [the documentation]
+(https://docs.micropython.org/en/latest/develop/natmod.html).
+
+This should not be confused with [User C Modules]
+(https://docs.micropython.org/en/latest/develop/cmodules.html) which are a
+mechanism to add additional out-of-tree modules into the firmware build.
+
+## Examples
+
+This directory contains several examples of writing dynamic native modules, in
+two main categories:
+
+1. Feature examples.
+
+ * `features0` - A module containing a single "factorial" function which
+ demonstrates working with integers.
+
+ * `features1` - A module that demonstrates some common tasks:
+ - defining simple functions exposed to Python
+ - defining local, helper C functions
+ - defining constant integers and strings exposed to Python
+ - getting and creating integer objects
+ - creating Python lists
+ - raising exceptions
+ - allocating memory
+ - BSS and constant data (rodata)
+ - relocated pointers in rodata
+
+ * `features2` - This is a hybrid module containing both Python and C code,
+ and additionally the C code is spread over multiple files. It also
+ demonstrates using floating point (only when the target supports
+ hardware floating point).
+
+ * `features3` - A module that shows how to use types, constant objects,
+ and creating dictionary instances.
+
+ * `features4` - A module that demonstrates how to define a class.
+
+2. Dynamic version of existing built-ins.
+
+ This provides a way to add missing functionality to firmware that doesn't
+ include certain built-in modules. See the `heapq`, `random`, `re`,
+ `deflate`, `btree`, and `framebuf` directories.
+
+ So for example, if your firmware was compiled with `MICROPY_PY_FRAMEBUF`
+ disabled (e.g. to save flash space), then it would not include the
+ `framebuf` module. The `framebuf` native module provides a way to add the
+ `framebuf` module dynamically.
+
+ The way these work is they define a dynamic native module which
+ `#include`'s the original module and then does the necessary
+ initialisation of the module's globals dict.
+
+## Build instructions
+
+To compile an example, you need to have the same toolchain available as
+required for your target port. e.g. `arm-none-eabi-gcc` for any ARM Cortex M
+target. See the port instructions for details.
+
+You also need to have the `pyelftools` Python package available, either via
+your system package manager or installed from PyPI in a virtual environment
+with `pip`.
+
+Each example provides a Makefile. You should specify the `ARCH` argument to
+make (one of x86, x64, armv6m, armv7m, xtensa, xtensawin):
+
+```
+$ cd features0
+$ make ARCH=armv7m
+$ mpremote cp features0.mpy :
+```
diff --git a/examples/natmod/features4/Makefile b/examples/natmod/features4/Makefile
new file mode 100644
index 000000000..f76a31a7c
--- /dev/null
+++ b/examples/natmod/features4/Makefile
@@ -0,0 +1,14 @@
+# Location of top-level MicroPython directory
+MPY_DIR = ../../..
+
+# Name of module
+MOD = features4
+
+# Source files (.c or .py)
+SRC = features4.c
+
+# Architecture to build for (x86, x64, armv7m, xtensa, xtensawin)
+ARCH = x64
+
+# Include to get the rules for compiling and linking the module
+include $(MPY_DIR)/py/dynruntime.mk
diff --git a/examples/natmod/features4/features4.c b/examples/natmod/features4/features4.c
new file mode 100644
index 000000000..336f4ecf6
--- /dev/null
+++ b/examples/natmod/features4/features4.c
@@ -0,0 +1,73 @@
+/*
+ This example extends on features0 but demonstrates how to define a class.
+
+ The Factorial class constructor takes an integer, and then the calculate
+ method can be called to get the factorial.
+
+ >>> import features4
+ >>> f = features4.Factorial(4)
+ >>> f.calculate()
+ 24
+*/
+
+// Include the header file to get access to the MicroPython API
+#include "py/dynruntime.h"
+
+// This is type(Factorial)
+mp_obj_full_type_t mp_type_factorial;
+
+// This is the internal state of a Factorial instance.
+typedef struct {
+ mp_obj_base_t base;
+ mp_int_t n;
+} mp_obj_factorial_t;
+
+// Essentially Factorial.__new__ (but also kind of __init__).
+// Takes a single argument (the number to find the factorial of)
+STATIC mp_obj_t factorial_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args_in) {
+ mp_arg_check_num(n_args, n_kw, 1, 1, false);
+
+ mp_obj_factorial_t *o = mp_obj_malloc(mp_obj_factorial_t, type);
+ o->n = mp_obj_get_int(args_in[0]);
+
+ return MP_OBJ_FROM_PTR(o);
+}
+
+STATIC mp_int_t factorial_helper(mp_int_t x) {
+ if (x == 0) {
+ return 1;
+ }
+ return x * factorial_helper(x - 1);
+}
+
+// Implements Factorial.calculate()
+STATIC mp_obj_t factorial_calculate(mp_obj_t self_in) {
+ mp_obj_factorial_t *self = MP_OBJ_TO_PTR(self_in);
+ return mp_obj_new_int(factorial_helper(self->n));
+}
+STATIC MP_DEFINE_CONST_FUN_OBJ_1(factorial_calculate_obj, factorial_calculate);
+
+// Locals dict for the Factorial type (will have a single method, calculate,
+// added in mpy_init).
+mp_map_elem_t factorial_locals_dict_table[1];
+STATIC MP_DEFINE_CONST_DICT(factorial_locals_dict, factorial_locals_dict_table);
+
+// This is the entry point and is called when the module is imported
+mp_obj_t mpy_init(mp_obj_fun_bc_t *self, size_t n_args, size_t n_kw, mp_obj_t *args) {
+ // This must be first, it sets up the globals dict and other things
+ MP_DYNRUNTIME_INIT_ENTRY
+
+ // Initialise the type.
+ mp_type_factorial.base.type = (void*)&mp_type_type;
+ mp_type_factorial.flags = MP_TYPE_FLAG_NONE;
+ mp_type_factorial.name = MP_QSTR_Factorial;
+ MP_OBJ_TYPE_SET_SLOT(&mp_type_factorial, make_new, factorial_make_new, 0);
+ factorial_locals_dict_table[0] = (mp_map_elem_t){ MP_OBJ_NEW_QSTR(MP_QSTR_calculate), MP_OBJ_FROM_PTR(&factorial_calculate_obj) };
+ MP_OBJ_TYPE_SET_SLOT(&mp_type_factorial, locals_dict, (void*)&factorial_locals_dict, 1);
+
+ // Make the Factorial type available on the module.
+ mp_store_global(MP_QSTR_Factorial, MP_OBJ_FROM_PTR(&mp_type_factorial));
+
+ // This must be last, it restores the globals dict
+ MP_DYNRUNTIME_INIT_EXIT
+}