フレームバッファ動作

 Linuxが動いていると、フレームバッファは、/dev/fb0、/dev/fb1...の
 ように単純なブロックデバイスでアクセスできます。

 それを以下のURLで知ったので、簡単に終わるだろうと思っていました。

  http://telecom0.eng.niigata-u.ac.jp/index.php?FrameBuffer

 が、思わぬところで、はまってしまいました。

 その顛末を紹介しますので、他山の石としてください。m(_ _)m


テストプログラム仕様

 フレームバッファをアクセスするためには、プログラムが必要です。  そこで、先に紹介したURLを参考に次の仕様を作成しました。
  • RGBを、上から下に帯のように表示する
  • 240ラインあるので、色は80ラインごとに変える
  • フレームバッファアクセス不能の場合、情報を表示する
 画面は、下図のようになるはずです。  仕様が決まれば、各部分をコツコツ記述するだけです。

帯表示

 80ラインごとに、色を変更していけばよいので、単純に  2重ループで構成します。  後で、利用しやすいように、横のピクセル数と縦のライン数を  マクロ定義しておきます。 #define X_PIXEL_MAX 320 #define Y_LINE_MAX 240  外ループはライン処理、内ループはピクセル処理に割り当て  まずは、擬似コードで記述です。 for ( y = 0 ; y < Y_LINE_MAX ; y++ ) { /* 色決定 */ /* 1ライン処理 */ for ( x = 0 ; x < X_PIXEL_MAX ; x++ ) { /* 格納位置計算 */ /* 着色 */ } }  色は、既にラベルで定義されているとして、80ラインごとに  新しい色を設定します。  #define BORDER1 80  #define BORDER2 160 tcolor = COLOR_BLUE ; if ( y > BORDER2 ) { tcolor = COLOR_RED ; } else { if ( y > BORDER1 ) { tcolor = COLOR_GREEN ; } }  色が決まれば、それを320ピクセルに出すだけです。  フレームバッファの作り方に依存しますが、通常は、1ピクセルに  何ビット割り当てるかで、1、2、4バイトとアクセス方法が決ま  ってしまいます。  今回は、デバイスドライバの仕様で、1ピクセル=16ビットなので  ワード(2バイト)アクセスとします。  1ラインは、320ピクセルあるので、320×16ビットです。  メモリは、バイトごとにアドレスを割り当てので、320×2×8ビット  =640バイトで1ラインとなります。  1ピクセルは、2バイトを占めるので、フレームバッファのポインタを  fbptr、メモリのバイトごとに振ったアドレスをlocationに入れて、次の  処理で1ピクセルの着色ができます。 *((unsigned short *)(fbptr + location)) = tcolor;    バイトごとのアドレスを、符号なし16ビットのアドレスに変換し  16ビットを一気に書き込みます。  次に、バイトごとに振ったアドレスを求める方法を検討します。  通常、フレームバッファは、アドレス計算を簡単にするために  2のベキ乗の位置から該当ラインのデータを格納します。  下図のように、ラインとラインの間に、隙間があるようになります。  メモリ節約を考えると、隙間がないように、押し込みます。  どちらで構成しているのかを、ユーザーが考えるのは面倒なので  Linuxの場合は、その情報を関数ioctlを実行すると構造体変数の 中に、必要な情報を入れてくれます。  次の2つ構造体変数を宣言して、システムコール関数openで  フレームバッファのファイルディスクリプタを取得できると  情報を取得できる準備が整います。 struct fb_fix_screeninfo finfo; struct fb_var_screeninfo vinfo;  finfoは、システム固有の固定情報を格納します。  vinfoは、スクリーンごとの可変情報を格納します。  実際に情報を取得する場合は、関数ioctlを利用します。  ここでは、x方向のオフセット値、y方向のオフセット値、  1ピクセルあたりのビット数等を取得します。  次のコードで、1ピクセルあたりのビット数とピクセル換算  の1ライン長を取得しておきます。 vbpp = vinfo.bits_per_pixel ; line_len = finfo.line_length ;  ワードアクセスのメモリアドレスは、ベースアドレスからの  オフセットに相当するので、次の計算で求められます。 #define DIV_BYTE 8 location = ((x+vinfo.xoffset) * vbpp / DIV_BYTE) + (y+vinfo.yoffset) * line_len ;  処理としてまとめると、以下になります。 for ( y = 0 ; y < Y_LINE_MAX ; y++ ) { /* 色決定 */ tcolor = COLOR_BLUE ; if ( y > BORDER2 ) { tcolor = COLOR_RED ; } else { if ( y > BORDER1 ) { tcolor = COLOR_GREEN ; } } /* 1ライン処理 */ for ( x = 0 ; x < X_PIXEL_MAX ; x++ ) { /* 格納位置計算 */ location = ((x+vinfo.xoffset) * vbpp / DIV_BYTE) + (y+vinfo.yoffset) * line_len ; /* 着色 */ *((unsigned short *)(fbptr + location)) = tcolor; } }

色指定

 色は、TrueColorで24ビット利用しますが、LCDディスプレイは  出力できる色は、512色までなので、16ビットを使うことに  します。(デバイスドライバの仕様です。)  16ビットを使う場合は、RGBの占有するビットをどうするかを  指定します。
  • R:G:B=5:5:5
  • R:G:B=5:6:5
 と2通りの指定方法がありますが、デバイスドライバの関係で5:6:5  を使います。  各色が占めるビット数が決まると、次のように色を定義できます。
  • #define COLOR_RED 0xf800 /* 11111 000000 00000 */
  • #define COLOR_GREEN 0x07e0 /* 00000 111111 00000 */
  • #define COLOR_BLUE 0x001f /* 00000 000000 11111 */
  • #define COLOR_WHITE 0xffff /* 11111 111111 11111 */
  • #define COLOR_BLACK 0x0000 /* 00000 000000 00000 */
  • #define COLOR_YELLOW 0xffe0 /* 11111 111111 00000 */

情報取得

 フレームバッファを利用するためには、ファイルディスクリプタを  を取得しなければなりません。そのために、システムコール関数  openを使います。 #define DEVICE_NAME "/dev/fb0" /* 読み書き用にファイルを開く */ fd_framebuffer = open( DEVICE_NAME , O_RDWR); if ( !fd_framebuffer ) { send_current_error_msg("Framebuffer device open error !"); exit(1); } send_current_information("The framebuffer device was opened !");  フレームバッファをオープンできないと、何をやっても無駄なので、  そのまま終了します。  フレームバッファに関連する情報を、関数ioctlで構造体変数に格納させます。 /* 固定スクリーン情報取得 */ if ( ioctl( fd_framebuffer , FBIOGET_FSCREENINFO , &finfo ) ) { send_current_error_msg("Fixed information not gotton !"); exit(2); } /* 変動スクリーン情報取得 */ if ( ioctl( fd_framebuffer , FBIOGET_VSCREENINFO , &vinfo ) ) { send_current_error_msg("Variable information not gotton !"); exit(3); }  固定、変数の情報を入手できないと、正しい処理ができないので、  終了させます。

メモリアクセス処理

 フレームバッファは、メモリなので、メモリへのアクセスに次の  2つの関数を利用します。
  • デバイスをメモリにマップ → mmap
  • メモリからデバイスをアンマップ → munmap
 関数mmapは、void型のポインタを返すので、キャストして  1バイトアクセスのポインタに変換します。 fbptr = (char *)mmap(0,screensize,PROT_READ | PROT_WRITE,MAP_SHARED,fd_framebuffer,0);  ポインタがNULLでは、メモリにマップできないので、エラー種別を  表示して、終了させます。 if ( (int)fbptr == -1 ) { send_current_error_msg("Don't get framebuffer device to memory !"); exit(4); } send_current_information("The framebuffer device was mapped !");  処理が終了したなら関数munmapで、メモリを解放します。 munmap(fbptr,screensize);

全ソースコード

 これまでの内容をまとめて、ひとつのソースコードにします。 /* * fbtst.c * 2006.7.19 Kensuke Ooyu */ #include <unistd.h> #include <stdio.h> #include <fcntl.h> #include <linux/fb.h> #include <linux/fs.h> #include <sys/mman.h> #define DEVICE_NAME "/dev/fb0" #define DIV_BYTE 8 #define X_PIXEL_MAX 320 #define Y_LINE_MAX 240 #define BORDER1 80 #define BORDER2 160 #define COLOR_RED 0xf800 #define COLOR_GREEN 0x07e0 #define COLOR_BLUE 0x001f #define COLOR_WHITE 0xffff #define COLOR_BLACK 0x0000 #define COLOR_YELLOW 0xffe0 /* function prototype */ void send_current_error_msg(char *ptr); void send_current_information(char *ptr); int main(void) { int fd_framebuffer ; struct fb_var_screeninfo vinfo; struct fb_fix_screeninfo finfo; long int screensize ; long int location; char *fbptr ; char tmp[DIV_BYTE*10]; int x , y ; int xres,yres,vbpp,line_len; unsigned short tcolor ; /* 読み書き用にファイルを開く */ fd_framebuffer = open( DEVICE_NAME , O_RDWR); if ( !fd_framebuffer ) { send_current_error_msg("Framebuffer device open error !"); exit(1); } send_current_information("The framebuffer device was opened !"); /* 固定スクリーン情報取得 */ if ( ioctl( fd_framebuffer , FBIOGET_FSCREENINFO , &finfo ) ) { send_current_error_msg("Fixed information not gotton !"); exit(2); } /* 変動スクリーン情報取得 */ if ( ioctl( fd_framebuffer , FBIOGET_VSCREENINFO , &vinfo ) ) { send_current_error_msg("Variable information not gotton !"); exit(3); } xres = vinfo.xres ; yres = vinfo.yres ; vbpp = vinfo.bits_per_pixel ; line_len = finfo.line_length ; sprintf( tmp , "%d(pixel)x%d(line), %dbpp(bits per pixel)",xres,yres,vbpp); send_current_information( tmp ); /* バイト単位でのスクリーンのサイズを計算 */ screensize = xres * yres * vbpp / DIV_BYTE ; /* デバイスをメモリにマップする */ fbptr = (char *)mmap(0,screensize,PROT_READ | PROT_WRITE,MAP_SHARED,fd_framebuffer,0); if ( (int)fbptr == -1 ) { send_current_error_msg("Don't get framebuffer device to memory !"); exit(4); } send_current_information("The framebuffer device was mapped !"); /* 表示 */ for ( y = 0 ; y < Y_LINE_MAX ; y++ ) { /* 色決定 */ tcolor = COLOR_BLUE ; if ( y > BORDER2 ) { tcolor = COLOR_RED ; } else { if ( y > BORDER1 ) { tcolor = COLOR_GREEN ; } } /* 1ライン処理 */ for ( x = 0 ; x < X_PIXEL_MAX ; x++ ) { /* 格納位置計算 */ location = ((x+vinfo.xoffset) * vbpp / DIV_BYTE) + (y+vinfo.yoffset) * line_len ; /* 着色 */ *((unsigned short *)(fbptr + location)) = tcolor; } } munmap(fbptr,screensize); close(fd_framebuffer); return 0; } void send_current_error_msg(char *ptr) { fprintf( stderr , "%s\n" , ptr ); } void send_current_information(char *ptr) { fprintf( stdout , "%s\n" , ptr ); }

コンパイル、リンク

 SH-4用のロードモジュールを作成します。  コンパイルは、簡単です。  $ sh4-linux-gcc -o fbtst fbtst.c  何度も同じことをタイプするのが面倒なので、次のシェル  スクリプトを作成しました。  #!/bin/bash  filename=$1  #  if [ -f ${filename}.c ] ; then   sh4-linux-gcc -o $filename ${filename}.c  else   echo "${filename}.c not exist !"  fi  実行権を付加しておきます。  $ chmod 766 sh4gcc  使い方は、次のようにします。  $ ./sh4gcc fbtst

動作確認

 実行ファイルが出来上がったので、次の経路でCAT760に転送です。
  1. coLinuxディレクトリ → Windowsディレクトリ (Samba利用)
  2. Windowsディレクトリ → Fedra Machineディレクトリ (FTP利用)
  3. Fedra Machineディレクトリ → CAT760ディレクトリ (FTP利用)
 実行権を付加しておきます。  $ chmod 766 fbtst  実行します。  $ ./fbtst  ここで、エラーが出されました。  Framebuffer device open error !  この意味は、フレームバッファが存在しないということ。  これで、半日近く頭を抱えていました。  何故なら、ブート時に、次のように表示されるからです。  実行した時点で、次のように表示されるはずです。  でも、表示は、次のままです。  Framebuffer device open error !  少し冷静になって考えてみました。  フレームバッファは、Linuxから見て、デバイスファイルです。  デバイスファイルが存在しないならば、/dev/fb0もないはずです。  CAT760上のメモリに、ルートファイルシステムがあり、DebinaSH  をコンパクトフラッシュ等で動かしてはいないので、今まで設定  に関して無頓着でした。  コマンドlsで、デバイスファイルを表示させました。  $ ls -la /dev/fb* crw-rw-rw---- 1 root root 2936 Jan 1 0:00 /dev/fb0  日常からデバイスファイルを扱っている人なら、すぐに  わかることですねえ。  デバイスドライバに必須のメジャー番号、マイナー番号が  ありません。だから、そんなデバイスはないと、エラーが  返るのです。(T_T)  他のLinuxマシンで、フレームバッファーのマイナー番号と  メジャー番号を確認して、設定することにしました。  メジャー番号は29、マイナー番号は0からとなっている  ので、次のように操作し、新しいデバイスファイルを作成  しました。  $ rm -R /dev/fb0  $ mknod /dev/fb0 b 29 0  $ ls -la /dev/fb* brw---------- 1 root root 29, 0 Jul 18 15:04 /dev/fb0  $ chmod 666 /dev/fb0  気を取り直して、再チャレンジです。  ターミナルを見ると、フレームバッファの処理はうまく  いっているようですし、取得情報も表示されています。  画面も、指定した内容で表示されました。v(^o^)v

戻る