汉扬编程 C语言入门 MicroPython 中用C语言扩展模块教程(一)

MicroPython 中用C语言扩展模块教程(一)

1. 概要介绍

MicroPython 中用C语言扩展模块教程(一)

假设你已经基本入门并喜欢上了MicroPython,还在某款开发板上试验了一些功能特性,比如学习了如何操作加速度计、按键、温度传感器,还尝试了通过I2C、SPI、UART或CAN接口与其它设备进行通信。也许,你还自己设计并实现了某python函数,将其编译为固化模块,并烧写到单片机中。另外,还尝试过在python中嵌入汇编代码,以便获得更快的代码执行速度。

所有这些,也许都还不够,你希望能以某种特殊方式存取外设设备,或需要一些特殊功能函数,但发现直接用python代码实现需要耗费太多RAM资源,或者执行起来太耗时,但若嵌入汇编代码又显得特别麻烦。或者,你不想用python代码实现全部功能,因为这使得源码很容易被其他人读取,所以,希望能将实现细节隐藏起来,同时又能保留python代码的优雅性。

当遇到上述情况时,有两个选择:1> 放弃这些想法,维持现状;2> 学习MicroPython背后实现原理,用C语言实现自己的函数,类或方法。事实会证明,一旦掌握窍门后,这并不困难。其中关键便是“掌握窍门”,这也是本系列文档主旨所在。

在下面章节中,我会向你展示如何添加新功能模块,并将其注册给python解释器。我会尽量尝试采用易于理解的方式来讨论MicroPython的方方面面,用代码实现每个概念的时候也努力保持其使用最少依赖项,以使你可以立即自己动手验证。每章最后,也会列出完整代码,你可以通过简单的复制粘贴即可进行试验,同时,还包含一段演示代码,以便可以真实地验证其是如何工作的。

我会从一个简单模块开始,在其基础上持续构建,来逐渐涉及MicroPython的许多常用特性。最后需要提醒的是,本篇所有示例均可很容易地用python代码实现,并且我也不提倡将这些实验代码编译进最终系统固件。选择这些示例基于两方面考虑:1> 它们足够简单,甚至仅能称得上是原型,但也正是这样,才使你能够保持目标专一而避免分心;2> 所用的一些配套python代码也很有用,其不仅展示了运行结果,还能鼓励我们去实现其C语言版本,从而显得更为“pythonic”。

1.1 代码块

在本系列文档中,你会遇到各种形式代码块,其均有不同含义,简单列举如下:

若某代码块以感叹号开始,其内容需要在命令行中执行。若某代码块看起来像python代码,其应该在主机的python解释器中运行。若某代码块的开始处是%%micropython,也许你已经猜到了,其内容应该传递给所选的micropython解释器。其它代码段可能为C语言代码,或者makefile。它们就很容易辨认了,因为其开始处均有对应说明,还附带其文档的位置链接。2. MicroPython代码仓库

我们准备主要基于unix移植版本进行代码编程测试,所以便将其目录设置为当前工作目录。

!cd ../../micropython/ports/unix/该目录完整路径为:

micropython/v1.14/micropython/ports/unixMicroPython代码仓库本身是以模块化方式进行组织的。假设你已经采用如下命令将其克隆到了本地(或直接从github上下载完整的zip源码压缩包):

!git clone ://github.com/micropython/micropython.git从其顶层目录查看,大致结构如下:

!ls ../../../micropython/

在如上所有目录中,至少两个应予以特别关注:py目录,其包含了MicroPython解释器的实现代码文件;ports目录,其包含了具体硬件平台相关代码文件(包括unix平台),所有关于用C语言来进行MicroPython编程的问题基本都可以通过查阅研究这两个目录中源码而得以解决。

2.1 MicroPython中用户模块

从1.10版本开始, 在MicroPython固件中添加自定义C语言模块变得十分简单。你仅需要在任意目录中添加两到三个文件,然后向make命令传递两个编译标志即可:

!make USER_C_MODULES=../../../user_modules CFLAGS_EXTRA=-DMODULE_EXAMPLE_ENABLED=1 all这里,USER_C_MODULES变量为新添加代码文件所在目录(路径相对于你调用make命令的目录),CFLAGS_EXTRA对应于你的模块自定义编译标志。如果有多个自定义模块,但只想在编译时包含一部分,也可以通过该方式进行对应选择性关联。

另外,你也可以在mpconfigport.h中设置自定义模块的编译标志(该文件在你所选的移植版本的ports目录下),然后在调用make命令编译代码时便可以不带CFLAGS_EXTRA选项了:

!make USER_C_MODULES=../../../user_modules allmpconfigport.h中大致以如下方式定义:

#define MODULE_SIMPLEFUNCTION_ENABLED (1)#define MODULE_SIMPLECLASS_ENABLED (1)#define MODULE_SPECIALCLASS_ENABLED (1)#define MODULE_KEYWORDFUNCTION_ENABLED (1)#define MODULE_CONSUMEITERABLE_ENABLED (1)#define MODULE_VECTOR_ENABLED (1)#define MODULE_RETURNITERABLE_ENABLED (1)#define MODULE_PROFILING_ENABLED (1)#define MODULE_MAKEITERABLE_ENABLED (1)#define MODULE_SUBSCRIPTITERABLE_ENABLED (1)#define MODULE_SLICEITERABLE_ENABLED (1)#define MODULE_VARARG_ENABLED (1)#define MODULE_STRINGARG_ENABLED (1)这种方法能够将用户自定义代码和MicroPython仓库源码分离开,一方面使得跟踪MicroPython源代码变更变得简单,另一方面也不至于搞乱MicroPython源码本身。你还可以通过使用不带USER_C_MODULE选项的make命令重新编译系统固件,使其回退到上一个正确版本,从而排除自定义代码的影响。

3. MicroPython内部揭秘

在使用C语言实现MicroPython函数之前,应首先了解python对象在底层是如何存储的,以及如何在系统层面对其进行理解。

3.1 实体对象展现

当你在python控制台写下如下代码时:

>>> a = 1>>> b = 2>>> a + b在内存中会首先创建两个新变量:a和b,并存储其引用,然后新变量会关联上其对应值1和2。在最后一行,计算两变量之和,此时解释器必须知道如何解码存储在变量a和b之中的值:在RAM中,这两个变量只是一串字节数据,但依赖于其所属类型不同,其会关联上不同含义。由于变量类型不能在编译时确定,所以必须有一种机制能够保存该额外类型信息,这便是mp_obj_t(其在obj.h中定义)所要发挥的作用。

如果大概看一下注册给python解释器的C语言函数代码,你会看到许多类似如下处理:

mp_obj_t some_function(mp_obj_t some_variable, …) { // some_variable is converted to fundamental C types (bytes, ints, floats,pointers, structures, etc.) …}类型为mp_obj_t的变量被传递给函数,函数也以mp_obj_t类型对象返回结果。这是怎么回事呢?本质上来讲,mp_obj_t并不神秘,其仅仅是8字节长的一段内存,所有实体类对象均编码于此。实际运行时,有各种各样具体的对象编码方式。比如,在A编码中,整数可表示为如下对象:其8字节表示的最右比特位为1,可以通过将整个8字节右移1个比特位进行掩码来取得其整数值,而对于B编码,变量与0x03进行位与操作的结果为0x03则表示其为整数类型,具体整数值可通过将其8字节表示右移两位便可取得。

3.2 类型检查

幸运的是,我们无需关心对象内部具体是如何表示以及需要怎样进行移位操作,而是已经有一些预定义好的宏操作可用。比如,若要检查some_variable是否为整数,可以检查如下操作所返回的布尔值即可:

MP_OBJ_IS_SMALL_INT(some_variable)存储在some_variable中的整数值可以通过如下调用获取:

int value_of_some_variable = MP_OBJ_SMALL_INT_VALUE(some_variable);这些解码步骤需要在函数体中,我们实际使用C语言本地数据类型开始具体工作之前进行。一旦函数执行完毕,需要返回mp_obj_t类型变量,以使解释器可以处理其执行结果(比如在控制台中显示,或者将其推入下一个执行指令)。此时,编码操作可以通过如下调用完成:

mp_obj_new_int(value_of_some_variable)更为通用的类型操作可以通过宏mp_obj_is_type来进行,其将数据对象作为第一个参数,类型指针作为第二个参数。比如,若需要检查some_variable是否为一个元组,可以通过如下形式进行:

mp_obj_is_type(some_variable, &mp_type_tuple)其中,可用的数据对象类型均定义在obj.h中,形式为mp_type_+python数据类型。大部分情况下,你不必一一查找,根据该规则即可对应使用。另外,需要提醒的是,自定义新的python数据类型也是可以的,一旦正确定义后,也可以使用mp_obj_is_type进行相关操作,后面我们再对这一点进行具体讨论。

mp_obj_is_type(myobject, &my_type)3.3 python常量

python中的常量True(对应C语言中mp_const_true)、False(对应C语言中mp_const_false)和None(对应C语言中mp_const_none)也在obj.h中有定义。这些均是mp_obj_t类型对象,当某函数执行完毕需要返回时,也可以使用这些值。

注:下一节让我们来真正动手实现自己的第一个模块吧,欢迎关注。。。

本文来自网络,不代表汉扬编程立场,转载请注明出处:http://www.hyzlch.com/cjia/7077.html

(new创建对象)C++编程笔记:C++用new与不用new创建对象的区别

C语言备受争议的冷门知识goto语句,\”慎用\”非\”禁用\”

发表评论

您的电子邮箱地址不会被公开。 必填项已用*标注

返回顶部