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 に近い値が表示されるはず