この記事で分かること
・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とノンブロッキング化について
取り組んでいきたいと思います。


コメント