From a67989aa201817efaaefebdc7fd491358da45df8 Mon Sep 17 00:00:00 2001 From: Laurens Valk Date: Mon, 21 Nov 2022 14:10:02 +0100 Subject: [PATCH] examples/usercmodule: Add example of a native C class. This shows how ports can add their own custom types/classes. It is part of the unix coverage build, so we can use it for tests too. Signed-off-by: Laurens Valk --- docs/develop/cmodules.rst | 19 ++++++- examples/usercmodule/cexample/examplemodule.c | 57 +++++++++++++++++++ tests/misc/cexample_class.py | 20 +++++++ tests/misc/cexample_class.py.exp | 2 + 4 files changed, 95 insertions(+), 3 deletions(-) create mode 100644 tests/misc/cexample_class.py create mode 100644 tests/misc/cexample_class.py.exp diff --git a/docs/develop/cmodules.rst b/docs/develop/cmodules.rst index 1b3ba04da4..6bc2f62ab4 100644 --- a/docs/develop/cmodules.rst +++ b/docs/develop/cmodules.rst @@ -95,9 +95,12 @@ A MicroPython user C module is a directory with the following files: Basic example ------------- -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 +The ``cexample`` module provides examples for a function and a class. The +``cexample.add_ints(a, b)`` function adds two integer args together and returns +the result. The ``cexample.Timer()`` type creates timers that can be used to +measure the elapsed time since the object is instantiated. + +The module can be found in the MicroPython source tree `in the examples directory `_ and has a source file and a Makefile fragment with content as described above:: @@ -272,3 +275,13 @@ can now be accessed in Python just like any other builtin module, e.g. import cexample print(cexample.add_ints(1, 3)) # should display 4 + +.. code-block:: python + + from cexample import Timer + from time import sleep_ms + + watch = Timer() + sleep_ms(1000) + print(watch.time()) + # should display approximately 1000 diff --git a/examples/usercmodule/cexample/examplemodule.c b/examples/usercmodule/cexample/examplemodule.c index 93a58be2ed..b2457b2801 100644 --- a/examples/usercmodule/cexample/examplemodule.c +++ b/examples/usercmodule/cexample/examplemodule.c @@ -1,6 +1,9 @@ // Include MicroPython API. #include "py/runtime.h" +// Used to get the time in the Timer class example. +#include "py/mphal.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. @@ -13,6 +16,59 @@ STATIC mp_obj_t example_add_ints(mp_obj_t a_obj, mp_obj_t b_obj) { // Define a Python reference to the function above. STATIC MP_DEFINE_CONST_FUN_OBJ_2(example_add_ints_obj, example_add_ints); +// This structure represents Timer instance objects. +typedef struct _example_Timer_obj_t { + // All objects start with the base. + mp_obj_base_t base; + // Everything below can be thought of as instance attributes, but they + // cannot be accessed by MicroPython code directly. In this example we + // store the time at which the object was created. + mp_uint_t start_time; +} example_Timer_obj_t; + +// This is the Timer.time() method. After creating a Timer object, this +// can be called to get the time elapsed since creating the Timer. +STATIC mp_obj_t example_Timer_time(mp_obj_t self_in) { + // The first argument is self. It is cast to the *example_Timer_obj_t + // type so we can read its attributes. + example_Timer_obj_t *self = MP_OBJ_TO_PTR(self_in); + + // Get the elapsed time and return it as a MicroPython integer. + mp_uint_t elapsed = mp_hal_ticks_ms() - self->start_time; + return mp_obj_new_int_from_uint(elapsed); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(example_Timer_time_obj, example_Timer_time); + +// This represents Timer.__new__ and Timer.__init__, which is called when +// the user instantiates a Timer object. +STATIC mp_obj_t example_Timer_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) { + // Allocates the new object and sets the type. + example_Timer_obj_t *self = m_new_obj(example_Timer_obj_t); + self->base.type = (mp_obj_type_t *)type; + + // Initializes the time for this Timer instance. + self->start_time = mp_hal_ticks_ms(); + + // The make_new function always returns self. + return MP_OBJ_FROM_PTR(self); +} + +// This collects all methods and other static class attributes of the Timer. +// The table structure is similar to the module table, as detailed below. +STATIC const mp_rom_map_elem_t example_Timer_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR_time), MP_ROM_PTR(&example_Timer_time_obj) }, +}; +STATIC MP_DEFINE_CONST_DICT(example_Timer_locals_dict, example_Timer_locals_dict_table); + +// This defines the type(Timer) object. +MP_DEFINE_CONST_OBJ_TYPE( + example_type_Timer, + MP_QSTR_Timer, + MP_TYPE_FLAG_NONE, + make_new, example_Timer_make_new, + locals_dict, &example_Timer_locals_dict + ); + // Define all properties of the module. // Table entries are key/value pairs of the attribute name (a string) // and the MicroPython object reference. @@ -21,6 +77,7 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_2(example_add_ints_obj, example_add_ints); 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) }, + { MP_ROM_QSTR(MP_QSTR_Timer), MP_ROM_PTR(&example_type_Timer) }, }; STATIC MP_DEFINE_CONST_DICT(example_module_globals, example_module_globals_table); diff --git a/tests/misc/cexample_class.py b/tests/misc/cexample_class.py new file mode 100644 index 0000000000..bdeb9a8cb2 --- /dev/null +++ b/tests/misc/cexample_class.py @@ -0,0 +1,20 @@ +# test custom native class + +try: + import cexample + import time +except ImportError: + print("SKIP") + raise SystemExit + +t = cexample.Timer() + +print(t) +print(t.time() <= 1) + +time.sleep_ms(100) + +elapsed = t.time() + +if not (99 <= elapsed <= 110): + print("Elapsed time should be approx. 100ms but it is", elapsed) diff --git a/tests/misc/cexample_class.py.exp b/tests/misc/cexample_class.py.exp new file mode 100644 index 0000000000..67d98761b0 --- /dev/null +++ b/tests/misc/cexample_class.py.exp @@ -0,0 +1,2 @@ + +True