home1.gif

next1.gif
back1.gif
 

LCD Monitor

 

基礎編
 PORT入出力
 シリアル通信
 タイマー機能
 A/Dコンバータ
 D/Aコンバータ
 外部割り込み
 液晶モニタ

応用編
 アウトプットコンペア
 インプットキャプチャ
 SRAM増設

Tips
 関数集
 シリアルEEPROM
 20桁×4行液晶モニタ
  (SC2004C)
 加速度センサを読む

液晶モニタ

 イントロダクション
 ここまではきっとつまらない内容だったと思います。書いてる本人もあまり面白い話題ではありませんでした。唯一手応えがあったのはシリアル通信だけではないでしょうか? そう思う理由はきっと、いくらプログラムを組んでみたところで、マイコンの反応が乏しかったからだと思います。

 その点、液晶モニタ(LCD)が使えだすと状況が変わります。デバッグの役にも立ちますし、実用的にも役に立ちます。そして何より自分が動かしているという実感が持てます。

 ここで扱うのはノートパソコンなどに搭載されているような巨大なフルカラーの物ではなく、もっとショボイ液晶ですが、動かす理屈が分からなければウンともスンとも言いません。まったくのゼロから始めた場合、この基礎講座の中ではSRAM増設に次ぐ難易度ではないでしょうか。他はやってることはそこそこ高度でも、結局は適切にレジスタに値を書けばやってくれるので案外簡単なんですよ。

 LCDの紹介
 これから扱うLCDモニタはサンライクという海外メーカーの液晶モニタです。半角英数字と、半角カタカナを扱うことができる、16桁×2行の
SC1602BSLBという物です。
 これに慣れたら同メーカーから20桁×4行の製品も出ていますので、挑戦してみると面白いと思います。

 LCDの概要
 LEDやスイッチといったなじみ安い物とは違って、初めて本格的な外部機器を繋ぐわけですが、そもそもどうやれば動く物なのか最初は見当も付かないと思います。
 まず、表示できる文字の種類を上に述べましたが、これらはアスキーコード表にある文字ばかりで、言い換えれば1バイトで表現できる文字です。ということは、液晶モニタに送りつけるデータはchar型かunsigned char型であることは見当が付くと思います。
 次に、文字を表示しようにも、任意の場所に表示するにはそうすればよいとか、画面を消去するにはどうすればいいとか、そういう命令コード(インストラクションコードと言います)が存在することも予測が付くと思います。
 さらに、電気を流せば灯るとか、そういう単純な物でない以上、初期化という作業が必要であるということも分かるでしょう。

 さて、ここまでの事から考えると、まずこの液晶モニタにはchar型のデータを送りつけるために8本の線(DB0〜DB7)が必要であることが分かります。次に、それが文字コードなのかインストラクションコードなのかを見分けるためにもう1本信号線(R/S線)が必要です。さらに、RS-232C通信のように厳密なコンマ数msecの時間誤差も許されないような制御はしたくないので、データの書き込みタイミングを知らせる信号線(E)も有った方が良いでしょう。さらに、瞬時に文字を表示できるわけではないので、液晶の状態をマイコン側に伝える線も必要です(RW)。もちろん電源線(Vcc)とGND(Vss)は必須です。
 というわけで、必要性から液晶モニタには最低でも13本の端子があることが分かります(実際にはバックライト用電源もあるので14本です)。存在理由さえ分かれば、これであのやたらといっぱい生えている端子も怖くなくなったのではないでしょうか。

 さて、たしかに13本とも繋いでも良いのですが、「ハードウェアはできるだけシンプルに。ソフトでできる物はできるだけソフトでやる」が回路を設計する上での全世界共通の揺るがぬ鉄則です。そこで、この液晶に付いてもいくつか細工をします。

 まず、液晶の状態(Busy or Ready)を調べるために必要なRWの線ですが、よほど急がない限り要はBusyの時にデータを送らなければいいので、たっぷりと待ち時間をとってやることで解決します。というわけで、RW線(Read / Write)はWriteの一方通行と言うことでGNDに落とします。
 次に、データバスですが、幸いにもこのLCDモニタには4ビットモードというモードがあります。つまり、上位4ビットと下位4ビットを分けて書き込むことでデータバスを半分にできるのです。というわけで、使わないデータバス(DB0〜DB3)もGNDに落とします。これで配線は13本から一気に8本に減りました。

 LCDとH8を接続
 秋月電子のマザーボードキットでは標準でこのLCDモニタを接続するパターンが有りますので、プログラムもそれに合わせましょう。マザーボードの結線は次のようになっています。

H8/3048F

SC1602BSLB

Port3-0

DB4

Port3-1

DB5

Port3-2

DB6

Port3-3

DB7

Port3-4

RS

Port3-5

E

5V

Vcc

GND

Vss

 ところで、ここまでのサンプルを見ていると、出力というとPort-Bを使いたい衝動に駆られる人もいるかも知れませんが、あれはLEDを直接駆動したいから大電力ポートを使ったのであって、今回は信号さえ送れればそれでいいのでPort-3で十分役を果たせるのです。

 LCDへの書き込みタイミング
 LCDへの書き込みにはデータバス4本と書き込みトリガ(E)と、レジスタセレクト(RS)の6本を使います。
インストラクションコードの場合はRSをLowに維持し、文字コードの場合にはHighにします。
 それぞれのタイミングは次のようになります。

インストラクションコード

 


文字コード

 この中でもっとも重要なのはデータバスに書き込む順序と書き込みトリガの順序です。赤い矢印で示した部分でLCDモニタにデータが書き込まれるわけですが、これよりも先にデータバスの内容が書き変わっていなければ意味がありません。もちろんRSもこの時点でHighかLowの目的とする方(インストラクション or 文字)になっていなければいけません。それ以外の部分のタイミングについては、仕様書には細かく明記されていますが、それほどこだわらなくても認識してくれるようです。
 次に、図中の
w の時間ですが、これは最低でも220nsec以上待たなければいけません。

 データバス4ビット設定ではEの最初のサイクルで上位4ビット分が書き込まれます。引き続き下位4ビット分のデータを書き込むわけですが、そのときにはRSは現状維持のままで、データバスと書き込みトリガだけ操作すればOKです。ただし、第1サイクルと第2サイクルの間隔は500nsec以上空ける必要があります。

 LCDの初期化
 ここからはようやく実際のプログラミングの話に入っていきます。まずはこのファイルをダウンロードしてください。
 このLCDモニタを使うにはまず初期化をしなければいけません。手順としては次のようになります。

  1. 電源投入後15msec以上待つ
  2. インストラクションコードの0x03を書き込む
  3. 4.1msec以上待つ
  4. インストラクションコードの0x03を書き込む
  5. 0.1msec以上待つ
  6. インストラクションコードの0x03を書き込む
  7. 0.1msec以上待つ
  8. インストラクションコードの0x02を書き込む
  9. 0.1msec以上待つ
  10. インストラクションコードの0x28を書き込む(モード設定。他のモードもあり)
  11. 0.1msec以上待つ
  12. インストラクションコードの0x0eを書き込む(表示設定。他の設定もあり)
  13. 0.1msec以上待つ
  14. インストラクションコードの0x06を書き込む(カーソル設定など。他の設定もあり)

 以上の一連の作業で初期化は終わりです。ちょっと長いですね。なお、10番目以降の0.1msec以上待つ部分は実は省略可能です。私のプログラムでは上位と下位のデータを送った後に少しのウェイトが入っているので、その部分でこの0.1msec分を吸収できるのです。

 これをC言語で書くとこのようになります。

main.c

    void initLCD(void)
    {
        P3.DDR     = 0xff;   // Port-3を出力設定
        P3.DR.BYTE = 0x00;

        longwait(50);        // 電源投入後15msec以上待つ

        lcd_out(0x03,0);     // 8ビットモードにする その壱
        longwait(50);        // 4.1msec以上待つ

        lcd_out(0x03,0);     // 8ビットモードにする  その弐
        longwait(50);        // 0.1msec以上待つ

        lcd_out(0x03,0);     // 8ビットモードにする  その参
        longwait(50);        // 0.1msec以上待つ

        lcd_out(0x02,0);     // 4ビットモードにする

        lcd_write(0x28,0);   // 4ビットモード、2行表示、ドットサイズ5*10
        lcd_write(0x0e,0);   // 文字表示、カーソルON
        lcd_write(0x06,0);   // 通常表示、カーソルは右移動

        cls_lcd();
    }

 この中にある lcd_out()は4ビットのデータを1回だけ出力します。似ていますが lcd_write()は8ビットデータを書き込むための関数なので、4ビットずつ2回に分けてlcd_out()を呼び出しています。どちらも2つの引数を持っていますが、1つ目の引数は実際に送るコード、だからlcd_out()の方は上位4ビットは必ず0で、下位4ビットだけがしか値が入っていません。lcd_write()は上位下位の合計8ビットとも値を入れれます。2つ目の引数はインストラクションコードならば「0」、文字コードならば「1」を書き込みます。それによりRSのHighとLowを切り換えます。lcd_out()とlcd_write()の本体は次のようになっています。

main.c

    void lcd_out(unsigned char dat,char id)
    {
        P3.DR.BYTE = dat;          // 下位4ビットのデータを出力

        if(id == 1)                // id = 1の時は文字コード
            P3.DR.BIT.B4 = 1;      //    →RSをHigh
        else                       // id = 0の時はインストラクションコード
           P3.DR.BIT.B4 = 0;       //    →RSをLow

        wait(20);                  // 500msec 待機

        P3.DR.BIT.B5 = 1;          // E端子をHigh
        wait(20);                  // 待機

        P3.DR.BIT.B5 = 0;          // E端子をLow
        wait(20);                  // 待機
    }

main.c

    void lcd_write(unsigned char dat,char id)
    {
        lcd_out((( dat >> 4 ) & 0x0f),id);     // 上位4ビット
        lcd_out((( dat >> 0 ) & 0x0f),id);     // 下位4ビット
    }

 lcd_write()を使えば1文字ずつ文字をLCDモニタに表示できるわけですが、それだけではちょっと面倒です。文字列を扱うには次のようなprint_lcd()という関数を使います。

main.c

    void print_lcd(char* dat)
    {
        while(*dat){
            lcd_write(*dat,1);
            dat++;
        }
    }

 やっていることは簡単ですね。文字列というものは最後に「\0」が入っているので、それが来るまで順番にlcd_write()に文字を1文字ずつ渡しているのです。
 なお、ポインタを使っているのにはちゃんと理由があります。途方もない大容量を持つパソコンのプログラミングと違い、マイコンでは常にメモリの残量を気にしなければいけません。だから、配列を新たに用意したりするのではなく、メモリはできるだけ共用することが望ましいのです。

 ここまでの関数(初期化ルーチン、4ビット転送ルーチン、文字表示ルーチン、文字列表示ルーチン)だけでとりあえずLCDモニタに文字を映すことができます。ではプログラミングの世界ではお約束の「Hello World」を表示してみましょう。

main.c

    #include "iodefine.h"

    void main(void);
    void initLCD(void);                     // LCD初期化
    void cls_lcd(void);                     // LCD画面クリア
    void cur_pos(char x,char y);            // LCDカーソル移動
    void print_lcd(char* dat);              // LCD文字列表示
    void wait(int t);                       // ウェイト(短い)
    void longwait(int t);                   // ウェイト(長い)
    void lcd_out(unsigned char dat,char id);// LCD出力(下位4ビット)
    void lcd_write(unsigned char dat,char id);
                                      // 1バイトのデータをLCDへの出力

    #ifdef __cplusplus
    }
    #endif

    void main(void)
    {

        initLCD();


        print_lcd("Hello World.");

        while(1);

    }

 なんだかプロトタイプ宣言が大半を占めていますね。この中で cls_lcd()とcur_pos()の2つは説明がまだでしたね。それぞれ画面クリアとカーソル移動の為の関数です。

 

main.c

    void cls_lcd(void)
    {
        lcd_write(0x01,0);                // クリアコマンド発行
        longwait(50);
    }

 使う頻度が高いので一つの関数として独立させましたが、実際にやっていることはクリアコマンド(0x01)を
lcd_write()を使って書いているだけです。

 cur_pos()の方はもう少しだけ複雑です。

 

main.c

void cur_pos(char x,char y)
{
    char i;
    lcd_write(0x02,0);                // カーソルを(0,0)に一旦戻す
    longwait(50);
    for(i = 0; i < (y*40)+x; i++){    // 2行目へは40桁進むと行ける
        lcd_write(0x14,0);            // 表示そのままカーソル右シフト
    }
}

 カーソル移動はダイレクトに座標を指定することはできません。現在地から相対座標で左右方向に進むか、もしくは原点(0,0)に戻るかの2通りです。ここでは現在地を知る方法が有りませんので、一旦原点に戻り、それから移動することにしています。

 原点に戻るインストラクションコードは0x02です。2番目の引数はインストラクションコードなので「0」を渡しています。

 次のforループはちょっとわかりにくいですね。というのも、(y×40)という部分が理解に苦しむのではないでしょうか。このLCDモニタは表示部は16桁なのですが、内部的には40桁ある事になっています。そして、40桁目を超えると2行目に移るのです。だから、2行目=y座標で1ですから、結局1行下に移動することになるのです。なお、今表示されている文字を消さずにカーソルだけを右に移動させるインストラクションコードは0x14です。

  question_mark_white.gifなんで?

    液晶表示部は16桁なのに、どうして内部座標は40桁まであることになっているのでしょうか?

    これにはちゃんと理由があります。

    まず、LCDモニタというものは本来キャラクタパターンなど持ってはいません。つまり、0x41と書き込んだところで「A」とは表示してくれません。それを実現しているのはLCDコントローラドライバというチップです。今回使用したサンライク社のSC1602BSLBには沖データのMSM6562B-01というコントローラドライバが使われています。沖データとしてはもちろんSC1602BSLB専用に開発したつもりなど無く、もっと汎用的に使われることを想定しています。そしてその上限のスペックが40桁というわけです。だから、世の中にはきっとこのLCDモニタとまったく同じ手順で起動できる40桁表示のLCDモニタが存在すると思われます(現行機種で有るかどうかは別問題ですが)。
     

 さて、この2つの命令ですが、プログラム中で使う場合にはそれぞれ次のように使います。

pin_blue.gifcls_lcd(void)

        cls_lcd();


pin_blue.gifcur_pos(char x,char y)

        cur_pos(5,1);            // 2行目の5桁目に移動するとき

 

 


home1.gif
 next1.gif back1.gif