フレームバッファ動作
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の占有するビットをどうするかを
指定します。
と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に転送です。
- coLinuxディレクトリ → Windowsディレクトリ (Samba利用)
- Windowsディレクトリ → Fedra Machineディレクトリ (FTP利用)
- 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