108.開発キットの液晶表示器を使う


2001/05/17 【ソフトウエア編TOPに戻る】

AKI-H8/3048Fの開発キットには、16桁x2行の文字が表示できる液晶表示器が付いています。開発キットの配線でAKI-H8のパラレルポートPORT3と接続されており、比較的手軽に表示させることができます。液晶表示器を使えば、動作のモードや、外部から取り込んだデータなどを表示させることができ、マンマシンインターフェイスとして、一段上のシステムを作り上げることができます。

液晶表示器を制御するためのプログラムは、開発キットに付属の取り扱い説明書の中にアセンブラの形で紹介されています。今回ここでご紹介するのは、単純にアセンブラをC言語に移植しただけのものです。開発キットには、液晶表示器(SC1602BSLB)の取り扱い説明書も付属されており、イニシャル方法や使用方法も載っているのですが、イマイチ良く分からないので、何となく使っているに留まっています...。なお、同様のことは仙台電波高専電子制御工学科 熊谷研究室ホームページでも既に紹介されていて、非常に丁寧に制御方法の紹介もされているので、そちらを見て頂いたほうがためになると思います。というより、そちらを見て頂ければ、わざわざここで力弥のプログラムを紹介する必要もないのですが、ここで載せておかないと片手落ちになるし、力弥なりのC言語への移植を行なっているので、やっぱりご紹介しておこうかなあーといった感じです...。内容的には熊谷研究室さんとほとんど一緒ですが、決して真似っこした訳けではありませんので、ご了承下さい。(熊谷先生ごめんなさいm(_ _;m )

開発キット上の接続については、電子回路編3.AKI-H8開発キットの回路[9]液晶表示器の項目を参照して下さい。


108−1.ソフトウエアの紹介

先ほどもお話した通り、この液晶表示器の使い方は、イマイチ良く分かりません!なので、細かい説明は抜きにして、早速ソフトウエアの紹介をさせて頂きます...(^^;

以下のプログラムでは、液晶表示器のリセット関数、8BITデータ書き込み関数、4BITデータ書き込み関数、書き込み位置指定関数、および文字列書き込み関数の各関数からなります。また、液晶表示器のハードウエア制御上、時間稼ぎ関数も使用していますが、そちらは103.タイマーで正確な時間稼ぎをするの項目を参照して下さい。

AKI-H8開発ボードでは、回路を見てもらうと分かりますが、液晶表示器へのデータ線が4bit分しかつながっていません。あとの2本はハードウエア制御線です。なので、液晶表示器を4bitモードで動作させています。

ソースプログラムは、ここをクリックするとダウンロードできます。(lcdtest1.c) プログラムが長くてイヤになるかもしれませんが大丈夫です。大したことはありません。

なお、スタートアップオブジェクトは、今回割込みを利用していないので、startup.marで結構です。(ちなみに割込み対応のstartup2.marでも問題なく動作します)

/**************************************************/
/* 液晶表示テスト                RIKIYA 2001.5.17 */
/*                                     lcdtest1.c */
/*                                       H8-3048/F*/
/* AKI-H8開発キットの液晶表示器(LCD)に            */
/* 文字列を表示させる                             */
/**************************************************/

#include <3048f.h>

void main(void){
     char moji[16] = "AKI-H8";

     timer_init();                  /* タイマーの初期化       */
     lcd_init();                    /* 液晶表示器の初期化     */

     lcd_locate(0,0);               /* カーソル位置の指定     */
     lcd_print("HELLO!");           /* 文字列の表示           */

     lcd_locate(3,1);               /* カーソル位置の指定     */
     lcd_print(moji);               /* 文字列変数の中身を表示 */

     while(1){}                     /* 無限ループ             */
}

/* lcdのリセット動作**********************************/
lcd_init(void){
     P3.DDR = 0xff;                 /*P3全bit出力モード             */
     wait(50);
     P3.DR.BIT.B4 = 0;              /*RS clear                    */
     lcd_write8(0x23);              /*X,X,E,RS,7,6,5,4 = 0010 0011*/
     P3.DR.BIT.B4 = 0;              /*RS clear                    */
     lcd_write8(0x23);              /*X,X,E,RS,7,6,5,4 = 0010 0011*/
     P3.DR.BIT.B4 = 0;              /*RS clear                    */
     lcd_write8(0x23);              /*X,X,E,RS,7,6,5,4 = 0010 0011*/
     P3.DR.BIT.B4 = 0;              /*RS clear                    */
     lcd_write8(0x22);              /*X,X,E,RS,7,6,5,4 = 0010 0010*/

     lcd_write4(0x28,0);            /*X,X,E,RS,7,6,5,4 = 0010 1000*/
     lcd_write4(0x0e,0);            /*X,X,E,RS,7,6,5,4 = 0000 1110*/
     lcd_write4(0x06,0);            /*X,X,E,RS,7,6,5,4 = 0000 0110*/

return;
}

/* lcdに8bitデータを書き込む****************************/
lcd_write8(char str){
     P3.DR.BIT.B5 = 1;              /*E-bitを1とする */
     P3.DR.BYTE = str;              /*strをport3に出力 */
     wait(2);
     P3.DR.BIT.B5 = 0;              /*E-bitを0とし、このタイミングで書き込む */
     wait(2);
return;
}

/* lcdに4bitデータを書き込む ****************************/
lcd_write4(int str,int rs){         /*str:文字列、rs:データ/コマンド識別*/
     int dummy = str;
     int status = 0;

     switch (rs){
          case 1:                   /* rs = 1 データ */
                 P3.DR.BIT.B4 = 1;
                 break;
          case 0:                   /* rs = 0 コマンド*/
                 P3.DR.BIT.B4 = 0;
                 break;
          default: break;
     }

     /*上位4bitの転送*/
     P3.DR.BIT.B5 = 1;              /*E-bitを1とする                 */
     dummy = dummy>>4;              /*上位4bitを下位4bitにシフトする */
     dummy &= 0x0f;                 /*上位4bitをマスクする           */
     status = P3.DR.BYTE & 0xf0;    /* E,RS信号以外をマスク          */
     P3.DR.BYTE = status | dummy;   /* statusとdummyの合成を出力     */
     wait(2);
     P3.DR.BIT.B5 = 0;              /*このタイミングでlcdに書き込み  */
     wait(2);

     /*下位4bitの転送*/
     P3.DR.BIT.B5 = 1;              /*E-bitを1とする                 */
     dummy = str;
     dummy &= 0x0f;                 /*上位4bitをマスクする           */
     status = P3.DR.BYTE & 0xf0;    /* E,RS信号以外をマスク          */
     P3.DR.BYTE = status | dummy;   /* statusとdummyの合成を出力     */
     wait(2);
     P3.DR.BIT.B5 = 0;              /*このタイミングでlcdに書き込み  */
     wait(2);

return;
}

/* lcd書き込み位置指定 x:0,y:0,1**********************/
lcd_locate(int x,int y){
     lcd_write4(0x80+x+y*0x40,0);   /*DDRAM アドレスセット          */
}

/* lcdへの文字列書き込み ***************************/
lcd_print(char *str){
     while(*str != 0){              /* 文字列の最後eof(0)を検出      */
          lcd_write4(*str,1);       /* 文字の書き込み                */
          str++;                    /* 次の文字ポインタに移動        */
     }
return;
}


/* 待ち時間発生 *********************************/

/* 待ち時間発生初期化 */
timer_init(void){
     ITU0.TCR.BYTE = 0x23;          /* GRAコンペアマッチ clock 1/8   */
     ITU0.GRA = 0x07d0;             /* GRAを2000に設定               */
     ITU.TSTR.BIT.STR0 = 0;         /* カウント停止状態              */
return;
}
/* 待ち時間発生 引数に必要なミリ秒を指定する */
wait(int msec){
     int i;
     ITU.TSTR.BIT.STR0 = 1;         /* ITU0 TCNTカウント開始             */
     for(i=0;i<msec;i++){
          do{                       /* TCNT = GRAになるまで待つ(1mS) */
               }while(ITU0.TSR.BIT.IMFA == 0);
          ITU0.TSR.BIT.IMFA = 0;    /* 検知フラグを戻して再開        */
     }
     ITU.TSTR.BIT.STR0 = 0;         /* ITU0 TCNTカウント終了             */
return;
}

 【先頭に戻る】


108−2.プログラムlcdtest1.cの説明

 

#include <3048f.h>

先ず初めは例によって、3048f.hのヘッダーファイルを読み込んでおきます。

では、以下に各関数のご説明をします。

 

■main関数

void main(void){
     char moji[16] = "AKI-H8";

     timer_init();            /* タイマーの初期化       */
     lcd_init();              /* 液晶表示器の初期化     */

     lcd_locate(0,0);         /* カーソル位置の指定     */
     lcd_print("HELLO!");     /* 文字列の表示           */

     lcd_locate(3,1);         /* カーソル位置の指定     */
     lcd_print(moji);         /* 文字列変数の中身を表示 */

     while(1){}               /* 無限ループ             */
}

main関数は全然大したことありません。

初めにmoji[16]という文字配列変数に AKI-H8 という文字列を入れておきます。

その後は見ての通り、各種初期化の関数を呼び出し、実際に液晶表示器に文字列を表示させるために、lcd_locate( )関数で表示位置の指定、lcd_print( )関数で文字の表示を行なっています。

lcd_locate( )関数では、16x2文字分の表示領域のどこから文字列の表示を開始させるか、を指定します。パラメータは0(ゼロ)から数えるので、lcd_locate(3,1)なら、4文字目の2行目から書き始めます。

lcd_print( )関数では、実際に表示させたい文字列を引数に渡して使用します。lcd_print("HELLO!")なら、単純に指定の位置からHELLO!と表示します。lcd_print(moji)なら、mojiという変数に入れてある文字列を表示させます。なので、今回はAKI-H8と表示します。分かりますか?

直接文字列を引数で渡す場合は、ダブルクォーテーション " で囲います。変数を利用したい場合は引数として変数名を渡してあげます。

最後に付けている無限ループですが、main関数の処理を終わった後も、なぜか繰り返して処理が行なわれているみたいで表示がバタバタするため、ひとまず処理を止めておくために追加してあります。(原因は分かりませんが...)

【先頭に戻る】

■液晶表示器のリセット関数 lcd_init( );

lcd_init(void){
     P3.DDR = 0xff;      /*P3全bit出力モード             */
     wait(50);
     P3.DR.BIT.B4 = 0;   /*RS clear                    */
     lcd_write8(0x23);   /*X,X,E,RS,7,6,5,4 = 0010 0011*/
     P3.DR.BIT.B4 = 0;   /*RS clear                    */
     lcd_write8(0x23);   /*X,X,E,RS,7,6,5,4 = 0010 0011*/
     P3.DR.BIT.B4 = 0;   /*RS clear                    */
     lcd_write8(0x23);   /*X,X,E,RS,7,6,5,4 = 0010 0011*/
     P3.DR.BIT.B4 = 0;   /*RS clear                    */
     lcd_write8(0x22);   /*X,X,E,RS,7,6,5,4 = 0010 0010*/

     lcd_write4(0x28,0); /*X,X,E,RS,7,6,5,4 = 0010 1000*/
     lcd_write4(0x0e,0); /*X,X,E,RS,7,6,5,4 = 0000 1110*/
     lcd_write4(0x06,0); /*X,X,E,RS,7,6,5,4 = 0000 0110*/

return;
}

ここでは、RS端子のクリアと、E,RS,7,6,5,4の各BIT入力を交互に繰り返しているだけです。この手順は液晶表示器の取り扱い説明書に記載されている4BITモードでのイニシャル動作をしているだけです。なので、こういうものだと考え、深く追求しません。

ただし、本当は間に一定の時間稼ぎを挟むことになっているのですが、無くても動くため、取りあえず省いてしまいました。(すごくいい加減ですみません...)

【先頭に戻る】


■8BIT書き込み関数 lcd_write8(str)

lcd_write8(char str){
     P3.DR.BIT.B5 = 1;  /*E-bitを1とする   */
     P3.DR.BYTE = str;  /*strをport3に出力 */
     wait(2);
     P3.DR.BIT.B5 = 0;  /*E-bitを0とし、このタイミングで書き込む */
     wait(2);
return;
}

液晶表示器のリセット関数の中で、一時的に8BIT書き込み関数を使用しています。ここでは8bitのポート3に、8bitデータを一気に出力してしまっています。とは言っても、上位4bit分にデータを与えているのと、RS端子を1にしているだけなので、イニシャル時の液晶表示器としてはそれで良いのでしょう...?ここでも深入りは止めておきます。

【先頭に戻る】

 

■4BIT書き込み関数 lcd_write4(str,rs)

この関数では、先ずこれから送るデータが文字列なのか、制御コマンドなのか、を識別させるための処理を行ないます。その後、上位4BITを送り、続いて下位4BITを送って、8BIT分のデータ転送を終了します。

     int dummy = str;
     int status = 0;

     switch (rs){
          case 1: /* rs = 1 データ */
                 P3.DR.BIT.B4 = 1;
                 break;
          case 0: /* rs = 0 コマンド*/
                 P3.DR.BIT.B4 = 0;
                 break;
          default: break;
     }

ここでは、引数として受け取ったstrというデータを、dummyという変数に保管しておきます。また、statusという変数を宣言して0(ゼロ)に初期化しています。

次のswitch分で、引数として受け取ったrsが1か0(ゼロ)か判別しています。

【rs = 1の場合】

引数strで受け取ったデータは、液晶に表示させるための文字データとして扱います。この時、P3.DR.BIT.B4 つまりRS端子を1にセットして、文字列であることを液晶表示器に教えています。

【rs = 0の場合】

引数strで受け取ったデータは、液晶表示器を制御するためのコマンドデータとして扱います。この時、 P3.DR.BIT.B4 つまりRS端子を0(ゼロ)にセットして、コマンドであることを液晶表示器に教えています。

 

     /*上位4bitの転送*/
     P3.DR.BIT.B5 = 1;              /*E-bitを1とする                */
     dummy = dummy>>4;              /*上位4bitを下位4bitにシフトする*/
     dummy &= 0x0f;                 /*上位4bitをマスクする          */
     status = P3.DR.BYTE & 0xf0;    /* E,RS信号以外をマスク         */
     P3.DR.BYTE = status | dummy;   /* statusとdummyの合成を出力    */
     wait(2);
     P3.DR.BIT.B5 = 0;              /*このタイミングでlcdに書き込み */
     wait(2);

ここでは、引数strで受け取った8BITデータの上位4BITだけを液晶表示器に転送しています。データの加工は、初めにstrのデータを保管したdummyという変数を使って行い、オリジナルのstrのデータは温存しておきます。

先ず、8BITのdummyデータを下位4BITまでシフトさせ、上位4BITをマスクします。

bit 7 6 5 4 3 2 1 0
dummy 7 6 5 4 3 2 1 0

元のデータが上の場合、下位4BITにシフトさせることによって、以下のようになります。なお、赤文字の部分は0(ゼロ)か1の値になります。

dummy = dummy>>4; 

bit 7 6 5 4 3 2 1 0
dummy - - - - 7 6 5 4

- は保証されない不定な値になります。なので、きれいにマスクしてあげます。

dummy &= 0X0f

bit 7 6 5 4 3 2 1 0
dummy 0 0 0 0 7 6 5 4

ところで、現状のP3.DR.BYTEの状態は、以下の通りになっています。

bit 7 6 5 4 3 2 1 0
P3.DR x x E RS D7 D6 D5 D4

xは未使用で動作には無関係ですが、イニシャルでlcd_write8(0x22)を実行しているため、0(ゼロ)になっているはずです。ここで、下位4BITにマスクを掛けると以下のようになります。

status = P3.DR.BYTE & 0xf0;

bit 7 6 5 4 3 2 1 0
status x x E RS 0 0 0 0

で、このstasusと先ほどのdummyをOR(オア)すると、以下の通りになります。

P3.DR.BYTE = status | dummy; 

bit 7 6 5 4 3 2 1 0
status x x E RS 7 6 5 4

つまり、このデータがP3.DR.BYTEとして液晶表示器に転送されることになります。これが、上位4BITのみの転送です。実際はその後のP3.DR.BIT.B5 = 0;で、E端子が0(ゼロ)になったタイミングで書き込みが行なわれます。

 

下位4BITの転送については、4BITのシフト動作をさせていないだけで、あとは同じです。ここでは細かな説明は省略します。

【先頭に戻る】

 

■書き込み位置指定関数 lcd_locate(x,y)

lcd_locate(int x,int y){
     lcd_write4(0x80+x+y*0x40,0); /*DDRAM アドレスセット */
}

この関数では、どこから文字列を書き始めるかという位置を指定します。

先ほどの4BIT書き込み関数を使って、DDRAMのアドレスをセットしてあげます。lcd_write4関数の2個目の引数が0(ゼロ)なので、これは制御用のコマンドであることが分かります。

1個目の引数0x80+x+y*0x40とは何でしょう?

液晶表示器の表示個所に相当するDDRAMアドレスは、以下のようになっています。

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
00h 01h 02h 03h 04h 05h 06h 07h 08h 09h 0Ah 0Bh 0Ch 0Dh 0Eh 0Fh
17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
40h 41h 42h 43h 44h 45h 46h 47h 48h 49h 4Ah 4Bh 4Ch 4Dh 4Eh 4Fh

先ず0x80 は、DDRAMのアドレス設定を行ないますというコマンドを表します。

x は、表示領域の左0(ゼロ)から数えて何文字目から表示させたいかを指定します。

y は、表示領域の0(ゼロ)列目か1列目のどちらに表示させるかを指定します。

【例1】

0(ゼロ)列目の4文字目(黄色の位置)から書き込みたい場合、lcd_locate(3,0)と指定すると、0x80 + 3 + 0*0x40で、0x80 + 3 となります。これは0x80 + 0x03と同じことで、80hのコマンドによって03hのアドレスが指定される、という意味になります。

【例2】

1列目の4文字目(緑色の位置)から書き込みたい場合、lcd_locate(3,1)と指定すると、0x80 + 3 + 1*0x40で、0x80 + 0x43 となります。これは、80hのコマンドによって43hのアドレスが指定される、という意味になります。

【先頭に戻る】

 

■文字列書き込み関数 lcd_print(*str)

lcd_print(char *str){
     while(*str != 0){          /* 文字列の最後eof(0)を検出 */
          lcd_write4(*str,1);   /* 文字の書き込み           */
          str++;                /* 次の文字ポインタに移動   */
     }
return;
}

ここでは、引数としてポインタ変数*strを受け取ります。ポインタの扱いについては追々ご説明しますが、*strという表記で文字データそのもの、*なしのstrという表記で、文字データのアドレスを示す、とだけ憶えて下さい。

ここでも、4BIT書き込み関数のlcd_write4を使用しています。2個目の引数が1なので、今度は文字データであることが分かります。

lcd_print関数の動作としては、渡された文字列データの先頭アドレスから順に1文字ずつ液晶表示器にデータを転送して行き、文字データの最後を検出したら止めるという動作です。

【先頭に戻る】


またしても、だらだらと長い説明になってしまいました。すみません。でも、main関数を見て頂ければ分かりますが、実際の液晶表示器の利用方法は簡単でしょ?

上のプログラムがちゃんと動くと、写真のように表示されます。

表示動作写真

リセット関数や、その他の関数をいつもソースプログラムに入れていると煩雑になるので、そのうち標準的に使える関数だけを集めて、ヘッダファイルにしてしまえばすっきりします。

この液晶表示器自体は色々と多機能のようなので、興味のある方は探ってみてください。私の場合は取りあえず表示してくれればいいや、といった所ですのでこの辺で失礼します。

【ソフトウエア編TOPに戻る】


更新履歴 2001/05/19 液晶表示の動作写真追加


【表紙に戻る】