μT-Kernel 3.0 を Raspberry Pi Pico で動かしてみよう
トロンフォーラムから公開されている国産リアルタイム OS「μT-Kernel 3.0」を Raspberry Pi Pico (以下 Pico) 向けにビルドしてみます。Pico への移植は、同じくトロンフォーラムから公開されている μT-Kernel 3.0 BSP (Board Support Package) というリポジトリで進められていますが、開発環境は Eclipse を 使用することになっています。これを GNU make でビルドできるようにして、Pico で動かしてみようというのが本稿の趣旨です (IDE はなんとなく苦手なもので…)。
目次
μT-Kernel 3.0 とは
- トロンフォーラムから公開されている国産リアルタイム OS です。
- ソースコードは GitHub で公開されており、T-License 2.2 のもとで誰でもオリジナルソースの再配布や改変が可能です。詳しくは下記のサイトを参照してください。
- リアルタイム OS の国際標準規格である IEEE 2050-2018 は μT-Kernel 2.0 の仕様がベースとなっています。
- μT-Kernel 3.0 では IEEE 2050-2018 と μT-Kernel 2.0 との間の差異を吸収し、IEEE 2050-2018 に完全に準拠しています。
μT-Kernel 3.0 BSP (Board Support Package) とは
μT-Kernel 3.0 は BSP (Board Support Package) というパッケージによって Pico をサポートしています。本家と同様、BSP も GitHub で公開されています。
BSP は Pico も含めて下記のマイコンボードをサポートしています (2023 年 11 月現在)。
CPU | ボード | 開発環境 |
---|---|---|
STM32L476 | Nucleo-64 | STM32CubeIDE |
STM32H723 | Nucleo-144 | STM32CubeIDE |
RX65N | Renesas Target Board | e2Studio |
RX65N | Renesas Starter Kit+ | e2Studio |
RP2040 | Raspberry Pi Pico | Eclipse CDT |
また、μT-Kernel 3.0 BSP のデフォルトのカーネルコンフィグレーションは、本家 μT-Kernel 3.0 とまったく同じというわけではないらしく、それぞれの config.h を 比較すると次のような違いがあります。
- カーネルオブジェクト数の上限を少なめに設定
- ランデブを無効化
※ ランデブはレガシーな機能で IEEE 2050-2018 の仕様にはありませんが、本家 μT-Kernel 3.0 では互換性のために残されています。 - いくつかのデバッグ機能を無効化
ソースコードの取得
早速 μT-Kernel 3.0 BSP のソースコードを GitHub から clone しましょう。その際 pico_rp2040 ブランチを指定します。取得先は ~/pico/repo/ とします。
% cd ~/pico/repo/
% git clone https://github.com/tron-forum/mtk3_bsp.git --branch pico_rp2040
GNU make でビルドしたい
取得したソースコード一式の docs ディレクトリに Pico 向けのマニュアルが用意されています (uTK3bsp_pico_man_jp.pdf)。この文書は Eclipse の使用を前提に書かれていますが、 3.2 節「プロジェクトのファイル構成」を見ると Make 構築用のディレクトリとして build_make が用意されていることがわかります。これは本家 μT-Kernel 3.0 のものがそのまま残っているようで「BSP では使用しない」とされていますが、このディレクトリの中を見れば GNU make でビルドするためのヒントがありそうです。というわけで、build_make ディレクトリの中を覗いてみます。
% cd mtk3_bsp
% tree build_make
build_make
├── iote_m367.mk
├── iote_rx231.mk
├── iote_rza2m.mk
├── iote_stm32l4.mk
├── makefile
├── mtkernel_3
│ ├── app_program
│ │ └── subdir.mk
│ ├── device
│ │ ├── adc
│ │ │ ├── subdir.mk
│ │ │ └── sysdepend
│ │ │ ├── rx231
│ │ │ │ └── subdir.mk
│ │ │ ├── rza2m
│ │ │ │ └── subdir.mk
│ │ │ ├── stm32h7
│ │ │ │ └── subdir.mk
│ │ │ ├── stm32l4
│ │ │ │ └── subdir.mk
│ │ │ └── tx03_m367
│ │ │ └── subdir.mk
│ │ ├── common
│ │ │ └── drvif
│ │ │ └── subdir.mk
│ │ ├── i2c
│ │ │ ├── subdir.mk
│ │ │ └── sysdepend
│ │ │ ├── rx231
│ │ │ │ └── subdir.mk
│ │ │ ├── rza2m
│ │ │ │ └── subdir.mk
│ │ │ ├── stm32h7
│ │ │ │ └── subdir.mk
│ │ │ ├── stm32l4
│ │ │ │ └── subdir.mk
│ │ │ └── tx03_m367
│ │ │ └── subdir.mk
│ │ ├── ser
│ │ │ ├── subdir.mk
│ │ │ └── sysdepend
│ │ │ ├── rx231
│ │ │ │ └── subdir.mk
│ │ │ ├── rza2m
│ │ │ │ └── subdir.mk
│ │ │ ├── stm32h7
│ │ │ │ └── subdir.mk
│ │ │ ├── stm32l4
│ │ │ │ └── subdir.mk
│ │ │ └── tx03_m367
│ │ │ └── subdir.mk
│ │ └── subdir.mk
│ ├── kernel
│ │ ├── inittask
│ │ │ └── subdir.mk
│ │ ├── sysdepend
│ │ │ ├── cpu
│ │ │ │ ├── core
│ │ │ │ │ ├── acm3
│ │ │ │ │ │ └── subdir.mk
│ │ │ │ │ ├── armv7a
│ │ │ │ │ │ └── subdir.mk
│ │ │ │ │ ├── armv7m
│ │ │ │ │ │ └── subdir.mk
│ │ │ │ │ └── rxv2
│ │ │ │ │ └── subdir.mk
│ │ │ │ ├── rx231
│ │ │ │ │ └── subdir.mk
│ │ │ │ ├── rza2m
│ │ │ │ │ └── subdir.mk
│ │ │ │ ├── stm32h7
│ │ │ │ │ └── subdir.mk
│ │ │ │ ├── stm32l4
│ │ │ │ │ └── subdir.mk
│ │ │ │ └── tx03_m367
│ │ │ │ └── subdir.mk
│ │ │ ├── iote_m367
│ │ │ │ └── subdir.mk
│ │ │ ├── iote_rx231
│ │ │ │ └── subdir.mk
│ │ │ ├── iote_rza2m
│ │ │ │ └── subdir.mk
│ │ │ ├── iote_stm32l4
│ │ │ │ └── subdir.mk
│ │ │ └── nucleo_h723
│ │ │ └── subdir.mk
│ │ ├── sysinit
│ │ │ └── subdir.mk
│ │ ├── tkernel
│ │ │ └── subdir.mk
│ │ ├── tstdlib
│ │ │ └── subdir.mk
│ │ └── usermain
│ │ └── subdir.mk
│ └── lib
│ ├── libtk
│ │ ├── subdir.mk
│ │ └── sysdepend
│ │ └── cpu
│ │ ├── core
│ │ │ ├── acm3
│ │ │ │ └── subdir.mk
│ │ │ ├── armv7a
│ │ │ │ └── subdir.mk
│ │ │ ├── armv7m
│ │ │ │ └── subdir.mk
│ │ │ └── rxv2
│ │ │ └── subdir.mk
│ │ ├── rx231
│ │ │ └── subdir.mk
│ │ ├── rza2m
│ │ │ └── subdir.mk
│ │ ├── stm32h7
│ │ │ └── subdir.mk
│ │ ├── stm32l4
│ │ │ └── subdir.mk
│ │ └── tx03_m367
│ │ └── subdir.mk
│ └── libtm
│ ├── subdir.mk
│ └── sysdepend
│ ├── iote_m367
│ │ └── subdir.mk
│ ├── iote_rx231
│ │ └── subdir.mk
│ ├── iote_rza2m
│ │ └── subdir.mk
│ ├── iote_stm32l4
│ │ └── subdir.mk
│ ├── no_device
│ │ └── subdir.mk
│ └── nucleo_h723
│ └── subdir.mk
└── nucleo_h723.mk
この構造はソースコード本体のディレクトリ構造を反映していて、ソースツリーの各サブディレクトリに収められているソースファイルと、build_make 以下の同じパスにある subdir.mk とが対応しています。例えば
- mtk3_bsp/build_make/mtkernel_3/kernel/tkernel/subdir.mk はソースファイル mtk3_bsp/kernel/tkernel/*.c に対応。
- mtk3_bsp/build_make/mtkernel_3/lib/libtm/sysdepend/nucleo_h723/subdir.mk はソースファイル mtk3_bsp/lib/libtm/sysdepend/nucleo_h723/tm_com.c に対応。
といった具合です。それぞれの subdir.mk では、基本的に次のことを行っています。
- 対応するソースコードをコンパイルする。
- 生成物であるオブジェクトファイルのパスを変数
OBJS
に追加する。
mtk3_bsp/build_make/makefile がそれらの各 Makefile を統べる大もとの Makefile です。この中では基本的に次のことを行っています。
- 各ターゲットボードに共通の Makefile をインクルードする。
- ターゲット依存部の Makefile をインクルードする。
例えば ST マイクロエレクトロニクスの Nucleo H723 というボードに依存する部分のビルド方法は nucleo_h723.mk にまとめられており、変数TARGET
に_NUCLEO_H723_
がセットされているとこのファイルがインクルードされます。 - 変数
OBJS
に列挙されているオブジェクトファイルをリンクして ELF ファイルを生成する。
これで μT-Kernel 3.0 を make する仕組みが何となくわかったので、実際の作業に取り掛かりましょう。
build_make ディレクトリを Pico 向けに変更する
ソースコード本体はすでに Pico に対応しているので手を加える必要はありません。build_make ディレクトリ内の修正・追加だけで済みます。具体的には次のような形で進めます。
- ソースツリーの Pico 依存部に対応する形で、build_make ディレクトリ以下にサブディレクトリを作成する。
- 手順 1 で作成した各ディレクトリに subdir.mk を作成する (たいていは他のターゲットボード用の subdir.mk をコピーしてちょっと修正するだけで済むでしょう)。
- Pico 依存部をまとめた Makefile を作成する (これも nucleo_h723.mk あたりをベースに作成します。ファイル名は pico_rp2040.mk としておきましょう)。
- 大もとの makefile で pico_rp2040.mk をインクルードする。
こうして Pico 向けに修正・作成したファイルは次のとおりです (★印が新規作成、その他は既存ファイルの修正)。
build_make
├── makefile
├── mtkernel_3
│ ├── device
│ │ ├── adc
│ │ │ ├── subdir.mk
│ │ │ └── sysdepend
│ │ │ └── rp2040 ★
│ │ │ └── subdir.mk ★
│ │ ├── i2c
│ │ │ ├── subdir.mk
│ │ │ └── sysdepend
│ │ │ └── rp2040 ★
│ │ │ └── subdir.mk ★
│ │ └── ser
│ │ ├── subdir.mk
│ │ └── sysdepend
│ │ └── rp2040 ★
│ │ └── subdir.mk ★
│ ├── kernel
│ │ └── sysdepend
│ │ ├── cpu
│ │ │ ├── core
│ │ │ │ └── armv6m ★
│ │ │ │ └── subdir.mk ★
│ │ │ └── rp2040 ★
│ │ │ └── subdir.mk ★
│ │ └── pico_rp2040 ★
│ │ └── subdir.mk ★
│ └── lib
│ ├── libbsp ★
│ │ └── sysdepend ★
│ │ └── cpu ★
│ │ └── rp2040 ★
│ │ └── subdir.mk ★
│ ├── libtk
│ │ └── sysdepend
│ │ └── cpu
│ │ ├── core
│ │ │ └── armv6m ★
│ │ │ └── subdir.mk ★
│ │ └── rp2040 ★
│ │ └── subdir.mk ★
│ └── libtm
│ └── sysdepend
│ └── pico_rp2040 ★
│ └── subdir.mk ★
├── pico_rp2040.mk ★
└── tools ★
基本的には既存の Nucleo H723 などのターゲットボード用の Makefile をコピーして修正していくだけの単純作業ですが、少しだけアレンジ的なことを加えました。
- ELF から UF2 への変換。
tools ディレクトリに変換ツール (elf2uf2) が置かれていれば、ビルド時に ELF から UF2 への変換まで行うようにしました (elf2uf2 の準備方法については「Raspberry Pi Pico の L チカプログラムをビルドして動かす」を参照)。 - Newlib をリンクする。
Newlib をリンクせずに (リンカオプション-nostdlib
を付けて) ビルドすると msdrvif.c の下記の部分でリンクエラーとなります。この原因は、GCC で上記のような構造体の代入処理を Cortex-M0+ 向けにコンパイルすると memcpy に置き換わるためのようです。μT-Kernel 3.0 自体は C 標準ライブラリに依存しないように書かれていると思われますが、これはとんだ誤算です。しかたがないので Newlib をリンクすることにしました。といっても特に何かする必要はなく、リンカオプションにmtk3_bsp/device/common/drvif/msdrvif.cEXPORT ER msdi_def_dev( T_DMSDI *dmsdi, T_IDEV *idev, T_MSDI **p_msdi ) { ... msdi->dmsdi = *dmsdi; /* Structure Copy */ ... }
-nostdlib
を付けたくなる気持ちを抑えるだけです (ちなみに-nostdlib
オプションがなければ暗黙のうちに libgcc.a もリンクされるので、「Raspberry Pi Pico のミニマムなクロス開発環境を FreeBSD に構築する」の記事で書いた「-lgcc
を付けないと割り算ができない問題」は自動的に解決します)。
ここで修正・作成した結果は GitHub で公開しています。各ファイルの中身はそちらを確認してください。
ユーザーアプリケーションの動作確認
ユーザーアプリケーションのサンプルが mtk3_bsp/app_program/app_main.c にあります。タスクを 1 個作って、その中で 500 ミリ秒ごとに LED 出力を反転させるというものです。これをビルドして動作確認を行いましょう。開発環境の構築方法については過去記事「Raspberry Pi Pico のミニマムなクロス開発環境を FreeBSD に構築する」を参照してください。ビルドは単に build_make ディレクトリで gmake を実行するだけです。
% cd ~/pico/repo/mtk3_bsp/build_make/
% gmake
ビルドに成功すると mtk3pico.uf2 という名前のファイルができるので、これを Pico に書き込んで実行し、LED がチカチカすることを確認します。UF2 ファイルの書き込み方法やデバッグの方法については次の過去記事を参照してください。
おわりに
ここまでの作業で μT-Kernel 3.0 を GNU make を使ってビルドできること、ユーザーアプリケーションが Pico で動作することを確認できました。現段階ではまだ μT-Kernel 3.0 を使う予定はありませんが (まだ学習中&活用方法の検討中…)、とりあえず一段落かなと思います。