Module constants¶
We have just seen, how we add a function to python. But functions are not the only objects that can be attached to a module, and of particular interest are constants. If for nothing else, you can give your module a version number. So, let us see, how that can be achieved.
Contstants, if they are true to their name, won’t change at run time, hence, they can be stored in ROM. We have already seen this, because the globals table of our very first module kicked out with the line
{ MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_simplefunction) }
Here, the MP_QSTR_simplefunction
was a constant, namely, the string
simplefunction
stored in ROM. This is why it is wrapped by the macro
MP_ROM_QSTR
. There are two other MP_ROM
macros defined in
obj.h
, namely, MP_ROM_INT
, and MP_ROM_PTR
.
Integer constants¶
It should not be a big surprise that the MP_ROM_INT
macro generates
ROM objects from integers. Thus, the following code will give you the
magic constant 42:
#define MAGIC_CONSTANT 42
...
{ MP_ROM_QSTR(MP_QSTR_magic), MP_ROM_INT(MAGIC_CONSTANT) },
...
Strings¶
Now, in MP_QSTR_simplefunction
, simplefunction
is a well-behaved
string, containing no special characters. But are we doomed, if we do
want to print out a version string, which would probably look like
1.2.3
, or something similar? And should we give up all hope, if our
string contains an underscore? The answer to these questions is no, and
no! This is, where the MP_ROM_PTR
macro comes to the rescue.
In general, MP_ROM_PTR
will take the address of an object, and
convert it to a 32-bit unsigned integer. At run time, micropython
works with this integer, if it has to access the constant. And this is
exactly what happens in the second line of the globals table of
simplefunction
:
{ MP_ROM_QSTR(MP_QSTR_add_ints), MP_ROM_PTR(&simplefunction_add_ints_obj) },
we associated the string add_ints
(incidentally, also stored in ROM)
with the 32-bit unsigned integer generated from the address of
simplefunction_add_ints_obj
. So, the bottom line is, if we can
somehow get hold of the address of an object, we can wrap it with
MP_ROM_PTR
, and we are done.
Thus, if we want to define a string constant, we have to convert it to
something that has an address. The MP_DEFINE_STR_OBJ
of objstr.h
does exactly that:
STATIC const MP_DEFINE_STR_OBJ(version_string_obj, "1.2.3");
takes 1.2.3
as a string, and turns is into a micropython object of
type mp_obj_str_t
. After this, &version_string_obj
can be passed
to the MP_ROM_PTR
macro.
Tuples¶
We don’t have to be satisfied with integers and strings, we can
definitely go further. There is a python type, the tuple
, that is,
by definition, constant (not mutable), and for this reason, we can
easily define a tuple type module constant. objtuple.h
defines
mp_rom_obj_tuple_t
for this purpose. This is a structure with three
members, and looks like this:
const mp_rom_obj_tuple_t version_tuple_obj = {
{&mp_type_tuple},
2,
{
MP_ROM_INT(1),
MP_ROM_PTR(&version_string_obj),
},
};
The first member defines the base type of the object, the second is the
number of elements of the tuple that we want to define, and the third is
itself a structure, listing the tuple elements. The key point here is
that we can apply the address-of operator to version_tuple_obj
, and
pass it to the MP_ROM_PTR
macro.
https://github.com/v923z/micropython-usermod/tree/master/snippets/constants/constants.c
#include "py/obj.h"
#include "py/runtime.h"
#include "py/objstr.h"
#include "py/objtuple.h"
#define MAGIC_CONSTANT 42
STATIC const MP_DEFINE_STR_OBJ(version_string_obj, "1.2.3");
const mp_rom_obj_tuple_t version_tuple_obj = {
{&mp_type_tuple},
2,
{
MP_ROM_INT(1),
MP_ROM_PTR(&version_string_obj),
},
};
STATIC const mp_rom_map_elem_t constants_module_globals_table[] = {
{ MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_constants) },
{ MP_ROM_QSTR(MP_QSTR___version__), MP_ROM_PTR(&version_string_obj) },
{ MP_ROM_QSTR(MP_QSTR_magic), MP_ROM_INT(MAGIC_CONSTANT) },
{ MP_ROM_QSTR(MP_QSTR_version_tuple), MP_ROM_PTR(&version_tuple_obj) },
};
STATIC MP_DEFINE_CONST_DICT(constants_module_globals, constants_module_globals_table);
const mp_obj_module_t constants_user_cmodule = {
.base = { &mp_type_module },
.globals = (mp_obj_dict_t*)&constants_module_globals,
};
MP_REGISTER_MODULE(MP_QSTR_constants, constants_user_cmodule, MODULE_CONSTANTS_ENABLED);
https://github.com/v923z/micropython-usermod/tree/master/snippets/constants/micropython.mk
USERMODULES_DIR := $(USERMOD_DIR)
# Add all C files to SRC_USERMOD.
SRC_USERMOD += $(USERMODULES_DIR)/constants.c
CFLAGS_USERMOD += -I$(USERMODULES_DIR)
!make clean
!make USER_C_MODULES=../../../usermod/snippets CFLAGS_EXTRA=-DMODULE_CONSTANTS_ENABLED=1 all
One comment before trying out what we have just implemented: the module is definitely pathological. If all you need is a set of constants organised in some way, then you should write it in python. There is nothing to be gained by working in C, while python is much more flexible.
%%micropython -unix 1
import constants
print(constants.magic)
print(constants.__version__)
print(constants.version_tuple)
42
1.2.3
(1, '1.2.3')