MicroPython中Python与C代码是如何交互的
1、Python代码和C代码字符关联
本节就来介绍下模块扩展过程中Python字符是怎么和C字符(例如:模块名,类名,变量名,函数名等)关联起来的?以ADC 类名字符来举例说明下关联过程的:
C代码字符ADC
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)}, ……
};
Python代码字符ADC
from driver import ADC
字符关联方法
通过执行脚本命令:(脚本路径:components/py_engine/engine/genhdr/gen_qstr.py)
python gen_qstr.py ADC
生成唯一的字符映射关系:
QDEF(MP_QSTR_ADC, (const byte*)"\x63\x03" "ADC")
将结果存放到文件 components/py_engine/adapter/haas/genhdr/qstrdefs.generated.h中,这样就完成了字符的映射,代码执行过程中,python 引擎通过qstrdefs.generated.h 信息,来映射关联字符。
2、Python代码调用C代码
Python引擎运行的时候,会寻找宏定义MICROPY_PORT_BUILTIN_MODULES,将它作为组件扩展的入口,netmgr_module和 driver_module 就是前文定义的扩展模块的名字。
代码路径:components/py_engine/adapter/haas/mpconfigport.h
#define MICROPY_PORT_BUILTIN_MODULES \ {MP_ROM_QSTR(MP_QSTR_netmgr), MP_ROM_PTR(&netmgr_module)}, \ {MP_ROM_QSTR(MP_QSTR_driver), MP_ROM_PTR(&driver_module)},
这样C扩展组件就和Python引擎关联上了。Python 应用就可以通过import netmgr导入netmgr模块,通过from driver import ADC从模块driver 中导入ADC类并呼叫该模块/类提供的功能了。
3、C代码调用Python代码
嵌入式开发中,多数外设(如Timer,UART,GPIO等)接口的事件通知都是通过回调函数实现的。部分模块的状态通知也是通过回调实现的,比如网络状态变化通知功能。常规的基于C语音的开发,ISR(中断回调函数)工作在系统进程/线程的上下文,回调通知机制容易控制。但Python应用工作在Python虚拟机进程的上下文,中断回调函数发生在C底层进程的上下文,C进程同Python虚拟机进程是相互隔离的,所以C语言直接调用Python代码的路径是不通的。
图 1 C和Python ISR处理过程
MicroPython提供了两种方式实现C底层进程到Python虚拟机进程的通信,实现了C语言通知Python应用层的功能。接下来本节以Timer模块为例,详细分析两种回调机制的原理以方便开发者扩展自己的模块到MicroPython系统中,共同丰富发展Python轻应用生态。
首先要创建并初始化ISR线程虚拟化环境,以便ISR线程能获得跟Python虚拟机进程使用相同的上下文。
//1 获取并保存当前虚拟机线程状态
void *old_state = mp_thread_get_state();
//2 分配并设置ISR线程的状态信息
mp_state_thread_t ts;
mp_thread_set_state(&ts);
//3 初始化ISR新虚拟机线程的堆栈指针, +1表示在跟指针扫描中需要包含ts信息
mp_stack_set_top(&ts + 1);
//4 根据ISR线程的堆栈大小设置新线程虚拟机堆栈大小,堆栈大小依赖于ISR线程堆栈,在不同的模块中该值会
// 有所变化。(痛点1)
mp_stack_set_limit(1024);
//5 传递当前虚拟机线程本地和全局状态信息到新创建线程中
mp_locals_set(mp_locals_get());
mp_globals_set(mp_globals_get());
//6 禁止虚拟机线程调度,防止虚拟机切换到其他MicroPython线程
mp_sched_lock();
//7 屏蔽内存分配
gc_lock();
//8 执行MicroPython APIs回调,完成C底层到Python应用层回调 (痛点2)
mp_call_function_1_protected(callback, MP_OBJ_FROM_PTR(arg));
//9 使能内存分配
gc_unlock();
//10 使能虚拟机线程调度
mp_sched_unlock();
//11 恢复虚拟机线程状态到第一步保存的状态
mp_thread_set_state(old_state);
在C语言的ISR中调用Python线程的回调函数共计需要11步才能完成,并且存在两处痛点:
- 第4步需要评估ISR线程的堆栈大小来设置新线程虚拟环境的堆栈,不容易评估线程堆栈大小。
- 第8步需要根据ISR的回调参数数目确定函数调用,当有更多的回调参数时,需要把多个参数转换成字典变量进行回调。目前MicroPython提供两个变量的回调函数定义,如下所示:
mp_obj_t mp_call_function_1_protected(mp_obj_t fun, mp_obj_t arg);
mp_obj_t mp_call_function_2_protected(mp_obj_t fun, mp_obj_t arg1, mp_obj_t arg2);
可以看出,上述方法虽然能够实现ISR到Python应用层的回调,但是需要11步才能完成且新线程的堆栈大小不容易评估。是否可以有另外一种的机制呢?microPython提供了第二种回调机制:Looper-Handler模式。在这种模式下,ISR线程把Python应用层传过来的回调函数句柄注入到虚拟机环境并通知Python主线程,在Python主线程查询并呼叫回调函数,如此则不需要创建新虚拟线程。
MicroPython Looper-Handler模式提供了mp_sched_schedule函数,允许ISR注册回调函数到虚拟机环境中。Python主线程在解析执行脚本代码的时候,会检查虚拟机调度状态,进而决定是否需要执行回调函数。如下是mp_sched_schedule函数的实现:
bool MICROPY_WRAP_MP_SCHED_SCHEDULE(mp_sched_schedule)(mp_obj_t function, mp_obj_t arg) {mp_uint_t atomic_state = MICROPY_BEGIN_ATOMIC_SECTION();bool ret;//1 检查调度队列是否已满,队列未满的情况下才可以继续注入回调函数if (!mp_sched_full()) {if (MP_STATE_VM(sched_state) == MP_SCHED_IDLE) {//2 设置调度状态,方便后续虚拟机主线程查询执行MP_STATE_VM(sched_state) = MP_SCHED_PENDING;}//3 增加调度队列的索引并注入回调函数uint8_t iput = IDX_MASK(MP_STATE_VM(sched_idx) + MP_STATE_VM(sched_len)++);MP_STATE_VM(sched_queue)[iput].func = function;MP_STATE_VM(sched_queue)[iput].arg = arg;//4 回调注入成功,返回trueret = true;} else {//5 调度队列已满,返回falseret = false;}MICROPY_END_ATOMIC_SECTION(atomic_state);return ret;
}
MicroPython通过预编译参数MICROPY_SCHEDULER_DEPTH设定调度队列的深度,默认情况下为4。
// Maximum number of entries in the scheduler
#ifndef MICROPY_SCHEDULER_DEPTH
#define MICROPY_SCHEDULER_DEPTH (4)
#endif
HaaS轻应用封装了MICROPY_EVENT_POLL_HOOK宏定义,在REPL(交互式解释器)模式或其他情形下需要立刻执行回调函数的时候调用该宏,以触发虚拟机线程调用 mp_handle_pending(true) ,完成对mp_handle_pending_tail函数的调用,最终实现对注入到调度队列函数的回调。这个宏定义的代码实现如下所示:
#define MICROPY_EVENT_POLL_HOOK \do { \extern void mp_handle_pending(bool); \mp_handle_pending(true); \MICROPY_PY_USOCKET_EVENTS_HANDLER \MP_THREAD_GIL_EXIT(); \MP_THREAD_GIL_ENTER(); \} while (0);
// A variant of this is inlined in the VM at the pending exception check
void mp_handle_pending(bool raise_exc) {if (MP_STATE_VM(sched_state) == MP_SCHED_PENDING) { // 检查调度状态是否有被设定为MP_SCHED_PENDING状态mp_uint_t atomic_state = MICROPY_BEGIN_ATOMIC_SECTION();// Re-check state is still pending now that we're in the atomic section.if (MP_STATE_VM(sched_state) == MP_SCHED_PENDING) {mp_obj_t obj = MP_STATE_VM(mp_pending_exception);if (obj != MP_OBJ_NULL) {...}mp_handle_pending_tail(atomic_state); // 呼叫回调函数} else {MICROPY_END_ATOMIC_SECTION(atomic_state);}}
}
在lexer(词法分析器)模式下可以直接调用mp_handle_pending_tail函数实现回调触发,本节不做详细的分析,mp_handle_pending_tai的代码实现如下所示。
// This function should only be called by mp_handle_pending,
// or by the VM's inlined version of that function.
void mp_handle_pending_tail(mp_uint_t atomic_state) {MP_STATE_VM(sched_state) = MP_SCHED_LOCKED;if (!mp_sched_empty()) {mp_sched_item_t item = MP_STATE_VM(sched_queue)[MP_STATE_VM(sched_idx)];MP_STATE_VM(sched_idx) = IDX_MASK(MP_STATE_VM(sched_idx) + 1);--MP_STATE_VM(sched_len);MICROPY_END_ATOMIC_SECTION(atomic_state);mp_call_function_1_protected(item.func, item.arg);} else {MICROPY_END_ATOMIC_SECTION(atomic_state);}mp_sched_unlock();
}
从上面的介绍可以看出第二种方式仅需要调用一个函数即可实现回调函数的注入,对开发者来说很方便。下面贴出Timer模块中ISR函数的示例代码供读者参考。
STATIC void driver_timer_isr(void *self_in) {driver_timer_obj_t *self = (driver_timer_obj_t*)self_in;if (self->callback != mp_const_none) {bool ret = mp_sched_schedule(self->callback, MP_OBJ_FROM_PTR(self));if(ret == false) {printf("[utility]: schedule queue is full !!!!\r\n");}}
}
4、小结
HaaS团队通过组件扩展的方式将底层HaaS丰富的软硬件积木能力封装成Python库,供Python应用层代码直接使用,大大提高了Python轻应用程序的产品化效率。
开发者支持
如需更多技术支持,可加入钉钉开发者群,或者关注微信公众号。
GITHUB: https://github.com/alibaba/AliOS-Things/tree/rel_3.3.0
GITEE: https://gitee.com/organizations/alios-things/projects
CODECHINA: https://codechina.csdn.net/alios-things/AliOS-Things/-/tree/rel_3.3.0
更多技术与解决方案介绍,请访问HaaS官方网站https://haas.iot.aliyun.com。
MicroPython中Python与C代码是如何交互的相关推荐
- 利用mem数组完成MM32 的 MicroPython中UART1的(REPL)的交互
简 介: 利用了UART中的CSR的RXVAL标志位,可以判断有新的byte从REPL中获得,通过查询该标志位,可以实现通过REPL(UART1)上位机发送的信息.进而可以提高软件调试效率.在程序中添 ...
- 为什么在MM32中的MicroPython中无法打开二进制文件呢?
简 介: 对于在MM32F3277的MicroPython下, 对于SD卡中的文件按照二进制方式进行读写进行测试,发现MicroPython下无法支持对于二进制文件的操作.根据现在这种测试,需要找到具 ...
- python中的object是什么意思_Python object类中的特殊方法代码讲解
python版本:3.8class object: """ The most base type """ # del obj.xxx或del ...
- 在windows中python安装sit-packages路径位置 在Pycharm中导入opencv不能自动代码补全问题
一.在windows中python安装sit-packages路径位置 C:\Users\shl\AppData\Local\Programs\Python\Python36\Lib\site-pac ...
- python数据预处理代码_Python中数据预处理(代码)
本篇文章给大家带来的内容是关于Python中数据预处理(代码),有一定的参考价值,有需要的朋友可以参考一下,希望对你有所帮助.1.导入标准库import numpy as np import matp ...
- maya python插件_Maya中Python代码插件编译技术视频教程
本教程是关于Maya中Python代码插件编译技术视频教程,时长:16分,大小:45 MB,MP4高清视频格式,教程使用软件:Maya,附源文件,共1个章节,作者:Chayan Vinayak,语言: ...
- python中使用什么表示代码块、不需要使用大括号_Python3 基础语法
Python3 基础语法 阅读 (129) | 发布于 2020-05-11 18:12:32 编码 # -*- coding: utf-8 -*- 这是代码的编码方式.不是程序要处理的数据的编码方式 ...
- Python深度学习一书中: 8.4代码VAE在tensorflow2.0错误‘lambda_1/random_normal/shape‘的解决方案
Python深度学习一书中: 8.4代码VAE在tensorflow2.0下会有Duplicate node name in graph: 'lambda_1/random_normal/shape' ...
- python海龟画图代码大全_python海龟库(turtle)中颜色的表示方法
在python海龟库中,表示颜色的方法有很多,总体来讲共分成以下两类. 1. 用字符串表示颜色 在turtle库中,我们常常用颜色所对应的英文单词来直观的表示颜色. 例如:"red" ...
最新文章
- STL中的map、unordered_map、hash_map
- 常用的130个vim命令
- Leetcode题库 136.只出现一次的数字(异或 C实现)
- 【oracle】关于处理小数点位数的几个oracle函数
- IXMLDOMDocument中的load方法返回值有BUG
- mysql utf8 4位_mysql中utf8和utf8mb4区别
- pq 中m函数判断嵌套_压轴题的热点,二次函数与几何的结合,谁会谁吃香
- php大量数据库抽象,PHP系列(十二)数据库抽象层pdo
- react-native viewpager用法
- D-Link 修复多个硬编码密码漏洞
- Make a Crystal UVA - 11014 (容斥定理)
- ps 显示当前进程的状态
- python投资_python计算投资
- 2020-08-05
- 网络安全先进技术与应用发展系列报告 用户实体行为分析技术(UEBA)
- Java 线程池常见误区
- OSNet 论文翻译
- git常用命令(持续更新)
- 好用的Chrome插件大全网站:插件小屋
- KNN算法原理与自实现电影分类