TFTの使い方
ここでは、MEMEs に搭載されているTFT の使い方について説明する。
MEMEs のTFT ディスプレイは、回路的には図1のようにSH7085と接続されている。
図1:TFT とSH7085との接続ブロック図
図1のようにTFT は、SH7085 と直接接続されておらず、FPGA を介して接続されている。この理由は、TFT というデバイスの代替わりが早いためである。デバイスが替わるたびに回路を設計しなおしていてはコストと時間がかかってしまう。そこで、FPGA を介すことにより物理的な回路の設計をする必要はなく、FPGA 内の回路を変更すれば新しいデバイスに対応できるようになるのである。
したがって、FPGA の内部回路を理解しなければTFT の制御を行うことはできないのであるが、本ページではそこまで要求はせずに、単にデバイスへのアクセス方法のみを説明し、サンプルのプログラムといくつかの課題を用意するにとどめる。
本格的に学びたい人は、MEMEs に付属しているFPGA のHDL(ハードウェア記述言語)で記述された回路を参照してほしい。
MEMEs に搭載されているTFT の物理的な接続は上記で説明した通りであるが、SH7085 のプログラムでTFT にアクセスする際にとても重要な論理的な接続について説明する。
図2は、SH7085 のメモリマップである。
図2:メモリマップ
今回の学習で使っているモニタプログラムは、SH7085 の内蔵ROM に常駐しており、ターミナルソフトと通信を行っている。
ユーザが開発したプログラム(モニタと区別するためユーザプログラムと呼ぶ)は実は、メモリマップ上のCS1 空間にマッピングされている外部のSRAM 空間へ書き込まれている。この外部のSRAM も回路的にはSH7085 とアドレスバスやデータバス、制御信号で接続されているが、論理的には単に 0x04000000 にアクセスすればそのメモリ空間にアクセスできるようになっている。
TFT も同様で、CS2 空間にマッピングされており、論理的には 0x08000000 にアクセスすればTFT のメモリ空間にアクセスできる。
しかし、TFT そのものにはメモリはなく、単にFPGA に描画データを送るだけになっている。FPGA では、送られてきたデータに対して、垂直同期信号と水平同期信号を制御しながらTFT に描画を行うのである。したがって、ユーザは特定のアドレスに対して1画面(240×320 画素)分のデータ書き込めばあとは、FPGA が描画を行ってくれる。
また、送るデータは、RGB が 5:6:5 のフォーマットになった2バイトのデータである。白は、0xFFFF 、黒は、0x0000 、赤は、0xF800、 緑は、 0x07E0、青は、0x001F である。
では、TFT にアクセス方法について説明する。TFT にアクセスするには、2つの異なる意味を持つアドレスに16ビットでアクセスする必要がある。1つは、描画データを送るアドレスである。もう一つは制御用のアドレスである。それぞれを TFTDATA と TFTCTRL とすると、下記のように定義しておく必要がある。
1: #define TFTDATA (*(volatile unsigned short*)0x08000000) 2: #define TFTCTRL (*(volatile unsigned short*)0x08000002)
1行目も2行目も指定されたアドレスに対して符号なしの16ビットでアクセスすることをしめしており、プログラム中では、単に TFTDATA や TFTCTRL に値を代入するだけでそのアドレスへデータが送られるのである。
TFT.tar
TFT のワークスペース
次にサンプルプログラム1を紹介する。このプログラムは、画面を白く塗りつぶすだけのプログラムである。
上のワークスペースをダウンロードし、ワークスペースフォルダにコピー、解凍 (tar -xvf TFT.tar) して得られる sample1.c を題材とする。
MEMEs にダウンロード可能な mot ファイルを生成するには make sample1 とする。
1: #include "7080S.H" 2: #include "typedef.h" 3: 4: #define TFTDATA (*(volatile unsigned short*)0x08000000) 5: #define TFTCTRL (*(volatile unsigned short*)0x08000002) 6: 7: void TFT_clear(void) { 8: int i; 9: TFTCTRL = 0x4001; 10: for (i = 0; i < (320*240); i++ ) { 11: TFTDATA = 0xFFFF; 12: } 13: } 14: 15: void TFT_On(void) { 16: TFTCTRL = 0x4000; 17: } 18: 19: void init_CS2(void) { 20: BSC.CS2BCR.LONG = 0x12490400; 21: BSC.CS2WCR = 0x000302C0; 22: PFC.PACRL4.BIT.PA15MD = 1; 23: PFC.PACRL2.BIT.PA6MD = 2; 24: } 25: 26: void main(void) { 27: init_CS2(); 28: TFT_On(); 29: TFT_clear(); 30: while(1); 31; }
サンプルプログラム1:TFT の画面を白で塗りつぶす
4行目と5行目で、TFTDATA と TFTCTRL を定義している。16 行目でTFTCTRL に 0x4000 を代入してTFT をON にする。実際にデータを送るときは、9 行目のようにTFTCTRL に 0x4001 を代入してから11 行目のようにTFTDATA に データを代入すればよい。この制御方法は、FPGA 内部の回路で決められている制御方法なので、実は、自由に変更することができる。
init_CS2 関数は、CS2 空間にTFT をマッピングするための関数であり、main() 関数の先頭でこの関数を呼び出しておかなければTFT へのアクセスはできない。
課題1(tft1.c):上記のサンプルプログラム1のTFT_clear() 関数を参考にして、色の値を引数とする関数 TFT_paint_color(unsigned short color) を作成せよ。
tft1.c を編集し make tft1 で生成される tft1.mot をダウンロードする。
次は、座標を意識したプログラムを考えてみる。
図3のように TFT は、左上を原点(0,0)として、x 軸方向に320 画素、y 軸方向に240 画素で構成されている。描画は原点から x 軸方向に進み、319 まで進んだら y 軸方向に1つ進み、再びx 軸方向に描画されていく。
図3:TFT の座標と描画方向
TFT への描画は FPGA 内部の回路で行われるが、描画データは、SH7085 のメモリを使用しなければならない。一般的には、描画データを格納するグラフィックメモリ(以下 GRAM と呼ぶ)と呼ばれる専用メモリが搭載されているのだが、今回はそれがない。
SH7085 が持っているメモリの一部を GRAM として使用するので、まず、その容量を計算してみる。1画素が RGB で 5:6:5 の2 バイトで構成されており、x 軸方向に320 画素、y 軸方向に 240 画素あるので、1 画面分のデータは、2 [バイト] × 320 [画素] × 240 [画素] = 153,600 [バイト] となる。 1,024 で割ると 150 となるので、150 [KB] のメモリ領域を確保しなければならないことになる。
ユーザプログラムは、512 [KB] の SRAM が接続された CS1 空間に転送されて実行される。この 512 [KB] のうち 150 [KB] を GRAM として使用することになる。ちょっともったいない気もするが、仕方がない。
(MEMEs の次の改版でこの辺を何とかしようと思う。)
さて、TFT への描画について、SH7085 のメモリを使うことは理解できたと思う。では、そのメモリを確保しなければならない。ここでは、単純に C 言語の 一次元配列で確保することとする。
すると、図4の青いドット、x 軸方向に 100 、y 軸方向に 50 の場所の画素を指定するには、y × 320 + 100 とすればよい。つまり、一次元配列なので、一本の紐だと思えばそんなに難しくないと思う。
図4:TFT の描画位置と座標の関係
次にサンプルプログラム2を紹介する。このプログラムは、白色でクリアされた画面上の任意の場所に赤色でドットを記す関数である。
同様に make sample2 で sample2.mot ファイルが生成される。
1: #include "7080S.H" 2: #include "typedef.h" 3: 4: #define TFTDATA (*(volatile unsigned short*)0x08000000) 5: #define TFTCTRL (*(volatile unsigned short*)0x08000002) 6: #define _COL_WHITE (0xFFFF) 7: #define _COL_RED (0xF800) 8: 9: volatile _UWORD FrameBuf[320 * 240]; 10:
11: void TFT_clear(void) {
12: _SINT i;
13: TFTCTRL = 0x4001;
14: for (i = 0; i < (320 * 240); i++ ) {
15: FrameBuf[i] = _COL_WHITE;
16: TFTDATA = _COL_WHITE;
17: }
18: }
19: 20: void TFT_dot(void) { 21: _UBYTE x, y; 22: _SINT i; 23: 24: x = 160; 25: y = 120; 26: 27: FrameBuf[y * 320 + x] = _COL_RED; 28: } 29: 30: void TFT_draw_screen() { 31: _SINT i; 32: 33: TFTCTRL = 0x4001; 34: for (i = 0; i < (320 * 240); i++ ) { 35: TFTDATA = FrameBuf[i]; 36: } 37: } : 50: void main(void) { 51: init_CS2(); 52: TFT_On(); 53: TFT_clear(); 54: TFT_dot(); 55: TFT_draw_screen(); 56: while(1); 57: }
サンプルプログラム2:任意の場所にドットを表示
9 行目で、GRAM の領域として150 [KB] を一次元配列で確保している。
14~17行目で GRAM エリアを白色で塗りつぶすと同時に TFT を白色で塗りつぶしている。
24 行目、25 行目でドットを表示する場所の座標を与えている。27 行目で指定された画素のみを赤色にしている。
その後、TFT_draw_screen() 関数で TFT への描画を行っている。
課題2(tft2.c):下記のファイルをダウンロードして上記のサンプルプログラム2のTFT_dot() 関数の代わりに、ドットの座標とドットの色を引数とする関数を作成せよ。
tft2.c を編集し、make tft2 で生成される tft2.mot を MEMEs に転送する。
void TFT_draw_point(_UWORD x_pix, _UWORD y_pix, _UWORD, p_color) { }
色の指定は、赤のほかに緑、青、黒を選べるように下記のように宣言してある。
#define _COL_GREEN (0x07E0) #define _COL_BLUE (0x001F) #define _COL_BLACK (0x0000)
ここまでで、TFT への描画方法については終わりである。
次は、TFT 上に正方形を描く関数を紹介する。
1: void TFT_draw_10pix_square(void) { 2: _SINT x_pix, y_pix, i; 3: _UWORD x, y, w; 4: 5: x = 160; 6: y = 120; 7: w = 10; 8: 9: for(x_pix = x; x_pix < (x + w); x_pix++) { 10: for(y_pix = y; y_pix < (y + w); y_pix++) { 11: FrameBuf[y_pix * 320 + x_pix] = _COL_RED; 12: } 13: }
14: }
サンプルプログラム3:任意の場所に正方形を描画するプログラム
GRAM (FrameBuf[])はあらかじめ白色で埋めておく(TFT_clear() 関数)
5 行目と 6 行目で描画場所の座標を与えている。7 行目で一辺の長さを与えている。単位はピクセルである。9 行目~13 行目で、指定された場所を赤色で塗りつぶしている。
その後 TFT_draw_screen() 関数を呼び出し TFT へ転送する。
配列の各要素が TFT の各画素に対応しているので、基本的に配列を操作することができれば TFT への描画は簡単に行うことができる。
課題3(tft3.c):未完成の関数を作成せよ。
-
- TFT_draw_line() :始点と終点の座標を指定し、直線を引く関数
- TFT_put_ptn16():指定した座標に、16×16ドットのビットパターンを描く関数。テスト用のビットパターンとしてptn_Aを用意した。文字
を描くことを想定している。
-
- TFT_put_img16():指定した座標に、16×16ドットの画像を描く関数。テスト用のイメージとしてimg_Aを用意した。カラー画像を描くことを想定している。