如何用 C 添加一个 MaixPy 模块
1. 预备知识
在 python
中万物皆对象
需要先知道 module,type, function, class 分别是什么,有什么关系和区别
在MaixPy
中,把每个类别的功能放到一个 模块 中, 比如内置的 uos
,usys
,machine
, 另外我们自己新建的文件, 比如test.py
也可以是一个模块, 我们使用模块都这样使用:
import uos
import machine
import test
在 C 源码中就是 mp_type_module
用来表示一个基本的类型, 它可以包含一些方法或者变量
在 C 源码中就是 mp_type_type
一个 class 其实就是一个 type
,比如
class A:pass
print(type(A))
会输出
<class 'type'>
当对A
进行了实例化
class A:pass
a = A()
print(type(a))
会输出
<class 'A'>
表示a
是A
的一个实例(对象)
在 C 中定义一个类其实就是定义一个 mp_type_type
2. 在 C 中添加模块
我们的目标是实现在MaixPy
层面可以使用以下代码:
import my_lib
print(my_lib.__name__)
my_lib.hello()
2.1. 在components/port/src目录下新建一个文件夹比如取名my_lib
2.2. 然后在my_lib文件夹下新建my_lib.c文件
2.3. 编辑my_lib.c添加代码
定义一个模块:
#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,
};
这里my_lib_module
是定义的my_lib
模块对象, mp_type_module
表明是一个模块, mp_module_my_lib_globals_dict
是模块的全局变量和函数,是一个dict
对象,有我们自己定义, 现在还没定义
定义模块的全局变量
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
);
这里定义了一组键值对数组,键值对数值, mp_map_elem_t
的定义如下:
typedef struct _mp_map_elem_t {
mp_obj_t key;
mp_obj_t value;
} mp_map_elem_t;
然后使用MP_DEFINE_CONST_DICT
宏定义将my_lib_globals_table
这个键值对变成MaixPy
层面能理解的dict
对象(mp_map_elem_t
只是C
层面能理解)mp_module_my_lib_globals_dict
, 这个对象也被上一步中定义模块的时候使用
到此一个模块就定义完成了, 在 MaixPy
层面,理论上可以使用如下语句进行使用了
import my_lib
print(my_lib.__name__)
my_lib.hello()
但是我们还没编译
将模块添加到固件, 并进行编译
MP_REGISTER_MODULE(MP_QSTR_my_lib, my_lib_module, MODULE_MY_LIB_ENABLED);
这行代码注册这个模块,但是是否编译进固件取决与MODULE_MY_LIB_ENABLED
这个宏定义在mpconfigport.h
中是否定义为1
- 所以我们打开
mpconfigport.h
文件,在里面添加
#define MODULE_MY_LIB_ENABLED (1)
- 打开
components/micropython/CMakeLists.txt
编辑
找到文件中有############## Add source files ###############
的地方, 在后面添加
append_srcs_dir(MPY_PORT_SRCS "port/src/my_lib")
到此,项目才会将my_lib
这个文件夹编译到固件
然后python project.py rebuild
编译固件即可,因为新增了文件,一定要用rebuild
命令而不是build
,注意编译提示,如果有报错,注意修改
3. 在模块中添加一个 type
前面定义了一个my_lib
模块,现在我们希望在my_lib
中定义一个类,叫A
,如下
import my_lib
a = my_lib.A()
print(a.add(1, 2))
这里只讲大致上的思路,然后提供样例,聪明的你一下就能理解了
- 定义一个
mp_obj_type_t
对象,正如前面定义mp_obj_module_t
一样
- 同样的,给这个类对象一个
dict
对象,作为这个类的成员,成员可以是常量或者函数甚至是另一个type
对象
- 将这个类对象注册到前面的
my_lib
模块
定义mp_obj_type_t
对象和成员定义可以参考port/src/standard_lib/machine/machine_i2c.c
中的实现
定义mp_obj_type_t
时有一个make_new
成员,这个函数是用来新建对象时会被调用的函数,比如a = my_lib.A(); a.add(1,2)
如果不新建对象,直接调用类方法或变量,这个函数不会被调用A.var_a
比如我们定义了一个const mp_obj_type_t my_lib_A_type ...
然后在my_lib/my_lib.c
中 my_lib_globals_table
中添加这个对象,并将其映射到key
A
即可
{ MP_ROM_QSTR(MP_QSTR_A), MP_ROM_PTR(&my_lib_A_type) },
4. 使用 C 语言编写固件时需要注意
mp_printf
vs printk
vs printf
: 因为IDE
使用了串口通信协议,所以在C
层面不要直接使用printk
或者printf
函数打印消息,必须使用mp_printf
函数来打印,不然会导致 IDE
运行时收到不理解的数据而断开连接!!
当然平时调试可以使用printk
,因为这个函数不会触发系统中断,可以在中断函数里面调用,但是仅限调试时使用, 实际提交代码时一定要删除掉!!