皆さんこんにちは、グレキチです。
夏真っ只中で、とても暑い日々が続いています。私のこの創作部屋にはエアコンが設置されていないので、窓全開と新しく調達した扇風機で、サウナ室のようなこの空間を凌いている状況です。室内熱中症に気をつけて、水分と塩を摂ることは欠かさないように気をつけつつ、何とか暑さのピークである7月末〜8月初を乗り越えることが出来たので、今年は、このまま家でのエアコンレスを貫いていきます。でも、たまに車に乗る際に使うエアコンの涼しいこと😆
さて今回は、ついに私、ベアメタルに辿り着いてしまった😱ので、ベアメタル学習の取り掛かりとして、Lチカ※にトライした内容について語ってみたいと思います。
※Lチカは以前のブログでもありましたが、LEDをチカチカと点滅させることです。
ベアメタルとは?
そもそも、ベアメタルって何かって?
メタルって聞いただけで、何だかワクワクしてしまうのは私だけでしょうか!?
残念ながら、ヘビーメタルやスラッシュメタルなどの音楽ジャンルのことでは無く、ましてや、やっつけると大量の経験値をくれるモンスターの類でも無いです🤣
組み込み開発者なら誰でも知っているんだと思いますが、ポンコツプログラマーだと知らない人も多いのですかね?(まあ、自分のことなんですけどね🙄)
最近知ったばかりの私が、さも昔から知っているかのような雰囲気で説明すると、ベアメタルとは、OSがインストールされていないハードウェアを、機械語に近いコードを使って動かすことをいいます。その時にコードを作成する言語のことを、広義にはアセンブリ言語と呼んでいるようですが、アセンブリ言語はC言語やPythonなどのように、これだ!っと一括りにできなくて、使用するハードウェア毎に使うコマンド(ニーモニックなど)が微妙に違っているので、それぞれで、例えば、ARMのアセンブリ言語とか、X86のアセンブリ言語、とかっていう言い方をするようです。
私が取り組みたいことの一つに組み込みシステム開発があり、組み込み開発で広く普及しているCPUアーキテクチャといえばARMとのことだったので、まずはARMのアセンブリ言語に狙いを定めて勉強してみることにした、という訳です。
開発環境構築
アセンブリ言語勉強は初めてなので、まずはプログラミング界隈では定番の開発環境構築から始めていきます。とは言っても、仮想環境上にすでに構築済みのLinuxOS(Ubuntu22.04)を用いて進めることを前提としているので、ここでいう開発環境の構築とは、LinuxOS環境上でのアセンブリ言語開発環境の構築(設定)のみに焦点を当てて説明します。
なお、コードエディタは、Linux上にインストールしたVSCodeを使いました。
それで、実際の環境設定ですが、ARM社の公式ホームページのこちらの情報に従ってインストールするのが楽で助かりました。ちなみに、ツールのダウンロードページはこちらです。
ここで、普通に“sudo apt install gcc-arm-none-eabi”とターミナルにコマンドを打ち込んでインストールしてもいいんですが、その場合、後々使うであろうデバッグツールであるGDBが含まれてないので、GDBも使いたいなら、カスタムインストールをすることになります。私はGDBも使いたかったので、カスタムでのインストールを行いました。
それと、今回はMakefileを使ってプログラムをビルドするので、makeコマンドなども実行できるように、下記もターミナルで実行しておきます。(通常、Linuxをインストールしているのであれば、既にインストール済みかもしれないので、特別必要ないかも)
sudo apt install build-essential
と言うわけで、これで開発環境が整いました。
Lチカ実行コード
実行コードについては、Web上で見つけたものを参考にしました。今回参考にしたのは、下記のサイトの情報です。説明文などは全て英語ですが、今のご時世、Google翻訳などを使えば訳ないですね。
David Welch氏のGithubレポジトリ:raspberrypi-pico/blinker00/
それで、このレポジトリのデータをマルっとコピーして実行してもいいんですが、それだと折角のベアメタルをあまり実感できないので、極力コードを写経して自分で書いてみました。
今回のプログラムは、いくつかC言語ファイルも使う構造となっているので、C言語ファイルは中身を確認するだけに留め、それ以外のMakefileとリンクディレクティブファイル(拡張子.ld)とアセンブリソースファイル(拡張子.s)の3ファイルを写経しました。
この3つのファイルについて、少しだけ解説しておきます。
まず、Makefileですが、このファイルは作成した各ファイルをどのようにコンパイル、リンクするかなど、実行プログラムをビルドするための指示を記載したファイルになります。C/C++のビルドで利用するCMakeLists.txtと似たようなものですね。
但し、似て非なるものなので、書き方なんかは、またこれ違うんですね〜😵💫
私の印象だと、Makefileの方が余計な文言の記述がほとんど無いので、記述が必要なものを直感的に書けて良いと思いました。また、どのファイルをコンパイルしり、リンクしたら、このファイルができるのかなどが明確にわかるので、ビルドって何をしているのかについても学ぶことが出来ました。よく使うコマンドなどの使い方も、使い続けていればすぐに慣れるだろうなという感じもしました。
次にリンクディレクティブファイル(通称リンカスクリプト)ですが、これは、リンカに対して入力ファイルや使用可能なメモリ領域,セグメントの配置など、リンク時の各種指示を行うための命令を記載したものです。
それで実際の中身ですが、このファイルの記述は短いので、下記に全文を掲載します。
MEMORY
{
flash : ORIGIN = 0x10000000, LENGTH = 0xFC
}
SECTIONS
{
.text : { *(.text*) } > flash
}
ファイル左端に大文字の英文で書かれているMEMORYとSECTIONSは、コマンドと呼ばれるものです。この内、最低限必要なのは、SECTIONSコマンドのみのようです。_φ(・_・
各々のコマンドの意味合いは以下の通りです。
MEMORY: システム全体の物理的なメモリ構成を定義する
SECTIONS: 各データ(セクション)をメモリのどこへ配置するのかを記述して指定する
また、SECTIONSコマンドの中で使われているセクションというものは、実行プログラムの中身や変数などを個別に管理するために設けた入れ物というイメージのもので、同じ種類のものを効率よく管理できるように設けられているようです。そして、主にデフォルトでは以下の4つが設定されているようです。
セクション名 | 内容 | 記述例 |
---|---|---|
.text | プログラムの実行コード | void func( ) { } |
.data | 初期化された変数 | int x = 5; |
.bss | 初期化されていない変数 (但し、デフォルトで0になる) | int y; |
.rodata | constな定数(読み取り専用) | const char day[ ] = “Monday”; |
以上を踏まえて、今回のリンカスクリプトの記述内容を文章で要約すると、
- “flash”と名付けたメモリ領域を、開始アドレス0x10000000番地から、長さ0xFCの範囲(252バイト)確保する
- “.text”セクション(出力)に、プログラムの実行コードを格納して、“flash”メモリ領域に配置する
という意味になります。
そして最後に、アセンブリソースファイルです。
このファイルの内容が、今後メインで学習を進めることとなるアセンブリ言語を使って記述したコードをまとめたものです。
アセンブリ言語は、ニーモニックと呼ばれるmovやldrといったコマンドを使って、ハードウェア各部に直接命令をする書き方をするため、プログラミング界隈ではよく言われているように、確かに、ハードウェアの内部構造を理解していないとコードが書けないなと思いました。
ARMのCPUコアの場合、高速で読み書きできるメモリのレジスタが16個デフォルトで設定されており、これとROMやRAMなどを使って、データの出し入れ、入れ替えや実行などを行う仕組みになっているようです。また、Thumb(もしくはThumb-2)という命令を使うことで、より高速に処理できるモードに変換するような仕組みもあるとのことで、今回のLチカコードにも使われています。
(とはいえ、今回はLチカだけなので、その高速化の恩恵は実感できないのかと推測しますが、今後のためにも必ず覚えておきたい機能だと思いました。)
ここで、少しだけ、今回Lチカのアセンブリソースファイル前半部分の内容について触れてみます。下記が実際の中身になります。
.cpu cortex-m0
.thumb
ldr r0,=0x20001000
mov sp,r0
bl notmain
b .
最初の行の“.cpu”は、使用するCPUコアの種類の指定ですね。今回は、ベースとなるマイコンはラズパイPicoを使うので、CPUコアには“ARM-Cortex-M0+”と初期のものが搭載されています。そのため、ここの記述は、“cortex-m0”になっています。(“+”はつけなくていいみたい)
2行目の“.thumb”は先ほど説明したThumb命令をこれ以降で使うと言う宣言になるようです。
さらに下の行には、ニーモニックを使っての記述があり、“ldr r0, =0x20001000” は、r0レジスタに0x20001000アドレスを代入するという意味になります。 また、“mov sp, r0”は、r0レジスタの中身をsp(スタックポインタ:r13)にコピーするという意味です。この2行をまとめて解釈すると、spに0x20001000アドレスが代入されたということになります。
このように、一行毎に一命令を指示し、最終的に全部を繋げて処理を行うという方法をとっているのが分かります。なんだか途方もない作業をしている感じは受けますが、プログラマからしてみれば、一つ一つの何をしているのか理解しやすいということになりますね。
もっとさらに下の行に“bl notmain”とあるものは、ちょっと特殊な形の命令で、特に”notmain”というところが特殊です。
“bl”の方は、アセンブリ言語では一般的なもので、分岐命令と呼ばれるもので、“branch with link” を省略形です。意味は、“notmain”へ飛びなさい、そして処理が終わったらここに戻って来なさいというものです。通常、“bl”の後には、ラベル名と呼ばれるリンク先を指示している命令が、同じアセンブリソースファイル内に記載されるのですが、今回は特殊なので、“notmain”というラベル名は、同ファイル内には存在していません。ではどうなるかというと、ここが特殊の所以なんですが、別のC言語ファイル内に記載されている“notmain”関数に飛んでいく(呼ばれる)ことになるんですねー。いやー摩訶不思議😳。
これ具体的にどういうことかというと、C言語に限っては、アセンブリソースファイル内から、.cファイル内の関数を呼べるようになっているんです。だから、アセンブリ言語で何かコーディングを行う場合、C言語は切っても切れない関係ということですね。
実に面白い!
ということで、コードの中身についての説明はこれ位にしておきます。
Lチカ実装
先ほど説明したコードをMakefileを使ってビルドすると、“notmain.uf2”というデータが生成されます。このデータがラズパイPico上での実行データになります。
このデータをUSB経由でラズパイPicoにインストールすると、ラズパイPicoにデフォルトで搭載されているLEDがチカチカとすぐに点滅を始めます。
実際のLチカの動作は、下記動画の通りです。
まとめ
ベアメタルはじめてみましたが、かなり奥が深いですね。しかし、組み込み開発をやるのであれば、絶対に避けては通れない道なので、何とか踏ん張って勉強を継続してモノにしたいです。
C言語との相関もかなり強いので、元々C言語スキルも上達させたい身である私にとっては、一石二鳥という感じがして、やる気が継続出来そうな気がします。
組み込みで何かガジェットを作りたいという自分の目標に、ちょっとづつ近づいていっているので、嬉しい限りです。
ということで、今後は、組み込み開発に関する内容についても発信していきたいと思っています。
それでは今回はこの辺で!
ではまた🖐️
〆