一款自研 Python 解释器
项目简介:
PikaScript 是一个完全重写的超轻量级 python 引擎,具有完整的解释器,字节码和虚拟机架构,可以在少于 4KB 的 RAM 下运行,用于小资源嵌入式系统。相比同类产品,如 MicroPython,LuaOS 等,资源占用减少 85%以上。 入选 2021 年度 Gitee 最有价值开源项目,加入 RT-Thread 嵌入式实时操作系统编程语言类软件包。 在 CH32V103 RISC-V 开发板上完成了 PikaScript 的部署,并为 CH32V103 提交了 PikaSciprt 标准 BSP 和驱动模块包,并完成了交互式运行的驱动。
硬件说明:
RT-Thread 使用情况概述:整个方案涉及的技术栈有:RT-Thread 线程和定时器, 编译原理、字节码设计、虚拟机设计、PikaScript 部署技术和驱动模块开发技术等等。通过这个作品,扩充了 PikaScript 的 BSP 支持列表,验证了 PikaScript 和 rt-thread 的兼容性,验证了 PikaScript 在小容量(64Kb)RISC-V 架构的部署能力和兼容性。内核部分:使用了线程、定时器 。
软件包:PikaScript 软件包硬件使用了 RTT 大赛提供的 CH32V103 开发板,使用了板上的 LED 资源用于指示脚本运行状态,为 GPIO 硬件开发了 Python 脚本模块,用于测试脚本驱动拓展功能。
软件说明:
0.摘要 PikaScript 是一个完全重写的超轻量级 python 引擎,具有完整的解释器,字节码和虚拟机架构,可以在少于 4KB 的 RAM 下运行,用于小资源嵌入式系统。相比同类产品,如 MicroPython,LuaOS 等,资源占用减少 85%以上。入选 2021 年度 Gitee 最有价值开源项目,加入 RT-Thread 嵌入式实时操作系统编程语言类软件包。本项目在 CH32V103 RISC-V 开发板上完成了 PikaScript 的部署,为 CH32V103 提交了 PikaSciprt 标准 BSP 和驱动模块包,并完成了交互式运行的驱动。
1.方案选型——CH32V103 运行 Python 脚本,并不好办
首先我们需要选择一个能够在 CH32 上运行的嵌入式 Python 解释器。能够在 flash 为 64Kb 的 RISC-V MCU 上部署 Python 解释器,需要有极小的编译体积,还不能依赖于 ARM 架构的独享技术。首先排除通用 Python 解释器 CPython,不说 CPython 需要依赖 linux,单是体积就可以排除。其次在嵌入式领域大火的 MicroPython 技术是有可能选用的备选项,但是 MicroPython 在 ARM 平台需要最少 128Kb 的体积,而 RISC-V 平台的 GCC 编译器优化成熟度不如 ARM 平台,所以编译体积只会更大不会更小,所以 MicroPython 不能在本次的 CH32V103 平台部署。好了,不卖关子了,能够在 CH32V103 平台部署的 Python 解释器,只有我目前在开发的 PikaScript 超轻量级 Python 解释器,(如果还有其他方案,请批评指正,我麻溜修改)。虽然相对于 MicroPython,PikaScript 没有那么完整的标准库支持,但基本的运行时对象、控制流、交互式运行都是可以实现的,且 PikaScript 的跨平台能力非常好,在极限的依赖管理策略下,PikaScript 只依赖 LibC,在任何平台都几乎没有依赖缺失问题,或许还能够运行在 FPGA 软核中(理论上可行,未验证)。另外感谢 Gitee 提供的开源平台,PikaScript 刚刚被 Gitee 评委大佬们选入 GVP——最有价值开源项目,所以如果你现在打开 Gitee 首页,大概率可以看到 PikaScript 的金色牌牌。
PikaScript 还入选了 rt-thread 软件包,rt-thread 真的是非常有活力的开源社区。
PikaScript 严苛的依赖管理策略,使得部署非常轻松,这是跨平台,易部署的特点。但是单纯的易部署并没有什么用,如果难以拓展功能,就只是一个花瓶而已。我们知道在 MCU 开发领域,一直是 C 语言的天下,C 语言的生态占据 MCU 开发的 80%以上,大部分 MCU 都有厂家提供的 C 语言开发套件,因此 MCU 平台的 Python 解释器,最重要的拓展手段,就是绑定 C 语言的原生库,将 C 语言库绑定为 Python 模块,这通常被称为 Python 的 C 模块。为 MicroPython 绑定 C 语言模块与通用的 CPython 类似,需要将 C 库编译为静态库,再进行链接,链接时需要手动注册许多全局表,且制作 C 模块的过程中需要使用大量 linux 平***有的工具,这对于以 Windows 平台开发为主的 MCU 工程师来说,门槛很高。而 PikaScript 可以在 MCU 工程师熟悉的 Windos 平台完成 C 模块的开发,通过自研的模块预编译器,能够自动完成模块的注册工作,C 模块的开发者需要提供的仅仅是一个用 Python 写成的模块的调用 API 而已,预编译器会自动将这个 Python 文件预编译为 C 文件,完成模块的链接和注册。而只要使用正确的命名,原生的 C 的函数就能够被自动注册进模块中,供解释器调用,也不需要编译静态库。让 PikaScript 在 CH32V103 跑起来,意思也就是开发一个能在 CH32V103 运行的 PikaScript 固件。我们先看一下一个 PikaScript 固件有哪些部分。
在图中标注黄色的部分是我们需要制作的,而绿色部分是跨平台的,我们只需要拉取源码进行编译即可,不需要修改。从下往上看,首先是需要一份 PikaScript 的 BSP,BSP 也就是板级支持包,这通常只要将厂商提供的 MCU 的标准库稍加整理即可获得。然后是 PikaScript 的启动器,这包含了固件入口 main.c,以及基本的设备初始化代码,包括对 printf 的支持。有了 BSP 和启动器,就已经可以运行 PikaScript 的固件了,只不过还只能使用 PikaScript 提供的标准库功能和 Python 的基本语法,还不能使用 MCU 上搭载的外设资源。为了使用 CH32V103 的外设资源,我们还需要开发 CH32V103 的驱动模块,在这个项目中,我们开发了 GPIO 的驱动模块和基于 rt-thread tick 定时器的延时模块。最上层的就是我们要运行的 Python 脚本了,模块预编译器也可以处理 Python 脚本,根据脚本中导入的模块来自动裁剪固件,在脚本中没有 import 的固件会被自动裁剪掉,我们可以在 main.py 中选择要加入固件的模块,以及编写系统初始化后最先运行的 Python 脚本,将其烧录进固件中。
2.制作 BSP 和启动器——先跑起来再说
BSP 通常是用芯片的原厂提供的例程制作的,在这个项目中,我们就使用 CH32V103 的官方例程中的 uart_printf 和 MounRiver River Studio 生成的 rt-thread 模板来制作。完成了对 rt-thread 模板的一些剪裁之后,再加入 printf 的初始化函数,对项目稍作整理,BSP 部分就完成了。PikaScript 的启动器的制作也比较简单,在 main.c 中添加 #include “pikaScript.h”并调用 pikaScriptInit()函数即可启动 PikaScript。pikaScript.h 和 pikaScriptInit()都是由预编译器自动生成的,在制作启动器之前,需要拉取 PikaScript 的源码。PikaScript 官方(其实就是我自己)提供了一个包管理工具,只需要编写 requestment.txt,就可以从 gitee 中自动拉取相应版本的源码和模块。在拉取内核源码时,预编译器也会自动被拉取下来,我们在 main.py 中写入 import PikaStdLib,然后用我们使用拉取下来的预编译器进行预编译,就能得到 pikaScriptInit()函数了。包管理工具不仅可以拉取内核,还可以拉取模块,也就是说我们自己制作的 CH32V103 的驱动模块,也可以挂到 PikaScript 模块库中,进行自动拉取。
BSP 和启动器的制作我录制了一个视频教程,想要了解细节或者想自己制作 BSP 的大佬可以看视频了解。https://www.bilibili.com/video/BV1Cq4y1G7Tj
3.制作 CH32V103 的驱动模块
接下来我们制作 CH32V103 的驱动模块,使得 CH32V103 上面的外设资源能够被 Python 脚本调用到。在这个项目中,我们制作了一个 PikaScript 的标准设备驱动,什么是标准设备驱动呢?我们先从其他的脚本技术说起,比如 MicroPython,并没有统一的外设调用 API,这使得用户在使用不同的平台时,都需要重新学习 API,比如下面这个是 MicroPython 在STM32F4 平台驱动 GPIO 的代码。
这个是 ESP8266 的
可以明显看到在选择 pin 的管脚时,一个用的是字符串,而另一个用的是整型数,驱动的 API 标准很混乱。有没有什么办法,能够统一外设的 API,使得用户只需要熟悉一套 API,就能够在任意平台通用呢?方法是有的,就是 PikaStdDevice 标准设备驱动模块!
PikaStdDevice 是一个抽象的设备驱动模块,定义了所有的用户 API,而各个平台的驱动模块只要从 PikaStdDevice 继承,就能够获得一模一样的用户 API,而 PikaStdDevice 内部会间接调用平台驱动,通过多态特性重写底层的平台驱动,就可以在不同的平台工作了!以 GPIO 模块为例,以下是 PikaStdDevice 定义的用户 API。
以下是 PikaStdDevice 需要重写的平台驱动
而我们要制作的 CH32V103 的 GPIO 模块,就从标准驱动模块中继承。
通过这个方法,我们就可以让 STM32 的驱动模块、CH32 的驱动模块、ESP32 的驱动模块有着一模一样的用户 API!用户只要熟悉了一套 API,就可以轻松使用支持了 PikaScript 标准驱动模块的所有平台!这才是真正的跨平台!下面是部分被注册在驱动模块里面 C 原生驱动函数。
驱动模块的开发,我也制作了两个视频,供想要了解细节的大佬们参考。
https://www.bilibili.com/video/BV1aP4y1L7pi
https://www.bilibili.com/video/BV1Jr4y117Z8
4.支持交互式运行
PikaScript 不依赖文件系统,只要传入字符串就可以运行,所以只要制作支持字符串读取的串口驱动,就可以支持交互式运行了!下面是本项目中支持交互式运行的驱动代码。
5.main.py 初始化脚本
最后我们编写一段用 Python 写成的初始化脚本,在固件启动后运行,初始化 GPIO,并且获得一个系统对象,用于提供延时功能。在初始化结束后,led 闪烁 10 次,并打印 hello pikascript!编写好初始化脚本后,用预编译器就可以集成在固件中了。
下面是预编译器生成的初始化函数。
项目地址:PikaScript-CH32V103 参赛项目仓库:
https://gitee.com/lyon1998/ch32v103-pika
PikaScript 总仓库:
https://gitee.com/lyon1998/pikascript
https://github.com/pikastech/pikascript
演示效果:在演示视频中,演示了 PikaScript 的启动和交互式运行,包括:1.测试了 PikaScript 的 Python 脚本交互式运行功能。2.使用 led 对象的 high()方法和 low()方法,控制 IO 的电平,进而控制 LED 灯 3.测试了 PikaScript 解释器对 Python 变量的动态创建的支持。4.测试了 PikaScript 对 Python 标准库函数 print 的支持,包括打印整型数和字符串数。5.测试了 PikaScript 解释器对运算符以及组合运算的支持。6.测试了 PikaScript 解释器对条件运算符的支持和对控制流的支持。7.测试了 PikaScript 对多行 Python 脚本交互式运行的支持。8.测试了 sys 对象的 delay()系统方法,该方法基于 rt-thread 延时函数,验证了与 rt-thread 操作系统的兼容性。9.循环打印 1-1000 整型数,测试了脚本的运行速度。
版权声明: 本文为 InfoQ 作者【攻城狮Wayne】的原创文章。
原文链接:【http://xie.infoq.cn/article/55077c088caafbb0a494713ac】。文章转载请联系作者。
评论