液晶ディスプレイを付けてみよう

よく使われる液晶ディスプレイは下の写真のような直接コントロールするタイプで、CPUから多くの配線が必要になります。

ここでは、I2Cプロトコル、これは、Inter-Integrated Circuit の略で、I-squared-C(アイ・スクエアド・シーと読む)同期式のシリアル通信のプロトコルで、信号線としては2本のみ、電源を入れて4本繋げば使えてしまいます。従って、配線が簡単な液晶ディスプレイです。ここで用いるのは下の写真のような小型のものです。ただし、二つの信号線は2.2kΩ程度の抵抗でプルアップする必要があります。


購入はこちらからどうぞ

I2Cプロトコルについて

I2Cのデータ転送のタイミングチャートを下に示します。

I2Cの信号にはSDA(Serial DAta)とSCL(Serial CLock)の二つを用います。
SDAは信号名の通りにシリアルのデータですが、双方向です。つまり、送受信を一手に引き受けます。また、SCLはこのデータ転送のタイミングをとるために必要になります。
上記タイミングチャートには書いてありませんが、前半がI2Cデバイスのアドレスとデータは書き込みか読み込みかのデータ方向の情報です。後半はそのデータで、一つとは限りません。 詳しくは、この資料を参考にしてください。

ディスプレイのプログラミング


デフォールトのコンストラクタでは、p9(sda)とp10(scl)となっています。
メインプログラム(main.cpp)
#include "mbed.h"
#include "I2CLCD.h"
I2CLCD lcd;

int main()
{
    int i=0;
    printf("LCD test\n");
    while(true)
    {
        lcd.write("*** LCD Test ***");
        lcd.write("M.Mizuno");
        lcd.write(i++,8,0);
        wait(0.2);
        lcd.clear();
    }
}
 1 mbed用ヘッダーファイル
 2 LCD用ヘッダーファイル
 3 I2CLCDのオブジェクトを作る
 4 
 5 
 6 
 7 カウンタiをゼロクリア
 8 パソコン側に「LCD test」と表示させる
 9 永久ループ
10 
11 「*** LCD Test ***」を一行目に表示させる。
12 「M.Mizuno」を二行目に表示させる。
13 8カラム確保し右詰でカウンタの値を表示、カウントアップ
14 0.2秒待つ
15 LCDを消す
16 
17 


ヘッダーファイルは基本的にクラスの構造を記述したものです。ここでは、I2CLCDというクラスのインスタンス変数とメソッドを宣言しています。クラスの中身のインスタンス変数は基本的にはプライベートにし、また、メソッドはパブリックで宣言します。メソッドはプロトタイプ宣言のみで、本体は後で記述するcppファイルの中で書きます。

また、最近ちょっとしたことだけを表示するためのさらに小さいディスプレイが発売されました。8×2行です。これにも対応するようにしました。



ヘッダーファイル(I2CLCD.h)
#ifndef _I2CLCD
#define _I2CLCD
#include <mbed.h>
#include <I2C.h>

class I2CLCD : public I2C
{
  private:
    int MaxChNum;
    int chCount;
    unsigned char LCD_WRITE;
    void init();
  public:
    I2CLCD();
    I2CLCD(int n);
    I2CLCD(PinName sda, PinName scl);
    I2CLCD(PinName sda, PinName scl, int n);
    void write(unsigned char control, unsigned char  dt);
    void write(unsigned char ch);
    void write(char* ch);
    void writeInt(int x);
    void write(int x, int column, char zeroPad);
    void setIcon(int x);
    void home();
    void clear();
    void setCG(char mode, char* cgData);
};
#endif
 1 ヘッダーファイルの多重読み込み禁止
 2 
 3 mbed用ヘッダーファイル
 4 I2C用ヘッダーファイル
 5 
 6 クラスの宣言, I2Cクラスから派生させる
 7 
 8 プライベートなインスタンス変数、メソッドの宣言
 9 一画面の文字数
10 表示している文字のカウント
11 LCDのI2C書き込み用アドレス
12 初期化
13 以下パブリック
14 コンストラクタ
15 コンストラクタ(8×2行用)
16 ピン位置指定用コンストラクタ
17 ピン位置指定用コンストラクタ(8×2行用)
18 詳細コントロール用
19 1文字出力
20 文字列出力
21 整数の表示
22 簡易書式整数表示
23 電池などの上部アイコンの表示
24 カーソルを先頭に持っていく
25 画面消去
26 外字操作
27 
28 


上で宣言したクラスの本体の記述です。

C++クラスの本体(I2CLCD.cpp)
#include "I2CLCD.h"
/*
I2C低電圧キャラクタ液晶モジュール
*/

I2CLCD::I2CLCD():I2C(p9,p10)
{
  MaxChNum = 32;
  init();
}

I2CLCD::I2CLCD(int n):I2C(p9,p10)
{
  MaxChNum = n;
  init();
}

I2CLCD::I2CLCD(PinName sda, PinName scl):I2C(sda,scl)
{
  MaxChNum = 32;
  init();
}

I2CLCD::I2CLCD(PinName sda, PinName scl, int n):I2C(sda,scl)
{
  MaxChNum = n;
  init();
}

void I2CLCD::init()
{
  int Wait = 30;
  chCount = 0;
  wait_ms(40);
  LCD_WRITE= 0x7c;
  write(0x0, 0x38);   //data8bit, numberOfLine=2
  wait_us(Wait);
  write(0x0, 0x39);   //data8bit, numberOfLine=2
  wait_us(Wait);
  write(0x0, 0x14);   //internal OSC frequency
  wait_us(Wait);
  if(MaxChNum==32) write(0x0, 0x7f);   // contrast 下4bit
  else write(0x0, 0x78);   // contrast mini
  wait_us(Wait);
  write(0x0, 0x5F);   // power/icon/contrast control
  wait_us(Wait);
  write(0x0, 0x6a);   // follower control
  wait_ms(200);          // 200ms
  write(0x0, 0x0c);   // Display ON
  wait_us(Wait);
  write(0x0, 0x01);   // clear disply
  wait_us(2000);         // 2ms
  write(0x00, 0x06);   // entry mode set
  wait_us(Wait);
}
void I2CLCD::home()
{
	write(0x00,0x02); // return to home
	chCount=0;
	wait_ms(2);
}

void I2CLCD::clear()
{
	write(0x00,0x01); // clear display
	chCount=0;
	wait_ms(2);
}
void I2CLCD::write(unsigned char  ch)
{
  int x;
  int n = MaxChNum/2;
  if(ch==0x0a){
    x= chCount %MaxChNum;
    if(x<n) {
      write(0x00,0xc0);
      chCount=n;
    }
    else {
      write(0x00,0x01); // clear display
      chCount=0;
      wait_ms(2);
    }
  }
  else
  {
    write(0x40,ch);
    x= ++chCount % MaxChNum;
    if( x ==n) write(0x00,0xc0);
  }
}

void I2CLCD::write(char*  ch)
{
  while(*ch){
    write(*ch++);
  }
}

void I2CLCD::write(unsigned char control, unsigned char  dt)
{
  start();
  ((I2C*)this)->write(LCD_WRITE);
  ((I2C*)this)->write(control);
  ((I2C*)this)->write(dt);
  stop();
}
void I2CLCD::write(int x, int column, char zeroPad)
{
  char Buffer[10];
  char num[5];
  int i,j, n=0, signFlag=0;
  if(x<0) {     // マイナスの値の場合
    signFlag=1;
    x *= -1;
  }
  do {
    num[n++] = x%10;
    x /= 10;
  } while(x != 0);
  i = column-n;
  if(i>0) {
    for(i=0; i<10; i++) Buffer[i]=' ';
    if(zeroPad) for(i=0; i<column-n; i++) Buffer[i]='0';
    Buffer[9]=0;
    for(i=column-n, j=n-1; i<column; i++, j--) Buffer[i]=num[j]|'0';
    Buffer[i]=0;
    if(signFlag) Buffer[column-n-1]='-';
  }
  else {
    j=0;
    if(signFlag) Buffer[j++]='-';
    for(i=--n; i>=0; i--) Buffer[j++]=num[i]|'0';
    Buffer[j]=0;
  }
  write(Buffer);
}

void I2CLCD::writeInt(int x)
{
  char num[5];
  int n=0;
  if(x<0) {     // マイナスの値の場合
    write('-');
    x *= -1;
  }
  do {
    num[n++] = x%10 +'0';
    x /= 10;
  } while(x != 0);
  while(--n >=0) write(num[n]);
}

void I2CLCD::setIcon(int x)
{
  write(0x00, 0x39);    // Function set
  write(0x00, 0x40 | 0x0f);   // flower icon
  if(x & 1) write(0x40, 0x10);
  else  write(0x40, 0x00);

  write(0x00, 0x40 | 0x0d);   // Battery icon
  write(0x40, 0);
  if(x & 0x0e) {
    write(0x00, 0x40 | 0x0d);   // Battery icon
    write(0x40, ((x & 0x0e)<<1) | 2);
  }

  write(0x00, 0x40 | 0x0b);   // no sound icon
  if(x & 0x10) write(0x40, 0x10);
  else write(0x40, 0);

  write(0x00, 0x40 | 0x09);   // Key icon
  if(x & 0x20) write(0x40, 0x10);
  else write(0x40, 0);

  write(0x00, 0x40 | 0x07);   // 矢印 icon
  write(0x40, 0);
  if(x & 0xC0) {
    write(0x00, 0x40 | 0x07);   // 矢印 icon
    write(0x40, (x & 0xC0)>>3);
  }

  write(0x00, 0x40 | 0x06);   // enter icon
  if(x & 0x100) write(0x40, 0x10);
  else write(0x40, 0);
  write(0x00, 0x40 | 0x04);   // ring icon
  if(x & 0x200) write(0x40, 0x10);
  else write(0x40, 0);
  write(0x00, 0x40 | 0x02);   // 電話 icon
  if(x & 0x400) write(0x40, 0x10);
  else write(0x40, 0);
  write(0x00, 0x40);    // antena icon
  if(x & 0x800) write(0x40, 0x10);
  else write(0x40, 0);
  write(0x00, 0x38);    // Function set
}

void I2CLCD::setCG(char mode, char* cgData)
{
  unsigned char i, j;
  write(0x00,0x38);     // Function set IS= 0
  write(0x00,0x40);     // CGRAM set address= 0
  if(mode == 0) {    // StringModeで呼ばれた時はデフォールトにセット
    for(i=0; i<8; i++) {
      for(j=0; j<8; j++) {
        if(j < (7-i))	write(0x40,0x00);
        else write(0x40,0x1f);     // 横棒をセット
      }
    }
  }
  else {
    for(i=0; i<64; i++ ) write(0x40, cgData[i]);
  }
}

2つのLCDを付ける

16×2行と8×2行のLCDはどちらも同じアドレスを持っています。同じラインに同じアドレスの装置を付けることはできません。しかし、mbedには2系統のI2Cを持っているので、同時に2つのLCDを付けることが出来ます。
ここでは、この2つがちゃんとコントロールできるかチェックしてみます。

main.cpp
int main()
{
    I2CLCD lcd2(16);
    I2CLCD lcd(p28,p27);
    int i=0;
    printf("LCD test\n");
    while(true)
    {
        lcd.write("*** LCD Test ***");
        lcd.write("M.Mizuno");
        lcd2.write("M.Mizuno");
        lcd.write(i,8,0);
        lcd2.write(i++,8,0);
        wait(0.2);
        lcd.clear();
        lcd2.clear();
    }
}
 1 上のヘッダは以前のと同じなので省略
 2 
 3 8×2行のLCDをp9,p10のポートを利用する
 4 16×2行のLCDをp28,p27のポートを利用する
 5 
 6 
 7 
 8 
 9 16×2行のLCDに表示
10 
11 8×2行のLCDに表示
12 8カラム確保して右詰で整数を表示
13 
14 
15 
16 
17 
18 


外字を作れるグラフィックモードのテストプログラムです。
インベーダー
UFO

インベーダーの移動(main.cpp)
int main()
{
  I2CLCD display;
  display.clear();
  display.write("LCD グラフィックテスト");
  wait_ms(1500);

  unsigned int invader[8] = {0x104, 0x88, 0x1fc, 0x376, 0x7ff, 0x5fd, 0x5f5, 0xd8};
  unsigned int invader2[8]= {0x104, 0x489, 0x5fd, 0x777, 0x7ff, 0x3fe, 0x104,0x202};
  unsigned int tako[8] = {0xf0,0x7fe,0xfff,0xe67,0xfff,0x198,0x36c,0xc03};
  unsigned int ufo[8] = {0x60,0x1f8,0x3fc,0x6f6,0xfff,0x39c,0x108,0};
  unsigned int ika[8] = {0x18,0x3c,0x7e,0xdb,0xff,0x24,0x5a,0xa5};
  unsigned int *inv, count=0;
  unsigned char cgData[64];
  int i, j, k, m=0, n=0, x=0;

  for(i=3; i<8; i++) {
     for(j=0; j<8; j++) cgData[i*8+j]=0;
  }
  inv = ufo;
  while(true)
  {
    for(k=0; k<14; k++) {
      n = 0;
      for(m=0; m<6; m++) {
        switch(count % 4) {
          case 3: inv = ika;  break;
          case 2: inv = ufo;  break;
          case 1: inv = tako; break;
          case 0:
                  if((x++ % 4)<2) inv = invader;
                  else inv = invader2;
        }
        for(i=0; i<8; i++) cgData[i]    = (inv[i]>>(6+m));
        for(i=0; i<8; i++) cgData[i+8]  = ((inv[i]>>m) & 0x1f);
        for(i=0; i<8; i++) cgData[i+16] = ((inv[i] & n)<<(6-m));
        n = n*2+1;

        display.setCG(1, (char*)cgData);
        display.write(0x00,0x80);
        for(j=0; j<k; j++) display.write(0x40, 3);
        for(j=0; j<3; j++) display.write(0x40, j);
        for(j=k+3; j<16; j++) display.write(0x40, 3);
        wait_ms(100);
      }    // m
    } // k
    count++;
  } // while
}

ダウンロード
Code::Blocks用(これは16×2行のみの古いバージョン)
グラフィック用のLCDも使用出来ます。