浅析MicroPython系统底层回调机制

在线体验各类最新模型,更有模型 免费Token 额度领取!
立即体验
简介: 浅析MicroPython系统底层回调机制

嵌入式开发中,多数外设接口的事件通知都是通过回调函数实现的,这体现在Timer,UART,GPIO等外设。部分模块的状态通知也是通过回调实现的,比如网络状态。

常规的基于C语音的开发,ISR(中断回调函数)工作在系统进程/线程的上下文,回调通知机制容易控制。但是在MicroPython中,python应用工作在虚拟机进程的上下文,中断回调函数发生在C底层进程的上下文,C进程同python虚拟机进程是相互隔离的,所以直接的调用是不通的。
 



MicroPython提供了两种方式实现C底层进程到Python虚拟机进程的通信,实现了底层回调函数到Python应用层的通知。

接下来我们以Timer模块为例,详细分析两种回调机制的原理,以便大家扩展自己的模块到MicroPython系统中,共同丰富发展python轻应用生态。

 

1、创建ISR线程虚拟化环境


创建并初始化ISR线程虚拟化环境,以便ISR线程能获得跟Python虚拟机进程相同的上下文。
 

  1. //1 获取并保存当前虚拟机线程状态

  2. void *old_state = mp_thread_get_state();

  3. //2 分配并设置ISR线程的状态信息,后续初始化均作用在该线程状态上

  4. mp_state_thread_t ts;

  5. mp_thread_set_state(&ts);

  6. //3 初始化ISR新虚拟机线程的堆栈指针, +1表示在跟指针扫描中需要包含ts信息

  7. mp_stack_set_top(&ts + 1);

  8. //4 根据ISR线程的堆栈大小设置新线程虚拟机堆栈大小,堆栈大小依赖于ISR线程堆栈,在不同的模块中该值会

  9. //  有所变化。(痛点1)

  10. mp_stack_set_limit(1024);

  11. //5 传递当前虚拟机线程本地和全局状态信息到新创建线程中

  12. mp_locals_set(mp_locals_get());

  13. mp_globals_set(mp_globals_get());

  14. //6 禁止虚拟机线程调度,防止虚拟机切换到其他MicroPython线程

  15. mp_sched_lock();

  16. //7 屏蔽内存分配

  17. gc_lock();

  18. //8 执行MicroPython APIs回调,完成C底层到Python应用层回调 (痛点2)

  19. mp_call_function_1_protected(callback, MP_OBJ_FROM_PTR(arg));

  20. //9 使能内存分配

  21. gc_unlock();

  22. //10 使能虚拟机线程调度

  23. mp_sched_unlock();

  24. //11 恢复虚拟机线程状态到第一步保存的状态

  25. mp_thread_set_state(old_state);

由ISR层调用到Python线程共计需要11步才能完成,并且存在两处痛点:

  • 第4步需要评估ISR线程的堆栈大小来设置新线程虚拟环境的堆栈,不容易实现。
  • 第8步需要根据ISR的回调参数数目确定函数调用,当有更多的回调参数时,需要把多个参数转换成字典变量进行回调。目前MicroPython提供两个回调函数:
     

  1. mp_obj_t mp_call_function_1_protected(mp_obj_t fun, mp_obj_t arg);

  2. mp_obj_t mp_call_function_2_protected(mp_obj_t fun, mp_obj_t arg1, mp_obj_t arg2);



可以看出,上述方法虽然能够实现ISR到Python应用层的回调,但是需要11步才能完成且新线程的堆栈大小不容易评估。

是否可以考虑一种新的机制呢?在ISR线程把Python应用层传过来的回调函数句柄注入到虚拟机环境,ISR线程仅需要通知Python主线程,在Python主线程查询并实现回调函数,如此则不需要创建新虚拟线程。这就引入第二种回调机制:Looper-Handler模式。

 

2、Looper-Handler模式

MicroPython提供了mp_sched_schedule函数,允许ISR注册回调函数到虚拟机环境中。

Python主线程在解析执行py代码的时候,在不同的状态下检查虚拟机调度状态,进而判决是否需要执行回调函数。
 

  1. bool MICROPY_WRAP_MP_SCHED_SCHEDULE(mp_sched_schedule)(mp_obj_t function, mp_obj_t arg) {

  2.    mp_uint_t atomic_state = MICROPY_BEGIN_ATOMIC_SECTION();

  3.    bool ret;

  4.  

  5.    //1 检查调度队列是否已满,队列未满的情况下才可以继续注入回调函数

  6.    if (!mp_sched_full()) {

  7.        if (MP_STATE_VM(sched_state) == MP_SCHED_IDLE) {

  8.          

  9.            //2 设置调度状态,方便后续虚拟机主线程查询执行

  10.            MP_STATE_VM(sched_state) = MP_SCHED_PENDING;

  11.        }

  12.      

  13.        //3 增加调度队列的索引并注入回调函数

  14.        uint8_t iput = IDX_MASK(MP_STATE_VM(sched_idx) + MP_STATE_VM(sched_len)++);

  15.        MP_STATE_VM(sched_queue)[iput].func = function;

  16.        MP_STATE_VM(sched_queue)[iput].arg = arg;

  17.      

  18.         //4 回调注入成功,返回true

  19.        ret = true;

  20.    } else {

  21.      

  22.        //5 调度队列已满,返回false

  23.        ret = false;

  24.    }

  25.    MICROPY_END_ATOMIC_SECTION(atomic_state);

  26.    return ret;

  27. }

 

MicroPython通过预编译参数MICROPY_SCHEDULER_DEPTH设定调度队列的深度,默认情况下为4。
 

        
        
        
  1. // Maximum number of entries in the scheduler

  2. #ifndef MICROPY_SCHEDULER_DEPTH

  3. #define MICROPY_SCHEDULER_DEPTH (4)

  4. #endif


  1. #define MICROPY_EVENT_POLL_HOOK \

  2.    do { \

  3.        extern void mp_handle_pending(bool); \

  4.        mp_handle_pending(true); \

  5.        MICROPY_PY_USOCKET_EVENTS_HANDLER \

  6.        MP_THREAD_GIL_EXIT(); \

  7.        MP_THREAD_GIL_ENTER(); \

  8.    } while (0);

  1. // A variant of this is inlined in the VM at the pending exception check

  2. void mp_handle_pending(bool raise_exc) {

  3.    if (MP_STATE_VM(sched_state) == MP_SCHED_PENDING) {

  4.        mp_uint_t atomic_state = MICROPY_BEGIN_ATOMIC_SECTION();

  5.        // Re-check state is still pending now that we're in the atomic section.

  6.        if (MP_STATE_VM(sched_state) == MP_SCHED_PENDING) {

  7.            mp_obj_t obj = MP_STATE_VM(mp_pending_exception);

  8.            if (obj != MP_OBJ_NULL) {

  9.                ...

  10.            }

  11.            mp_handle_pending_tail(atomic_state);

  12.        } else {

  13.            MICROPY_END_ATOMIC_SECTION(atomic_state);

  14.        }

  15.    }

  16. }

 

在lexer(词法分析器)模式下则会直接调用mp_handle_pending_tail函数实现回调触发,这里我们不做详细的分析,感兴趣的同学可以参考vm.c文件中的mp_execute_bytecode函数实现。
 

  1. // This function should only be called by mp_handle_pending,

  2. // or by the VM's inlined version of that function.

  3. void mp_handle_pending_tail(mp_uint_t atomic_state) {

  4.    MP_STATE_VM(sched_state) = MP_SCHED_LOCKED;

  5.    if (!mp_sched_empty()) {

  6.        mp_sched_item_t item = MP_STATE_VM(sched_queue)[MP_STATE_VM(sched_idx)];

  7.        MP_STATE_VM(sched_idx) = IDX_MASK(MP_STATE_VM(sched_idx) + 1);

  8.        --MP_STATE_VM(sched_len);

  9.        MICROPY_END_ATOMIC_SECTION(atomic_state);

  10.        mp_call_function_1_protected(item.func, item.arg);

  11.    } else {

  12.        MICROPY_END_ATOMIC_SECTION(atomic_state);

  13.    }

  14.    mp_sched_unlock();

  15. }

 

总结

 

至此我们完成了MicroPython中两种不同的C底层到Python应用层回调机制,可以看出第二种方式仅需要调用一个函数即可实现回调注入,极大地方便了开发者。

最后我们贴出Timer模块中ISR函数的示例代码供大家参考。
 

  1. STATIC void driver_timer_isr(void *self_in) {

  2.    driver_timer_obj_t *self = (driver_timer_obj_t*)self_in;

  3.    if (self->callback != mp_const_none) {

  4.        bool ret = mp_sched_schedule(self->callback, MP_OBJ_FROM_PTR(self));

  5.        if(ret == false) {

  6.            printf("[utility]: schedule queue is full !!!!\r\n");

  7.        }

  8.    }

  9. }

 

开发者支持

HaaS轻应用(Python)继承了Python易学易用的特点,同时提供了基于嵌入式硬件的基础库封装,让开发者可以很方便的通过交互式的环境,实时进行嵌入式开发,让嵌入式开发也变得简单方便。

如需更多技术支持,可加入钉钉开发者群,享受一对一的技术支持。

 

 

相关文章
|
SQL 开发框架 安全
Linux系统中ARMv8架构u-boot启动流程分析
Linux系统中ARMv8架构u-boot启动流程分析
1173 1
|
监控 NoSQL
JLink + GDB 调试方法
本节主要介绍嵌入式开发中常用的JLink+GDB调试方法。 调试所需软件 J-link,可以从https://www.segger.com下载对应操作系统的软件包,然后安装(注意:segger是仿真器的名字,相当常用的一款,仿真器的接口也是固定的,一般开发版上都会带有这个调试接口,如图) 运行JLinkGDBServer 按照上图中的配置,配置GDBServer,然后点击OK,进入下一个界面 注意,如果硬件连接没有问题,那么上图中的J-Link和 Device栏中显示绿色,GDB显示为红色,因为我们还没有运行GDB软件。
9507 46
|
25天前
|
SQL 人工智能 文字识别
阿里开源的 AI 代码审查工具 open-code-review 来了
阿里巴巴开源Open Code Review(OCR),融合确定性工程与LLM Agent,已服务数万开发者、发现百万级缺陷。内置NPE、SQL注入等安全规则,支持CLI、Agent集成及多平台部署,让AI代码审查更精准、稳定、可落地。(239字)
1260 1
|
8月前
|
人工智能 自然语言处理 开发者
Qoder 降价,立即生效!首购 2 美金/月
Qoder全球上线两月获数十万开发者信赖!现首购仅2美元/月,享全功能AI编程利器。集成顶尖模型,支持10万文件检索、智能体异步执行、CLI终端交互,助力高效开发。
1949 8
|
机器学习/深度学习 算法 安全
从量子芯片到纠错的漫长征途:量子计算硬件的进步与困境
从量子芯片到纠错的漫长征途:量子计算硬件的进步与困境
746 86
|
网络协议 网络安全
【网络安全 | HTTP】 gopher协议原理、语法及利用总结
【网络安全 | HTTP】 gopher协议原理、语法及利用总结
1404 0
|
人工智能 编解码 算法
Doubao-1.5-pro:字节跳动最新豆包大模型,性能超越GPT-4o和Claude 3.5 Sonnet
豆包大模型1.5是字节跳动推出的最新大模型,采用大规模稀疏MoE架构,支持多模态输入输出,具备低时延语音对话能力,综合性能优于GPT-4o和Claude 3.5 Sonnet。
3011 2
Doubao-1.5-pro:字节跳动最新豆包大模型,性能超越GPT-4o和Claude 3.5 Sonnet
|
缓存 Go C语言
使用 Python 的 ctypes 调用 C 的动态库
使用 Python 的 ctypes 调用 C 的动态库
1034 9
使用 Python 的 ctypes 调用 C 的动态库
|
存储 Serverless API
打造你的专属语音助手,基于函数计算托管 CosyVoice 语音模型
今天分享一下,基于阿里云函数计算 FC 以及 CAP(云应用开发平台),极速托管专属的 CosyVoice 应用。并且我们提供了 API 调用方案以及镜像构建源码方便您根据自己的业务任意 DIY。
1534 11

热门文章

最新文章