本文已参与「新人创作礼」活动,一起开启掘金创作之路。
为micropython添加全局模块(1)
官方提供的开发指导文档中, 描述了一个最简单的增加模块的样例:
docs.micropython.org/en/latest/d…
在本文中, 我分析官方开发指导文档中的代码撰写流程, 根据自己的理解, 创建一个新的模块. 并试图通过实践, 观察代码的实际工作效果.
在我的样例代码中, 我将要创建一个LED灯的模块, 并包含on()和off()两个函数对应开灯和关灯两个动作。
根据官方描述步骤创建一个led模块
为新模块创建一个源文件
参考官方样例的命名规范, 这里在lpc5500移植项目的目录下创建mod_led.c
PS: 原始的命名范例中没有使用下划线"_"将前缀"mod"单独分隔出来, 但我通过阅读代码发现, 增加下划线的命名方式更符合micropython的命名规范. 实际上, 在micropython的源代码中, 都是使用下划线作为命名单词的分隔符的. 我有点不明白为什么文件的命令没有使用分隔符. 按照我的开发习惯, 在规模比较大的软件项目中, 使用分隔符的命名方式可读性更好, 所以在我自己的练习代码中, 将会使用下划线作为名字之间的分隔符.
在新模块中首先编写最基本的led模块对应底层驱动的三个函数:
- led_init()
- led_on()
- 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. */
在Makefile文件中更新SRC_C和SRC_QSTR
在SRC_C中添加mod_led.c文件, 将新增代码编译firmware中.
在SRC_QSTR中添加mod_led.c文件, 让build过程扫描mod_led.c文件, 从中提取字符串关键字增加到micropython的qstr列表当中.
第二步操作是根据官方文档的说明进行的操作, 应该是比较正式的添加qstr的方式. 实际上, 在之前的研究中, 我是人为地在qstrdefsport.h文件中增加qstr关键字的. 这里终于看到官方推荐的做法了.
按照官方开发文档的说明, 到这里就可以重新make, 然后可以在micropython的交互命令行中引用led模块的. 但实际情况呢... 然并卵.
好不容易清除编译错误, 把firmware下载到板子上, 还是不能识别新模块, "import led"报错无效.
(未完待续。。。)