MicroPython添加Module
官方文档MicroPython external C modules中对这部分有说明, git上也有说明。
1. 背景
摘自:MircoPython 的组件扩展方法
本节内容将详细介绍如何扩展一个新的MicroPython 的组件。组件扩展方式分两种:模块扩展,模块+类扩展。 右边netmgr 功能以模块的方式扩展的,在使用的时候直接导入模块进行使用。左边的ADC 是通过模块+类的方式进行扩展,使用的时候需要通过模块导入ADC类进行使用。
组件扩展过程中会用到函数定义、参数类型转及如何通过Python呼叫C语言代码等功能,下面是对这些功能的说明。
2.基础知识
组件扩展过程中会用到函数定义、参数类型转及如何通过Python呼叫C语言代码等功能,下面是对这些功能的说明。
大体上分为三个层次的封装, 对应了三个封装的宏操作:
MP_DEFINE_CONST_FUN_OBJ_0. 将函数封装成对象. 在python中, 一切皆是对象.
MP_DEFINE_CONST_DICT. 将所有的函数对象封装成一个操作清单.
MP_REGISTER_MODULE. 将操作清单和对象绑定在一起, 注册到python系统中.
2.1 函数和参数定义方式
将函数封装成对象
MP_DEFINE_CONST_FUN_OBJ_0(obj_name, fun_name) //表示函数无参数
#define MP_DEFINE_CONST_FUN_OBJ_1(obj_name, fun_name) // 表示函数有一个参数
#define MP_DEFINE_CONST_FUN_OBJ_2(obj_name, fun_name) // 表示函数有两个参数
#define MP_DEFINE_CONST_FUN_OBJ_3(obj_name, fun_name) //表示函数有三个参数
#define MP_DEFINE_CONST_FUN_OBJ_VAR(obj_name, n_args_min, fun_name) // n_args_min 最小参数个数
#define MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(obj_name, n_args_min, n_args_max, fun_name) // n_args_min, n_args_max 表示函数的参数个数范围
2.2 类型定义和转换
MP_OBJ_NEW_SMALL_INT(small_int) //构造int 类型的obj 对象
MP_OBJ_NEW_QSTR(qst) // 构造qstr data类型的obj 对象
MP_OBJ_NEW_IMMEDIATE_OBJ(val) // 构造immediate类型的obj 对象
MP_ROM_INT(i) //构造Int类型的obj对象
MP_ROM_QSTR(q) //构造qstr data 类型的obj对象
MP_ROM_PTR //构造存储指针的obj对象
MP_OBJ_TO_PTR(o) //将obj 对象转换成obj指针
MP_OBJ_FROM_PTR(p) //将obj指针转换成obj对象
MP_ROM_NONE //构造空的obj对象
MP_ROM_FALSE //构造false值的obj对象
MP_ROM_TRUE //构造true值的obj对象
2.3 Python参数转换成C参数方法
mp_int_t mp_obj_get_int(mp_const_obj_t arg) //将int类型的obj 参数转换成int 类型
mp_float_t mp_obj_get_float(mp_obj_t self_in) //将float类型的obj参数转换成float类型
const char *mp_obj_get_type_str(mp_const_obj_t o_in) //将str 类型的obj参数转换成char类型
3.模块扩展
下面以netmgr功能为例讲解模块扩展的方式具体是怎么实现的。 代码路径: ./components/py_engine/modules/netmgr/modnetmgr.c 首先通过Python 引擎的mp_obj_module_t数据结构定义netmgr 模块。其中base成员是mp_obj_base_t类型的,一般作为Python对象的第一个成员变量。 globals成员是mp_obj_dict_t类型的,里面存储的是功能映射关系。
const mp_obj_module_t netmgr_module = { .base = {&mp_type_module}, .globals = (mp_obj_dict_t *)&netmgr_module_globals,
};
映射表netmgr_module_globals包含多个函数功能:
STATIC const mp_rom_map_elem_t netmgr_module_globals_table[] = {
{MP_OBJ_NEW_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_netmgr)},
{MP_OBJ_NEW_QSTR(MP_QSTR_init), MP_ROM_PTR(&netmgr_obj_init)},
{MP_OBJ_NEW_QSTR(MP_QSTR_getInfo), MP_ROM_PTR(&netmgr_obj_get_info)},
{MP_OBJ_NEW_QSTR(MP_QSTR_getType), MP_ROM_PTR(&netmgr_obj_get_type)},
{MP_OBJ_NEW_QSTR(MP_QSTR_getStatus), MP_ROM_PTR(&netmgr_obj_get_status)},
{MP_OBJ_NEW_QSTR(MP_QSTR_connect), MP_ROM_PTR(&netmgr_obj_connect_wifi)},
{MP_OBJ_NEW_QSTR(MP_QSTR_disconnect), MP_ROM_PTR(&netmgr_obj_disconnect_wifi)},
{MP_OBJ_NEW_QSTR(MP_QSTR_on), MP_ROM_PTR(&netmgr_obj_on)},
{MP_OBJ_NEW_QSTR(MP_QSTR_register_call_back), MP_ROM_PTR(&mp_wifi_register_call_back)},};
其中负责网络连线功能的函数是mp_obj_t connect_wifi ,该函数有两个参数,分别为Wi-Fi路由器名称(ssid)和路由器密码(pwd),通过这两个参数将Python代码中设定的路由器名称和密码传到C代码。拿到参数后,通过mp_obj_str_get_str 函数将 mp_obj 转换成 char 类型。然后呼叫 os_task_new 创建一个名为wifi_connect_task 的线程完成Wi-Fi连线功能。 下面是mp_obj_t_connect_wifi的C代码的具体实现:
STATIC mp_obj_t connect_wifi(mp_obj_t ssid,mp_obj_t pwd) {
char* _ssid = mp_obj_str_get_str(ssid); // 获取SSID字串
char* _pwd = mp_obj_str_get_str(pwd); // 获取密码字串
netmgr_wifi_connect_params_t *params;
params = (netmgr_wifi_connect_params_t*) malloc(sizeof(netmgr_wifi_connect_params_t));
if(params == NULL) {
LOGE(LOG_TAG, "%s:%d malloc failed\n", __func__, __LINE__);
return mp_obj_new_int(-1);
}
memset(params, 0, sizeof(netmgr_wifi_connect_params_t));
strncpy(params->ssid, _ssid, sizeof(params->ssid)-1);
params->timeout = 18000;// 设置Wi-Fi连线超时时间为1800 ms
strncpy((char* )params->pwd, _pwd, sizeof(params->pwd)-1);
aos_task_new("wifi_connect_task",wifi_connect_handle, params, 4*1024); // 创建新线程进行Wi-Fi连线操作
return mp_obj_new_int(0);
}
MP_DEFINE_CONST_FUN_OBJ_2(netmgr_obj_connect_wifi, connect_wifi);
netmgr组件扩展成Python联网模块之后就可以在Python应用程序中调用其提供的PythonAPI了,具体使用方法如下面代码所示:
import netmgr as nm # 导入netmgr模块,为该模块取别名为nm,后续访问此模块功能均通过nm
import utime as time # 导入utime模块,为该模块取名为time
import sys # 导入sys库
nm.init() # netmgr组件初始化
connected = nm.getStatus() # 获取Wi-Fi连接状态
def on_wifi_connected(status): # 定义Wi-Fi连线成功的回调函数
global connected
print('*******wifi connected*********')
connected = True # 设置connected变量为True
if not connected: # 如果连线没有成功
nm.register_call_back(1,on_wifi_connected) # 向nm组件注册连线成功回调函数
if(len(sys.argv) == 3):
nm.connect(sys.argv[1],sys.argv[2]) # 如果执行此python脚本的时候同时输入了Wi-Fi SSID和密码作为参数则连接用户输入的指定路由器
else:
nm.connect("KIDS","12345678") # 如果执行此python脚本的时候没有输入Wi-Fi SSID和密码,则连接预设的名为KIDS的路由器
while True : # 等待Wi-Fi连接成功
if connected:
break # 连线成功则调出此循环
else:
print('Wait for wifi connected')
time.sleep(1) # 没有连线成功则打印日志并休眠1秒
if nm.getStatus():
print('DeviceIP:' + nm.getInfo()['IP']) # 连线成功则通过呼叫getInfo获取IP地址信息
else:
print('DeviceIP:get failed')
print("ConnectWifi finished")
4.模块+类扩展
下面以ADC功能为例来讲解如何通过模块+类的扩展方式扩展一个Python类。 相关代码路径如下: ●模块代码: components/py_engine/adapter/haas/moddriver.c ●类代码: components/py_engine/modules/driver/adc.c 首先要通过Python引擎的mp_obj_module_t数据结构定义一个driver_module模块。
const mp_obj_module_t driver_module = {
.base = {&mp_type_module},
.globals = (mp_obj_dict_t *)&driver_locals_dict,
};
然后通过数据结构mp_rom_map_elem_t 定义此模块对应的类表,截止本文撰写的时候driver模块支持的类有:ADC、PWM、GPIO、I2C、UART、SPI、RTC、TIMER及DAC等。
STATIC const mp_rom_map_elem_t driver_locals_dict_table[] = {
{MP_OBJ_NEW_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_driver)},
{MP_OBJ_NEW_QSTR(MP_QSTR_ADC), MP_ROM_PTR(&driver_adc_type)},
{MP_OBJ_NEW_QSTR(MP_QSTR_PWM), MP_ROM_PTR(&driver_pwm_type)},
{MP_OBJ_NEW_QSTR(MP_QSTR_GPIO), MP_ROM_PTR(&driver_gpio_type)},
{MP_OBJ_NEW_QSTR(MP_QSTR_I2C), MP_ROM_PTR(&driver_i2c_type)},
{MP_OBJ_NEW_QSTR(MP_QSTR_UART), MP_ROM_PTR(&driver_uart_type)},
{MP_OBJ_NEW_QSTR(MP_QSTR_SPI), MP_ROM_PTR(&driver_spi_type)},
{MP_OBJ_NEW_QSTR(MP_QSTR_RTC), MP_ROM_PTR(&driver_rtc_type)},
{MP_OBJ_NEW_QSTR(MP_QSTR_TIMER), MP_ROM_PTR(&driver_timer_type)},
{MP_OBJ_NEW_QSTR(MP_QSTR_CAN), MP_ROM_PTR(&driver_can_type)},
//{MP_OBJ_NEW_QSTR(MP_QSTR_DAC), MP_ROM_PTR(&driver_dac_type)},
{MP_OBJ_NEW_QSTR(MP_QSTR_IR), MP_ROM_PTR(&driver_ir_type)},
{MP_OBJ_NEW_QSTR(MP_QSTR_WDT), MP_ROM_PTR(&driver_wdt_type)},
{MP_OBJ_NEW_QSTR(MP_QSTR_KeyPad), MP_ROM_PTR(&driver_keypad_type)},
{MP_OBJ_NEW_QSTR(MP_QSTR_Location), MP_ROM_PTR(&driver_location_type)},
{MP_OBJ_NEW_QSTR(MP_QSTR_UND), MP_ROM_PTR(&driver_und_type)},
{MP_OBJ_NEW_QSTR(MP_QSTR_Crypto), MP_ROM_PTR(&driver_crypto_type)},
};
接下来需要通过Python引擎的mp_obj_type_t数据结构定义driver_adc_type模块,此结构体的成员主要包含构造函数,打印函数,功能映射表等信息。
const mp_obj_type_t driver_adc_type = {
.base = {&mp_type_type},
.name = MP_QSTR_ADC, // ADC 模块名称
.print = adc_obj_print, // 打印函数
.make_new = adc_obj_make_new, // 构造函数
.locals_dict = (mp_obj_dict_t *)&adc_locals_dict, // 功能映射表
};
类和模块一样,也是通过数据结构mp_rom_map_elem_t来定义函数功能表。
STATIC const mp_rom_map_elem_t adc_locals_dict_table[] = {
{MP_OBJ_NEW_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_ADC)},
{MP_ROM_QSTR(MP_QSTR_open), MP_ROM_PTR(&adc_obj_open)},
{MP_ROM_QSTR(MP_QSTR_close), MP_ROM_PTR(&adc_obj_close)},
{MP_ROM_QSTR(MP_QSTR_read), MP_ROM_PTR(&adc_obj_read)},
};
下面是通过ADC进行读操作的具体函数实现:
STATIC mp_obj_t obj_read(size_t n_args, const mp_obj_t *args)
{
LOGD(LOG_TAG, "entern %s; n_args = %d;\n", __func__, n_args);
int ret = -1;
adc_dev_t *adc_device = NULL;
int32_t adc_value = -1;
if (n_args < 1)
{
LOGE(LOG_TAG, "%s: args num is illegal :n_args = %d;\n", __func__, n_args);
return mp_const_none;
}
mp_obj_base_t *self = (mp_obj_base_t*)MP_OBJ_TO_PTR(args[0]);
mp_adc_obj_t* driver_obj = (mp_adc_obj_t *)self;
if (driver_obj == NULL)
{
LOGE(LOG_TAG, "driver_obj is NULL\n");
return mp_const_none;
}
adc_device = py_board_get_node_by_handle(MODULE_ADC, &(driver_obj->adc_handle)); // 获取ADC设备的指针
if (NULL == adc_device) {
LOGE(LOG_TAG, "%s: py_board_get_node_by_handle failed;\n", __func__);
return mp_const_none;
}
(void)aos_hal_adc_value_get(adc_device, (void *)&adc_value, 0); // 呼叫C语言进行ACD读操作的API
LOGD(LOG_TAG, "%s:out adc_value = %d;\n", __func__, adc_value);
return MP_ROM_INT(adc_value);
}
STATIC MP_DEFINE_CONST_FUN_OBJ_VAR(adc_obj_read, 1, obj_read);
? 在driver模块中扩展ADC类之后,在Python应用层代码中使用ADC类的案例如下:
from driver import ADC # 从driver库中导入ADC类
adc = ADC() # 新建一个ADC设备对象
adc.open("ADC0") # 打开ADC的通道0
value = adc.read() # 进行ADC读操作
print(value)
adc.close() # 关闭ADC对象(即关闭ADC通道0)
5.分析官方的代码
官方代码
基础例程的位置
micropython/
└──examples/
└──usercmodule/
└──cexample/
├── examplemodule.c
├── micropython.mk
└── micropython.cmake
将C模块编译进MP
新建文件
在ports/stm32 文件夹下新建一个文件examplemodule.c , 内容为examplemodule.c
文件examplemodule.c 内创建了一个example_user_cmodule 的模块,它包含了一个funtion和一个type,type定义在其他文件内。
// Include MicroPython API.
#include "py/runtime.h"
// This is the function which will be called from Python as cexample.add_ints(a, b).
STATIC mp_obj_t example_add_ints(mp_obj_t a_obj, mp_obj_t b_obj) {
// Extract the ints from the micropython input objects.
int a = mp_obj_get_int(a_obj);
int b = mp_obj_get_int(b_obj);
// Calculate the addition and convert to MicroPython object.
return mp_obj_new_int(a + b);
}
// Define a Python reference to the function above.
STATIC MP_DEFINE_CONST_FUN_OBJ_2(example_add_ints_obj, example_add_ints);
// 将函数封装成对象. 在python中, 一切皆是对象
// Define all properties of the module.
// Table entries are key/value pairs of the attribute name (a string)
// and the MicroPython object reference.
// All identifiers and strings are written as MP_QSTR_xxx and will be
// optimized to word-sized integers by the build system (interned strings).
STATIC const mp_rom_map_elem_t example_module_globals_table[] = {
{ MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_cexample) },
{ MP_ROM_QSTR(MP_QSTR_add_ints), MP_ROM_PTR(&example_add_ints_obj) },
};
STATIC MP_DEFINE_CONST_DICT(example_module_globals, example_module_globals_table);
//将所有的函数对象封装成一个操作清单.
// Define module object.
const mp_obj_module_t example_user_cmodule = { //使用类型定义了一个`example_user_cmodule`
.base = { &mp_type_module },
.globals = (mp_obj_dict_t *)&example_module_globals,
}; //初始化了相关的部分,如基本类型、模块的字典 在字典中,定义了`__name__` 为"example"
// Register the module to make it available in Python.
// Note: the "1" in the third argument means this module is always enabled.
// This "1" can be optionally replaced with a macro like MODULE_CEXAMPLE_ENABLED
// which can then be used to conditionally enable this module.
MP_REGISTER_MODULE(MP_QSTR_cexample, example_user_cmodule, 1);//将操作清单和对象绑定在一起, 注册到python系统中.
上面这部分代码使用mp_obj_module_t 类型定义了一个example_user_cmodule ,并初始化了相关的部分,如基本类型、模块的字典。在字典中,定义了__name__ ,为machine ,这个就是我们这个module的名字。
在mpconfigport.h文件中注册新模块
为了让这个模块能在MicroPython中被import,需要将模块添加到mpconfigport.h的MICROPY_PORT_BUILTIN_MODULES 中。
要把定义的module注册到micropython中去,这个是在mpconfigport.h文件中修改,找到MICROPY_PORT_BUILTIN_MODULES 定义的地方按照格式添加定义的module:
extern const struct _mp_obj_module_t example_user_cmodule;
#define MICROPY_PORT_BUILTIN_MODULES \
{ MP_ROM_QSTR(MP_QSTR_cexample), MP_ROM_PTR(&example_user_cmodule) }, \
在Makefile文件中更新SRC_C和SRC_QSTR
在SRC_C中添加examplemodule.c 文件, 将新增代码编译firmware中.
在SRC_QSTR中添加examplemodule.c 文件, 让build过程扫描examplemodule.c 文件, 从中提取字符串关键字增加到micropython的qstr列表当中.
第二步操作是根据官方文档的说明进行正式的添加qstr 。
按照官方开发文档的说明, 到这里就可以重新 make , 然后可以在 micropython 的交互命令行中引用example_user_cmodule 模块的.
编译并烧录,在MicroPython中使用import cexample 测试是否成功,不报错就是成功了。
6. 新建一个machine模块(LPC1788 无参数)
参考MicroPython移植LPC1788添加module
创建machine.c
#include "py/obj.h"
#include "py/runtime.h"
#include "py/builtin.h"
STATIC const mp_rom_map_elem_t machine_module_globals_table[] = {
{MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_machine)},
};
STATIC MP_DEFINE_CONST_DICT(machine_module_globals, machine_module_globals_table);
const mp_obj_module_t machine_module = {
.base = {&mp_type_module},
.globals = (mp_obj_dict_t *)&machine_module_globals,
};
模块添加到mpconfigport
为了让这个模块能在MicroPython中被import,需要将模块添加到 mpconfigport.h 的MICROPY_PORT_BUILTIN_MODULES 中。
// extra built in modules to add to the list of known ones
extern const struct _mp_obj_module_t machine_module; // 注意_mp_obj_module_t是带下划线前缀的
#define MICROPY_PORT_BUILTIN_MODULES \
{MP_ROM_QSTR(MP_QSTR_machine), MP_ROM_PTR(&machine_module)},
编译并烧录
在MicroPython中使用import machine 测试是否成功,不报错就是成功了。
添加一个Function
现在,我们可以添加一个函数到这个模块里面。首先定义一个函数,这里以reset函数来具体说明。
STATIC mp_obj_t machine_reset(void)
{
NVIC_SystemReset();
return mp_const_none;
}
STATIC MP_DEFINE_CONST_FUN_OBJ_0(machine_reset_obj, machine_reset);
这段代码创建了一个名为machine_reset_obj 的函数对象,该函数没有参数,被调用时会执行C函数machine_reset 。添加一个函数时使用了MP_DEFINE_CONST_FUN_OBJ_0 宏来定义一个不带参数的函数。同样也可以定义一个或多个参数的函数。此外,所有的Python函数都必须返回一个mp_obj_t 结构对象,但是我们这没有什么可返回的,因此返回None。
接下来将这个函数添加到前面创建好的module中。
STATIC const mp_rom_map_elem_t machine_module_globals_table[] = {
{MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_machine)},
{MP_ROM_QSTR(MP_QSTR_reset), MP_ROM_PTR(&machine_reset_obj)},
};
7. 新建一个led模块(lpc5500 无参数)
为micropython添加led模块
创建一个LED灯的模块, 并包含on()和off()两个函数对应开灯和关灯两个动作。
为新模块创建一个源文件
参考官方样例的命名规范, 这里在lpc5500移植项目的目录下创建mod_led.c
PS: 原始的命名范例中没有使用下划线"_"将前缀"mod"单独分隔出来, 但我通过阅读代码发现, 增加下划线的命名方式更符合micropython的命名规范. 实际上, 在micropython的源代码中, 都是使用下划线作为命名单词的分隔符的. 我有点不明白为什么文件的命令没有使用分隔符. 按照我的开发习惯, 在规模比较大的软件项目中, 使用分隔符的命名方式可读性更好, 所以在我自己的练习代码中, 将会使用下划线作为名字之间的分隔符.
在新模块中首先编写最基本的led模块对应底层驱动的三个函数:
hw_led_init()
hw_led_on()
hw_led_off()
目前先做一个最简单的样例, 实现从python到c语言函数的调用. 目前的样例中仅仅传递函数指针, 不传入任何参数, 在后续的文章中将专门探讨传递参数的问题.
逐层封装
毕竟使用了armgcc编译器, 底层的代码还是以C语言方式运行的, 从python到底层的C就是层层调用. 反过来在开发过程中, 准备好底层的C代码之后, 想要在python层面上被识别, 就需要层层封装并注册.
从官方的样例代码中可以看到, 大体上分为三个层次的封装, 对应了三个封装的宏操作:
MP_DEFINE_CONST_FUN_OBJ_0. 将函数封装成对象. 在python中, 一切皆是对象.
MP_DEFINE_CONST_DICT. 将所有的函数对象封装成一个操作清单.
MP_REGISTER_MODULE. 将操作清单和对象绑定在一起, 注册到python系统中.
到目前为止, 完整的mod_led.c源文件内容如下:
/* mod_led.c */
#include "py/runtime.h"
#include "fsl_common.h"
#include "fsl_iocon.h"
#include "fsl_clock.h"
#include "fsl_gpio.h"
/******************************************************************************
* hardware level functions.
*****************************************************************************/
void hw_led_init(void)
{
CLOCK_EnableClock(kCLOCK_Iocon);
CLOCK_EnableClock(kCLOCK_Gpio1);
uint32_t pinmode = IOCON_FUNC0
| IOCON_MODE_INACT
| IOCON_GPIO_MODE
| IOCON_DIGITAL_EN
;
IOCON_PinMuxSet(IOCON, 1u, 6u, pinmode); /* pio1_6. */
gpio_pin_config_t gpio_pin_config;
gpio_pin_config.pinDirection = kGPIO_DigitalOutput;
gpio_pin_config.outputLogic = 1u;
GPIO_PinInit(GPIO, 1u, 6u, &gpio_pin_config);
}
void hw_led_on(void)
{
GPIO_PinWrite(GPIO, 1u, 6u, 0u);
}
void hw_led_off(void)
{
GPIO_PinWrite(GPIO, 1u, 6u, 1u);
}
/******************************************************************************
* object function wrappers.
*****************************************************************************/
STATIC mp_obj_t led_init_func(void)
{
hw_led_init();
return mp_const_none;
}
STATIC MP_DEFINE_CONST_FUN_OBJ_0(led_init_obj, led_init_func);
STATIC mp_obj_t led_on_func(void)
{
hw_led_on();
return mp_const_none;
}
STATIC MP_DEFINE_CONST_FUN_OBJ_0(led_on_obj, led_on_func);
STATIC mp_obj_t led_off_func(void)
{
hw_led_off();
return mp_const_none;
}
STATIC MP_DEFINE_CONST_FUN_OBJ_0(led_off_obj, led_off_func);
/******************************************************************************
* pack the objects into module.
*****************************************************************************/
STATIC const mp_rom_map_elem_t led_module_globals_table[] = {
{ MP_OBJ_NEW_QSTR(MP_QSTR___name__), MP_OBJ_NEW_QSTR(MP_QSTR_led) },
{ MP_ROM_QSTR(MP_QSTR_init), MP_ROM_PTR(&led_init_obj) },
{ MP_ROM_QSTR(MP_QSTR_on) , MP_ROM_PTR(&led_on_obj) },
{ MP_ROM_QSTR(MP_QSTR_off) , MP_ROM_PTR(&led_off_obj) },
};
STATIC MP_DEFINE_CONST_DICT(led_module_globals, led_module_globals_table);
const mp_obj_module_t led_module = {
.base = { &mp_type_module },
.globals = (mp_obj_dict_t *)&led_module_globals,
};
MP_REGISTER_MODULE(MP_QSTR_led, led_module, 1);
/* EOF. */
在mpconfigport.h文件中注册新模块
在mpconfigport.h文件中添加代码如下:
extern const struct _mp_obj_module_t led_module;
#define MICROPY_PORT_BUILTIN_MODULES \
{ MP_ROM_QSTR(MP_QSTR_led), MP_ROM_PTR(&led_module) }, \
在Makefile文件中更新SRC_C和SRC_QSTR
在SRC_C中添加mod_led.c 文件, 将新增代码编译firmware中.
在SRC_QSTR中添加mod_led.c 文件, 让build过程扫描mymodule.c 文件, 从中提取字符串关键字增加到micropython的qstr列表当中.
第二步操作是根据官方文档的说明进行的操作, 应该是比较正式的添加qstr 的方式.
按照官方开发文档的说明, 到这里就可以重新 make , 然后可以在 micropython 的交互命令行中引用led 模块的.
编译并烧录,在MicroPython中使用import led 测试是否成功,不报错就是成功了。
通过编译之后, 下载firmware到电路板上, 复位后通过终端命令行交互, 能够成功识别led模块, 并且在板子上确实看到led灯受控亮灭了.
8.新建mymodule (esp32 1个参数)
MicroPython 添加 mymodule
mymodule.c
#include "stdint.h"
#include "stdio.h"
#include "py/obj.h"
#include "py/runtime.h"
STATIC mp_obj_t mp_my_test_function(mp_obj_t myms)
{
uint32_t Myms = mp_obj_get_int(myms);
vTaskDelay(Myms / portTICK_PERIOD_MS);
return mp_const_none;
}
STATIC MP_DEFINE_CONST_FUN_OBJ_1(My_mp_my_test_function, mp_my_test_function);
STATIC const mp_rom_map_elem_t modnormal_globals_table[] = {
{MP_OBJ_NEW_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_my_test_module)},
{MP_OBJ_NEW_QSTR(MP_QSTR_mytestfunction), MP_ROM_PTR(&My_mp_my_test_function)},
{MP_OBJ_NEW_QSTR(MP_QSTR_test), MP_ROM_PTR(&modnormal_test_type)},
};
STATIC MP_DEFINE_CONST_DICT(mp_module_modnormal_globals, modnormal_globals_table);
const mp_obj_module_t mp_module_my_test_mod = {
.base = {&mp_type_module},
.globals = (mp_obj_dict_t *)&mp_module_modnormal_globals,
};
文件mymodule.c 内创建了一个mp_module_my_test_mod的模块,它包含了一个funtion和一个type,type定义在其他文件内。
注册module
我们要把我们定义的module注册到micropython中去,这个是在mpconfigport.h文件中修改,找到MICROPY_PORT_BUILTIN_MODULES 定义的地方按照格式添加我们定义的module:
// extra built in modules to add to the list of known ones
extern const struct _mp_obj_module_t esp_module;
extern const struct _mp_obj_module_t esp32_module;
extern const struct _mp_obj_module_t utime_module;
extern const struct _mp_obj_module_t uos_module;
extern const struct _mp_obj_module_t mp_module_usocket;
extern const struct _mp_obj_module_t mp_module_machine;
extern const struct _mp_obj_module_t mp_module_network;
extern const struct _mp_obj_module_t mp_module_onewire;
extern const struct _mp_obj_module_t mp_module_my_test_mod;//声明模块
#define MICROPY_PORT_BUILTIN_MODULES \
{ MP_OBJ_NEW_QSTR(MP_QSTR_esp), (mp_obj_t)&esp_module }, \
{ MP_OBJ_NEW_QSTR(MP_QSTR_esp32), (mp_obj_t)&esp32_module }, \
{ MP_OBJ_NEW_QSTR(MP_QSTR_utime), (mp_obj_t)&utime_module }, \
{ MP_OBJ_NEW_QSTR(MP_QSTR_uos), (mp_obj_t)&uos_module }, \
{ MP_OBJ_NEW_QSTR(MP_QSTR_usocket), (mp_obj_t)&mp_module_usocket }, \
{ MP_OBJ_NEW_QSTR(MP_QSTR_machine), (mp_obj_t)&mp_module_machine }, \
{ MP_OBJ_NEW_QSTR(MP_QSTR_network), (mp_obj_t)&mp_module_network }, \
{ MP_OBJ_NEW_QSTR(MP_QSTR__onewire), (mp_obj_t)&mp_module_onewire }, \
{ MP_OBJ_NEW_QSTR(MP_QSTR_uhashlib), (mp_obj_t)&mp_module_uhashlib }, \
{ MP_OBJ_NEW_QSTR(MP_QSTR_my_test_module), (mp_obj_t)&mp_module_my_test_mod }, \
这里就添加两行代码,第一行是应用外部定义的结构体时要先有声明。第二个需要解释一下,MP_QSTR_my_test_module这个名字要改成自定义的,MP_QSTR_这个一定要保留,后面的my_test_module就是在python中显示的module名字。
在Makefile文件中更新SRC_C和SRC_QSTR
在SRC_C中添加mymodule.c 文件, 将新增代码编译firmware中.
在SRC_QSTR中添加mymodule.c 文件, 让build过程扫描mymodule.c 文件, 从中提取字符串关键字增加到micropython的qstr列表当中.
第二步操作是根据官方文档的说明进行的操作, 应该是比较正式的添加qstr 的方式. ??然后再修改一下Makefile文件,将mymodule.c编译进去就可以了。
按照官方开发文档的说明, 到这里就可以重新 make , 然后可以在 micropython 的交互命令行中引用led 模块的.
编译并烧录,在MicroPython中使用import led 测试是否成功,不报错就是成功了。
通过编译之后, 下载firmware到电路板上, 复位后通过终端命令行交互, 能够成功识别led模块, 并且在板子上确实看到led灯受控亮灭了.
9.向固件里面添加module的原理
在文章C语言开发MicroPython模块(模块框架)里面已经介绍了,编译进内核的模块(module)有四种类型,MicroPython将它们组成四个不同的模块集合,并且定义了不同的名字。
摘自:向固件里面添加module的原理
2.1、模块集合
以我们编写的和硬件平台密切相关的 module集合mp_builtin_module_map为例,在objmodule.c文件内有
MP_DEFINE_CONST_MAP(mp_builtin_module_map, mp_builtin_module_table);
1
其中mp_builtin_module_map就是这个被编译进内核的模块集合的名字,mp_builtin_module_table是存储模块的结构体数组。??MP_DEFINE_CONST_MAP的定义在obj.h文件内,内容如下:
#define MP_DEFINE_CONST_MAP(map_name, table_name) \
const mp_map_t map_name = { \
.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_DEFINE_CONST_MAP就是定义了一个结构体,这个结构体的内容为mp_map_t
typedef struct _mp_map_t {
size_t all_keys_are_qstrs : 1;
size_t is_fixed : 1;
size_t is_ordered : 1;
size_t used : (8 * sizeof(size_t) - 3);
size_t alloc;
mp_map_elem_t *table;
} mp_map_t;
MP_ARRAY_SIZE的定义为:
#define MP_ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0]))
12
说明结构体的alloc是模块集合的指针合所占的长度,table是一个指针,这个指针指向了mp_builtin_module_table这个结构体数组。在.table = (mp_map_elem_t*)(mp_rom_map_elem_t*)table_name, \ 这行代码里面mp_rom_map_elem_t和mp_map_elem_t的定义为:
typedef struct _mp_map_elem_t {
mp_obj_t key;
mp_obj_t value;
} mp_map_elem_t;
typedef struct _mp_rom_map_elem_t {
mp_rom_obj_t key;
mp_rom_obj_t value;
} mp_rom_map_elem_t;
1234567891011
而mp_obj_t和mp_rom_obj_t的定义为:
typedef void *mp_obj_t;
typedef const void *mp_const_obj_t;
typedef mp_const_obj_t mp_rom_obj_t;
123
所以名字为mp_builtin_module_map的这个模块集合在最终包含了一系列的指针,mp_builtin_module_table这个结构体数组内元素。而mp_builtin_module_table这个结构体数组内的元素则是一个一个的模块。
2.2、模块数组
mp_builtin_module_table的定义如下:
STATIC const mp_rom_map_elem_t mp_builtin_module_table[] = {
{ MP_ROM_QSTR(MP_QSTR___main__), MP_ROM_PTR(&mp_module___main__) },
{ MP_ROM_QSTR(MP_QSTR_builtins), MP_ROM_PTR(&mp_module_builtins) },
{ MP_ROM_QSTR(MP_QSTR_micropython), MP_ROM_PTR(&mp_module_micropython) },
#if MICROPY_PY_ARRAY
{ MP_ROM_QSTR(MP_QSTR_array), MP_ROM_PTR(&mp_module_array) },
#endif
#if MICROPY_PY_IO
{ MP_ROM_QSTR(MP_QSTR_uio), MP_ROM_PTR(&mp_module_io) },
#endif
#if MICROPY_PY_COLLECTIONS
{ MP_ROM_QSTR(MP_QSTR_ucollections), MP_ROM_PTR(&mp_module_collections) },
#endif
#if MICROPY_PY_STRUCT
{ MP_ROM_QSTR(MP_QSTR_ustruct), MP_ROM_PTR(&mp_module_ustruct) },
#endif
#if MICROPY_PY_BUILTINS_FLOAT
#if MICROPY_PY_MATH
{ MP_ROM_QSTR(MP_QSTR_math), MP_ROM_PTR(&mp_module_math) },
#endif
#if MICROPY_PY_BUILTINS_COMPLEX && MICROPY_PY_CMATH
{ MP_ROM_QSTR(MP_QSTR_cmath), MP_ROM_PTR(&mp_module_cmath) },
#endif
#endif
#if MICROPY_PY_SYS
{ MP_ROM_QSTR(MP_QSTR_sys), MP_ROM_PTR(&mp_module_sys) },
#endif
#if MICROPY_PY_GC && MICROPY_ENABLE_GC
{ MP_ROM_QSTR(MP_QSTR_gc), MP_ROM_PTR(&mp_module_gc) },
#endif
#if MICROPY_PY_THREAD
{ MP_ROM_QSTR(MP_QSTR__thread), MP_ROM_PTR(&mp_module_thread) },
#endif
#if MICROPY_PY_UERRNO
{ MP_ROM_QSTR(MP_QSTR_uerrno), MP_ROM_PTR(&mp_module_uerrno) },
#endif
#if MICROPY_PY_UCTYPES
{ MP_ROM_QSTR(MP_QSTR_uctypes), MP_ROM_PTR(&mp_module_uctypes) },
#endif
#if MICROPY_PY_UZLIB
{ MP_ROM_QSTR(MP_QSTR_uzlib), MP_ROM_PTR(&mp_module_uzlib) },
#endif
#if MICROPY_PY_UJSON
{ MP_ROM_QSTR(MP_QSTR_ujson), MP_ROM_PTR(&mp_module_ujson) },
#endif
#if MICROPY_PY_URE
{ MP_ROM_QSTR(MP_QSTR_ure), MP_ROM_PTR(&mp_module_ure) },
#endif
#if MICROPY_PY_UHEAPQ
{ MP_ROM_QSTR(MP_QSTR_uheapq), MP_ROM_PTR(&mp_module_uheapq) },
#endif
#if MICROPY_PY_UTIMEQ
{ MP_ROM_QSTR(MP_QSTR_utimeq), MP_ROM_PTR(&mp_module_utimeq) },
#endif
#if MICROPY_PY_UHASHLIB
{ MP_ROM_QSTR(MP_QSTR_uhashlib), MP_ROM_PTR(&mp_module_uhashlib) },
#endif
#if MICROPY_PY_UCRYPTOLIB
{ MP_ROM_QSTR(MP_QSTR_ucryptolib), MP_ROM_PTR(&mp_module_ucryptolib) },
#endif
#if MICROPY_PY_UBINASCII
{ MP_ROM_QSTR(MP_QSTR_ubinascii), MP_ROM_PTR(&mp_module_ubinascii) },
#endif
#if MICROPY_PY_URANDOM
{ MP_ROM_QSTR(MP_QSTR_urandom), MP_ROM_PTR(&mp_module_urandom) },
#endif
#if MICROPY_PY_USELECT
{ MP_ROM_QSTR(MP_QSTR_uselect), MP_ROM_PTR(&mp_module_uselect) },
#endif
#if MICROPY_PY_USSL
{ MP_ROM_QSTR(MP_QSTR_ussl), MP_ROM_PTR(&mp_module_ussl) },
#endif
#if MICROPY_PY_LWIP
{ MP_ROM_QSTR(MP_QSTR_lwip), MP_ROM_PTR(&mp_module_lwip) },
#endif
#if MICROPY_PY_WEBSOCKET
{ MP_ROM_QSTR(MP_QSTR_websocket), MP_ROM_PTR(&mp_module_websocket) },
#endif
#if MICROPY_PY_WEBREPL
{ MP_ROM_QSTR(MP_QSTR__webrepl), MP_ROM_PTR(&mp_module_webrepl) },
#endif
#if MICROPY_PY_FRAMEBUF
{ MP_ROM_QSTR(MP_QSTR_framebuf), MP_ROM_PTR(&mp_module_framebuf) },
#endif
#if MICROPY_PY_BTREE
{ MP_ROM_QSTR(MP_QSTR_btree), MP_ROM_PTR(&mp_module_btree) },
#endif
MICROPY_PORT_BUILTIN_MODULES
};
结构体mp_rom_map_elem_t的定义如下:
typedef struct _mp_rom_map_elem_t {
mp_rom_obj_t key;
mp_rom_obj_t value;
} mp_rom_map_elem_t;
1234
说明mp_builtin_module_table这个数组里面每个元素都必须是mp_rom_map_elem_t类型。当增加一个模块时,就往这个数组里面添加一个元素。下面以第一个数组元素进行分析:
2.2.1、MP_ROM_QSTR
{ MP_ROM_QSTR(MP_QSTR___main__), MP_ROM_PTR(&mp_module___main__) },
1
MP_ROM_QSTR的定义为:
#define MP_ROM_QSTR(q) MP_OBJ_NEW_QSTR(q)
而MP_OBJ_NEW_QSTR的定义为:
#define MP_OBJ_NEW_QSTR(qst) ((mp_obj_t)((((mp_uint_t)(qst)) << 2) | 2))
其中mp_uint_t的定义为
typedef unsigned int mp_uint_t;
mp_obj_t的定义为
typedef void *mp_obj_t;
所以MP_ROM_QSTR(MP_QSTR___main__)的作用是将将MP_QSTR___main__强制转换为类型int,然后将结果值左移两位,在将二进制结果的值与2进行或操作,再将结果转换为无符号的指针类型。最终就变成了一个为_main__的无符号指针。
2.2.2、MP_ROM_PTR
MP_ROM_PTR的 定义为:
#define MP_ROM_PTR(p) (p)
2.3、MICROPY_PORT_BUILTIN_MODULES
MICROPY_PORT_BUILTIN_MODULES的定义在mpconfigport.h文件内为
#define MICROPY_PORT_BUILTIN_MODULES \
{ MP_OBJ_NEW_QSTR(MP_QSTR_esp), (mp_obj_t)&esp_module }, \
{ MP_OBJ_NEW_QSTR(MP_QSTR_esp32), (mp_obj_t)&esp32_module }, \
{ MP_OBJ_NEW_QSTR(MP_QSTR_utime), (mp_obj_t)&utime_module }, \
{ MP_OBJ_NEW_QSTR(MP_QSTR_uos), (mp_obj_t)&uos_module }, \
{ MP_OBJ_NEW_QSTR(MP_QSTR_usocket), (mp_obj_t)&mp_module_usocket }, \
{ MP_OBJ_NEW_QSTR(MP_QSTR_machine), (mp_obj_t)&mp_module_machine }, \
{ MP_OBJ_NEW_QSTR(MP_QSTR_network), (mp_obj_t)&mp_module_network }, \
{ MP_OBJ_NEW_QSTR(MP_QSTR__onewire), (mp_obj_t)&mp_module_onewire }, \
{ MP_OBJ_NEW_QSTR(MP_QSTR_uhashlib), (mp_obj_t)&mp_module_uhashlib }, \
MP_OBJ_NEW_QSTR的定义与MP_ROM_QSTR相同。 ??以上就是将module添加进固件的指针指向的原理。
10.添加myled 模块(stm32F411平台 1个参数)
编写myled.c
#include "stdint.h"
#include "stdio.h"
#include "py/obj.h"
#include "py/runtime.h"
#include "py/mphal.h"
#include "py/runtime.h"
STATIC mp_obj_t mp_myled_function(mp_obj_t myms)
{
uint32_t Myms = mp_obj_get_int(myms);
mp_hal_pin_low(pin_C13); //点亮灯
mp_hal_delay_ms(Myms);
mp_hal_pin_high(pin_C13);//熄灭灯
return mp_const_none;
}
STATIC MP_DEFINE_CONST_FUN_OBJ_1(My_mp_myled_function, mp_myled_function);
STATIC const mp_rom_map_elem_t modnormal_globals_table[] = {//映射表包含多个函数功能
{MP_OBJ_NEW_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_myled_module)},
{MP_OBJ_NEW_QSTR(MP_QSTR_myledfunction), MP_ROM_PTR(&My_mp_myled_function)},
};
STATIC MP_DEFINE_CONST_DICT(mp_module_modnormal_globals, modnormal_globals_table);
const mp_obj_module_t mp_module_myled_mod = {
.base = {&mp_type_module},
.globals = (mp_obj_dict_t *)&mp_module_modnormal_globals,
//globals成员是mp_obj_dict_t类型的,里面存储的是功能映射关系。
};
MP_REGISTER_MODULE(MP_QSTR_myled_module, mp_module_myled_mod, 1);
文件mymodule.c 内创建了一个mp_module_myled_mod的模块,它包含了一个funtion。
注册module
我们要把我们定义的module注册到micropython中去,这个是在mpconfigport.h文件中修改,找到MICROPY_PORT_BUILTIN_MODULES 定义的地方按照格式添加我们定义的module:
// extra built in modules to add to the list of known ones
extern const struct _mp_obj_module_t esp_module;
......
extern const struct _mp_obj_module_t mp_module_onewire;
extern const struct _mp_obj_module_t mp_module_myled_mod;//声明模块
#define MICROPY_PORT_BUILTIN_MODULES \
{ MP_OBJ_NEW_QSTR(MP_QSTR_esp), (mp_obj_t)&esp_module }, \
......
{ MP_OBJ_NEW_QSTR(MP_QSTR_uhashlib), (mp_obj_t)&mp_module_uhashlib }, \
{ MP_OBJ_NEW_QSTR(MP_QSTR_myled_module), (mp_obj_t)&mp_module_myled_mod }, \
这里就添加两行代码,第一行是应用外部定义的结构体时要先有声明。第二个需要解释一下,MP_QSTR_myled_module这个名字要改成自定义的,MP_QSTR_这个一定要保留,后面的myled_module就是在python中显示的module名字。
在Makefile文件中更新SRC_C和SRC_QSTR
在SRC_C中添加myled.c 文件, 将新增代码编译firmware中.
在SRC_QSTR中添加myled.c 文件, 让build过程扫描myled.c 文件, 从中提取字符串关键字增加到micropython的qstr列表当中.
第二步操作是根据官方文档的说明进行的操作, 正式的添加qstr . ??然后再修改一下Makefile文件,将myled.c编译进去就可以了。
按照官方开发文档的说明, 到这里就可以重新 make , 然后可以在 micropython 的交互命令行中引用led 模块的.
编译并烧录,在MicroPython中使用import myled 测试是否成功,不报错就是成功了。
通过编译之后, 下载firmware到电路板上, 复位后通过终端命令行交互, 能够成功识别led模块, 并且在板子上确实看到led灯受控亮灭了.
查看micropython已安装的库
help(‘modules’),已经能够查看到安装的模块。
在MicroPython中使用import myled_module 测试是否成功,不报错就是成功了。
模块中有闪灯的代码,运行模块中的函数 , 灯亮延时3S,后熄灭。
import myled_module as myled
myled.myledfunction(3000)
查看模块中的函数
11. 总结
Micropython入手半年,从最开始的 OpenMV开始, 根据开源资料自行制作了MV4, 成功后,对里面的软件代码产生兴趣,目前已经成功将开源 git 上的代码移植到ST的 F407 F411H743 三个硬件平台。其中OpenMV还移植了 miniH743开发板、和自做的 H743平台。具体的移植过程记录在 micropython下载及安装编译过程 micropython下载及安装编译过程 和 openmv下载及安装编译过程。
可能对于熟练使用C语言的编程方式,MP是一种新的方式,但是对于新手,MicroPython是极其友好的方式;同时ST对于 MP的支持是不遗余力的,同时未来对MP的使用,应该是越来越多的,2022年的智能汽车竞赛,已经允许使用MP作为开发语言。相信不久的将来,MP应该与python一样,使用的人会越来越多。
|