阅读micropyton源码-添加C扩展类模块(4)
苏勇,2021年8月
make_new()函数实现的相当于初始化一个类实例的操作。初始化之后,用户就可以使用类实例调用函数执行功能。此处再看一遍实现功能函数的写法。
没有输入参数的pin.on() & pin.off()
在“ports/mimxrt/machine_pin.c”找到machine_pin_off()和machine_pin_on()函数:
// pin.off()
STATIC mp_obj_t machine_pin_off(mp_obj_t self_in) {
machine_pin_obj_t *self = self_in;
mp_hal_pin_low(self);
return mp_const_none;
}
STATIC MP_DEFINE_CONST_FUN_OBJ_1(machine_pin_off_obj, machine_pin_off);
// pin.on()
STATIC mp_obj_t machine_pin_on(mp_obj_t self_in) {
machine_pin_obj_t *self = self_in;
mp_hal_pin_high(self);
return mp_const_none;
}
STATIC MP_DEFINE_CONST_FUN_OBJ_1(machine_pin_on_obj, machine_pin_on);
这些功能函数是登记到python中的,并不是直接在某个地方被用户直接调用的,所以,还是要关注一下传参的情况。
以machine_pin_off()为例:
- 返回值类型为mp_obj_t,如果没啥可返回的,就用“return mp_const_none”
- 传入的第一个参数self_in,实际上就是make_new()参数清单中的第一个参数,从中可以提取到硬件相关的信息,然后调用SDK的API操作底层硬件。这里可以直接调用API,没必要再调用一次mp_hal_pin_low()或者mp_hal_pin_high()。
实际上,在“ports/mimxrt/mphalport.h”中,mp_hal_pin_low()或者mp_hal_pin_high()的代码如下:
#define mp_hal_pin_high(p) (GPIO_PinWrite(p->gpio, p->pin, 1U))
#define mp_hal_pin_low(p) (GPIO_PinWrite(p->gpio, p->pin, 0U))
有输入参数的pin.init()
在“ports/mimxrt/machine_pin.c”找到machine_pin_init()函数:
// pin.init(mode, pull, [kwargs])
STATIC mp_obj_t machine_pin_init(size_t n_args, const mp_obj_t *args, mp_map_t *kw_args) {
return machine_pin_obj_init_helper(args[0], n_args - 1, args + 1, kw_args);
}
MP_DEFINE_CONST_FUN_OBJ_KW(machine_pin_init_obj, 1, machine_pin_init);
这个我有点看不懂了,我本来期望第一个参数也是self_in,但这里使用了参数列表的方式传参:n_args是用户传参的参数数量,args是位置参数列表,kw_args是穿好马甲的关键字参数列表。
我在想这里是不是特别为了复用machine_pin_obj_init_helper()函数才写成这个样子。看了rp2的实现,更是乱七八糟,至少mimxrt还是有点规范的,还是参考mimxrt的做法吧。
只要有参数,统一用参数列表的方式传参,就都能cover住。
宏函数“MP_DEFINE_CONST_FUN_OBJ_KW()”中的这个参数“1”,参考在“py/obj.h”中的定义,表示至少有1个参数。
#define MP_DEFINE_CONST_FUN_OBJ_KW(obj_name, n_args_min, fun_name) \
const mp_obj_fun_builtin_var_t obj_name = \
{{&mp_type_fun_builtin_var}, MP_OBJ_FUN_MAKE_SIG(n_args_min, MP_OBJ_FUN_ARGS_MAX, true), .fun.kw = fun_name}
有返回值的pin.value()
在“ports/mimxrt/machine_pin.c”找到machine_pin_init()函数:
// pin.value([value])
STATIC mp_obj_t machine_pin_value(size_t n_args, const mp_obj_t *args) {
return machine_pin_obj_call(args[0], (n_args - 1), 0, args + 1);
}
STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(machine_pin_value_obj, 1, 2, machine_pin_value);
有了在pin.init()上的前车之鉴,这个pin.value()使用位置参数清单保存传入参数,已经见怪不怪了。
这里也是复用了machine_pin_obj_call()函数。
最后使用了“MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN()”宏函数,表示至少有1参数,最多有2个参数?2个参数是什么鬼?
#define MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(obj_name, n_args_min, n_args_max, fun_name) \
const mp_obj_fun_builtin_var_t obj_name = \
{{&mp_type_fun_builtin_var}, MP_OBJ_FUN_MAKE_SIG(n_args_min, n_args_max, false), .fun.var = fun_name}
返回值的处理,只要返回对象就好,如果是整数,就用宏函数“MP_OBJ_NEW_SMALL_INT()”将整数转成OBJ的形式直接返回就好,这个返回值会直接返回到用户脚本中。
小结
功能函数的写法:无参数、有关键字参数(参数数量大于等于1)、仅有位置参数(参数数量大于1)、有返回值,几种要素都已经展现出来。
下一步,套用这里的模式实现在新平台上的移植,遇到具体问题再参考现有各家的实现。
从早写到晚,真写得块吐了。
END