2018年03月21日

Raspberry Piベアメタルプログラミングで画面表示をやってみた

baremetal_fb.jpg


ラズパイでのベアメタルプログラミング、再開しました。
これまで、LチカUARTUSBキーボードUSBキーボード(その2)と試してきましたが、今回は画面への描画です。
画面出力を制御するGPUから、フレームバッファのアドレスを取得し、フレームバッファに値を書き込むことで画面にピクセルを表示させます。

ベアメタルでのフレームバッファの使い方も、以前紹介したBaking Piというチュートリアルに解説されていますので、この通りやってみました。

Computer Laboratory – Raspberry Pi: Lesson 6 Screen01

今回のコードも以下のGitHubにアップロードしてあります。

bare_matal_rpi_zero/video at master ・ boochow/bare_matal_rpi_zero

予備知識として、グラフィックスを実際に処理するGPUと、メインのCPUとの関係を知っておく必要があります。
GPUは、独自のソフトウェアが動作する独立したCPUを持っています。これはPCのグラフィックスカード等とは別のシステムで、「VideoCore」と呼ばれています。

CPUとVideoCoreとの連携は、共有メモリを介して行います。
BCM2835 ARM PeripheralsのP5に、メモリマップが掲載されています。

rpi-memory-map.png


図の左側がVideoCore、中央がCPUのメモリマップです。右はLinuxでのメモリマップですが、今回は関係ありません。
VideoCoreとCPUでは、VC/ARM MMUを介して、SDRAMおよびI/O Peripheralsの2つのメモリ領域が共有されています。

注意が必要なのは、VideoCoreのメモリマップでは同じもの(SDRAMおよびI/O Peripherals)が4回登場している点です。
これについては後で述べます。

SDRAMにはフレームバッファが置かれ、VideoCoreとCPUとの通信はI/O Peripheralsを介して行います。
この通信は「Mailbox」という仕組みを使います。
簡単にいうと「送信用レジスタ」と「受信用レジスタ」と「ステータスレジスタ」があり、

送信:ステータスレジスタをチェックし、送信可なら送信用レジスタに値を書き込む
受信:ステータスレジスタをチェックし、受信可なら受信用レジスタから値を読み出す

というものです。
SDRAMを共有していますので、レジスタに書き込む値はポインタでもよく、大きなデータは送受信バッファを指すポインタを送信し、そのバッファから受信データを読み取ることも可能です。
また、Mailboxには「チャネル」があり、送受信レジスタの最下位4ビットはチャネル番号を表すことになっています。
従って、実際にやり取りできる情報は28ビットということになります。

このMailboxの仕組みを実装すると、以下のような感じになります。



画面出力を行うのに必要なのは、「VCへ画面の設定(解像度等)を送信し、VCが確保したフレームバッファのアドレスを受信する」という処理です。
アドレスを受け取れれば、あとはフレームバッファへ書き込めばGPUが画面へ反映してくれます。

Mailboxには7つのチャネルがありますが、今回使うのは1番のチャネルだけです。
このチャネルは、フレームバッファに関する情報交換を行います。
以下のページにマニュアル(?)があります。

Mailbox framebuffer interface ・ raspberrypi/firmware Wiki

フレームバッファのアドレスを取得するには、フレームバッファを表す構造体を用意し、その構造体へのポインタをVCへ渡します。
VCは、必要なメモリを確保し、そのメモリへのポインタを構造体のメンバ変数に書き込みます。
上のマニュアルにもあるとおり、構造体は以下のような構成になっています。
(変数名は、マニュアルとはちょっと変えています)



初期化処理では、フレームバッファのアドレス(fb_info->buf_addr)に0を書き込んでから、チャネル1にmailbox_writeし、mailbox_readしてチャネル1への応答をチェックします。
これをfb_info->buf_addrにアドレスが返るまで繰り返します。
VideoCoreの処理はCPUとは並列に行われていますので、実際に結果が得られるまでにはタイムラグがあり、結果が得られるまでループすることが必要です。

上記のコードでは、フレームバッファ構造体のアドレスに0x40000000を加えています。
これが、最初にメモリマップのところで「注意が必要」と書いた点です。

VideoCoreのメモリ空間では、SDRAMとI/O Peripheralsが4回出てきますが、アドレスによってキャッシュとメモリの値の一貫性(キャッシュコヒーレンシ)が異なります。
最初のメモリマップの図をよく見ると、以下のようになっている(らしい)ことが分かります。

・00000000〜 物理メモリにキャッシュの内容が未反映の場合がある
・40000000〜 物理メモリとL2キャッシュの値は常に一致している
・80000000〜 L2キャッシュされているメモリ空間のみ
・C0000000〜 キャッシュは利用されない(DMA用)

ARM側では、メモリ空間は00000000〜3FFFFFFFまでの1GBです。従ってポインタは常に00000000〜のエリアを指していますが、これをVideoCoreに渡すと、VideoCoreが書き込んだ応答はVideoCore側のL2キャッシュに入り、ARM側から読み取れません。
そのため、VideoCoreに渡すポインタは40000000〜のエリアを指すようにして、VideoCoreからの応答が物理メモリに反映されるようにします。

VideoCoreが確保するフレームバッファは、上で示した構造体の変数で表すと、論理的には「幅w」×「高さh」で1ピクセルあたりのビット長がbppになります。
また、横1ラインで消費するバイト数はrow_bytesになります。
y座標をフレームバッファ上のアドレスに変換する際には、y * row_bytesを使えばいいことになります。

この論理的なフレームバッファは、HDMIの解像度とは異なって構いません。
HDMIの解像度は「幅display_w」×「高さdisplay_h」であり、フレームバッファはこの解像度に拡大されて表示されます。

例えば
fb_info_t fb_info = {1920, 1080, 480, 270, 0, 16, 0, 0, 0, 0};

というパラメータを渡すと、480x270ピクセル、16 bits per pixelのフレームバッファを1920x1080に拡大して表示することになります。
最初に載せた画面写真は、以前紹介した3.5インチのLCDへこの設定で出力したものです。
posted by boochow at 15:48| Comment(0) | Raspberry Pi | このブログの読者になる | 更新情報をチェックする
この記事へのコメント
コメントを書く
お名前:

メールアドレス:

ホームページアドレス:

コメント:

認証コード: [必須入力]


※画像の中の文字を半角で入力してください。
※ブログオーナーが承認したコメントのみ表示されます。
人気記事