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 に値を代入するだけでそのアドレスへデータが送られるのである。
次にサンプルプログラム1を紹介する。このプログラムは、画面を白く塗りつぶすだけのプログラムである。
1: #include "iodefine.h" 2: #include "typedefine.h" 3: 4: #define TFTDATA (*(volatile unsigned short*)0x08000000) 5: #define TFTCTRL (*(volatile unsigned short*)0x08000002) 6: 7: void main(void); 8: void init_CS2(void); 9: void TFT_On(void); 10: void TFT_clear(void); 11: 12: void main(void) { 13: init_CS2(); 14: TFT_On(); 15: TFT_clear(); 16: while(1); 17: } 18: 19: void TFT_clear(void) { 20: _SINT i; 21: TFTCTRL = 0x4001; 22: for (i = 0; i < (320*240); i++ ) { 23: TFTDATA = 0xFFFF; 24: } 25: } 26: 27: void TFT_On(void) { 28: TFTCTRL = 0x4000; 29: } 30: 31: void init_CS2(void) { 32: BSC.CS2BCR.LONG = 0x12490400; 33: BSC.CS2WCR = 0x000302C0; 34: PFC.PACRL4.BIT.PA15MD = 1; 35: PFC.PACRL2.BIT.PA6MD = 2; 36: }
サンプルプログラム1:TFT の画面を白で塗りつぶす
4行目と5行目で、TFTDATA と TFTCTRL を定義している。28行目でTFTCTRL に 0x4000 を代入してTFT をON にする。実際にデータを送るときは、21行目のようにTFTCTRL に 0x4001 を代入してから23行目のようにTFTDATA に データを代入すればよい。この制御方法は、FPGA 内部の回路で決められている制御方法なので、実は、自由に変更することができる。
init_CS2 関数は、CS2 空間にTFT をマッピングするための関数であり、main() 関数の先頭でこの関数を呼び出しておかなければTFT へのアクセスはできない。
課題1:下記のファイルをダウンロードして上記のサンプルプログラム1のTFT_clear() 関数の代わりに、色の値を引数とする関数を作成せよ。
ワークスペースには、上記のサンプルプログラム1としてsample1 というプロジェクトがあるので、kadai1 プロジェクトを開いて作成すること。
void TFT_paint_color(_UWORD color) { }
TFT_prog1.zip
課題1のワークスペース
次は、座標を意識したプログラムを考えてみる。
図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を紹介する。このプログラムは、白色でクリアされた画面上の任意の場所に赤色でドットを記す関数である。
1: #include "iodefine.h" 2: #include "typedefine.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: void main(void); 10: void init_CS2(void); 11: void TFT_On(void); 12: void TFT_clear(void); 13: void TFT_dot(void); 14: 15: volatile _UWORD FrameBuf[320*240]; 16: 17: void main(void) { 18: init_CS2(); 19: TFT_On(); 20: TFT_clear(); 21: TFT_dot(); 22: while(1); 23: } 24: 25: void TFT_dot(void) { 26: _UBYTE x, y; 27: _SINT i; 28: 29: x = 160; 30: y = 120; 31: 32: for (i = 0; i < (320*240); i++ ) { 33: FrameBuf[i] = _COL_WHITE; 34: } 35: 36: FrameBuf[y*320+x] = _COL_RED; 37: 38: TFTCTRL = 0x4001; 39: for (i = 0; i < (320*240); i++ ) { 40: TFTDATA = FrameBuf[i]; 41: } 42: } 43: 44: void TFT_clear(void) { サンプルプログラム1と同じ } 45: void TFT_On(void) { サンプルプログラム1と同じ } 46: void init_CS2(void) { サンプルプログラム1と同じ }
サンプルプログラム2:任意の場所にドットを表示
15行目で、GRAM の領域として150 [KB] を一次元配列で確保している。29行目、30行目でドットを表示する場所の座標を与えている。32~34行目でGRAM エリアを白色で塗りつぶしている。36行目で指定された画素のみを赤色にしている。その後、TFT への描画を行っている。
課題2:下記のファイルをダウンロードして上記のサンプルプログラム2のTFT_dot() 関数の代わりに、ドットの座標とドットの色を引数とする関数を作成せよ。
void TFT_draw_point(_UWORD x_pix, _UWORD y_pix, _UWORD, p_color) { }
ワークスペースには、上記のサンプルプログラム2としてsample2 というプロジェクトがあるので、kadai2 プロジェクトを開いて作成すること。色の指定は、赤のほかに緑、青、黒を選べるように下記のように宣言してある。
#define _COL_GREEN (0x07E0) #define _COL_BLUE (0x001F) #define _COL_BLACK (0x0000)
TFT_prog2.zip
課題2のワークスペース
ここまでで、TFT への描画方法については終わりである。
次は、TFT 上に正方形を描くサンプルを紹介する。
1: #include "iodefine.h" 2: #include "typedefine.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: #define _COL_GREEN (0x07E0) 9: #define _COL_BLUE (0x001F) 10: #define _COL_BLACK (0x0000) 11: 12: void main(void); 13: void init_CS2(void); 14: void TFT_On(void); 15: void TFT_clear(void); 16: void TFT_draw_10pix_square(void); 17: 18: volatile _UWORD FrameBuf[320*240]; 19: 20: void main(void) { 21: init_CS2(); 22: TFT_On(); 23: TFT_clear(); 24: TFT_draw_10pix_square(); 25: while(1); 26: } 27: 28: void TFT_draw_10pix_square(void) { 29: _SINT x_pix, y_pix, i; 30: _UWORD x, y, w; 31: 32: x = 160; 33: y = 120; 34: w = 10; 35: 36: for ( i = 0; i < (320*240); i++ ) { 37: FrameBuf[i] = _COL_WHITE; 38: } 39: 40: for(x_pix = x; x_pix < (x + w); x_pix++) { 41: for(y_pix = y; y_pix < (y + w); y_pix++) { 42: FrameBuf[y_pix*320 + x_pix] = _COL_RED; 43: } 44: } 45: 46: TFTCTRL = 0x4001; 47: for (i = 0; i < (320*240); i++ ) { 48: TFTDATA = FrameBuf[i]; 49: } 50: } 51: 52: void TFT_clear(void) { サンプルプログラム1と同じ } 53: void TFT_On(void) { サンプルプログラム1と同じ } 54: void init_CS2(void) { サンプルプログラム1と同じ }
サンプルプログラム3:任意の場所に正方形を描画するプログラム
28行目の関数が正方形を描画する関数である。32行目と33行目で描画場所の座標を与えている。34行目で一辺の長さを与えている。単位はピクセルである。36行目~38行目で配列を白色でクリアし、40行目~44行目で、指定された場所を赤色で塗りつぶしている。
配列の各要素がTFT の各画素に対応しているので、基本的に配列を操作することができればTFT への描画は簡単に行うことができる。
課題3:下記のファイルをダウンロードして、未完成の関数を作成せよ。
TFT_prog3
課題3のワークスペース
1. TFT_draw_line() :始点と終点の座標を指定し、直線を引く関数。
2. TFT_draw_circle():中心座標と半径を指定し、円を描く関数
3. TFT_put_ptn16():指定した座標に、16×16ドットのビットパターンを描く関数。テスト用のビットパターンとしてptn_Aを用意した。文字を描くことを想定している。
4. TFT_put_img16():指定した座標に、16×16ドットの画像を描く関数。テスト用のイメージとしてimg_Aを用意した。カラー画像を描くことを想定している。
5.そのほか、楕円や三角関数など表示してみると良い。