MicroPython 外部 C モジュール

MicroPython で使うモジュールを開発する際、Python 環境では壁に突き当たってしまうことがあります。これは多くの場合、特定のハードウェアリソースにアクセスできないとか、Python の実行速度の遅さによるものです。

MicroPython 性能の最大化 に提示しているもので解決できない場合、モジュールの一部または全部を C 言語で書くのが現実的な解です。

作ろうとしているモジュールが一般的に入手可能なハードウェアやライブラリで動くように設計するのであれば、MicroPython ソースツリー中の似たモジュールに倣い、ソースツリー内で実装して、プルリクエストを出すことを検討してください。ただし、よく知られていないシステムた独自のシステムをターゲットにしている場合は、メインの MicroPython リポジトリの外部に保持するほうが合理的です。

この章では、そのような外部モジュールを MicroPython の実行ファイルまたはファームウェアイメージにコンパイルする方法について説明します。

外部 C モジュールの構造

MicroPython のユーザー C モジュールは、次のファイルを含むディレクトリーです:

  • モジュールのソースコードファイルである *.c*.h

    これらのファイルには通常、C言語レベルでの機能実装と、それを MicroPython の公開関数/モジュールにバインディングするものから成っています。

    現在のところ、このような関数/モジュールを書くための最良の参考資料は MicroPython のソースツリー内で似たモジュールを見つけて、それらを例として使うことです。

  • micropython.mk はモジュール用の Makefile の一部を含みます。

    $(USERMOD_DIR) は micropython.mk 内で利用可能で、モジュールのディレクトリパスを示します。これは各 C モジュールに対して再定義されるので、micropython.mk の中でローカルの make 変数に展開しておくべきです。たとえば EXAMPLE_MOD_DIR := $(USERMOD_DIR) とします。

    micropython.mk ではモジュールの C ファイルを SRC_USERMOD に追加する必要があります。これは $(USERMOD_DIR) の展開コピーのディレクトリ中に置かれているものとして設定します。たとえば SRC_USERMOD += $(EXAMPLE_MOD_DIR)/example.c のようにします。

    カスタムの CFLAGS 設定またはインクルードファイルのフォルダ定義がある場合には、 CFLAGS_USERMOD に追加します。

    完全な使用例については下記を参照してください。

基本的な例

ここでとりあげる単純な名前のモジュール example は、2つの整数引数を加算して結果を返す単一の関数 example.add_ints(a, b) を提供します。

ディレクトリ構成:

example/
├── example.c
└── micropython.mk

example.c

// 第一に必要となるインクルードファイル
#include "py/obj.h"
#include "py/runtime.h"
#include "py/builtin.h"

// Python から example.add_ints(a, b) として呼び出す関数
STATIC mp_obj_t example_add_ints(mp_obj_t a_obj, mp_obj_t b_obj) {
    // MicroPython の入力オブジェクトから int 値を取得
    int a = mp_obj_get_int(a_obj);
    int b = mp_obj_get_int(b_obj);

    // 加算を行い、MicroPython オブジェクトに変換
    return mp_obj_new_int(a + b);
}
// 上記の関数への Python 参照を定義
STATIC MP_DEFINE_CONST_FUN_OBJ_2(example_add_ints_obj, example_add_ints);

// example モジュールのすべてのプロパティを定義。
// テーブルのエントリは、属性名(文字列)と MicroPython オブジェクト参照の
// キー/値のペア。
// すべての識別子と文字列は MP_QSTR_xxx として書き、ビルドシステムによって// ワードサイズの整数に最適化されます(内部文字列)。
STATIC const mp_rom_map_elem_t example_module_globals_table[] = {
    { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_example) },
    { MP_ROM_QSTR(MP_QSTR_add_ints), MP_ROM_PTR(&example_add_ints_obj) },
};
STATIC MP_DEFINE_CONST_DICT(example_module_globals, example_module_globals_table);

// モジュールオブジェクトを定義
const mp_obj_module_t example_user_cmodule = {
    .base = { &mp_type_module },
    .globals = (mp_obj_dict_t*)&example_module_globals,
};

// Python で利用できるように make するようモジュールを登録
MP_REGISTER_MODULE(MP_QSTR_example, example_user_cmodule, MODULE_EXAMPLE_ENABLED);

micropython.mk

EXAMPLE_MOD_DIR := $(USERMOD_DIR)

# すべての C ファイルを SRC_USERMOD に追加
SRC_USERMOD += $(EXAMPLE_MOD_DIR)/example.c

# 必要であればモジュールのフォルダをインクルードパスに追加
# この例では実際には必要ない
CFLAGS_USERMOD += -I$(EXAMPLE_MOD_DIR)

最後に MODULE_EXAMPLE_ENABLED を 1 に定義する必要があります。これを行うには CFLAGS_EXTRA=-DMODULE_EXAMPLE_ENABLED=1make コマンドに追加するか、 mpconfigport.hmpconfigboard.h を編集して次のものを追加します。

#define MODULE_EXAMPLE_ENABLED (1)

ポートによって異なる構造になっているので、正しい方法はポートに依存することに注意してください。適切に行われなかった場合、コンパイルはされたとしてもモジュールのインポートはできないでしょう。

C モジュールを MicroPython にコンパイルする

モジュールをビルドするには make コマンドに追加のフラグ USER_C_MODULES で組み込みたいモジュールすべて(このモジュール自体ではない)のあるディレクトリを指定して、MicroPython をコンパイルします(getting started を参照)。たとえば次のような構成の場合:

ディレクトリ構成:

my_project/
├── modules/
│   └──example/
│       ├──example.c
│       └──micropython.mk
└── micropython/
    ├──ports/
   ... ├──stm32/
      ...

stm32 ポート用のビルド:

cd my_project/micropython/ports/stm32
make USER_C_MODULES=../../../modules CFLAGS_EXTRA=-DMODULE_EXAMPLE_ENABLED=1 all

MicroPython でのモジュールの使い方

MicroPython 自前でビルドすると、上記で実装したモジュール example.c は、他の組み込みモジュールと同じように Python でアクセスできるようになります。たとえば次のように使います。

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