MicroPython 外部 C 模块

在开发用于 MicroPython 的模块时,您可能会发现您遇到 Python 环境的限制,通常是由于无法访问某些硬件资源或 Python 速度限制。

如果通过最大化 MicroPython 速度中, 的建议无法解决您的限制,则用C(和/或 C++,如果为您的端口实现)编写部分或全部模块是一个可行的选择。

如果您的模块旨在访问或使用常用硬件或库,请考虑在 MicroPython 源代码树中与类似模块一起实现它,并将其作为拉取请求提交。但是,如果您的目标是晦涩的或专有的系统,则将其保留在主 MicroPython 存储库的外部可能更有意义。

本章介绍如何将此类外部模块编译为 MicroPython 可执行文件或固件映像。Make 和 CMake 构建工具都受支持,在编写外部模块时,最好为这两个工具添加构建文件,以便该模块可以在所有端口上使用。但是在编译特定端口时,您只需要使用一种构建方法,Make 或 CMake。

另一种方法是 在 .mpy 文件中 使用本机机器代码,它允许编写放置在 .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 必须将模块源文件添加到$(USERMOD_DIR)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 源代码树中找到, 并且有一个源文件和一个包含上述内容的 Makefile 片段:

micropython/
└──examples/
   └──usercmodule/
      └──cexample/
         ├── examplemodule.c
         ├── micropython.mk
         └── micropython.cmake

有关其他说明,请参阅这些文件中的注释。在cexample模块旁边还有cppexample 以相同方式工作的模块,但显示了在 MicroPython 中混合 C 和 C++ 代码的一种方式。

将 cmodule 编译成 MicroPython

要构建这样的模块,请编译 MicroPython(请参阅 入门),应用 2 处修改:

  1. 将构建时标志设置 USER_C_MODULES 为指向要包含的模块。对于使用 Make 这个变量的端口应该是一个自动搜索模块的目录。对于使用 CMake 的端口,这个变量应该是一个包含要构建的模块的文件。详情请见下文。

  2. 通过将相应的 C 预处理器宏设置为 1 来启用模块。仅当您正在构建的模块未自动启用时才需要这样做。

为了构建MicroPython附带的示例模块,Make将USER_C_MODULES 设置为 examples/usercmodule 目录, CMake将USER_C_MODULES设置为 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 的端口,例如 rp2,这看起来会有些不同(注意 CMake 实际上是由 调用的make):

cd micropython/ports/rp2
make USER_C_MODULES=../../examples/usercmodule/micropython.cmake

同样,您可能需要先运行CMake 才能获取用户模块。CMake 构建输出按名称列出模块: make clean

...
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_MODULESmy_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

请注意, .. 由于其主CMakeLists.txt 文件的位置,esp32 端口需要额外的相对路径。您还可以指定USER_C_MODULES.

USER_C_MODULES 变量指定的所有模块(使用 Make 时在此目录中找到,或include 使用 CMake 时通过添加)将被编译,但只有那些启用的模块可用于导入。用户模块通常是默认启用的(这由模块的开发人员决定),在这种情况下,除了USER_C_MODULES 如上所述设置之外别无他法。

如果默认情况下未启用模块,则必须启用相应的 C 预处理器宏。可以通过搜索 MP_REGISTER_MODULE模块源代码中的行(它通常出现在主源文件的末尾)来找到该宏名称。to 的第三个参数MP_REGISTER_MODULE是宏名称,必须将其设置为 1CFLAGS_EXTRA才能使模块可用。如果第三个参数只是数字 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);

在这种情况下,模块是通过将启用CFLAGS_EXTRA=-DMODULE_CEXAMPLE_ENABLED=1make 命令,或编辑 mpconfigport.hmpconfigboard.h 添加

#define MODULE_CEXAMPLE_ENABLED (1)

请注意,确切的方法取决于端口,因为它们具有不同的结构。如果没有正确完成,它将编译但导入将无法找到模块。

MicroPython 中的模块使用

一旦内置到您的 MicroPython 副本中,该模块现在可以像任何其他内置模块一样在 Python 中访问,例如

import cexample
print(cexample.add_ints(1, 3))
# should display 4