forked from micropython/micropython
-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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>
- Loading branch information
Showing
4 changed files
with
162 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 : | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters