【番外編2】PIC16F1938でLCDを8bit接続・4bit接続する方法【HD44780互換LCD】

この記事で分かること

・PIC16F1938でLCDを8bit接続する方法
・4bit接続に変更する方法
・8bitと4bitの違い

前回の記事では、I2CによるLCD(AQM1602XA-RN-GBW)の
表示させる方法を学びました。

今回もLCDの表示方法ですが、
14pin LCDを使って、
14pinすべてをPICのGPIOに接続する
「8bit」制御と、
14pinの内、10pinだけをPICのGPIOに接続する
「4bit」制御の両方を試し、
比較検討を行いました。
4bit制御の方がコードは複雑になってしまいますが、
GPIOは4bit 分少なくて済むメリットがあります。

今回使用したLCDは秋月電子のSC2004CSWB-XA-LB-Gです。
20文字 × 4行のものです。

今回もなかなかに苦戦を強いられました。
が、どちらも(8bit & 4bit)世界で最も表示されている
「Hello World」
を表示させることができました。

今回の回路図

8bit用

今回、LCD通信用のGPIO8bit分はRAを使用しました。
RBはPICkit5に使うRB6, RB7が含まれているため、
またRCはRS485通信で使用するTX, RXが含まれているためです。

4bit用

4bitの通信用GPIOはRAでもRBでもRCでもよかったのですが、
先に8bitから表示させたので、4bitはRA4~RA7を使用しました。

実際のコードと解説

pinの初期設定

pinの初期設定は、8bit モードも 4bit モードも全く同じです。

c


void pin_init(void)
{
    TRISA = 0x00;        //RA0~7すべて出力
    TRISCbits.TRISC1 = 0;   //RC1出力
    TRISCbits.TRISC2 = 0;   //RC2出力
    ANSELA = 0x00;       //RA0~7すべてデジタルモード
}

コマンドとデータの書き込み(8bit と 4bit 共通)

データシートの資料です。
上から順番に

RS : 0 → コマンド書き込み
1 → データ書き込み

R/W : 今回はGNDに接続

E : 0 → 1 → 0 データの書き込み

となります。
以下、この図を元にコードを書いていきます。

8bit時のコマンドおよびデータの書き込みとLCDの初期設定

コマンドの書き込み、つまり
LCDへの命令をさせる方法は上の図より次の通りです。

c

void lcd_cmd(unsigned char cmd)
{
    
    LATCbits.LATC1 = 0; //RS=0
    LATA = cmd;
    
    LATCbits.LATC2 = 1; //E=1
    __delay_us(1);
    LATCbits.LATC2 = 0; //E=0
    
    __delay_us(50);
}

データの書き込み、つまり
LCDに表示させるもの(データ)を
送る方法は次の通りです。

c

void lcd_data(unsigned char data)
{
    LATCbits.LATC1 = 1; //RS=1
    LATA = data;
    
    LATCbits.LATC2 = 1; //E=1
    __delay_us(1);
    LATCbits.LATC2 = 0; //E=0
    
    __delay_us(50);
}

これからLCDの初期設定をすることになります。

データシートにはLCDの初期化は次の通りに書かれています。

Function set:
N = 0; 1-line display (1; 2-line display)
F = 0; 5×8 dot character font (1; 5×11 dot character font)

Display on/off control:
D = 0; Display off
C = 0; Cursor off
B = 0; Blinking off

Entry mode set:
I/D = 1; Increment by 1
S = 0; No shift

これを元にコードを生成すると以下の通りです。

c

void lcd_init(void)
{
    __delay_ms(50);  // >40mS の待ちなので、50mSの待ち

    lcd_cmd(0x38);   // *1
    __delay_us(40);  // >37μS の待ち

    lcd_cmd(0x38);   // 
    __delay_us(40);  // >37μS の待ち

    lcd_cmd(0x0C);   // 0b00001010 ディスプレイON, カーソルON, カーソル点滅OFF
    __delay_us(40);  // >37μS の待ち

    lcd_cmd(0x01);   // 0b00000001 ディスプレイクリア
    __delay_ms(20);  // >1.52mS の待ち

    lcd_cmd(0x06);   // *2
}

上のクリップ留めの英語表記の部分にもありますが、
*印のところをもう少し解説します。

lcd_cmd(0x38);
0x38
つまり
0011 1000

DL=1 → 8bit
N =1 → 2行モード(20×4 LCDでもこの設定を使用)
F =0 → 5×8フォント

lcd_cmd(0x06)
0x06
つまり
0000 0110

I/D = 1
S = 0

文字を書いた後、
カーソルを右へ1文字移動する
画面シフトは行わない

4bit時のコマンドおよびデータの書き込みとLCDの初期設定

コマンドとデータの書き込みは、
8bit時と同様なのですが、
LCDとPICで4ビット分しかつながっていないので、
8bit を 上位4bit と 下位4bit に分けて送る必要があります。

上位4bit と 下位4bit を分ける分け方として、
次の様な関数を一つ用意しました。

c

void lcd_send_nibble(unsigned char nibble)
{
    LATA &= 0x0F;            //RAのマスク処理
    LATA |= (nibble << 4);   //4bitをRA4~7に書き込み

    LATCbits.LATC2 = 1;      //E=1
    __delay_us(1);           //Eの"1" "0"をしっかり分ける処理
    LATCbits.LATC2 = 0;      //E=0

    __delay_us(50);          //安定待ち
}

変数nibbleに代入された8ビット内、
下位4bitをRA4~RA7に出力させる関数です。

3行目の
LATA &= 0x0F;
はRA0~RA3が別の仕事を
割り当てられている場合、
それに影響を与えないような
処理(マスク処理)をしています。

c

/*コマンド入力用コード (cmdは8bits指定)*/
void lcd_cmd(unsigned char cmd)
{
    
    LATCbits.LATC1 = 0;              //RS=0  コマンド書き込み指定
    
    lcd_send_nibble(cmd >> 4);       //上位4bitを送る
    lcd_send_nibble(cmd & 0x0F);     //下位4bitを送る
}


/*データ入力用コード (dataは8bits指定)*/
void lcd_data(unsigned char data)
{
    LATCbits.LATC1 = 1;              //RS=1  データ書き込み指定
    
    lcd_send_nibble(data >> 4);      //上位4bitを送る
    lcd_send_nibble(data & 0x0F);    //下位4bitを送る
}

lcd_send_nibble(data >> 4);
lcd_send_nibble(data & 0x0F);
についての補足です

例えば “0x48″を送るとき、
0x48
つまり
0100 1000

↓上位4bit送信

0100

↓下位4bit送信

1000

といった形で、8bitを 4bitずつ送ることができます。

4bit時の初期化シーケンスは、
商品の仕様書には書かれておらず、
LCDコントローラICのST7066Uのデータシートに
以下の通りの記載がありました。

Function set:
N = 0; 1-line display (1; 2-line display)
F = 0; 5×8 dot character font (1; 5×11 dot character font)

Display on/off control:
D = 0; Display off
C = 0; Cursor off
B = 0; Blinking off

Entry mode set:
I/D = 1; Increment by 1
S = 0; No shift

上記は8bitと全く同じです。
実際のコードは以下の通りです。

c

void lcd_init(void)
{
    __delay_ms(50);
    LATCbits.LATC1 = 0;       //RS=0 データ書き込み用
    
    lcd_send_nibble(0x03);    //0011
    __delay_us(40);
    
    lcd_send_nibble(0x02);    //0010 4bit mode
    __delay_us(40);

    lcd_cmd(0x28);            //4bit, 2-line, 5*8
    __delay_us(40);
    
    lcd_cmd(0x0F);            //Display ON, cursor ON
    __delay_us(40);
    
    lcd_cmd(0x01);       //clear
    __delay_ms(2);
    
    lcd_cmd(0x06);       //entry mode
}

変数が増えただけで初期化のコードは大きく変わりません。
ただ8bitモードではカーソル点滅をOFFにしていますが、
4bitモードではカーソル点滅をONにしました。

 

mainのコード

8bitモードも4bitモードもmain()の中身は
同じものにしました。

c

unsigned char str1[] = "Hello";
unsigned char str2[] = "World";

void main(void)
{
    pin_init();
    lcd_init();
    
    lcd_cmd(0x80);
    for(unsigned char i = 0; str1[i] != '\0'; i++)
    {
        lcd_data(str1[i]);
    }
    
    lcd_cmd(0xC0);
    for(unsigned char i = 0; str2[i] != '\0'; i++)
    {
        lcd_data(str2[i]);
    }
    
    while(1)
    {
        
    }
}

ここで、1行目にstr[1]、2行目にstr[2]を表示させるように
lcd_cmdでカーソルの移動をさせています。
データシートには

となっており、1行目の1文字目は ”00” やし ”0x00” やん!って思ってました。
しかし、実際にカーソル指定の時は、
8bit目は “1” を入れないといけないため、
それぞれの行の開始アドレスは

  • 1行目:0x80
  • 2行目:0xC0
  • 3行目:0x90
  • 4行目:0xD0

といった具合になります。

動作確認とまとめ

8bitモード

4bit モード

どちらもうまく世界で最もよく表示されている

「Hello World」

を表示させることができました。

ここに漕ぎつけるのに、
main()関数の中のwhile()を忘れていたり、
lcd_cmd()の中に
pin_init()のRAやRCのIO設定を入れていたりと、
なかなか大変なことをやらかしてしまっていましたが、
何とか表示させることができました。

これで晴れて

  • I2C通信のLCD表示
  • パラレルの8bit通信のLCD表示
  • パラレルの4bit通信のLCD表示

に成功しました。

ようやくPIC-PIC間のRS485通信の
Lチカ以外の通信成功の証拠を確認できる準備が整いました。

しかし、LCD表示の中にはたくさんのdelay()関数が存在しており、
このままではCPUも「待て!!」が多くなるので、
通信させられません。

次回は、LCD表示動作における
Busy Flagとノンブロッキング化について
取り組んでいきたいと思います。

コメント

タイトルとURLをコピーしました