How to add a MaixPy module in C

Preliminary knowledge

Everything is an object in python

You need to know what module, type, function, and class are, what are the relationships and differences

  • module (module)

In MaixPy, the functions of each category are put into a module,
For example, the built-in uos, usys, machine,
In addition, our own new file, such as test.py, can also be a module,
We use modules like this:

import uos
import machine
import test

In C source code, it is mp_type_module

  • type (type)

Used to represent a basic type, it can contain some methods or variables

In C source code, it is mp_type_type

  • class (class)

A class is actually a type, for example

class A:pass
print(type(A))

Will output

<class'type'>

When A is instantiated

class A:pass
a = A()
print(type(a))

Will output

<class'A'>

Indicates that a is an instance (object) of A

Defining a class in C is actually defining a mp_type_type

Add module in C

Our goal is to realize that the following code can be used at the MaixPy level:

import my_lib
print(my_lib.__name__)
my_lib.hello()

Create a new folder in the components/port/src directory, for example, name it my_lib

Then create a new my_lib.c file under the my_lib folder

Edit my_lib.c to add code

Define a module:

#include "obj.h"

const mp_obj_module_t my_lib_module = {
    .base = {&mp_type_module },
    .globals = (mp_obj_dict_t*)&mp_module_my_lib_globals_dict,
};

Here my_lib_module is the defined my_lib module object,
mp_type_module indicates that it is a module,
mp_module_my_lib_globals_dict is the global variables and functions of the module. It is a dict object, which has our own definitions. It has not been defined yet

Define module global variables

STATIC mp_obj_t hello()
{
    mp_printf(&mp_plat_print, "hello from my_lib");
    return mp_const_none;
}

MP_DEFINE_CONST_FUN_OBJ_0(my_lib_func_hello_obj, my_lib_func_hello);

STATIC const mp_map_elem_t my_lib_globals_table[] = {
    {MP_OBJ_NEW_QSTR(MP_QSTR___name__), MP_OBJ_NEW_QSTR(MP_QSTR_my_lib) },
    {MP_OBJ_NEW_QSTR(MP_QSTR_hello), (mp_obj_t)&my_lib_func_hello_obj },

};

STATIC MP_DEFINE_CONST_DICT (
    mp_module_my_lib_globals_dict,
    my_lib_globals_table
);

Here defines a set of key-value pair arrays, key-value pair values, mp_map_elem_t is defined as follows:

typedef struct _mp_map_elem_t {
    mp_obj_t key;
    mp_obj_t value;
} mp_map_elem_t;
  • The first value is key, and the type is str object, which is called by my_lib.key at the level of MaixPy. Here, MP_OBJ_NEW_QSTR(MP_QSTR___name__) is used to generate a str object with a value of __name__. You may have questions about where the __name__ this c variable is defined. This is automatically generated by the tool during the compilation phase Variables, in short, remember this can be written to generate a constantstr` object and save it in the firmware.
  • The second value is a number, and the type is an object, which can be str/function/int/float/tuple/list/dict, etc., in the following way:
    • str: Here is also the definition of a str type value my_lib, that is, using my_lib.__name__ at the level of MaixPy to get the result my_lib.
    • Other constant objects: You can use mp_obj_new_xxx, such as int variable mp_obj_new_int(10), function search in obj.h
    • Function: The corresponding value of key``hello here is (mp_obj_t)&my_lib_func_hello_obj, which is a function object. Note that it is not a C function. As mentioned earlier, everything in python is an object. A function object is used, and then the address is cast to mp_obj_t. This function object uses the macro definition of MP_DEFINE_CONST_FUN_OBJ_0 to define the C function of my_lib_func_hello as the object of my_lib_func_hello_obj. Note that the hello function needs to return a value mp_const_none, and note that it cannot return NULL because NULL Not a (MaixPy) object, this return value is the return value when thehello()function is called at theMaixPy` level
    In addition to MP_DEFINE_CONST_FUN_OBJ_0, which has no parameters, there are also 1/2/3/n parameters, as well as keywords with keywords. Please refer to the source code to learn by analogy

Then use the MP_DEFINE_CONST_DICT macro definition to turn the key-value pair of my_lib_globals_table into a dict object that can be understood at the MaixPy level (mp_map_elem_t is only understandable at the C level) mp_module_my_lib_globals_dict, this object is also the previous step Used when defining modules in

At this point, the definition of a module is complete. At the level of MaixPy, in theory, the following statement can be used to use

import my_lib
print(my_lib.__name__)
my_lib.hello()

But we haven't compiled

Add the module to the firmware and compile it

  • Add at the end of the my_lib.c file:
MP_REGISTER_MODULE(MP_QSTR_my_lib, my_lib_module, MODULE_MY_LIB_ENABLED);

This line of code registers the module, but whether it is compiled into the firmware depends on whether the macro definition of MODULE_MY_LIB_ENABLED is defined as 1 in mpconfigport.h

  • So we open the mpconfigport.h file and add
#define MODULE_MY_LIB_ENABLED (1)
  • Open components/micropython/CMakeLists.txt to edit

Find the place where there is ############## Add source files ###############
Add after

append_srcs_dir(MPY_PORT_SRCS "port/src/my_lib")

At this point, the project will compile the folder my_lib to the firmware

Then python project.py rebuild can compile the firmware, because there are new files, you must use the rebuild command instead of build, pay attention to the compilation prompt, if there is an error, pay attention to modify

Add a type to the module

A my_lib module was previously defined, now we want to define a class in my_lib, called A, as follows

import my_lib

a = my_lib.A()
print(a.add(1, 2))

I only talk about the general idea here, and then provide a sample, you can understand it if you are smart

  • Define a mp_obj_type_t object, just like the previous definition of mp_obj_module_t
  • Similarly, give this class object a dict object, as a member of this class, the member can be a constant or function or even another type object
  • Register this class object to the previous my_lib module

The definition of mp_obj_type_t object and member definition can refer to the implementation in port/src/standard_lib/machine/machine_i2c.c

When defining mp_obj_type_t, there is a make_new member, this function is used to create a new object function will be called, such as a = my_lib.A(); a.add(1,2)
If you don't create a new object and call the class method or variable directly, this function will not be called A.var_a

For example, we define a const mp_obj_type_t my_lib_A_type ...

Then add this object to my_lib_globals_table in my_lib/my_lib.c and map it to key A

{MP_ROM_QSTR(MP_QSTR_A), MP_ROM_PTR(&my_lib_A_type) },

Note when writing firmware in C language

  • mp_printf vs printk vs printf:

Because IDE uses the serial communication protocol, do not directly use the printk or printf function to print messages at the C level, must use the mp_printf function to print, otherwise it will cause the IDE to run When receiving unintelligible data and disconnecting! !

Of course, you can use printk for debugging, because this function will not trigger a system interrupt and can be called in the interrupt function, but it is only used for debugging and must be deleted when the code is actually submitted! !