看看machine_pin_type实例的定义
回到ports/mimxrt/machine_pin.c文件,同machine_pin_type并列定义的还有“machine_pin_af_type”,在“pin.h”和“ports/mimxrt/boards/mimxrt_prefix.c”文件中有所涉及,关于指定引脚功能复用的,似乎也作为一个实例对象定义的,但未被注册到任何模块中,暂且放过,待看完machine_pin_type后再回来看。
至于“machine_pin_irq_methods”,仍是在定义machine_pin_type内所属的方法中被调用,等下会看到。
交待了旁枝末节,终于可以静下心来关注machine_pin_type了。
const mp_obj_type_t machine_pin_type = {
{&mp_type_type},
.name = MP_QSTR_Pin,
.print = machine_pin_obj_print,
.call = machine_pin_obj_call,
.make_new = mp_pin_make_new,
.locals_dict = (mp_obj_dict_t *)&machine_pin_locals_dict,
};
整个machine_pin.c就是为了填充这一个mp_obj_type_t类型的结构体实例,此处用到的是“硬回调”的编码模式。 关于“mp_obj_type_t”的定义,具体可参见“py/obj.h”文件,此处仅对必要的字段进行解释。
“&mp_type_type”指定本模块的基类是一个类型对象,直接继承了一些类型(type)对象的方法。“mp_type_type”在dynruntime.h文件中定义,同mp_type_type并列定义的还有mp_type_str、mp_type_tuple、mp_type_list等。
“.name”中登记的是本模块的名字,在python的实现中,都是通过名字字符串进行索引的,关键字字符串都是用qstr类型,qstr类型的字符串直接用“MP_QSTR_”前缀,并且不用双引号引起来。这在编译过程中,会通过编译主机python脚本自动提取这些qstr的实际字符串,这个操作类似于c编译器工具链中的预处理环节。
“.print”指定的函数,是在用户在python中调用print(Pin)函数时,将Pin的实例化对象打印成字符串。
machine_pin对print函数的实现如下,打印“machine_pin_obj_t”结构体类型中“name”字段的字符串。
STATIC void machine_pin_obj_print(const mp_print_t *print, mp_obj_t o, mp_print_kind_t kind) {
(void)kind;
const machine_pin_obj_t *self = MP_OBJ_TO_PTR(o);
mp_printf(print, "Pin(%s)", qstr_str(self->name));
}
“machine_pin_obj_t”结构体类型是在“pin.h”文件中定义的,但我感觉也可以直接放在“machine_pin.h”文件中。在mimxrt的实现中,machine_pin_type和machine_pin_af_type都归在了“pin.h”中。至于这个“name”字段是么时候填进去的呢,我猜是new的时候填入的,让我们拭目以待。“mp_print_t”是打印的对象,可以是交互终端,也可以是log文件。
“.call”指定的函数,对应的是Python中一个非常特殊的类的实例方法,即 __call__()。该方法的功能类似于在类中重载 () 运算符,使得类实例对象可以像调用普通函数那样,以“对象名()”的形式使用。在直白一点,就是将实例对象的名字本身也当成作为一个函数名。machine_pin对call函数的实现如下:
STATIC mp_obj_t machine_pin_obj_call(mp_obj_t self_in, mp_uint_t n_args, mp_uint_t n_kw, const mp_obj_t *args) {
mp_arg_check_num(n_args, n_kw, 0, 1, false);
machine_pin_obj_t *self = self_in;
if (n_args == 0) {
return MP_OBJ_NEW_SMALL_INT(mp_hal_pin_read(self));
} else {
mp_hal_pin_write(self, mp_obj_is_true(args[0]));
return mp_const_none;
}
}
结合此处的实现举例call()属性函数的用法:
pin1 = Pin() # 实例化一个Pin的对象pin1
print(pin1()) # 若参数数量为0个,则返回mp_hal_pin_read()函数读到的引脚值
pin1(0) #若参数为1个,则将传入的第一个参数args[0]作为mp_hal_pin_write()函数的参数,写入引脚指定电平值
参见:Python call()方法(详解版)
“.make_new”指定的函数,是实例化一个类对象的操作过程。这个new函数会“无中生有”,在gc管理的内存区中动态申请一块内存,填入必要的属性信息,然后将这块内存的句柄(指针)返回给调用者。在下文中将详细介绍对应“mp_pin_make_new()”函数的实现,这个函数实现的传参过程比较有趣,可以以不同的编号方式指定对象,下文会有专门的篇幅进行详细介绍。
补充一句,如果按照看起来统一的命名规范,这里的“mp_pin_make_new()”函数名字应同其它属性函数一样使用“machine_pin_obj”前缀,称为“machine_pin_obj_make_new”
“.locals_dict”是本类包含的局部关键字列表以及它们对应的功能(常量?函数?)。在python中有一个内建函数locals(),locals() 函数会以字典类型返回当前位置的全部局部变量。猜测这里的“locals_dict”是提供一个可返回的字典,同时用作模块内部可通过“.”算符继续访问的属性和方法资源。
参见:Python locals() 函数
machine_pin对locals_dict的实现如下:
STATIC const mp_rom_map_elem_t machine_pin_locals_dict_table[] = {
// instance methods
{ MP_ROM_QSTR(MP_QSTR_off), MP_ROM_PTR(&machine_pin_off_obj) },
{ MP_ROM_QSTR(MP_QSTR_on), MP_ROM_PTR(&machine_pin_on_obj) },
{ MP_ROM_QSTR(MP_QSTR_low), MP_ROM_PTR(&machine_pin_off_obj) },
{ MP_ROM_QSTR(MP_QSTR_high), MP_ROM_PTR(&machine_pin_on_obj) },
{ MP_ROM_QSTR(MP_QSTR_value), MP_ROM_PTR(&machine_pin_value_obj) },
{ MP_ROM_QSTR(MP_QSTR_init), MP_ROM_PTR(&machine_pin_init_obj) },
{ MP_ROM_QSTR(MP_QSTR_irq), MP_ROM_PTR(&machine_pin_irq_obj) },
// class attributes
{ MP_ROM_QSTR(MP_QSTR_board), MP_ROM_PTR(&machine_pin_board_pins_obj_type) },
{ MP_ROM_QSTR(MP_QSTR_cpu), MP_ROM_PTR(&machine_pin_cpu_pins_obj_type) },
// class constants
{ MP_ROM_QSTR(MP_QSTR_IN), MP_ROM_INT(PIN_MODE_IN) },
{ MP_ROM_QSTR(MP_QSTR_OUT), MP_ROM_INT(PIN_MODE_OUT) },
{ MP_ROM_QSTR(MP_QSTR_OPEN_DRAIN), MP_ROM_INT(PIN_MODE_OPEN_DRAIN) },
{ MP_ROM_QSTR(MP_QSTR_PULL_UP), MP_ROM_INT(PIN_PULL_UP_100K) },
{ MP_ROM_QSTR(MP_QSTR_PULL_UP_47K), MP_ROM_INT(PIN_PULL_UP_47K) },
{ MP_ROM_QSTR(MP_QSTR_PULL_UP_22K), MP_ROM_INT(PIN_PULL_UP_22K) },
{ MP_ROM_QSTR(MP_QSTR_PULL_DOWN), MP_ROM_INT(PIN_PULL_DOWN_100K) },
{ MP_ROM_QSTR(MP_QSTR_PULL_HOLD), MP_ROM_INT(PIN_PULL_HOLD) },
{ MP_ROM_QSTR(MP_QSTR_DRIVER_OFF), MP_ROM_INT(PIN_DRIVE_OFF) },
{ MP_ROM_QSTR(MP_QSTR_POWER_0), MP_ROM_INT(PIN_DRIVE_POWER_0) }, // R0 (150 Ohm @3.3V / 260 Ohm @ 1.8V)
{ MP_ROM_QSTR(MP_QSTR_POWER_1), MP_ROM_INT(PIN_DRIVE_POWER_1) }, // R0/2
{ MP_ROM_QSTR(MP_QSTR_POWER_2), MP_ROM_INT(PIN_DRIVE_POWER_2) }, // R0/3
{ MP_ROM_QSTR(MP_QSTR_POWER_3), MP_ROM_INT(PIN_DRIVE_POWER_3) }, // R0/4
{ MP_ROM_QSTR(MP_QSTR_POWER_4), MP_ROM_INT(PIN_DRIVE_POWER_4) }, // R0/5
{ MP_ROM_QSTR(MP_QSTR_POWER_5), MP_ROM_INT(PIN_DRIVE_POWER_5) }, // R0/6
{ MP_ROM_QSTR(MP_QSTR_POWER_6), MP_ROM_INT(PIN_DRIVE_POWER_6) }, // R0/7
{ MP_ROM_QSTR(MP_QSTR_IRQ_RISING), MP_ROM_INT(1) },
{ MP_ROM_QSTR(MP_QSTR_IRQ_FALLING), MP_ROM_INT(2) },
“mp_rom_map_elem_t”顾名思义是存放在rom中的map元素类型,内部包含若干的“键值对”,左边为qstr类型的关键字字符串,右边对应资源。例如:“MP_QSTR_off”对应的是“off”关键字和函数“machine_pin_off_obj”,“MP_ROM_PTR()”宏特别指定了这是一个“PTR”指针;“MP_QSTR_IN”对应的是“IN”关键字和“PIN_MODE_IN”常量,“MP_ROM_INT()”宏特别指定了这是一个“INT”整数,“PIN_MODE_INT”的值也是在“pin.h”文件中定义的。
特别说明
在machine_pin.c文件中使用了一些宏操作,将一个函数或者一个数组封装成对象。切记,在python中,一切皆对象,函数、数组、变量甚至常量。
小如指针、常量:
MP_ROM_PTR(&machine_pin_off_obj)
MP_ROM_INT(1)
大如函数、数组:
MP_DEFINE_CONST_FUN_OBJ_1(machine_pin_off_obj, machine_pin_off);
MP_DEFINE_CONST_FUN_OBJ_KW(machine_pin_init_obj, 1, machine_pin_init);
MP_DEFINE_CONST_DICT(machine_pin_locals_dict, machine_pin_locals_dict_table);
它们的实现代码在“py/obj.h”文件中,实际上是定义了一个引用了当前资源的新的变量:
#define MP_DEFINE_CONST_FUN_OBJ_0(obj_name, fun_name) \
const mp_obj_fun_builtin_fixed_t obj_name = \
{{&mp_type_fun_builtin_0}, .fun._0 = fun_name}
#define MP_DEFINE_CONST_FUN_OBJ_1(obj_name, fun_name) \
const mp_obj_fun_builtin_fixed_t obj_name = \
{{&mp_type_fun_builtin_1}, .fun._1 = fun_name}
#define MP_DEFINE_CONST_FUN_OBJ_2(obj_name, fun_name) \
const mp_obj_fun_builtin_fixed_t obj_name = \
{{&mp_type_fun_builtin_2}, .fun._2 = fun_name}
#define MP_DEFINE_CONST_FUN_OBJ_3(obj_name, fun_name) \
const mp_obj_fun_builtin_fixed_t obj_name = \
{{&mp_type_fun_builtin_3}, .fun._3 = fun_name}
...
#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}
...
#define MP_DEFINE_CONST_DICT(dict_name, table_name) \
const mp_obj_dict_t dict_name = { \
.base = {&mp_type_dict}, \
.map = { \
.all_keys_are_qstrs = 1, \
.is_fixed = 1, \
.is_ordered = 1, \
.used = MP_ARRAY_SIZE(table_name), \
.alloc = MP_ARRAY_SIZE(table_name), \
.table = (mp_map_elem_t *)(mp_rom_map_elem_t *)table_name, \
}, \
}
至于其中“mp_type_fun_builtin_1”的定义,位于“py/objfun.c”文件中,是一个常量结构体,而不是一个类型:
STATIC mp_obj_t fun_builtin_1_call(mp_obj_t self_in, size_t n_args, size_t n_kw, const mp_obj_t *args) {
assert(mp_obj_is_type(self_in, &mp_type_fun_builtin_1));
mp_obj_fun_builtin_fixed_t *self = MP_OBJ_TO_PTR(self_in);
mp_arg_check_num(n_args, n_kw, 1, 1, false);
return self->fun._1(args[0]);
}
const mp_obj_type_t mp_type_fun_builtin_1 = {
{ &mp_type_type },
.flags = MP_TYPE_FLAG_BINDS_SELF | MP_TYPE_FLAG_BUILTIN_FUN,
.name = MP_QSTR_function,
.call = fun_builtin_1_call,
.unary_op = mp_generic_unary_op,
};
mp_type_fun_builtin_1”的“.call”指向“fun_builtin_1_call()”函数,从这个函数的实现代码可以看出,其中通过“mp_arg_check_num()”函数检查了参数,有效传入参数仅为1个。之后通过“self->fun._1()”函数执行MP_DEFINE_CONST_FUN_OBJ_1(obj_name, fun_name)执行其中的fun_name,而obj_name就是这里的“self”。
类似地,在“fun_builtin_2_call()”函数中,也过滤成有效参数为2个,“self->fun._2()”函数的传入参数为两个,args[0]和args[1]。
至于func._1和func._2,以及后面可能会经常看到的变参数或关键字参数的函数指针var和kw,其实都是函数指针,在结构体里定义成共享一块内存的union。在“py/obj.h”中有:
typedef struct _mp_obj_fun_builtin_fixed_t {
mp_obj_base_t base;
union {
mp_fun_0_t _0;
mp_fun_1_t _1;
mp_fun_2_t _2;
mp_fun_3_t _3;
} fun;
} mp_obj_fun_builtin_fixed_t;
typedef struct _mp_obj_fun_builtin_var_t {
mp_obj_base_t base;
uint32_t sig; // see MP_OBJ_FUN_MAKE_SIG
union {
mp_fun_var_t var;
mp_fun_kw_t kw;
} fun;
} mp_obj_fun_builtin_var_t;