自定义 MicroPython 接口封装教程

Viewed 74

问题描述


还在为 K230 的硬件功能调用发愁?想让传感器、外设轻松接入 MicroPython 生态?

这波干货请收好 —— 从零开始的接口封装指南来啦!跟着操作,你也能把自定义硬件功能打包成简洁的 MicroPython 接口,让代码像这样清爽:

import your_module
dev = your_module.init()
dev.read_data()

一、 硬件准备

  • a. PC
  • b. K230 开发板

二、 软件准备

三、软件框架

image.png

四、用一个例子告诉你怎么添加

在 canmv/port/ 下创建新的文件夹 mymodules ,存放自己定义的模块 .C 文件

1.添加 mymod.c 创建自己的基本模块

// 添加实现micropython模块的必要头文件
#include"py/obj.h"
#include"py/runtime.h"

// 定义 printHelloWorld 函数,用于打印 "Hello World! -- [name]"
STATIC mp_obj_t printHelloWorld(mp_obj_t name){
    // 使用 mp_printf 打印字符串,并获取传入的 name 参数的字符串值
    mp_printf(&mp_plat_print, "Hello World! -- %s\n", mp_obj_str_get_str(name));
    // 返回 mp_const_none 表示没有返回值
    return mp_const_none;
}
// 定义 printHelloWorld 函数为 MicroPython 的一个函数对象,接受 1 个参数
STATIC MP_DEFINE_CONST_FUN_OBJ_1(mymodules_printHelloWorld_obj, printHelloWorld);

// 定义 addNumbers 函数,用于对传入的多个数字进行求和
STATIC mp_obj_t addNumbers(size_t n_args, const mp_obj_t *args){
    int result = 0;

    // 遍历所有传入的参数并进行求和
    for (size_t i = 0; i < n_args; i++) {
        result += mp_obj_get_int(args[i]);
    }
    
    // 返回求和结果,作为 MicroPython 的整数对象
    return mp_obj_new_int(result);
}
// 定义 addNumbers 函数为 MicroPython 的一个函数对象,接受 2 到 4 个参数
STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mymodules_addNumbers_obj, 2, 4, addNumbers);

// 定义模块的全局变量表,包含模块名、方法和类等
STATIC const mp_rom_map_elem_t mymodules_module_globals_table[] = {

    // 定义模块名 -- mymodules
    { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_mymodules) },

    // 绑定方法 sayHi 到 printHelloWorld 函数对象
    { MP_ROM_QSTR(MP_QSTR_sayHi), MP_ROM_PTR(&mymodules_printHelloWorld_obj) },

    // 绑定方法 add 到 addNumbers 函数对象
    { MP_ROM_QSTR(MP_QSTR_add), MP_ROM_PTR(&mymodules_addNumbers_obj) },
};

// 定义 mymodules_globals_table 为一个常量字典,包含模块的全局变量表
STATIC MP_DEFINE_CONST_DICT(mymodules_globals_table, mymodules_module_globals_table);

// 定义 mp_module_mymodules 对象,表示整个模块
const mp_obj_module_t mp_module_mymodules = {
    .base = { &mp_type_module }, // 设置模块的基础类型为 mp_type_module
    .globals = (mp_obj_dict_t*)&mymodules_globals_table, // 设置模块的全局变量表
};

// 注册模块到 MicroPython 模块系统中,模块名为 mymodules
MP_REGISTER_MODULE(MP_QSTR_mymodules, mp_module_mymodules);

2. 在 canmv/port/Makefile 中添加 mymodules 文件夹的编译

image.png

3. 最后在 SDK 根目录执行 make canmv 重新编译 canmv模块

将编译得到的 output/canmv/micropython 替换掉 sd 卡内的 sdcard/micropython 文件重新开机即可完成 micropython 更新。

五、运行

成功添加 mymodules 模块。
image.png

六、对mymod.c 文件的说明

1.包含必要的头文件

#include"py/obj.h"
#include"py/runtime.h"

这两个头文件是 MicroPython C API 的核心:

  • py/obj.h:定义了 MicroPython 对象系统的基础结构
  • py/runtime.h:提供了运行时功能,如内存管理、解释器状态等

2. 定义C函数

STATIC mp_obj_t printHelloWorld(mp_obj_t name){
    mp_printf(&mp_plat_print, "Hello World! -- %s\n", mp_obj_str_get_str(name));
    return mp_const_none;
}

STATIC mp_obj_t addNumbers(size_t n_args, const mp_obj_t *args){

    int result = 0;

    for (size_t i = 0; i < n_args; i++) {
        result += mp_obj_get_int(args[i]);
    }
    
    return mp_obj_new_int(result);
}

这里定义了两个C函数:

a. printHelloWorld:实现 HelloWorld + 输入字符串的打印输出

  • mp_obj_t :返回类型为通用对象指针(所有 MicroPython C 函数都使用mp_obj_t类型)

  • mp_obj_str_get_str() : 将 Python 对象 (mp_obj_t) 转换为 C 字符串 (char*)类型

  • return mp_const_none; :表示无返回值或返回值为空值

b. addNumbers :多个参数输入的函数相加,返回结果

  • n_args:传入的参数数量

  • *args:参数对象数组,按顺序存储传入的参数

  • mp_obj_get_int() :将 Python 对象 (mp_obj_t) 类型转换为 C 整型

  • mp_obj_new_int() :将 C 整型转换为 Python 对象 (mp_obj_t) 类型

3. 注册C函数对象

STATIC MP_DEFINE_CONST_FUN_OBJ_1(mymodules_printHelloWorld_obj, printHelloWorld);
STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mymodules_addNumbers_obj, 2, 4, addNumbers);

a. MP_DEFINE_CONST_FUN_OBJ_1 :宏定义,用于注册带 1 个参数的函数

  • 第二个参数:关联的 C 函数名
  • 第一个参数:生成的函数对象名称

b. MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN :宏定义,用于注册参数数量可变的函数

  • 第四个参数:关联的 C 函数名
  • 第一个参数:生成的函数对象名称
  • 第二个参数:最小参数数量
  • 第三个参数:最大参数数量

c. 其他注册函数定义:(参数定义与 MP_DEFINE_CONST_FUN_OBJ_1 一致)

  • MP_DEFINE_CONST_FUN_OBJ_0 :注册带 0 个参数输入的函数
  • MP_DEFINE_CONST_FUN_OBJ_2 :注册带 2 个参数输入的函数
  • MP_DEFINE_CONST_FUN_OBJ_3 :注册带 3 个参数输入的函数

4. 定义全局变量表

STATIC const mp_rom_map_elem_t mymodules_module_globals_table[] = {

    // 定义模块名 -- mymodules
    { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_mymodules) },

    // 绑定方法sayHi
    { MP_ROM_QSTR(MP_QSTR_sayHi), MP_ROM_PTR(&mymodules_printHelloWorld_obj) },

    // 绑定方法add
    { MP_ROM_QSTR(MP_QSTR_add), MP_ROM_PTR(&mymodules_addNumbers_obj) },
};

a. 定义全局变量表:mymodules_module_globals_table
b. 模块元信息:

  • MP_QSTR_mymodules :表示模块的实际名称为 mymodules,决定import时的标识符
  • MP_QSTR___name__ :固定 QSTR,表示 Python 模块的name属性
    c. 函数绑定机制:
  • 参数:C 函数对象(步骤3定义)的指针
  • MP_ROM_PTR(&mymodules_printHelloWorld_obj) :定义与前面方法对应的 C 函数对象的指针
  • 参数:MP_QSTR_ + "方法名称"
  • MP_ROM_QSTR(MP_QSTR_sayHi) :定义Python 中调用的方法名称 (sayHi)
    通过绑定,在 Python 中使用相应方法即调用对应的 C 实现:
    (mymodules.sayHi → mymodules_printHelloWorld_obj → printHelloWorld )
    (mymodules.add → mymodules_addNumbers_obj → addNumbers )

5. 定义模块全局字典,将全局变量表封装为一个常量字典对象

// 定义模块的全局变量表,包含模块名、方法和类等
STATIC MP_DEFINE_CONST_DICT(mymodules_globals_table, mymodules_module_globals_table);

a. 第一个参数:生成的字典对象名 -- my_modules_globals_tabel ,将作为模块的全局命名空间
b. 第二个参数:全局变量表(步骤4定义) -- mymodules_module_globals_table

6. 定义模块对象


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

模块对象定义

  • 创建 mp_obj_module_t类型的模块对象mp_module_mymodules
  • .base字段设置为模块类型的基类
  • .globals字段指向定义的全局变量字典

7.注册模块

MP_REGISTER_MODULE(MP_QSTR_mymodules, mp_module_mymodules);

MP_REGISTER_MODULE :

  • 第一个参数:模块的 QSTR 名称(步骤4定义)
  • 第二个参数:模块对象(步骤6定义)
1 Answers