Episode 20 ベアメタルでLチカやってみた2

ブログ

皆さんこんにちは、グレキチです。

暦ではもう9月になりました。9月は旧暦では長月とも呼ばれていました。
なぜ長月と呼ばれているのか、それには諸説あるとのことで、一つは、昼の時間がどんどん短くなって、夜の時間が長くなってくるからとか。他には、雨が長く降る月だからとか、稲を刈る月(いなかりづき)だから、とかといったもののようです。夏が終わっていく寂しさを少しづつ感じている今日この頃ですが、9月は私の誕生日月でもあるので、毎年何かいいことあるんじゃないかと期待を持てる時期なので良しとします😄

前回ベアメタル学習の導入として、Lチカを参考資料に則ってやってみたわけですが、基礎的なことが分かれば即応用しないと、ということで、前回の構成をちょっと変更して、スイッチを押したら LEDが光るように改造してみることにしました。
ということで、今回のテーマは、ベアメタルでLチカやってみたのパート2と題して、その内容について紹介します。

プログラム分析

今回、LEDとスイッチを基盤に外付けする方法で行いました。それにあたり、前回使用したプログラムを改造して使いたかったので、まずは元のプログラムがどういう構成で、どんな意図で組まれていたのかを分析する必要がありました。このセクションでは、その分析内容についてまとめてみました。

アドレス番号

ラズパイPico基盤を操作するためのプログラムでは、基盤の機能やポートを認識するためのアドレス番号がメーカーで細かく決められており、それをまとめた公式のデータシートを見ながら、使いたい機能やポートに該当するものを設定する必要があります。例えば、下記のような感じです。

#define RESETS_BASE                 0x4000C000

#define RESETS_RESET_RW             (RESETS_BASE+0x0+0x0000)
#define RESETS_RESET_XOR            (RESETS_BASE+0x0+0x1000)
#define RESETS_RESET_SET            (RESETS_BASE+0x0+0x2000)
#define RESETS_RESET_CLR            (RESETS_BASE+0x0+0x3000)

元データでは、C言語ファイル(元データでいうところの“notmain.c”)内に各アドレスをマクロ定数として定義する方法で記載してありました。0x4000C000などが、データシート内容に沿ったアドレス番号です。
なお、マクロ定義せずに、使いたい機能のアドレス番号のみを直接実行プログラム内に記述して指示してもいいのですが、上記のように一箇所にまとめた方が見つけ易く分かりやすいので、元データに則って今回もこの方法を踏襲しました。
そして、今回のプログラムでは、GPIO14にスイッチ、GPIO15にLEDを接続したので、この部分に関係するアドレス番号を、下記の通り追加でCファイル内に定義しました。(※実際には全部は使わないので、必要な定数だけ定義してもよかったのかも・・・)

#define IO_BANK0_GPIO14_STATUS_RW   (IO_BANK0_BASE+0x070+0x0000)
#define IO_BANK0_GPIO14_STATUS_XOR  (IO_BANK0_BASE+0x070+0x1000)
#define IO_BANK0_GPIO14_STATUS_SET  (IO_BANK0_BASE+0x070+0x2000)
#define IO_BANK0_GPIO14_STATUS_CLR  (IO_BANK0_BASE+0x070+0x3000)

#define IO_BANK0_GPIO14_CTRL_RW     (IO_BANK0_BASE+0x074+0x0000)
#define IO_BANK0_GPIO14_CTRL_XOR    (IO_BANK0_BASE+0x074+0x1000)
#define IO_BANK0_GPIO14_CTRL_SET    (IO_BANK0_BASE+0x074+0x2000)
#define IO_BANK0_GPIO14_CTRL_CLR    (IO_BANK0_BASE+0x074+0x3000)

#define IO_BANK0_GPIO15_STATUS_RW   (IO_BANK0_BASE+0x078+0x0000)
#define IO_BANK0_GPIO15_STATUS_XOR  (IO_BANK0_BASE+0x078+0x1000)
#define IO_BANK0_GPIO15_STATUS_SET  (IO_BANK0_BASE+0x078+0x2000)
#define IO_BANK0_GPIO15_STATUS_CLR  (IO_BANK0_BASE+0x078+0x3000)

#define IO_BANK0_GPIO15_CTRL_RW     (IO_BANK0_BASE+0x07C+0x0000)
#define IO_BANK0_GPIO15_CTRL_XOR    (IO_BANK0_BASE+0x07C+0x1000)
#define IO_BANK0_GPIO15_CTRL_SET    (IO_BANK0_BASE+0x07C+0x2000)
#define IO_BANK0_GPIO15_CTRL_CLR    (IO_BANK0_BASE+0x07C+0x3000)

アドレス番号の設定は以上です。

関数の定義と意味

プログラム変更を進める中で理解に苦しんだのが、PUT32、GET32という関数についてでした。Cファイル中にプロトタイプ宣言されてはいるものの、中身の実装がどこにも見当たりませんでした😳
困ったなーということで、ネットで目ぼしいワードで検索などしてみたところ、元データの製作者が、十数年前にラズパイのフォーラムページに投稿されていた内容を見つけることができたので、それを読み解くことで関数の使い方を学びました。その投稿内容によると、これらの関数の実装部分はアセンブリファイル内に記載されており、直接レジスタを操作するようにしているとのことで、こうすることで、アセンブラやコンパイラが間違った解釈をしないようにしているのだとか。
“start.s”ファイル内の下記の部分のところですね。

.thumb_func
.global PUT32
PUT32:
    str r1, [r0]
    bx lr

.thumb_func
.global GET32
GET32:
    ldr r0, [r0]
    bx lr

確かに、PUT32は、R1レジスタの内容をR0メモリに書き込む指示で、GET32は、R0メモリに保存された内容をR0レジスタに読み出す指示となっていて、至極簡単な処理内容になってました。
長年アセンブリ言語での開発に取り組まれた経験から導き出した手法とのことだったので、私も有り難く🙏その方法を、今後活用させて頂こうと思いました。

ところで、ここでふと思ったんですが、前回の記事で、アセンブリソースファイルからC言語ファイルの関数を直接呼び出すことができると説明しました。今回は、その逆のような意味合いで、C言語ファイルの関数定義をアセンブリソースファイルに記述できるということなんですねー🤔
最も、主体はアセンブリソースファイルになるので、当然と言えば当然なのかも。
いやー、奥が深い‼️

それで関数の中身はわかったんですが、その使い方もちょっと特殊だったので、それについてもちょっと説明したいと思います。

PUT32、GET32の使い方

元データでのPUT32/GET32の記述内容を確認すると、例えば下記のようになっていました。

//release reset on IO_BANK0
PUT32(RESETS_RESET_CLR,1<<5); //IO_BANK0

//wait for reset to be done
while(1)
{
    if((GET32(RESETS_RESET_DONE_RW)&(1<<5))!=0) break;
}

最初のPUT32ですが、上にコメントで“release reset on IO_BANK0”と記載されてます。日本語だと、“IO_BANK0のリセットを解除する”という意味になりそうです。この意図を考察すると、IO_BANK0の機能を初期化するための指示のようでした。そのために、“RESETS_RESET_CLR”のレジスタ値を操作したい訳で、その値を“1 << 5”、つまり、“1”の値を左に5回シフトするという指示がされています。
ここでラズパイPicoのマニュアルを改めて確認すると、“RESETS:RESET Register” の説明の箇所に、“5Bit” 目は “IO_BANK0” を指している、ということが確認できました。これらの状況から、“PUT32(RESETS_RESET_CLR, 1<<5);” の意味は、R0レジスタに “RESETS_RESET_CLR” を、R1レジスタに “1 << 5” を各々セットすることで、“RESETS_RESET_CLR” のレジスタの5Bit目(つまりIO_BANK0)を1に設定するという処理になります。
(※ちょっとややこしいですが、リセットクリアを1にして実行するので、実際のIO_BANK0は“0”にセット(つまり、リセット解除)されることになります)

次に、GET32です。“GET32(RESETS_RESET_DONE_RW)” とすることで、R0レジスタに“RESETS_RESET_DONE_RW” を読み込んだということになります。そして、 if文の中身の解釈ですが、“RESETS_RESET_DONE_RW” と “1 << 5” の論理積が “0” でなければループを抜ける、ということを表しています。論理積が “0” でないということは、比較するBit値がいずれも“1”になってないといけないので、以上を要約すると、“RESETS_RESET_DONE_RW” の5Bit目、つまり “IO_BANK0” のリセットが完了した(値が0から1になった)かどうかを確認していることになりそうです。 リセットが完了しないと次に進まないよー、ということですね😄

そんなこんなで、PUT32/GET32の使い方が把握できたので、これを踏まえた上で、ボタンプッシュするとLEDが光るというプログラムを作ることが出来ました。
なお、プログラム内容についてはここでは割愛しますが、ご興味のある方は私のGithubにアップしているので、そちらをご覧下さい。

実装回路の構成

結線図は下記の通りです。
ブレッドボードを使うと、実装をすぐに試すことができるので、大変便利ですね。
Picoボードの結線箇所は、電源の3V3OUT(Pin36)とGND(Pin38)およびGPIO14(Pin19)とGPIO15(Pin20)を使いました。また、金属抵抗は220Ωを使いました。

実行結果

最終的な実行結果は、以下の動画になります。

無事に実行できて、めでたしめでたし😃

まとめ

ということで、今回はベアメタルでLチカやってみたパート2について紹介しました。
こうして前回学んだことの応用が出来るようになると、スキルアップを実感出来て非常に嬉しい気持ちになります☺️
ベアメタル勉強はまだまだ始まったばかりですが、また違った回路実装を考えて、引き続き試してみたいと思います。

それでは、今回はここまです🖐️