MicroPython 外部 C モジュール
MicroPython で使うモジュールを開発する際、Python 環境では壁に突き当たってしまうことがあります。これは多くの場合、特定のハードウェアリソースにアクセスできないとか、Python の実行速度の遅さによるものです。
MicroPython 性能の最大化 に提示しているもので解決できない場合、モジュールの一部または全部を C 言語で書くのが現実的な解です(ポートによっては C++ も使います)。
作ろうとしているモジュールが一般的に入手可能なハードウェアやライブラリで動くように設計するのであれば、MicroPython ソースツリー中の似たモジュールに倣い、ソースツリー内で実装して、プルリクエストを出すことを検討してください。ただし、よく知られていないシステムた独自のシステムをターゲットにしている場合は、メインの MicroPython リポジトリの外部に保持するほうが合理的です。
この章では、そのような外部モジュールを MicroPython の実行ファイルまたはファームウェアイメージにコンパイルする方法について説明します。Make と CMake の両方のビルドツールがサポートされているので、外部モジュールを作成する際には、すべての ports でモジュールを使用できるように、両方のビルドツール用のビルドファイルを追加することをお勧めします。しかし、特定のポートをコンパイルする際には、Make または CMake のどちらか一方のビルド方法のみを使うことになります。
ネイティブマシンコードの .mpy ファイルへの埋込み を使って、カスタム C コード を .mpy ファイル中に置くこともできます。この .mpy ファイルは、メインのファームウェアを再コンパイルすることなく、利用中の MicroPython システムに動的にインポートできます。
外部 C モジュールの構造
MicroPython のユーザー C モジュールは、次のファイルを含むディレクトリーです:
モジュールのソースコードファイルである
*.c/*.cpp/*.h。これらのファイルには通常、C言語レベルでの機能実装と、それを MicroPython の公開関数/モジュールにバインディングするものから成っています。
現在のところ、このような関数/モジュールを書くための最良の参考資料は MicroPython のソースツリー内で似たモジュールを見つけて、それらを例として使うことです。
micropython.mkはモジュール用の Makefile の一部を含みます。$(USERMOD_DIR)は micropython.mk 内で利用可能で、モジュールのディレクトリパスを示します。これは各 C モジュールに対して再定義されるので、micropython.mkの中でローカルの make 変数に展開しておくべきです。たとえばEXAMPLE_MOD_DIR := $(USERMOD_DIR)とします。micropython.mkでは、モジュールのソースファイルをSRC_USERMOD_CまたはSRC_USERMOD_LIB_C変数に追加する必要があります。前者はMP_QSTR_とMP_REGISTER_MODULEの定義で処理され、後者は処理されません(例: MicroPython 固有ではないヘルパーやライブラリのコードなど)。これらのパスには、$(USERMOD_DIR)を展開コピーを含めます。たとえば次のようにします:SRC_USERMOD_C += $(EXAMPLE_MOD_DIR)/modexample.c SRC_USERMOD_LIB_C += $(EXAMPLE_MOD_DIR)/utils/algorithm.c
同様に、C++ ソースファイルには
SRC_USERMOD_CXXとSRC_USERMOD_LIB_CXXを使ってください。アセンブリファイルを含めたい場合はSRC_USERMOD_LIB_ASMを使ってください。カスタムのコンパイラオプション(ヘッダファイルを探すディレクトリを追加する
-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) 関数は2つの整数の引数を足し合わせて結果を返します。 cexample.Timer() 型は、オブジェクトがインスタンス化されてからの経過時間を計測するために使用できるタイマーを作成します。
このモジュールは MicroPython のソースツリーの examples ディレクトリ に用意していて、上記のような内容を持つソースファイルと Makefile があります。
micropython/
└──examples/
└──usercmodule/
└──cexample/
├── examplemodule.c
├── micropython.mk
└── micropython.cmake
より詳しい説明は、これらのファイルのコメントを参照してください。 cexample モジュールの次には、同じように動作する cppexample もあり、MicroPython で C と C++ コードを混ぜる方法の一つを示しています。
cmodule を MicroPython に組み込む方法
このようなモジュールをビルドするには、2つの変更を加えて MicroPython をコンパイルします(getting started を参照):
ビルド時フラグ
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
...
rp2 のような 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 行を検索することで見つけられます(通常、メインソースファイルの最後に表示されます)。このマクロは #if X / #endif のペアで囲む必要があり、モジュールを有効化するには CFLAGS_EXTRA を使って X を 1 に設定する必要があります。 #if X / #endif のペアがない場合、モジュールはデフォルトで有効になります。
たとえば examples/usercmodule/cexample モジュールは、デフォルトで有効になっているので、ソースコードに次のような行があります:
MP_REGISTER_MODULE(MP_QSTR_cexample, example_user_cmodule);
デフォルトではモジュールを無効にして、プリプロセッサの設定オプションで選択できるようにするには次のようにします:
#if MODULE_CEXAMPLE_ENABLED MP_REGISTER_MODULE(MP_QSTR_cexample, example_user_cmodule); #endif
この場合、モジュールを利用できるようにするには 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))
# 4 が表示されるはず
from cexample import Timer
from time import sleep_ms
watch = Timer()
sleep_ms(1000)
print(watch.time())
# 1000 に近い値が表示されるはず