如何用 C 添加一个 MaixPy 模块
预备知识
在 python
中万物皆对象
需要先知道 module,type, function, class 分别是什么,有什么关系和区别
- module(模块)
在MaixPy
中,把每个类别的功能放到一个 模块 中,
比如内置的 uos
,usys
,machine
,
另外我们自己新建的文件, 比如test.py
也可以是一个模块,
我们使用模块都这样使用:
import uos
import machine
import test
在 C 源码中就是
mp_type_module
- type(类型)
用来表示一个基本的类型, 它可以包含一些方法或者变量
在 C 源码中就是
mp_type_type
- class(类)
一个 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
在 C 中添加模块
我们的目标是实现在MaixPy
层面可以使用以下代码:
import my_lib
print(my_lib.__name__)
my_lib.hello()
在components/port/src
目录下新建一个文件夹比如取名my_lib
然后在my_lib
文件夹下新建my_lib.c
文件
编辑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;
- 第一个值是
key
,类型是str
对象, 即在MaixPy
层面使用my_lib.key
来调用。这里用了MP_OBJ_NEW_QSTR(MP_QSTR___name__)
生成了一个值为__name__
的str
对象,你可能有疑问__name__
这个c
变量定义在哪里,这是在编译阶段使用工具自动生成c
变量,总之记住这样可以写可以生成一个常量str
对象保存在固件里就好了 - 第二个值是数值,类型是一个对象,可以是
str/function/int/float/tuple/list/dict
等, 方式如下:str
: 这里同样是定义了一个str
类型的值为my_lib
,即在MaixPy
层面使用my_lib.__name__
得到结果my_lib
。其它常量对象
: 可以使用mp_obj_new_xxx
,比如int
变量mp_obj_new_int(10)
, 函数在obj.h
中搜索函数
: 这里的key``hello
对应的值为为(mp_obj_t)&my_lib_func_hello_obj
,是一个函数对象,注意不是C
函数,前面说了python
中一切皆对象, 这里也是使用了一个函数对象,然后去地址强制转换成mp_obj_t
。这个函数对象使用了MP_DEFINE_CONST_FUN_OBJ_0
宏定义将my_lib_func_hello
这个C
函数定义为my_lib_func_hello_obj
这个对象,注意hello
函数需要返回一个值mp_const_none
,注意不能返回NULL
, 因为NULL
不是一个(MaixPy
)对象, 这个返回值也就是MaixPy
层面调用hello()
函数时的返回值
除了
MP_DEFINE_CONST_FUN_OBJ_0
即没有参数之外,还有1/2/3/n
个参数,以及带关键字参数,这些请翻阅源码举一反三学习
然后使用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()
但是我们还没编译
将模块添加到固件, 并进行编译
- 在
my_lib.c
文件末尾添加:
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
,注意编译提示,如果有报错,注意修改
在模块中添加一个 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) },
使用 C 语言编写固件时需要注意
mp_printf
vsprintk
vsprintf
:
因为IDE
使用了串口通信协议,所以在C
层面不要直接使用printk
或者printf
函数打印消息,必须使用mp_printf
函数来打印,不然会导致 IDE
运行时收到不理解的数据而断开连接!!
当然平时调试可以使用printk
,因为这个函数不会触发系统中断,可以在中断函数里面调用,但是仅限调试时使用, 实际提交代码时一定要删除掉!!