bare metal という言葉を最近知って、おもしろそうだなあと思ったので Raspberry Pi 上で試してみた。bare metal とは OS レスで直接動かす (つまりリッチなマイコン的に使う) ことのようです。bare metal で動かすだけだと目標が低い感じなので、まず mruby を動かすところまでやろう、という感じです。
yamanekko さんというかたが既にやって日本語で書いているので、それを見てコピペしながらやってみつつという感じです。ちょいちょいうまくいかなかったのでかなりハマった……
ARM クロスコンパイル環境の構築
https://launchpad.net/gcc-arm-embedded にあるバイナリをインストールするのが一番良い。これを入れれば以下にあるような fpu 問題にも悩まない。
大変めんどう。ググるといろいろでてくるけど、今流行りの全部うまいことやってくれる感じのスクリプトがあったので試してみた。
https://github.com/jsnyder/arm-eabi-toolchain
git clone git@github.com:jsnyder/arm-eabi-toolchain.git cd arm-eabi-toolchain make install-cross
大変時間がかかる。Makefile を開いて、--enable-languages="c,c++" となってるところから c++ を消したほうがいいっぽい。使わないし…
入るライブラリ
- arm-none-eabi-
- gcc
- binutils (objdump とか)
- gdb
- GMP
- 数値演算ライブラリ
- MPFR
- 浮動小数点ライブラリ
- MPC
- MPFR ベースのさらに複雑な浮動小数点ライブラリ
- newlib
- 組み込み環境用のライブラリ。 libc とかを提供
少し古いバージョンが入る
FPU 問題
最初
arm-none-eabi-ld: error: main.elf uses VFP register arguments, /Users/cho45/arm-cs-tools/arm-none-eabi/lib//libc.a(lib_a-errno.o) does not
とか出て ld が通らなかった。
yamanekko さんのレポジトリにある Makefile だと fpu ライブラリへのパスが書いてあるけど、上のスクリプトでクロスコンパイル環境を構築してもでてこない……
gcc の configure オプションか何かなのかもしれないけど、時間がかかるしむずかしいので諦めた。
よくわからなすぎるのでググったらARM EABIでの浮動小数点演算というが参考になった。
結局 -mfloat-abi=soft にして完全にソフトウェア命令しか出さないようにしたら動くようになった。-mfloat-abi=soft=softfp だと浮動小数点演算が走ったタイミングで死んだりしてよくわからない。
オプションの種類は arm-none-eabi-gcc --target-help で見ることができる。-mfpu=vfp のところもいろいろ種類があるけどよくわからない。
未だ完全には解決してない。
リンカスクリプト
memmap というファイルになってる。面倒だった。間違えるといろいろよくわかないことになる。今回遭遇したのは
- .ARM.exidx を定義してなかったせいでおこられる
- C++ 用のセクションらしいけど 特定の関数を使おうとすると必要っぽい
- 定義の順番がおかしいとできるバイナリがデカくなったり、動かなくなったりする
- アラインメントがおかしかったせい?で newlib の mALLOc が無限ループする
- 当然ヒープを実装せず使わないなら再現しないので簡単なLチカだと気付かない
newlib が要求してくるシステムコール類は syscalls.c でモック的に定義されている(コピペしたのものをクリーンアップした)。だいたいはどうでもいい関数だけど、_sbrk が大変重要で、これによってヒープが実現されている。
メモリ割当のマッピングとかはリンカスクリプトで定義したものを vectors.s (アセンブラ) で C 側から見れるようにして、それを使ってヒープとかの実装をしているみたい。
gdb でシミュレーションデバッグ
.gdbinit に書いてあるけど
file main.elf target sim load
とすることで、gdb 上でシミュレーションして動かすことができるので、周辺機器が絡まない、とりあえずのデバッグはこれでも十分できる。
ただ、一部の演算、確認したのは % (余剰)で変なエラーがでておかしい。
実際やった手順
mruby なしでLチカ
まず mruby なしでLチカができないとどうしようもないので、まずそこまでできるプロジェクトツリーを作って動くことを確認するまでやった。
https://github.com/cho45/raspberrypi-bare-metal
この過程だけでも上記の通りかなりハマった……
mruby ありでLチカ
次に mruby ありでLチカに挑戦した。yamanekko さんのmruby-rs-led を参考に、Linux の gpio 的な GPIO module gem を実装した。
- https://github.com/cho45/raspberrypi-mruby-bare-metal/blob/master/mrbgems/mruby-raspberrypi-gpio/src/gpio.c
- https://github.com/cho45/raspberrypi-mruby-bare-metal/blob/master/mrbgems/mruby-raspberrypi-gpio/src/gpio.h
GPIO のアドレスとかはデータシート記載のものを手動で変換せずにできるだけそのまま書いておきたいと思うので、そうしてる。バスアドレスというのと、物理アドレスというのがあって、データシートにはバスアドレスで書いてあって、しかし実際使うのは物理アドレスなので、変換 (固定値) が必要みたい。
これによって以下のように書けるように (完全にちゃんと動いているか微妙だけど) なった。Ruby にすると一気に簡単になった感じがして落差がウケます。ほぼ Linux 上で適当にラッパー書いて書くのと同じコードになった。うまくラップできれば主要コードは Ruby レベルでテストが書けてよさそう。
pin = 16
GPIO.direction(pin, :low)
bool = false
loop do
bool = !bool
GPIO.write(pin, bool)
10000.times { }
end
しかしここまで動かすのも大変で、何時間もハマった。ここでハマったのは特にリンカスクリプトでアラインメントがおかしかったせいで、mrb_open() がそもそも返ってこないというのがつらかった。
gdb で追ってもなぜか newlib の中で無限ループにになるので意味不明だった。
そしてそれがようやくなおったと思っても mrb_open() の最中に浮動小数点演算もしていて、ここでハングするということが起きてつらかった。最終的には上記の通り、とりあえず常にソフトウェアにしてなんとか動かした。
sleep の実装を書いてないので Ruby 側でビジーループしてる。16ピンにしてるのは、Raspberry Pi の基板上に実装済みの LED だからで、デバッグ用に新規配線をしなくてすむからです。
sleep の実装
さすがにビジーループをRuby側でやるのはダサいので次に sleep を実装した。
system timer と arm timer というのがあるけど、今回は system timer を使った。arm timer はシステムの状態によってクロックが変わるみたい。system timer は独立したクロックを持ってるようだ。
ただ、肝心の system timer clock がよくわからなかった。試行錯誤したかんじだと、1Mhz ぐらいで動いてる。分周比設定とかがなくて謎。要追試
Cレベルではビジーループしてる。うまくCPUスリープさせたいけど、まだよくわかってない。
mruby でハマったところ
mruby ではあまりハマらなかった。mrbgem 作るのは大変簡単になっていておもてなし感がある。
ただ、build_conf.rb で gem を指定せずにビルドすると、あとから gem を指定しても、clean するまで反映されないのが罠ぽかった。深くおってないけど、DISABLE_GEMS が永続されてしまうぽい?
この件は再現させて1行パッチ送ったらマージされたのでたぶん大丈夫です。build_config.rb を書きかえたら .c ソースのリビルドが走るはず…
レポジトリ
これも習作だけど別レポジトリにおいてる
課題点
- fpu の問題の解決
- GPIO モジュールの拡張
- TIMER や割込みなどを使えるように
- ハードウェア I2C SPI の実装
- bit banging での I2C 実装
- JTAG を試す
- ブートローダーを使うように
- SDカードいちいち抜き差ししてるとつらいので
感想
mruby は夢が広がる。mruby で OS とか作ったらかっこよさそう。