详述micropython中py代码调用底层c代码流程

c模组的实现

从一个简单的例子来看,计算两个数之和返回结果,计算和函数用c来写,并提供给micropython调用接口来供py脚本使用。这也就是给micropython编写c模组的例子。看下面写好的代码:

// Include MicroPython API.
#include "py/runtime.h"

// 这是将从Python作为cexample.add_ints(a,b)调用的函数。
STATIC mp_obj_t example_add_ints(mp_obj_t a_obj, mp_obj_t b_obj) {
    // 从micropython输入对象中提取int变量。
    int a = mp_obj_get_int(a_obj);
    int b = mp_obj_get_int(b_obj);

    // 计算加法并转换为MicroPython对象。
    return mp_obj_new_int(a + b);
}
// 定义对上述函数的Python引用。
STATIC MP_DEFINE_CONST_FUN_OBJ_2(example_add_ints_obj, example_add_ints);

// 定义模组的所有属性。
// 表条目是属性名称(字符串)和MicroPython对象引用的键/值对。
// 所有标识符和字符串都写为MP_QSTR_xxx,并将由构建系统优化为字大小的整数(内部字符串)
STATIC const mp_rom_map_elem_t example_module_globals_table[] = {
    { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_cexample) }, //这句定义了模组的名字,import cexample
    { MP_ROM_QSTR(MP_QSTR_add_ints), MP_ROM_PTR(&example_add_ints_obj) }, //这是模组里面的可用的方法函数, cexample.add_ints(a,b)
};
STATIC MP_DEFINE_CONST_DICT(example_module_globals, example_module_globals_table);

// 定义模组对象
const mp_obj_module_t example_user_cmodule = {
    .base = { &mp_type_module },
    .globals = (mp_obj_dict_t *)&example_module_globals,
};

// 自动注册该模组,使得py可以调用
MP_REGISTER_MODULE(MP_QSTR_cexample, example_user_cmodule, 1);
// MP_REGISTER_MODULE(MP_QSTR_cexample, example_user_cmodule); //for micropython 1.19

这个例子是micropython里自带的c模组例子,可以发现由几个部分构成:

  1. 引用需要用到的MicroPython API头文件

  2. 编写c实现函数:
    example_add_ints(mp_obj_t a_obj, mp_obj_t b_obj),注意函数传入参数都是mp_obj_t 类型也就是micropython对象(在python层面一切都是对象),想要获得真正的int变量需要使用api函数来转换得到,比如mp_obj_get_int()函数就是从对象获取int变量的,MP_DEFINE_CONST_FUN_OBJ_2宏定义就是包装c函数为micropython对象的,末尾的2代表有2个入参。

  3. 定义模组对象:
    模组所有函数方法都会列入到一个globals_table[]中,以键值对的形式关联python层面的调用名称与具体执行的函数,MP_QSTR_xxx里面的xxx就是出现在python层的函数名。然后定义模组对象结构体。

  4. 上面定义完成模组对象结构体后就可以注册了,使用 MP_REGISTER_MODULE(module_name, obj_module, 1)注册。另外也可以将example_user_cmodule加入到头文件components/micropython/port/include/mpconfigport.h中完成手动注册。

c模组的api接口查询

通过上面一个简单的c模组实现例子,已经有个大概的py与c调用关系印象了。在写python代码过程中想详细得知某些c模组api接口该如何传参,或者返回什么内容,首先会想到去查阅对应的api手册文档,但文档往往写的不够完善或者更新滞后过时了,这时候就可以去看源码中对应的函数实现。

例如查询time.localtime()具体如何使用,使用vscode或者别的什么代码编辑器打开源码工程文件,全局搜索MP_QSTR_localtime,只需要在对应的函数名前加MP_QSTR_就行,然后找到对应的芯片源码位置,例如我的位置
详述micropython中py代码调用底层c代码流程_第1张图片
然后找到这句

    { MP_ROM_QSTR(MP_QSTR_localtime), MP_ROM_PTR(&time_localtime_obj) },

追查time_localtime_obj的定义位置,能找到如下代码

STATIC mp_obj_t time_localtime(size_t n_args, const mp_obj_t *args) {
    timeutils_struct_time_t tm;
	mp_int_t seconds;
    if (n_args == 0 || args[0] == mp_const_none) {
		uint32_t year = 0;uint32_t mon = 0;uint32_t mday = 0;uint32_t hour = 0;
		uint32_t min = 0;uint32_t sec = 0;uint32_t wday = 0;uint32_t yday = 0;
		rtc_timer_get((int*)&year, (int*)&mon, (int*)&mday, (int*)&hour, (int*)&min, (int*)&sec);
		wday = rtc_get_wday(year,mon,mday);
		yday = rtc_get_yday(year,mon,mday);
		tm.tm_year = (uint16_t)year;
		tm.tm_mon = (uint8_t)mon;
		tm.tm_mday = (uint8_t)mday;
		tm.tm_hour = (uint8_t)hour;
		tm.tm_min = (uint8_t)min;
		tm.tm_sec = (uint8_t)sec;
		tm.tm_wday = (uint8_t)wday;
		tm.tm_yday = (uint8_t)yday;
    } else {
        seconds = mp_obj_get_int(args[0]);
		timeutils_seconds_since_2000_to_struct_time(seconds, &tm);
    }
    mp_obj_t tuple[8] = {
        tuple[0] = mp_obj_new_int(tm.tm_year),
        tuple[1] = mp_obj_new_int(tm.tm_mon),
        tuple[2] = mp_obj_new_int(tm.tm_mday),
        tuple[3] = mp_obj_new_int(tm.tm_hour),
        tuple[4] = mp_obj_new_int(tm.tm_min),
        tuple[5] = mp_obj_new_int(tm.tm_sec),
        tuple[6] = mp_obj_new_int(tm.tm_wday),
        tuple[7] = mp_obj_new_int(tm.tm_yday),
    };
    return mp_obj_new_tuple(8, tuple);
}
STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(time_localtime_obj, 0, 1, time_localtime);

这个函数就是python接口的localtime最终调用的c函数, 由此可以知道调用后会返回一个8成员的元组数据,里面记录的日期时间信息。对于其他的模块接口查询也是一样的道理。

你可能感兴趣的:(micropython,python,c语言,python,开发语言)