MachiKania type Pで、BASICコードからCで書かれたコードを呼び出す、その2
2023年3月26日
MachiKania type P の ver 1.2 では、BASIC コードから C で書かれたコードを呼び出すための機能追加がされた。ひとつ前の記事では、CプロジェクトをHEXファイルに変換し、そこから必要なC関数を呼び出す方法について述べた。この記事では、もう一つの方法について述べる。
HEXファイルに変換する方法においては、複雑なCでのコーディングが必要な多くの事例に対応できる。一つの例として、正規表現エンジンを実装するMachiKaniaクラス(REGEXP クラス)の実装について述べた。
この記事で述べる方法では、Cのプロジェクトから必要なコードを抜き取って BASIC コードに変換してプログラムに埋め込む方法を取る。HEXを介する方法と比べた長所は、以下の通り。
1.一つの BASIC ファイルの中に C コードを埋め込むことができる
2.HEX を介した方法では HEX コードのメモリ上での配置位置を考慮する必要があったが、この方法ではその必要が無い
3.C プログラムの中から必要なものだけを抜き取って BASIC プログラムに埋め込むので、メモリー占有領域を最小限にできる
他方、短所は以下の通り
1.複雑な C プログラムでは対応できない場合がある
従って、それほど大きくない C のプログラムの場合はこの記事で述べる方法が有利である。後に述べる「c_convert.php」スクリプトの実行でエラーが出る、あるいは、作成されたBASIC プログラムが意図したように動作しない場合は、HEXを介する方法に移行すればよい。
BASIC に C コードを埋め込む例(hcw1アプリケーション)
先の記事と同様、次のような C 関数を BASIC から呼び出す例について述べる。
この例を説明するために、GitHubレポジトリーを作成したので、参照していただきたい。Git Cloneした後に「hcw1」ブランチにスイッチし、Commit Log を見ていただければ、説明が分かりやすいと思う。
また、この記事では、1から5までの手順ごとに該当レポジトリーへのリンクを付けたので、そちらも見ていただきたい。
1.まずは、Hello, World! から
まず、Cのプロジェクトを一つ作成する必要がある。何でも良いのだが、ここではpico-examplesからhello_usb.cを選んで、若干変更した物から始める事とする。これは、ビルドした後にPi Picoにインストールすれば、USB シリアル接続を通して、コンソール上に「Hello, World!」と表示し続けるプログラムだ(前の記事と同じ)。
2.目的のコードを、プロジェクト内に記述する
Cアプリケーションを記述してテストする環境が整ったので、ここで目的のコードを書いてみる。なお、ひとつ前の記事の方法とは異なり、この記事の方法では、プログラムはRAM上ではなくフラッシュ上に構築する方法を取る。上で述べたadd_values()関数を追加して、テスト結果を表示するようなものにした。
3.C コンパイラーの最適化を無効にする
ARM gcc コンパイラーの最適化を無効にするため、CMakeFiles.txtに次の記述を追加している。
ひとつ前の記事でも述べたが、この記事で述べる方法でも、C のコードを埋め込もうとする場合、以下の点に注意が必要だ。
a.呼び出そうとするC関数が、必ずビルドオブジェクトに含まれるようにする
b.呼び出そうとするC関数の引数は、4つまで
c.呼び出そうとするC関数の引数の取り扱いに関して、ビルドの際の最適化で標準のARMレジスター使用(R0, R1, R2, R3を使用する)から変更されないようにする
a.の対策のためには、必要な関数がすべて、「main()」関数、もしくはそこから呼び出される関数の中で、最低一度は呼び出される必要がある。でないと、リンクの際に不必要な関数コードとして、削除されてしまう。
また、Cコンパイラー(gcc)が行う最適化のために、関数がインライン展開されてしまう事もあり、そうなると外部から呼び出すことができなくなってしまう。これを防ぐ方法にはさまざまあり、1:目的の関数を「main.c」とは別のファイル内に記述する、2:「__declspec(noinline)」アトリビュートを使う、3:最適化を行わない、などがある。
b.は ARM gcc コンパイラーの規約に関係するもので、これを遵守しないと(引数を5つ以上にしてしまうと)、正常に動作しない。
c.の対策は色々と複雑で、同一のCファイル内で目的の関数を呼び出している場合に変なレジスター使用が起こるようだ。最適化の一つなので、当然ながら最適化を行わないようにすることで回避することができる。
以上の事から、トラブルを防ぐにはgccの最適化を停止することが有効である。CMakeLists.txtに「add_definitions(-O0)」の一行を加えたのは、そのためである。特殊な作例で、実行速度が最大でないといけないようなケースにはこの方法は使えないが、そうでなければ、最適化を停止して使用するのが無難だろう。
4.PHPスクリプトを配置する
作成されたHEXファイルを呼び出すBASICプログラムを自動作成するためのスクリプト「c_convert.php」を用意した。このスクリプトは、PHPで記述されているので、PHPがない場合はインストールが必要である。このPHPスクリプトは汎用であるが、冒頭の記述(13-34行目辺り)を若干編集して使用する。今回の例では、次のようになっている。
5.Cプログラムのビルドと、PHPスクリプトの実行
ここまで準備できれば、Cプログラムをビルドした後に、PHPスクリプトを実行する。「result.txt」と「log.txt」の二つのファイルが出来ているはずだ。ここでの例のように進めると、「result.txt」の内容は次のようになる。
以上が、簡単なCコードをBASICプログラムに埋め込んで呼び出すための手順である。
エラーが出た場合
「c_convert.php」は、ひとつ前の記事で述べた「c_convert2.php」と比べて少し複雑なアルゴリズムを用いていて、C のコードから必要部分を抜き出して BASIC に埋め込む動作をしている。残念ながら、複雑な構造の C コードに関しては、解析がうまく行かず、PHPスクリプトの実行中にエラーになったり、完成した BASIC プログラムが正常に動作しない事が起きうる。そういう場合は、ひとつ前の記事で述べた HEX ファイルを作成する方法の方が対応できる範囲が広いので、そちらを試していただきたい。
どんどん C コードを取り込もう
ひとつ前の記事では、複雑な C プログラムの MachiKania BASIC への取り込みについて述べたが、手続きが複雑であった。簡単な C プログラムならこの記事で書いた方法でより簡便に取り込める。それにより、MachiKania の応用範囲がさらに広くなることを、期待している。
HEXファイルに変換する方法においては、複雑なCでのコーディングが必要な多くの事例に対応できる。一つの例として、正規表現エンジンを実装するMachiKaniaクラス(REGEXP クラス)の実装について述べた。
この記事で述べる方法では、Cのプロジェクトから必要なコードを抜き取って BASIC コードに変換してプログラムに埋め込む方法を取る。HEXを介する方法と比べた長所は、以下の通り。
1.一つの BASIC ファイルの中に C コードを埋め込むことができる
2.HEX を介した方法では HEX コードのメモリ上での配置位置を考慮する必要があったが、この方法ではその必要が無い
3.C プログラムの中から必要なものだけを抜き取って BASIC プログラムに埋め込むので、メモリー占有領域を最小限にできる
他方、短所は以下の通り
1.複雑な C プログラムでは対応できない場合がある
従って、それほど大きくない C のプログラムの場合はこの記事で述べる方法が有利である。後に述べる「c_convert.php」スクリプトの実行でエラーが出る、あるいは、作成されたBASIC プログラムが意図したように動作しない場合は、HEXを介する方法に移行すればよい。
BASIC に C コードを埋め込む例(hcw1アプリケーション)
先の記事と同様、次のような C 関数を BASIC から呼び出す例について述べる。
int add_values(int a, int b){ return a+b; }
この例を説明するために、GitHubレポジトリーを作成したので、参照していただきたい。Git Cloneした後に「hcw1」ブランチにスイッチし、Commit Log を見ていただければ、説明が分かりやすいと思う。
また、この記事では、1から5までの手順ごとに該当レポジトリーへのリンクを付けたので、そちらも見ていただきたい。
1.まずは、Hello, World! から
まず、Cのプロジェクトを一つ作成する必要がある。何でも良いのだが、ここではpico-examplesからhello_usb.cを選んで、若干変更した物から始める事とする。これは、ビルドした後にPi Picoにインストールすれば、USB シリアル接続を通して、コンソール上に「Hello, World!」と表示し続けるプログラムだ(前の記事と同じ)。
#include <stdio.h> #include "pico/stdlib.h" int main() { stdio_init_all(); while (true) { printf("Hello, world!\n"); sleep_ms(1000); } return 0; }このプロジェクトをcmake/makeでビルドすると、hello_cw.uf2に加えてhello_cw.hexが作成されることが分かる。このようにして作成されたhexファイルを用いることになる。
2.目的のコードを、プロジェクト内に記述する
Cアプリケーションを記述してテストする環境が整ったので、ここで目的のコードを書いてみる。なお、ひとつ前の記事の方法とは異なり、この記事の方法では、プログラムはRAM上ではなくフラッシュ上に構築する方法を取る。上で述べたadd_values()関数を追加して、テスト結果を表示するようなものにした。
#include <stdio.h> #include "pico/stdlib.h" int add_values(int a, int b){ return a+b; } int main() { int a=0,b=0; stdio_init_all(); while (true) { printf("%d + %d = %d\n", a, b, add_values(a,b)); a+=1; b+=2; sleep_ms(1000); } return 0; }
3.C コンパイラーの最適化を無効にする
ARM gcc コンパイラーの最適化を無効にするため、CMakeFiles.txtに次の記述を追加している。
add_definitions(-O0)
ひとつ前の記事でも述べたが、この記事で述べる方法でも、C のコードを埋め込もうとする場合、以下の点に注意が必要だ。
a.呼び出そうとするC関数が、必ずビルドオブジェクトに含まれるようにする
b.呼び出そうとするC関数の引数は、4つまで
c.呼び出そうとするC関数の引数の取り扱いに関して、ビルドの際の最適化で標準のARMレジスター使用(R0, R1, R2, R3を使用する)から変更されないようにする
a.の対策のためには、必要な関数がすべて、「main()」関数、もしくはそこから呼び出される関数の中で、最低一度は呼び出される必要がある。でないと、リンクの際に不必要な関数コードとして、削除されてしまう。
また、Cコンパイラー(gcc)が行う最適化のために、関数がインライン展開されてしまう事もあり、そうなると外部から呼び出すことができなくなってしまう。これを防ぐ方法にはさまざまあり、1:目的の関数を「main.c」とは別のファイル内に記述する、2:「__declspec(noinline)」アトリビュートを使う、3:最適化を行わない、などがある。
b.は ARM gcc コンパイラーの規約に関係するもので、これを遵守しないと(引数を5つ以上にしてしまうと)、正常に動作しない。
c.の対策は色々と複雑で、同一のCファイル内で目的の関数を呼び出している場合に変なレジスター使用が起こるようだ。最適化の一つなので、当然ながら最適化を行わないようにすることで回避することができる。
以上の事から、トラブルを防ぐにはgccの最適化を停止することが有効である。CMakeLists.txtに「add_definitions(-O0)」の一行を加えたのは、そのためである。特殊な作例で、実行速度が最大でないといけないようなケースにはこの方法は使えないが、そうでなければ、最適化を停止して使用するのが無難だろう。
4.PHPスクリプトを配置する
作成されたHEXファイルを呼び出すBASICプログラムを自動作成するためのスクリプト「c_convert.php」を用意した。このスクリプトは、PHPで記述されているので、PHPがない場合はインストールが必要である。このPHPスクリプトは汎用であるが、冒頭の記述(13-34行目辺り)を若干編集して使用する。今回の例では、次のようになっている。
class configclass{ // File names public $dis_file='./build/hello_cw.dis'; public $map_file='./build/hello_cw.elf.map'; public $hex_file='./build/hello_cw.hex'; //public $debug_file='./machikap/machikap.bas'; // Functions to be exported public $functions=array( // 'machikania_init', 'add_values', ); // Additional .rodata to be used (function names are excluded) public $rodata=array( ); // Excluded addresses as .rodata and RAM public $excluded=array( 0x20040000, ); };$dis_file, $map_file, $hex_fileでは、ビルドの際に作成された「*.dis」「*.map」「*.hex」の3つのファイルの位置を指定する。$debug_fileはオプションで、PC-Connect機能を利用してデバッグを行っている際の、BASICファイルの位置を指定する。必要が無い場合は、「//」でコメントアウトしておく。また、$functionsには、BASICから呼び出したいCの関数名を列挙しておく。$rodataには、自動認識されないが必要な部品名を文字列で指定したリストを指定する。逆に、$excludedには必要のない部品の物理アドレスのリストを指定する。通常は、$rodataと$excludedの設定は変更の必要は無いはずだ。
5.Cプログラムのビルドと、PHPスクリプトの実行
ここまで準備できれば、Cプログラムをビルドした後に、PHPスクリプトを実行する。「result.txt」と「log.txt」の二つのファイルが出来ているはずだ。ここでの例のように進めると、「result.txt」の内容は次のようになる。
USEVAR C_RAM GOSUB INIT_C END LABEL INIT_C DIM C_RAM(0) REM ram vectors REM rodata vectors REM ram function vectors REM callback function vectors RETURN ALIGN4 LABEL C_ADD_VALUES EXEC $68f0,$6931,$6972,$69b3,$f000,$f802,$bd00,$46c0 REM 28 bytes LABEL C_FUNCTIONS EXEC $b580,$b082,$af00,$6078,$6039,$687a,$683b,$18d3,$0018,$46bd,$b002,$bd80,$b580,$b082 REM 0 bytes LABEL C_RODATAこのBASICプログラムは、C コードを埋め込んで呼び出しできるようにした、必要最小限の構成になっている。目的のコードを、3行目の「gosub INIT_C」と4行目の「end」の間に書けばよい。例えば、次のように変更して、BASICプログラムを完成させてみよう。
USEVAR C_RAM GOSUB INIT_C for i=1 to 10 print a,b,a+b,gosub(C_ADD_VALUES,a,b) a=a+1 b=b+2 next END LABEL INIT_C DIM C_RAM(0) REM ram vectors REM rodata vectors REM ram function vectors REM callback function vectors RETURN ALIGN4 LABEL C_ADD_VALUES EXEC $68f0,$6931,$6972,$69b3,$f000,$f802,$bd00,$46c0 REM 28 bytes LABEL C_FUNCTIONS EXEC $b580,$b082,$af00,$6078,$6039,$687a,$683b,$18d3,$0018,$46bd,$b002,$bd80,$b580,$b082 REM 0 bytes LABEL C_RODATAあとは、このBASICプログラムをMMC/SDカードにコピーすれば、実行することができる。実行時のスナップショットは、以下の通り。
以上が、簡単なCコードをBASICプログラムに埋め込んで呼び出すための手順である。
エラーが出た場合
「c_convert.php」は、ひとつ前の記事で述べた「c_convert2.php」と比べて少し複雑なアルゴリズムを用いていて、C のコードから必要部分を抜き出して BASIC に埋め込む動作をしている。残念ながら、複雑な構造の C コードに関しては、解析がうまく行かず、PHPスクリプトの実行中にエラーになったり、完成した BASIC プログラムが正常に動作しない事が起きうる。そういう場合は、ひとつ前の記事で述べた HEX ファイルを作成する方法の方が対応できる範囲が広いので、そちらを試していただきたい。
どんどん C コードを取り込もう
ひとつ前の記事では、複雑な C プログラムの MachiKania BASIC への取り込みについて述べたが、手続きが複雑であった。簡単な C プログラムならこの記事で書いた方法でより簡便に取り込める。それにより、MachiKania の応用範囲がさらに広くなることを、期待している。