MicroPython 外部 C 模块¶
当开发 MicroPython 的模块时,你会发现可能会遇到一些 Python 环境的限制,这通常是由于无法访问某些硬件资源或 Python 速度限制。
如果你的限制无法通过使用 最大化 MicroPython 速度 中的建议解决的话,可以尝试使用 C (如果你的适配端支持的话甚至可以是 C++ ) 编写你的模块。
如果你的模块是旨在访问或与常用的硬件或库协作,请考虑在 MicroPython 源码树中内嵌一些相似的模块,并提交 pull request 。如果你的目标是模糊的或专有的系统,则可能更适合将这个模块保持在 主 MicroPython 仓库之外。
这一章节描述了如何将这样的外部模块编译到 MicroPython 可执行文件或固件镜像。不论 Make 和 CMake 的编译工具都是支持的,并且当写一个外部模块时,建议同时为这两个工具添加的编译配置文件,以便于让这个模块就可在所有的适配端上使用。但是,当只为一个特定的适配端构建时,你可能只需要使用一种方法来编译,可以是 Make 或 CMake。
另一种方法是使用 Native machine code in .mpy files ,这一方式允许编写自定义的、被放置在 .mpy 文件中的 C 代码,这些代码可以在运行的 MicroPython 系统中动态导入,而不需要重新编译主固件。
外部 C 模块的结构¶
一个 MicroPython 用户 C 模块是一个具有以下文件的目录:
你的模块的
*.c
/*.cpp
/*.h
源代码文件。这些文件通常包括实现的底层功能和提供给 MicroPython 的、用于暴露这些函数和模块的绑定函数。
目前编写这些函数/模块的最佳参考是,在 MicroPython 树中找到相似的模块作为示例。
micropython.mk
包含了这个模块的 Makefile 片段。$(USERMOD_DIR)
在micropython.mk
中作为模块目录路径的变量。它在每个 c 模块中都会重新定义,因此在你的micropython.mk
中应该将它赋值为一个本地 make 变量,例如EXAMPLE_MOD_DIR := $(USERMOD_DIR)
你的
micropython.mk
必须在SRC_USERMOD` `中,添加你的模块源代码,其路径应当是你赋值的对应 ``SRC_USERMOD
的变量的相对路径,例如SRC_USERMOD += $(EXAMPLE_MOD_DIR)/example.c
如果你有自定义的编译选项(例如
-I
来添加头文件所在的目录),则这些选项,为了在 C 代码可用,添加到CFLAGS_USERMOD
中;为了在 C++ 代码可用,配置到CXXFLAGS_USERMOD
中。micropython.cmake
包含了这个模块的 CMake 配置。在
micropython.cmake
中,你可以使用${CMAKE_CURRENT_LIST_DIR}
作为当前模块的路径。你的
micropython.cmake
应该定义一个INTERFACE
库,并将你的源代码、编译定义和头文件目录与它关联。这个库应该被链接到usermod
目标。add_library(usermod_cexample INTERFACE) target_sources(usermod_cexample INTERFACE ${CMAKE_CURRENT_LIST_DIR}/examplemodule.c ) target_include_directories(usermod_cexample INTERFACE ${CMAKE_CURRENT_LIST_DIR} ) target_link_libraries(usermod INTERFACE usermod_cexample)
在下文参考完整示例。
基本示例¶
该简单的模块名为 cexample
,用于提供一个函数 cexample.add_ints(a, b)
,该函数把两个整数相加并返回结果。它可以在 MicroPython 源代码树中的 examples 目录 中找到,其中包含一个源码文件和一个 Makefile 片段内容如上所述。
micropython/
└──examples/
└──usercmodule/
└──cexample/
├── examplemodule.c
├── micropython.mk
└── micropython.cmake
参考这些文件的注释以获取额外的说明。紧接 cexample
模块的还有 cppexample
模块,该模块与 cexample
模块执行相同的行为,但展示了一种方式,说明在 MicroPython 中如何使用 C 和 C++ 代码混合开发。
将 c 模块编译到 MicroPython¶
要构建这样的模块,编译 MicroPython (参考 快速开始 ),并执行 2 个修改:
将构建时标识
USER_C_MODULES
指向来你要包含的模块。 如果使用 Make ,这个变量应该是一个指向自动搜索模块的目录。如果使用 CMake ,这个变量应该是一个包含要构建的模块的文件。详情见下文。当且仅当你要构建的模块不被自动启用时,需要将对应的 C 预处理宏设置为 1。
为了构建和 MicroPython 关联的示例模块,使用 Make 时将 USER_C_MODULES
设置为 examples/usercmodule
目录,使用 CMake 时则是 examples/usercmodule/micropython.cmake
。
以下示例介绍了如何构建 unix 适配端,并使用示例模块的方法:
cd micropython/ports/unix
make USER_C_MODULES=../../examples/usercmodule
如果你要在构建时包含新的用户模块,你可能需要在构建开始时运行 make clean
。构建时的输出会展示找到的模块:
...
Including User C Module from ../../examples/usercmodule/cexample
Including User C Module from ../../examples/usercmodule/cppexample
...
如果是基于 CMake 构建的适配端,例如树莓派,这种方法会有些不同(请注意 CMake 最终是调用 make
来完成构建的):
cd micropython/ports/rp2
make USER_C_MODULES=../../examples/usercmodule/micropython.cmake
你需要先为 CMake 再运行一遍 make clean
来启用用户模块。 CMake 构建时的输出会按名称逐个列出模块:
...
Including User C Module(s) from ../../examples/usercmodule/micropython.cmake
Found User C Module(s): usermod_cexample, usermod_cppexample
...
最上层的 micropython.cmake
文件的内容可以用来控制哪些模块要启用。
对于自己的项目,更方便的是将自定义代码移出 MicroPython 源代码树,所以通常一个项目目录结构会如下:
my_project/
├── modules/
│ ├── example1/
│ │ ├── example1.c
│ │ ├── micropython.mk
│ │ └── micropython.cmake
│ ├── example2/
│ │ ├── example2.c
│ │ ├── micropython.mk
│ │ └── micropython.cmake
│ └── micropython.cmake
└── micropython/
├──ports/
... ├──stm32/
...
使用 Make 构建时,将 USER_C_MODULES
设为 my_project/modules
目录。例如构建 stm32 适配端使用:
cd my_project/micropython/ports/stm32
make USER_C_MODULES=../../../modules
使用 CMake 构建时,最上层的 micropython.cmake
文件(可在 my_project/modules
目录中找到)应该 include
所有要使用的模块:
include(${CMAKE_CURRENT_LIST_DIR}/example1/micropython.cmake) include(${CMAKE_CURRENT_LIST_DIR}/example2/micropython.cmake)
然后这样构建:
cd my_project/micropython/ports/esp32
make USER_C_MODULES=../../../../modules/micropython.cmake
请注意, esp32 适配端需要额外的 ..
来表示相对路径,因为它的主 CMakeLists.txt
文件在项目根目录中。你也可以用 USER_C_MODULES
指定到绝对路径。
所有通过 USER_C_MODULES
变量指定的模块(使用 Make 时,在这个变量指定的目录中查找;使用 CMake 时,则通过 include
添加)都会被编译,但只有启用的模块才会被 使用。用户模块通常会被默认启用(这是由模块开发者决定的),所以如果你不 想要某些模块,只需要将 USER_C_MODULES
变量设为空即可。
如果模块不默认启用,则必须启用对应的 C 预处理器宏。这个宏名称可以在模块的源代码中搜索到(通常是模块的主源文件最后一行)。MP_REGISTER_MODULE
的第三个参数是宏名称,这个宏名称必须使用 CFLAGS_EXTRA
来设置为 1 ,以使模块可用。如果第三个参数是 1 ,则模块默认启用。
例如,examples/usercmodule/cexample
模块默认启用,所以它的源代码中有如下一行:
MP_REGISTER_MODULE(MP_QSTR_cexample, example_user_cmodule, 1);
另外,如果想要使这个模块不默认启用,但通过预处理器配置选项来选择,则是:
MP_REGISTER_MODULE(MP_QSTR_cexample, example_user_cmodule, MODULE_CEXAMPLE_ENABLED);
这种情况下,若要启用模块,可通过在 make
命令添加 CFLAGS_EXTRA=-DMODULE_CEXAMPLE_ENABLED=1
,或者编辑 mpconfigport.h
或 mpconfigboard.h
添加以下行
#define MODULE_CEXAMPLE_ENABLED (1)
请注意,这种方法取决于适配端,因为不同适配端有不同的结构。如果没有按照正确做法配置,也会加入编译,但导入将失败。
在 MicroPython 中使用模块¶
一旦编译到你的 MicroPython 固件中,模块就可以在 Python 中被直接访问,例如:
import cexample
print(cexample.add_ints(1, 3))
# should display 4