Toggle navigation
首页
问答
文章
积分商城
专家
专区
更多专区...
文档中心
返回主站
搜索
提问
会员
中心
登录
注册
MicroPython
C函数绑定
MicroPython中C函数与Python的绑定
发布于 2020-10-09 17:31:15 浏览:1436
订阅该版
[tocm] 这几天想给孩子做个编程玩具玩,找来找去,相中了MicroPython,这几年Python异常的火爆,原来只有程序员碰的东西,现在恨不得公司前台都得学会。 某天孩子从学校回来,带回来张表让填,我一看,竟然是编程培训,二年级开始学Python?? 好吧,我已经落伍了…… RT-Thread已经玩了一段时间了,但始终没有拿他做点东西,正好趁这个机会,用RTT+MicroPython给孩子做一款硬件编程游戏机。 设计是这样的:*&%¥@#!#¥%&)*&%#¥@#%¥*(*&…… 所以,里面很多函数需要C直接调用硬件,这就需要将C函数映射到MicroPython中,官方给出的资料太少了,按照那点少的可怜的资料,虽然搞出来了,但是还是不明白怎么实现的,于是有看了大量其他的资料,包括Python的源码都翻了,最终找到了合理的答案。 这里,贡献给大家,帮大家踩踩坑。 (声明一下,我对Python不是很熟,之前一直用的是JAVA,所以里面有些术语表达上可能有些词不达意的,还请多包含) /**************************************以下是正文**************************************/ 具体实现自己的Python接口有另种方法,一种是用现有的Python函数基础上,使用Python的语法直接封装,实现自己的功能,这种实现比较方便,就不讲了;我们主要将第二种方法,了利用C语言实现对底层硬件的操作,再把调用的方法以Python的语法开放给其他人用,就是俗称的C到Python的映射。 在此之前,我们先看一下Python的接口分类: ![1.png](/uploads/20201009/5cc91aa61b09402329fb1f73e3ef3315.png) Python中我们要实现的接口主要包含module、type和function三类,从上图的结构中能看出,module相当于JAVA中的包的概念,Python中叫啥?模块?type相当于类的概念,module和type中都可以包含function,函数。 接下来的章节中,我会在RTT的MicroPython中分别创建module、function、type进行详细讲解。 **一、 MicroPython的工作原理** _> Micropython技术是依赖Byte Code的执行,在编译阶段就将py文件先转换成MicroPython文件,在通过MicroPython-tool.py生成Byte Code,Byte Code在执行时会依赖Virtual Machine入口表,找到对应的Module入口,最终找到对应的Funcion binary code执行。其中所有的Function都通过Dictionary的形式存储,而每一个Dictionary都有自己的QSTR,Micropython有buildin的QSTR和用户扩展的QSTR。具体流程可参考如下图。_ ![771d37d4f02954d3fa33b6e5bfe5191a.png](/uploads/20201009/771d37d4f02954d3fa33b6e5bfe5191a.png) 这段我也是抄过来的,你不需要看懂,主要明白里面有一个叫QSTR的东西就行了,这玩意儿贯穿整个Python,开始的时候不明白什么意思,就没管,结果费老劲了! 他大概的意思就是说,我们在Python中使用任何一个名称的时候,都需要QSTR进行定义,包括module的名称、type的名字、function的名字等等。 ``` QDEF(MP_QSTR___main__, (const byte*)"\x8e\x13\x08" "__main__") ``` 就像是这样,其他都好理解,按格式来就行了,但是中间有个\x8e\x13\x08的东西,需要经过一些计算,而计算的方法,人家也已经给了: ``` def dbj2_hash(qstr, bytes_hash): hash = 5381 for b in qstr: hash = (hash * 33) ^ b # Make sure that valid hash is never zero, zero means "hash not com puted" return (hash & ((1 << (8 * bytes_hash)) - 1)) or 1 ``` 这个函数在prot/genhdr/gen_qstr.py中,可以直接用python运行这个文件来获得QSTR。 不过我做的时候没用这个算法,直接去 https://summergift.top/RT-MicroPython-Generator/ 输入函数名字,下面自己就算出来了,具体为啥和上面不一样就不知道了,估计是RTT中有自己的规则吧,总之大家用上面的网址计算就行了,然后把算出的QSTR贴到port/genhdr/qstrdefs.generated.h即可。 这个问题后面遇到的时候再具体说吧。 **二、 添加module** RTT的MicroPython中,为我们提供了一个自己添加函数的模板,下载好MicroPython的包后,在工程目录下有个packages,里面有micropython-v1.10.4(具体可能版本不同),进入就是MicroPython的源码。 今天我们要修改的是port/modules/user/moduserfunc.c这个文件,把我们自己写的函数添加到这个文件中。 本来我是想在这个目录下在建一个自己的文件的,但是添加后不起作用,肯定还需要改其他地方,这个放在以后慢慢研究吧。 这个章节中我们主要是创建一个module,要做的只有三件事: 1.定义module的全局字典 2.把定义的字典注册到.globals里面 3.定义module的原型 以下是源码: ``` /* * 定义mars这个module的全局字典 * 之后我们要将所有的type和function都放到这里 * 这里需要注意几点: * 1. MP_QSTR_的前缀不能改变 * 2. __name__前后有两个下划线,加上MP_QSTR_最后的下划线,MP_QSTR___name__中间的下划线是三个 * 3. MP_QSTR_mars一类的标签必须在qstrdefs.generated.h中定义过 * 4. 别少了末尾的分号 */ STATIC const mp_rom_map_elem_t mars_globals_table[] = { { MP_OBJ_NEW_QSTR(MP_QSTR___name__) , MP_ROM_QSTR(MP_QSTR_mars)} , // 定义module的名称,在python中可以用import mars直接导入了 }; // 将mars_globals_table注册到mars_globals_table.globals中去,定义mp_module_mars_globals STATIC MP_DEFINE_CONST_DICT(mp_module_mars_globals , mars_globals_table); //定义module类型 const mp_obj_module_t mp_module_mars = { .base = {&mp_type_module}, .globals = (mp_obj_dict_t*)&mp_module_mars_globals, }; ``` 完成这一步后,还需要在mpconfigport.h留下点痕迹,否则import会失败的 ``` extern const struct _mp_obj_module_t mp_module_mars; //将自己的module类型导入 ``` 找到#define MICROPY_PORT_BUILTIN_MODULES,在其中添加宏定义 有两种方式,一种是可以参考USERFUNC的定义 ``` #define MARS_PORT_BUILTIN_MODULES { MP_ROM_QSTR(MP_QSTR_mars), MP_ROM_PTR(&mp_module_mars) }, //别少了最后的逗号 ``` 然后在#define MICROPY_PORT_BUILTIN_MODULES后面加上自己的标签 另一种是直接写 ``` { MP_ROM_QSTR(MP_QSTR_mars), MP_ROM_PTR(&mp_module_mars) }, ``` 其实结构都一样。 到此为止,module添加完毕。 编译运行: ``` >>> import mars >>> type(mars)
>>> dir(mars) ['__class__', '__name__'] >>> ``` **三、 添加function** 上一章节中我们仅仅是创建了一个module,但是这个module中没有函数可用,本章节中,我会演示如何添加无参、带参、有返回值、无返回值的函数。 首先我们添加一个无参无返回值的参数,代码如下: ``` //定义函数原型 STATIC mp_obj_t mars_sayhello() { printf("Hello ,This is a function without parameters and return values.\n"); return mp_const_none; } //注册这个函数 STATIC const MP_DEFINE_CONST_FUN_OBJ_0(mars_obj_sayhello,mars_sayhello); ``` 函数原型中,永远返回mp_obj_t类型的值,如果这个函数在Python中没有任何返回值,就直接return mp_const_none。 注册函数时,MicroPython给我提供了很多个方法,因为我们没有参数,所以用了MP_DEFINE_CONST_FUN_OBJ_0,另外还可以用MP_DEFINE_CONST_FUN_OBJ_1、MP_DEFINE_CONST_FUN_OBJ_2、MP_DEFINE_CONST_FUN_OBJ_3,那对于多余3个参数的函数咋办??查了查资料,大多用的都是MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN,其他的几个宏没研究是什么意思,暂时也用不到。 将函数原型和注册完成之后,还需要在刚才定义的mars_globals_table对函数进行声明 ``` STATIC const mp_rom_map_elem_t mars_globals_table[] = { { MP_OBJ_NEW_QSTR(MP_QSTR___name__) , MP_ROM_QSTR(MP_QSTR_mars)} , // 定义module的名称,在python中可以用import mars直接导入了 { MP_ROM_QSTR(MP_QSTR_sayhello), MP_ROM_PTR(&mars_obj_sayhello) }, // 定义无参函数,首先确保MP_QSTR_sayhello在qstrdefs.generated.h中注册过 }; ``` 这里有几点需要注意的: 1.在编码过程中,用到的所有名称,必须要在qstrdefs.generated.h中定义 2.定义函数的时候用的是MP_ROM_PTR,而不是MP_ROM_QSTR 3.用printf输出(stdio的),rt_kprint不好使 4.输出的最后要加\n否则打印不出来。 运行结果: ``` >>> import mars >>> mars.sayhello() Hello ,This is a function without parameters and return values. >>> ``` 下面,我们再给这个module添加一个有两个参数和一个返回值的函数 代码基本相同: ``` //定义函数原型 STATIC mp_obj_t mars_add(mp_obj_t one , mp_obj_t two) { mp_int_t a = mp_obj_get_int(one); mp_int_t b = mp_obj_get_int(two); mp_int_t ret_val; ret_val = a + b; printf("You are calling this function, passing in two parameters, %d and %d, and the result is %d!\n",a,b,ret_val); return mp_obj_new_float(ret_val); } //注册这个函数 STATIC const MP_DEFINE_CONST_FUN_OBJ_2(mars_obj_add,mars_add); //添加定义 STATIC const mp_rom_map_elem_t mars_globals_table[] = { { MP_OBJ_NEW_QSTR(MP_QSTR___name__) , MP_ROM_QSTR(MP_QSTR_mars)} , // 定义module的名称,在python中可以用import mars直接导入了 { MP_ROM_QSTR(MP_QSTR_sayhello), MP_ROM_PTR(&mars_obj_sayhello) }, // 定义无参函数,首先确保MP_QSTR_sayhello在qstrdefs.generated.h中注册过 { MP_ROM_QSTR(MP_QSTR_add), MP_ROM_PTR(&mars_obj_add) }, // 定义有两个参数的函数,因为MP_QSTR_add已经被别人注册过了,我们不用重复注册 }; ``` 运行结果: ``` >>> import mars >>> c = mars.add(100,200) You are calling this function, passing in two parameters, 100 and 200, and the result is 300! >>> print(c) 300.0 >>> ``` 在映射中,所有的参数传递都用mp_obj_t类型,在C的原型函数中,根据需要通过mp_obj_get_XXX转换成具体类型,再参与运算。 这个函数返回一个float类型的值,所以使用mp_obj_new_float进行封装,其他类型的一律仿造mp_obj_new_XXX。 **四、 添加type** 终于到了重头戏了,做JAVA十几年了,对面向对象编程情有独钟,不太习惯面向过程的,所以在任何语言中都想找到对象这东西,Python的松散结构跟JS一样让人很不舒服(个人感觉),个人总想把一切都装箱,还好,Python提供Type这个概念。 首先我们创建一个叫做children的类,然后给这个类添加一个叫sayhello的函数。 和module不同,type得到创建稍微复杂点,大概分为4步: 1.定义type的结构体 2.定义locals_dict_type字典,并注册 3.创建type的类型结构 4.添加type的构造函数(这一步可以省略) 源码如下: ``` // 定义一个children的结构体 typedef struct _children_obj_t { mp_obj_base_t base; // 定义的对象结构体要包含该成员 char* name; // 成员函数 uint8_t age; uint8_t sex; }children_obj_t; ``` 在创建这个类型的结构体时,我们给这个类定义了三个成员变量,分别是name,age,sex,最上面的base是每个对象必须包含的,而且必须放在开头,类型必须是mp_obj_base_t,这是Python的语法规定,咱也不好破坏人家的规矩,老实就范吧。 ``` // 定义type的locals_dict_type STATIC const mp_rom_map_elem_t children_locals_dict_table[] = { }; //定义字典的宏 STATIC MP_DEFINE_CONST_DICT(children_locals_dict,children_locals_dict_table); ``` 创建字典并定义,这里我们还没有成员函数,所以字典中是空的,这里要注意一下,和module有点不同,type的名称不用放在字典中,直接写到结构体定义中即可,如下: ``` const mp_obj_type_t mars_children_type = { .base = { &mp_type_type }, .name = MP_QSTR_children, //名字要在这里定义,不是写在DICT中,同样要经过注册才行,但是这个单词已经被注册过了,所以就不用重复注册了 .make_new = mars_children_make_new, //构造函数 .locals_dict = (mp_obj_dict_t*)&children_locals_dict, //注册math_locals_dict }; ``` MP_QSTR_children的标签一样要在qstrdefs.generated.h中定义 至此,这type就定义完成了。 当然,如果有必要的话,可以定义一个构造函数,就是make_new所指向的那个函数。 ``` // 添加构造函数 STATIC mp_obj_t mars_children_make_new(const mp_obj_type_t *type, size_t n_args , size_t n_kw,const mp_obj_t *args) { mp_arg_check_num(n_args ,n_kw,1,3,true); // 检查参数个数,最少1个参数,最多3个参数 children_obj_t *self = m_new_obj(children_obj_t); // 创建对象,分配空间 self->base.type = &mars_children_type; // 定义对象类型 if(n_args >=1 ) { self->name = mp_obj_str_get_str(args[0]); } if(n_args >=2 ) { self->age = mp_obj_get_int(args[1]); } if(n_args ==3 ) { self->sex = mp_obj_get_int(args[2]); } printf("Create a new children , name:%s , age:%d , sex:%s\n" ,self->name,self->age,self->sex==0?"girl":"boy"); return MP_OBJ_FROM_PTR(self); //返回对象 } ``` 这里我写了一个比较全的成员函数,包含了传入参数与参数的使用方法,下面一点点分析 第一句用mp_arg_check_num检查构造函数的参数是否符合规定,这里我规定的是最少1个,最多3个,超出这个范围一一律报错,如果没有入参,这两个都写0就好了,框架还提供了一个MP_OBJ_FUN_ARGS_MAX,应该是最大数量的参数。 下一句是用来创建这个对象,并未对象分配内存空间。 第三句,设定了这个类的具体类型,这里就写我们定义过的mars_children_type类型 在后面几个判断,是用来根据参数的个数设置成员变量的。 mp_obj_str_get_str需要注意一下,这东西让我找的好苦,其他类型的数据都是通过mp_obj_get_XXX就能获得,唯独这个string类型的比较特殊。 最后一句,返回这个对象。 运行结果: ``` >>> import mars >>> type(mars.children)
>>> dir(mars.children) ['__class__', '__name__'] >>> d = mars.children("Aday") Create a new children , name:Aday , age:0 , sex:girl >>> d = mars.children("Claire",8) Create a new children , name:Claire , age:8 , sex:girl >>> d = mars.children("Komy",3,1) Create a new children , name:Komy , age:3 , sex:boy >>> type(d)
>>> dir(d) ['__class__'] >>> ``` 最后一步,我们给这个类添加一个sayhello的成员函数,和module类似,但有些不一样的地方。 ``` //children的成员函数 STATIC mp_obj_t mars_children_sayhello(mp_obj_t self_in , mp_obj_t name) { children_obj_t *self = MP_OBJ_TO_PTR(self_in); //从第一个参数中提取对象指针 printf("Hi %s:\n",mp_obj_str_get_str(name)); printf(" I'm %s. \n I'm %d years old. \n I'm a very lovely %s!\n", self->name,self->age,self->sex==0?"girl":"boy"); return mp_const_none; } STATIC MP_DEFINE_CONST_FUN_OBJ_2(mars_children_sayhello_obj,mars_children_sayhello); ``` 首先定义这个函数的原型,函数本身有一个入参,但是所有type的成员函数必须将mp_obj_t self_in放在第一个,所有这时候我们会得到两个入参,学习Python的时候应该也注意到了吧,不过多解释。 在注册函数原型的时候用的是STATIC MP_DEFINE_CONST_FUN_OBJ_2,不是STATIC MP_DEFINE_CONST_FUN_OBJ_1,这点是不一样的地方 然后在字典总加入这个函数 ``` STATIC const mp_rom_map_elem_t children_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_sayhello),MP_ROM_PTR(&mars_children_sayhello_obj) }, }; ``` 因为sayhello这个字符串在之前已经定义过了,所以不用重复定义。 运行结果: ``` >>> import mars >>> type(mars.children)
>>> dir(mars.children) ['__class__', '__name__', 'sayhello'] >>> d = mars.children("Claire",8,0) Create a new children , name:Claire , age:8 , sex:girl >>> d.sayhello("mars") Hi mars: I'm Claire. I'm 8 years old. I'm a very lovely girl! >>> ``` **所有注册的QSTR** ``` QDEF(MP_QSTR_mars, (const byte*)"\x68\x04" "mars") QDEF(MP_QSTR_sayhello, (const byte*)"\xec\x08" "sayhello") QDEF(MP_QSTR_children, (const byte*)"\xf6\x08" "children") ``` 有些例如add一类的,自己已经有了,就不再重复注册了 **五、结束语** 总结完了,希望对各位有帮助。 Python语言没怎么用过,只是辅导孩子的时候才偶尔看了一下,感觉天下的语言基本都是想通的,理解上应该是不成问题,只是对里面的一些机制不太了解,有时间再慢慢补吧。 里面还有很多不完善的地方,希望各位大牛补充。
2
条评论
默认排序
按发布时间排序
登录
注册新账号
关于作者
Mars.CN
这家伙很懒,什么也没写!
文章
2
回答
23
被采纳
0
关注TA
发私信
相关文章
1
请问rt-thread有没有移植micropython呢
2
micropython import 文件名的方式执行脚本问题
3
第一篇:Micropython 的起源和发展
4
第二篇:RT-Thread Micropython 简介
5
第三篇:RT-Thread Micropython 快速入门
6
第四篇:Micropython DIY 项目汇总
7
第五篇:Micropython 教程和资源
8
第六篇: RT-Thread MicroPython 学习经验和学习路线
9
RT-Thread MicroPython 最新开发板固件汇总【已失效】
10
有Mpy专门的板块啦~
推荐文章
1
RT-Thread应用项目汇总
2
玩转RT-Thread系列教程
3
国产MCU移植系列教程汇总,欢迎查看!
4
机器人操作系统 (ROS2) 和 RT-Thread 通信
5
五分钟玩转RT-Thread新社区
6
【技术三千问】之《玩转ART-Pi》,看这篇就够了!干货汇总
7
关于STM32H7开发板上使用SDIO接口驱动SD卡挂载文件系统的问题总结
8
STM32的“GPU”——DMA2D实例详解
9
RT-Thread隐藏的宝藏之completion
10
【ART-PI】RT-Thread 开启RTC 与 Alarm组件
热门标签
RT-Thread Studio
串口
Env
LWIP
SPI
AT
Bootloader
Hardfault
CAN总线
FinSH
ART-Pi
USB
DMA
文件系统
RT-Thread
SCons
RT-Thread Nano
线程
MQTT
STM32
RTC
FAL
rt-smart
ESP8266
I2C_IIC
WIZnet_W5500
ota在线升级
UART
PWM
cubemx
freemodbus
flash
packages_软件包
BSP
潘多拉开发板_Pandora
定时器
ADC
GD32
flashDB
socket
中断
Debug
编译报错
msh
SFUD
keil_MDK
rt_mq_消息队列_msg_queue
at_device
ulog
C++_cpp
本月问答贡献
踩姑娘的小蘑菇
7
个答案
3
次被采纳
张世争
8
个答案
2
次被采纳
rv666
5
个答案
2
次被采纳
用户名由3_15位
11
个答案
1
次被采纳
KunYi
6
个答案
1
次被采纳
本月文章贡献
程序员阿伟
6
篇文章
2
次点赞
hhart
3
篇文章
4
次点赞
大龄码农
1
篇文章
2
次点赞
ThinkCode
1
篇文章
1
次点赞
Betrayer
1
篇文章
1
次点赞
回到
顶部
发布
问题
投诉
建议
回到
底部