MicroPython运行原理以及如何通过C语言扩展MicroPython Module

解释器原理

解释器是能够执行用其他计算机语言编写的程序的系统软件,是一种翻译程序。它的执行方式是一边翻译一边执行,因此其执行效率一般偏低,但是解释器的实现较为简单。

解释器执行时,每次从程序的源代码中读入一个标识符。如果读入的是关键字,解释器就按照该关键字的要求执行规定的操作。不同的关键字一般在程序中对应不同的数据结构。在到达程序的结尾之前,这个过程将反复进行。

以一个Scheme语句为例

(+ (- 2 1) 3)

首先遇到(创建一个栈帧。

遇到+表明需要对后两个操作数进行加法运算。

又遇到(创建一个栈帧。

遇到-,需要对后面两个操作数进行减法运算。

然后相继遇到2和1,减法运算得到第一个操作数结果为1

遇到),带着结果执行pop操作回到上一个栈帧

然后遇到3,第一个操作数的结果为1,两个操作数进行加法运算得到结果为4

遇到),返回上级栈帧,最终结果为4

MicroPython原理

Micropython技术是依赖Byte Code的执行,在编译阶段就将py文件先转换成mpy文件,在通过mpy-tool.py生成Byte Code,Byte Code在执行时会依赖Virtual Machine入口表,找到对应的Module入口,最终找到对应的Funcion binary code执行。其中所有的Function都通过Dictionary的形式存储,而每一个Dictionary都有自己的QSTR,Micropython有buildin的QSTR和用户扩展的QSTR。具体流程可参考如下图。

avatar

每次编译前makemodulesdefs.py, makeqstrdefs.py, makeqstrdata.py脚本会收集和生成所有的QSTR,以QDEF(MP_QSTR_sizeof, (const byte*)"\x49\x73\x06" "sizeof")的形式写入genhdr/qstrdefs.generated.h文件。其中\x49\x73是字符串哈希值,\x06是字符串长度。MicroPython通过哈希值和长度进行字符串比较从而尽可能地保证性能。

qstrdefs.generated.h主要在qstr.hqstr.c中被include。

/* qstr.h */
enum {
    #ifndef NO_QSTR
#define QDEF(id, str) id,
    #include "genhdr/qstrdefs.generated.h"
#undef QDEF
    #endif
    MP_QSTRnumber_of, // no underscore so it can't clash with any of the above
};

typedef struct _qstr_pool_t {
    struct _qstr_pool_t *prev;
    size_t total_prev_len;
    size_t alloc;
    size_t len;
    const byte *qstrs[];
} qstr_pool_t;

/* qstr.c */
const qstr_pool_t mp_qstr_const_pool = {
    NULL,               // no previous pool
    0,                  // no previous pool
    MICROPY_ALLOC_QSTR_ENTRIES_INIT,
    MP_QSTRnumber_of,   // corresponds to number of strings in array just below
    {
        #ifndef NO_QSTR
#define QDEF(id, str) str,
        #include "genhdr/qstrdefs.generated.h"
#undef QDEF
        #endif
    },
};

不同文件里QDEF的意义是不同的,qstr.h里是取了前半部分也就是MP_QSTR_xx形式的操作符,加入到enum中作为index,qstr.c中则提取了由哈希值长度以及实际字符串组成的字符串,即MicroPython字节码,将其加入到qstr_pool这个数据结构中。qstr_pool中都是预定义的qstr,包括MicroPython内置qstr和用户通过c语言定义的qstr。这个pool称为interned pool。

mpy-cross将py文件编译成mpy文件,mpy文件就是由字节码组成,字节码输入MicroPython虚拟机后就会查找qstr pool执行对应的函数。

我们通过C语言扩展MicroPython时也需要将关键字注册到qstr pool中。

如何通过C语言扩展MicroPython

阅读前需要对Python面向对象由初步认识。

直接看代码注释应该就能明白。C语言的相关API主要在py/obj.hpy/obj.c中。

#include <math.h>
#include "py/obj.h"
#include "py/runtime.h"
#include "py/builtin.h"

const mp_obj_type_t vector_vector3D_type;

typedef struct _vector_vector3D_obj_t {
    mp_obj_base_t base;
    float x, y, z;
} vector_vector3D_obj_t;

// helper function
mp_obj_t create_new_vector3D(float _x, float _y, float _z) {
    vector_vector3D_obj_t *vector3D = m_new_obj(vector_vector3D_obj_t);
    vector3D->base.type = &vector_vector3D_type;
    vector3D->x = _x;
    vector3D->y = _y;
    vector3D->z = _z;
    return MP_OBJ_FROM_PTR(vector3D);
}

STATIC void vector3D_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) {
    (void)kind;
    vector_vector3D_obj_t *self = MP_OBJ_TO_PTR(self_in);
    mp_print_str(print, "vector3D(");
    mp_obj_print_helper(print, mp_obj_new_float(self->x), PRINT_REPR);
    mp_print_str(print, ", ");
    mp_obj_print_helper(print, mp_obj_new_float(self->y), PRINT_REPR);
    mp_print_str(print, ", ");
    mp_obj_print_helper(print, mp_obj_new_float(self->z), PRINT_REPR);
    mp_print_str(print, ")");
}

/*
 * class vector3D:
 *      def __init__(self, _x, _y, _z):
 *          self.x = _x
 *          self.y = _y
 *          self.z = _z
 */
STATIC mp_obj_t vector3D_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, 3, 3, true);
    vector_vector3D_obj_t *self = m_new_obj(vector_vector3D_obj_t);
    self->base.type = &vector_vector3D_type;
    self->x = mp_obj_get_float(args[0]);
    self->y = mp_obj_get_float(args[1]);
    self->z = mp_obj_get_float(args[2]);

    return MP_OBJ_FROM_PTR(self);
}

/*
 * class vector3D:
 *
 *      @property
 *      def x(self):
 *          return self.x
 *
 *      @property
 *      def y(self):
 *          return self.y
 *
 *      @property
 *      def z(self):
 *          return self.z
 */
STATIC mp_obj_t vector3D_x(mp_obj_t self_in) {
    vector_vector3D_obj_t *self = MP_OBJ_TO_PTR(self_in);
    return mp_obj_new_float(self->x);
}

STATIC mp_obj_t vector3D_y(mp_obj_t self_in) {
    vector_vector3D_obj_t *self = MP_OBJ_TO_PTR(self_in);
    return mp_obj_new_float(self->y);
}

STATIC mp_obj_t vector3D_z(mp_obj_t self_in) {
    vector_vector3D_obj_t *self = MP_OBJ_TO_PTR(self_in);
    return mp_obj_new_float(self->z);
}

/*
 * class vector3D:
 *      def normalize(self):
 *          length = self.__abs__()
 *          if length == 0:
 *              raise ZeroDivisionError("vector's norm is zero")
 *          self.x /= length
 *          self.y /= length
 *          self.z /= length
 */
STATIC mp_obj_t vector3D_normalize(mp_obj_t self_in) {
    vector_vector3D_obj_t *self = MP_OBJ_TO_PTR(self_in);
    float length = sqrtf(self->x * self->x + self->y * self->y + self->z * self->z);
    if (length == 0) {
        mp_raise_msg(&mp_type_ZeroDivisionError, MP_ERROR_TEXT("vector's norm is zero"));
        return mp_const_none;
    }
    self->x /= length;
    self->y /= length;
    self->z /= length;
    return mp_const_none;
}

STATIC MP_DEFINE_CONST_FUN_OBJ_1(vector3D_normalize_obj, vector3D_normalize);

STATIC void vector3D_attr(mp_obj_t self, qstr attribute, mp_obj_t *destination) {
    if (attribute == MP_QSTR_x) {
        destination[0] = vector3D_x(self);
    } else if (attribute == MP_QSTR_y) {
        destination[0] = vector3D_y(self);
    } else if (attribute == MP_QSTR_z) {
        destination[0] = vector3D_z(self);
    } else if (attribute == MP_QSTR_normalize) {
        destination[0] = (mp_obj_t)&vector3D_normalize_obj;
        destination[1] = self;
    }
}

/*
 * class vector3D:
 *      def __bool__(self):
 *          return self.x != 0 or self.y != 0 or self.z != 0
 *      def __abs__(self):
 *          return sqrt(self.x * self.x + self.y * self.y + self.z * self.z)
 *      def __eq__(self, other):
 *          return self.x == other.x and self.y = other.y and self.z = other.z
 *      def __add__(self, other):
 *          return vector3D(self.x + other.x, self.y + other.y, self.z + other.z)
 *      def __sub__(self, other):
 *          return vector3D(self.x - other.x, self.y - other.y, self.z - other.z)
 *      def __mul__(self, other):
 *          return sqrt(self.x * other.x + self.y * other.y + self.z * other.z)
 */
STATIC mp_obj_t vector3D_unary_op(mp_unary_op_t op, mp_obj_t self_in) {
    vector_vector3D_obj_t *self = MP_OBJ_TO_PTR(self_in);
    switch (op) {
        case MP_UNARY_OP_BOOL: return mp_obj_new_bool((self->x != 0) || (self->y != 0) || (self->z != 0));
        case MP_UNARY_OP_ABS:
            return mp_obj_new_float(sqrtf(self->x * self->x + self->y * self->y + self->z * self->z));
        default: return MP_OBJ_NULL; // operator not supported
    }
}

STATIC mp_obj_t vector3D_binary_op(mp_binary_op_t op, mp_obj_t lhs, mp_obj_t rhs) {
    vector_vector3D_obj_t *left_hand_side = MP_OBJ_TO_PTR(lhs);
    vector_vector3D_obj_t *right_hand_side = MP_OBJ_TO_PTR(rhs);
    switch (op) {
        case MP_BINARY_OP_EQUAL:
            return mp_obj_new_bool((left_hand_side->x == right_hand_side->x) \
                                && (left_hand_side->y == right_hand_side->y) \
                                && (left_hand_side->z == right_hand_side->z));
        case MP_BINARY_OP_ADD:
            return create_new_vector3D(left_hand_side->x + right_hand_side->x, \
                                    left_hand_side->y + right_hand_side->y, \
                                    left_hand_side->z + right_hand_side->z);
        case MP_BINARY_OP_SUBTRACT:
            return create_new_vector3D(left_hand_side->x - right_hand_side->x, \
                                    left_hand_side->y - right_hand_side->y, \
                                    left_hand_side->z - right_hand_side->z);  
        case MP_BINARY_OP_MULTIPLY: // inner product
            return mp_obj_new_float(left_hand_side->x * right_hand_side->x +\
                                    left_hand_side->y * right_hand_side->y +\
                                    left_hand_side->z * right_hand_side->z);                            
        default:
            return MP_OBJ_NULL; // operator not supported
    }
}

// STATIC const mp_rom_map_elem_t vector3D_locals_dict_table[] = {
//     { MP_ROM_QSTR(MP_QSTR_normalize), MP_ROM_PTR(&vector3D_normalize_obj) },
// };
// STATIC MP_DEFINE_CONST_DICT(vector3D_locals_dict, vector3D_locals_dict_table);

const mp_obj_type_t vector_vector3D_type = {
    { &mp_type_type },
    .name = MP_QSTR_vector3D,
    .print = vector3D_print,
    .make_new = vector3D_make_new,
    .unary_op = vector3D_unary_op,
    .binary_op = vector3D_binary_op,
    .attr = vector3D_attr,
    // .locals_dict = (mp_obj_dict_t*)&vector3D_locals_dict,
};

/*
 * def reverse(vec):
        return vector3D(-vec.x, -vec.y, -vec.z)
 */
STATIC mp_obj_t vector_reverse(mp_obj_t o_in) {
    if (!mp_obj_is_type(o_in, &vector_vector3D_type)) {
        mp_raise_TypeError(MP_ERROR_TEXT("argument is not a vector"));
    }
    vector_vector3D_obj_t *vector3D = MP_OBJ_TO_PTR(o_in);
    return create_new_vector3D(-vector3D->x, -vector3D->y, -vector3D->z);
}

STATIC MP_DEFINE_CONST_FUN_OBJ_1(vector_reverse_obj, vector_reverse);

STATIC const mp_rom_map_elem_t vector_module_globals_table[] = {
    { MP_OBJ_NEW_QSTR(MP_QSTR___name__), MP_OBJ_NEW_QSTR(MP_QSTR_vector) },
    { MP_OBJ_NEW_QSTR(MP_QSTR_vector3D), (mp_obj_t)&vector_vector3D_type },
    { MP_OBJ_NEW_QSTR(MP_QSTR_reverse), (mp_obj_t)&vector_reverse_obj },
};
STATIC MP_DEFINE_CONST_DICT(vector_module_globals, vector_module_globals_table);

const mp_obj_module_t vector_user_cmodule = {
    .base = { &mp_type_module },
    .globals = (mp_obj_dict_t*)&vector_module_globals,
};

MP_REGISTER_MODULE(MP_QSTR_vector, vector_user_cmodule, MODULE_VECTOR_ENABLED);

以下是运行结果:

>>> from vector import vector3D
>>> a = vector3D(1, 2, 3)
>>> a
vector3D(1.0, 2.0, 3.0)
>>> vector3D
<class 'vector3D'>
>>> a.x
1.0
>>> a.y
2.0
>>> a.z
3.0
>>> a.normalize()
>>> a
vector3D(0.2672612, 0.5345225, 0.8017837)
>>> import vector
>>> vector.reverse(a)
vector3D(-0.2672612, -0.5345225, -0.8017837)
vector3D(4.0, 5.0, 6.0)
>>> a + b
vector3D(5.0, 7.0, 9.0)
>>> a - b
vector3D(-3.0, -3.0, -3.0)
>>> a * b
32.0

接下来的任务

需要能够通过MicroPython直接调用ArduSensorPlatform的Host指针进行Read和Write操作。

现在的想法是封装一个结构体,成员是一个Host,在HostWrapper.h中extern这个结构体变量并定义函数对结构体进行操作。然后在micropython中引入这个HostWrapper.h。但问题是HostWrapper.h肯定是要include Host.h的,这个c++头文件要怎么解决?

可能可以用空指针搞。今天先摸了。


本文章使用limfx的vscode插件快速发布