龙空技术网

MicroPython 中用C语言扩展模块教程(二)

橙子物联网 100

前言:

今天小伙伴们对“stm32移植python”大概比较关切,咱们都想要学习一些“stm32移植python”的相关资讯。那么小编同时在网络上汇集了一些关于“stm32移植python””的相关资讯,希望你们能喜欢,你们快快来了解一下吧!

上接MicroPython 中用C语言扩展模块教程(一)

已经明白解释器是如何处理python对象之后,便可以开始添加自定义模块了。我们首先以向MicroPython添加一个较为简单的模块为开始,该模块只有一个函数,其将两个整数进行加法运算。

首先,全部代码列举如下(大概有20行左右),然后我们再一起进行讨论其中细节:

/simplefunction/simplefunction.c

#include "py/obj.h"#include "py/runtime.h"STATIC mp_obj_t simplefunction_add_ints(mp_obj_t a_obj, mp_obj_t b_obj) {    int a = mp_obj_get_int(a_obj);    int b = mp_obj_get_int(b_obj);    return mp_obj_new_int(a + b);}STATIC MP_DEFINE_CONST_FUN_OBJ_2(simplefunction_add_ints_obj, simplefunction_add_ints);STATIC const mp_rom_map_elem_t simplefunction_module_globals_table[] = {    { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_simplefunction) },    { MP_ROM_QSTR(MP_QSTR_add_ints), MP_ROM_PTR(&simplefunction_add_ints_obj) },};STATIC MP_DEFINE_CONST_DICT(simplefunction_module_globals, simplefunction_module_globals_table);const mp_obj_module_t simplefunction_user_cmodule = {    .base = { &mp_type_module },    .globals = (mp_obj_dict_t*)&simplefunction_module_globals,};MP_REGISTER_MODULE(MP_QSTR_simplefunction, simplefunction_user_cmodule, MODULE_SIMPLEFUNCTION_ENABLED);

4.1 头文件

MicroPython模块通常都需要包含如下两个头文件:1> py/obj.h——所有相关常量和宏操作均在该文件中进行定义;2> py/runtime.h——该文件包含了python解释器的声明。大部分时候,还需要包含py/builtin.h文件,其中声明了许多python内置函数和模块。

4.2 自定义用户函数

在包含了必要的头文件之后,我们定义了所要进行主要操作的函数。通过传递mp_obj_t类型的变量,可以确保该函数能够接受从python控制台传递过来的数据值。如果你已经在模块中定义了一些内部使用的辅助函数,那么也可以传递任何其它类型的变量。同样地,通过返回mp_obj_t类型对象,使得函数结果可以被解释器接受而使用,比如,可以将其返回值赋给其它某变量。

直接传递mp_obj_t类型变量的一个弊端是你无法直接将其赋值给常规C语言本地变量。比如,当需要对其进行某种运算操作时,你需要先将其数据值解析出来,这也是为什么我们不得不调用mp_obj_get_int()函数的原因。相对应地,在返回最终结果之前,我们也不得不通过调用mp_obj_new_int()对mp_obj_t变量进行类型转换。这就是我们上面讨论的解码/编码步骤。

4.2.1 用户函数交付

某种意义上讲,我们已经具有了可以工作的函数(其还没有任何错误检查,所以,比如当你向其传递浮点数时,具体后果不可预料),但其还不能与python解释器相配合使用。因此,我们还必须将其转换为函数对象。可以通过如下操作进行:

STATIC MP_DEFINE_CONST_FUN_OBJ_2(simplefunction_add_ints_obj, simplefunction_add_ints);

该宏操作的第一个参数是第二个参数所指定实际函数要绑定的函数对象名。py/obj.h中大概定义了七种类似MP_DEFINE_CONST_FUN_OBJ_*形式的宏操作,依据函数所支持的参数类型和数量均有所不同。在此示例中,我们的函数预期使用两个参数,因此该宏操作的后缀为2。带有0-4个参数的函数均能够以类似这种方式进行绑定使用。

但是,如果我们的函数需要多于4个参数呢?这也是python中较为常见的一种形式。此时,可以使用如下形式宏操作:

STATIC MP_DEFINE_CONST_FUN_OBJ_VAR(obj_name, n_args_min, fun_name);

其中第二个参数为整数类型,代表参数的最少个数。参数数量还可以通过如下形式进行绑定:

STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(obj_name, n_args_min, n_args_max, fun_name);

稍后我们还会看到如何对支持关键字参数的函数进行定义。

至此,我们已经或多或少地完成了函数的C语言代码实现,但是还没有将其注册给python解释器。这可以通过添加一个列表,即在模块中定义一个全局键值对数组,并且使用MP_DEFINE_CONST_DICT宏操作将该列表绑定到对应_module_globals变量来进行。该列表至少应包含一条数据:模块的名字,其需要存储到字符串MP_QSTR___name__中。

这些MP_QSTR_开头的条目,是其后缀所对应的python字符串在C语言中的展现形式。所以,C语言中的MP_QSTR_foo_bar对应python中的foo_bar。foo_bar可能为常量,函数或者类,类型等等,具体依赖于所关联对象,foo_bar在控制台中被引用时,便会产生不同的效果。这里的关键点是:如果你希望foo_bar在python中有实际意义,则应该在C语言代码中的某个地方首先定义MP_QSTR_foo_bar。

该列表的第二个键值对是我们刚刚实现的函数的指针和其在python中所对应的名字。所以,在此示例中,当我们在控制台中调用add_ints时,所对应的simplefunction_add_ints函数便会被实际调用到。

STATIC const mp_rom_map_elem_t simplefunction_module_globals_table[] = {    { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_simplefunction) },    { MP_ROM_QSTR(MP_QSTR_add_ints), MP_ROM_PTR(&simplefunction_add_ints_obj) },};STATIC MP_DEFINE_CONST_DICT(simplefunction_module_globals, simplefunction_module_globals_table);

这三步对于所有的函数实现过程都是通用的,总结如下:

1> 实现函数逻辑;

2> 将其转换为函数对象(比如,调用相关形式的MP_DEFINE_CONST_FUN_OBJ_*宏);

3> 在模块的命名空间中注册该函数(比如,将其添加到模块的全局列表中,再将该列表通过MP_DEFINE_CONST_DICT转换为字典)。

无论函数需要位置参数还是关键字参数或者两者都需要支持,它们都需要这些步骤。

定义好函数对象后,我们最终使用如下方式注册模块:

MP_REGISTER_MODULE(MP_QSTR_simplefunction, simplefunction_user_cmodule, MODULE_SIMPLEFUNCTION_ENABLED);

最后这一行很重要,通过在mpconfigport.h中定义MODULE_SIMPLEFUNCTION_ENABLED为对应值,你可以选择性地将该模块链接到MicroPython系统固件中(mpconfigport.h在你所要编译的ports目录中所对应移植版本的根目录下),比如:

#define MODULE_SIMPLEFUNCTION_ENABLED (1)

可以将simplefunction包含进系统固件,而

#define MODULE_SIMPLEFUNCTION_ENABLED (0)

则会使编译系统在链接阶段丢弃该模块(其仍然会被编译,但不会被链接)。

4.3 编译模块

代码实现完毕后,我们当然希望看看结果如何。首先,需要创建一个makefile文件,将其放入自定义模块目录下,比如simplefunction。

/simplefunction/micropython.mk

USERMODULES_DIR := $(USERMOD_DIR)# Add all C files to SRC_USERMOD.SRC_USERMOD += $(USERMODULES_DIR)/simplefunction.cCFLAGS_USERMOD += -I$(USERMODULES_DIR)

如果mpconfigport.h中有如下参数定义

#define MODULE_SIMPLEFUNCTION_ENABLED (1)

你可以使用如下命令编译上述模块:

!make clean!make USER_C_MODULES=../../../usermod/snippets all

正如上面提到过的,如果你不想对MicroPython仓库源代码做任何修改,可以通过如下形式将宏定义传递给make命令:

!make clean!make USER_C_MODULES=../../../usermod/snippets CFLAGS_EXTRA=-DMODULE_SIMPLEFUNCTION_ENABLED=1 all

注意,在编译之前可以先运行make clean命令。当你开发自定义模块时,这是一个好习惯。

我们能够采用如下方法测试该模块:

%%micropythonimport simplefunctionprint(simplefunction.add_ints(123, 456))
579

是不是很神奇,它已经能够正常工作了!

4.4 编译单片机版本

正如开始时所指出的,该第一个模块是为unix移植版本编译的,这也是为什么我们将../../micropython/ports/unix/设为当前工作目录。有时候,我们希望将其编译成单片机上可运行的版本,那么,就需要修改对应ports目录中目标移植版本根目录下的moconfigport.h文件(比如micropython/ports/stm32/)。

然后,在执行编译命令时,还必须设置目标开发板,比如1.1版本的pyboard,如果交叉编译工具链没有添加到全局环境变量中,还可能需要设置交叉编译工具链的路径。你可以在对应移植版本目录下调用make命令,比如micropython/ports/stm32/目录,同时要么以绝对路径,要么以相对于micropython/ports/stm32/的相对路径指定到CROSS_COMPIL编译选项中:

make BOARD=PYBV11 CROSS_COMPILE=/bin/arm-none-eabi

编译成功后的程序固件在micropython/ports/stm32/build-PYBV11/firmware.dfu位置,之后你可以调用如下命令将其下载安装到设备中:

!python ../../tools/pydfu.py -u build-PYBV11/firmware.dfu

关于该操作的细节,可以参考MicroPython官方文档中的教程。

标签: #stm32移植python