はじめに
MicroPython 用の自作 C モジュールを作成するため、公式ドキュメントの手順に従ってビルドを試してみた。
目次
MicroPythonのBuild手順
WindowsのUbunt(WSL)上でBuildする手順をまとめる。詳細は下記参照。
1.MicroPythonのコードを取得する
# 任意のフォルダ内にClone
$ git clone --recursive https://github.com/micropython/micropython.git
# 必要な依存パッケージ取得
$ sudo apt update
$ sudo apt install build-essential git python3 pkg-config libffi-dev
2.MicroPythonのクロスコンパイラmpy-crossのビルド
$ cd mpy-cross
$ make
3.Linux用のMicroPythonビルド
Linux上で動作するMicroPythonビルド。
$ cd ports/unix
$ make submodules
$ make
結果を動作確認する。replできればOK。「Ctrl+D」で抜ける。
$ cd build-standard
$ ./micropython
$ MicroPython v1.29.0-preview.115.g9f396bba8d on 2026-05-04; linux [GCC 13.3.0] version
$ Type "help()" for more information.
>>> print("hello")
hello
>>>
4.Pico用のMicroPythonビルド
Pico用のビルドをしてuF2ファイルを生成する。
# 必要な依存パッケージ取得
$ sudo apt install cmake
$ sudo apt install gcc-arm-none-eabi build-essential
# Buildする
$ cd ports/rp2
$ make
# uf2ファイル置き場
$ cd build-RPI_PICO
$ ls
$ CMakeCache.txt cmake_install.cmake firmware.elf.map frozen_mpy pico_flash_region.ld pioasm-install
$ CMakeFiles firmware.bin firmware.hex generated picotool
$ Makefile firmware.dis firmware.uf2 genhdr pins_RPI_PICO.c
$ _deps firmware.elf frozen_content.c pico-sdk pioasm
# firmware.uf2 が生成結果
Picoの種類によってmakeのコマンドが異なる。
| 種類 | make |
|---|---|
| pico | make BOARD=PICO |
| pico w | make BOARD=PICO_W |
| pico2 | make BOARD=PICO2 |
| pico2 w | make BOARD=PICO2_W |
自作のCモジュールを追加してBuildする
/examples/usercmodule/cexampleのサンプルコードを一部変更して試してみる。user_sample(a,b)という関数名で引数a,bの掛け算をreturnする。
// sample add
static mp_obj_t user_sample(mp_obj_t a_obj, mp_obj_t b_obj) {
// Extract the ints from the micropython input objects.
int a = mp_obj_get_int(a_obj);
int b = mp_obj_get_int(b_obj);
return mp_obj_new_int(a * b);
}
static MP_DEFINE_CONST_FUN_OBJ_2(user_sample_obj, user_sample);
// sample add
//・・・
static const mp_rom_map_elem_t example_module_globals_table[] = {
{ MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_cexample) },
{ MP_ROM_QSTR(MP_QSTR_user_sample), MP_ROM_PTR(&user_sample_obj) }, // sample add
{ MP_ROM_QSTR(MP_QSTR_add_ints), MP_ROM_PTR(&example_add_ints_obj) },
{ MP_ROM_QSTR(MP_QSTR_Timer), MP_ROM_PTR(&example_type_Timer) },
{ MP_ROM_QSTR(MP_QSTR_AdvancedTimer), MP_ROM_PTR(&example_type_AdvancedTimer) },
};
makeする
$ cd ports/rp2/
# 追加モジュールをオプションにつける
$ make USER_C_MODULES=../../examples/usercmodule/micropython.cmake
# uf2ファイル置き場
$ cd build-RPI_PICO
$ ls
$ CMakeCache.txt cmake_install.cmake firmware.elf.map frozen_mpy pico_flash_region.ld pioasm-install
$ CMakeFiles firmware.bin firmware.hex generated picotool
$ Makefile firmware.dis firmware.uf2 genhdr pins_RPI_PICO.c
$ _deps firmware.elf frozen_content.c pico-sdk pioasm
# firmware.uf2 が生成結果
Pico の BOOTSEL ボタンを押しながら PC と USB 接続して、UF2ファイルをドラックアンドドロップしてreplを起動。自作モジュール をコールするとuser_sample(5*10)=50で返答される。成功!!
MicroPython v1.29.0-preview.115.g9f396bba8d.dirty on 2026-05-04; Raspberry Pi Pico with RP2040
Type "help()" for more information.
>>> import cexample
>>> print(cexample.user_sample(5,10))
50
>>>
自作のCモジュールを.mpy化
自作のCモジュールを追加してBuildするでは、自作の C モジュールを MicroPythonのファームウェアごとビルドする必要がある。しかし、自作 C モジュールだけを個別にビルドし、独立した .mpy ファイル としてライブラリ化する方法もある。公式サイトに記載されている例を試してみる。
factorial.cに自作のコードを書く場合は、ファイル配置を下記のようなフォルダ構成にする。
├── /micropython
│ ├── ports/
│ ├── py/
│ │ └── dynruntime.mk ← ビルドに必須
│ ├── mpy-cross/
│ ├── tools/
│ └── ...(その他 MicroPython のソース)
└── /factorial
├── Makefile
└── factorial.c
Cとmakefileは下記の通り。階乗を計算する例。
// MicroPython API にアクセスするためのヘッダファイルをインクルード
#include "py/dynruntime.h"
// 階乗を計算するヘルパー関数
static mp_int_t factorial_helper(mp_int_t x) {
if (x == 0) {
return 1;
}
return x * factorial_helper(x - 1);
}
// Python から factorial(x) として呼び出される関数
static mp_obj_t factorial(mp_obj_t x_obj) {
// MicroPython 入力オブジェクトから整数を抽出
mp_int_t x = mp_obj_get_int(x_obj);
// 階乗を計算
mp_int_t result = factorial_helper(x);
// 計算結果を MicroPython 整数オブジェクトに変換して戻す
return mp_obj_new_int(result);
}
// 上の関数の Python 参照を定義
static MP_DEFINE_CONST_FUN_OBJ_1(factorial_obj, factorial);
// これはエントリポイントであり、モジュールをインポートしたときに呼び出される
mp_obj_t mpy_init(mp_obj_fun_bc_t *self, size_t n_args, size_t n_kw, mp_obj_t *args) {
// これが最初になければならず、グローバル辞書などをセットアップする
MP_DYNRUNTIME_INIT_ENTRY
// モジュールの名前空間で関数を利用できるようにする
mp_store_global(MP_QSTR_factorial, MP_OBJ_FROM_PTR(&factorial_obj));
// これが最後になければならず、グローバル辞書をリストアする
MP_DYNRUNTIME_INIT_EXIT
}
# 最上位の MicroPython ディレクトリの場所
MPY_DIR = ../micropython
# モジュールの名前
MOD = factorial
# ソースファイル(.c や .py)
SRC = factorial.c
# ビルド対象のアーキテクチャ(x86, x64, armv6m, armv7m, xtensa, xtensawin, rv32imc, rv64imc)
ARCH = armv6m
# このインクルードにより、モジュールをコンパイル・リンクするルールを取得
include $(MPY_DIR)/py/dynruntime.mk
make時は対象のARCHを指定必要。Picoの場合は、armv6mを指定。
$ make ARCH=armv6m
# 結果
$ GEN build/factorial.config.h
$ CC factorial.c
$ LINK build/factorial.o
$ arch: EM_ARM
$ text size: 128
$ bss size: 0
$ GOT entries: 2
$ GEN factorial.mpy
生成される facorial.mpyをThonny等でPicoに書き込む。replを起動。自作モジュール をコールするとfactorial.factorial(10)=3628800で返答される。成功!!
10!=1×2×3×4x5x6x7x8x9x10 = 3628800
MicroPython v1.29.0-preview.115.g9f396bba8d.dirty on 2026-05-04; Raspberry Pi Pico with RP2040
Type "help()" for more information.
>>> import factorial
>>> print(factorial.factorial(10))
3628800
>>>
LEDをOnOffする自作のCモジュールを.mpy化
Picoのレジスタを直接触って動作する例としてLEDをOnOffするモジュールを作成する。フォルダ構成は下記の通り。
├── /micropython
│ ├── ports/
│ ├── py/
│ │ └── dynruntime.mk ← ビルドに必須
│ ├── mpy-cross/
│ ├── tools/
│ └── ...(その他 MicroPython のソース)
└── /myled
├── Makefile
└── myled.c
Cとmakefileは下記の通り。
#include "py/dynruntime.h"
// RP2040 レジスタベース
#define SIO_BASE 0xD0000000
#define IO_BANK0_BASE 0x40014000
#define PADS_BANK0_BASE 0x4001C000
// SIO レジスタ
#define GPIO_OUT_SET (SIO_BASE + 0x14)
#define GPIO_OUT_CLR (SIO_BASE + 0x18)
#define GPIO_OE_SET (SIO_BASE + 0x24)
// IO_BANK0 / PADS_BANK0
#define IO_BANK0_GPIO_CTRL(n) (IO_BANK0_BASE + 0x04 + (n) * 8)
#define PADS_BANK0_GPIO(n) (PADS_BANK0_BASE + 0x04 + (n) * 4)
// FUNCSEL GPIOは5
#define FUNCSEL_GPIO 5
// -------------------------
// LED(GPIO25) 初期化
// -------------------------
static void init_led_gpio(void) {
uint32_t pin = 25;
// IO_BANK0: GPIO 機能に設定
*(volatile uint32_t *)IO_BANK0_GPIO_CTRL(pin) = FUNCSEL_GPIO;
// PADS_BANK0: IE=1, OD=0, PU=0, PD=0
*(volatile uint32_t *)PADS_BANK0_GPIO(pin) = (1 << 6);
// SIO: 出力許可
*(volatile uint32_t *)GPIO_OE_SET = (1u << pin);
}
// -------------------------
// led(val) : 1=ON, 0=OFF
// -------------------------
static mp_obj_t led(mp_obj_t val_obj) {
uint32_t val = mp_obj_get_int(val_obj);
uint32_t mask = 1u << 25;
init_led_gpio();
if (val) {
*(volatile uint32_t *)GPIO_OUT_SET = mask;
} else {
*(volatile uint32_t *)GPIO_OUT_CLR = mask;
}
return mp_const_none;
}
static MP_DEFINE_CONST_FUN_OBJ_1(led_obj, led);
// -------------------------
// dynruntime 初期化
// -------------------------
mp_obj_t mpy_init(mp_obj_fun_bc_t *self, size_t n_args, size_t n_kw, mp_obj_t *args) {
MP_DYNRUNTIME_INIT_ENTRY
mp_store_global(MP_QSTR_led, MP_OBJ_FROM_PTR(&led_obj));
MP_DYNRUNTIME_INIT_EXIT
}
# 最上位の MicroPython ディレクトリの場所
MPY_DIR = ../micropython
# モジュールの名前
MOD = myled
# ソースファイル(.c や .py)
SRC = myled.c
# ビルド対象のアーキテクチャ(x86, x64, armv6m, armv7m, xtensa, xtensawin, rv32imc, rv64imc)
ARCH = armv6m
# このインクルードにより、モジュールをコンパイル・リンクするルールを取得
include $(MPY_DIR)/py/dynruntime.mk
makeをするとmyled.mpyが生成されるので、Picoに書き込む。replを起動。自作モジュール をコールするとPicoのledがON/OFFする成功!!
MicroPython v1.29.0-preview.115.g9f396bba8d.dirty on 2026-05-04; Raspberry Pi Pico with RP2040
Type "help()" for more information.
>>> import myled
>>> myled.led(1)
>>> myled.led(0)
>>>