LCDモジュールを使ってみる
スタンドアローンのねじ切り専用NC旋盤製作の為に今回はマイコンを使ってLCD表示をしてみます。
今回使用するLCDは秋月電子で購入したLCDキャラクタディスプレイモジュール(16×2行バックライト付)です。700円でした。
今回もニコニコ動画の剣菱Pさんを参考にします。
回路図
今回もマイコンはATmega88を使用します。
このようになります。LCDモジュールの為に今回は5V電源を使用しています。
R1は5.1kΩにしました。LCDの3番に繋ぐR2は10kΩの半固定抵抗を使ってコントラストの調整が出来るようにします。
4ビットモードと8ビットモードというものがあるそうなのですが、配線の少ない4ビットモードを使用します。一度にやりとりするデータ量が多いか少ないかだそうです。
LCDモジュールのピン配置はこのようになっています。各ピンの機能は次の表のようになっています。
No | SYmbol | Function |
---|---|---|
1 | VDD | 5V |
2 | VSS | 0V |
3 | V0 | CONTRAST ADJ. |
4 | RS | REDISTER SELECT |
5 | R/W | READ/WRITE |
6 | E | ENABLE SIGNAL |
7 | DB0 | DATA BIT0 |
8 | DB1 | DATA BIT1 |
9 | DB2 | DATA BIT2 |
10 | DB3 | DATA BIT3 |
11 | DB4 | DATA BIT4 |
12 | DB5 | DATA BIT5 |
13 | DB6 | DATA BIT6 |
14 | DB7 | DATA BIT7 |
プログラムコード
“Hello World!”と表示させるコードを書きます。
/* AVRマイコンのLCD表示テスト */
#define F_CPU 1000000UL // 1MHz
#include <avr/io.h>
#include <util/delay.h>
#define LCD_RS 0b00000001 // レジスター・セレクト・シグナル
#define LCD_E 0b00000010 // イネーブル・シグナル
#define LCD_PORT PORTD // LCDに使うポートの指定(PIN4,5,6,7)
/* LCD用関数 */
void lcd_init(void);
void lcd_out(char code, char rs);
void lcd_cmd(char cmd);
void lcd_data(char asci);
void lcd_pos(char line, char col); // 今回は使用していません
void lcd_clear(void); // 今回は使用していません
void lcd_str(char *str);
int main(void) {
PORTD = 0b00000000; // PORTDをLで初期化
DDRD = 0b11111111; // 出力に設定
lcd_init();
lcd_str("Hello World!");
while(1);
return 0;
}
void lcd_out(char code, char rs) {
LCD_PORT = (code & 0xF0) | (LCD_PORT & 0x0F);
if (rs == 0)
LCD_PORT = LCD_PORT & ~LCD_RS;
else
LCD_PORT = LCD_PORT | LCD_RS;
_delay_ms(1);
LCD_PORT = LCD_PORT | LCD_E;
_delay_ms(1);
LCD_PORT = LCD_PORT & ~LCD_E;
}
void lcd_cmd(char cmd) {
lcd_out(cmd, 0);
lcd_out(cmd<<4, 0);
_delay_ms(2);
}
void lcd_data(char asci) {
lcd_out(asci, 1);
lcd_out(asci<<4, 1);
_delay_ms(0.05);
}
void lcd_pos(char line, char col) {
if (line == 1)
lcd_cmd(0x80 + col - 1);
else if (line == 2)
lcd_cmd(0xC0 + col -1);
}
void lcd_clear(void) {
lcd_cmd(0x01);
}
void lcd_str(char *str) {
while(*str != '¥0') {
lcd_data(*str);
str++;
}
}
void lcd_init(void) {
_delay_ms(15);
lcd_out(0x30, 0); // 8ビットモードに設定
_delay_ms(5);
lcd_out(0x30, 0); // 8ビットモードに設定
_delay_ms(1);
lcd_out(0x30, 0); // 8ビットモードに設定
_delay_ms(1);
lcd_out(0x20, 0); // 4ビットモードに設定
_delay_ms(1);
lcd_cmd(0x28); // 2行表示設定, 液晶2行, フォント5x7ドット
lcd_cmd(0x0C); // 画面表示ON, カーソルOFF, カーソル位置で点滅0FF
lcd_cmd(0x06); // カーソル移動右(インクリメントモード), 表示をシフトOFF
lcd_cmd(0x01); // 画面クリア
}
7行目の#define LCD_RS 0b00000001
はLCDモジュールの4番ピンと繋ぐピンを指定しています。今回はPD0と接続します。名前を付けておくと分かりやすいので#define擬似命令を使っています。後ほど出てきます。
8行目の#define LCD_E 0b00000010
も同じようにLCDモジュールの6番ピンと繋ぐピンを指定しています。
9行目の#define LCD_PORT
PORTD
はLCDモジュールとの通信にポートDを使用する事を示しています。
12〜18行目の関数の羅列はプロトタイプ宣言といいます。関数を使用する際、呼び出される関数は呼び出す側よりも先に記述する必要があるのですが、予めプロトタイプ宣言しておくと記述の順番が自由になります。
さてLCDモジュールを使用する為にはまず初期化する必要があります。
lcd_init();
という関数でLCDモジュールの初期化を行なっています。
電源ON → 15ms以上待つ → 8ビットモード指定 → 4.1ms以上待つ → 8ビットモード指定 → 100μs以上待つ → 8ビットモード指定 → 4ビットモード指定 → 表示設定等 → 初期化完了
というようにマイコンからLCDモジュールにコマンドを送る必要があります。LCDモジュールがどのように設定されているのか分からない為、8ビットモード指定のコマンドを3回送る事で最初に8ビットモードに設定してしまいます。
それから今回は4ビットモードを使用するので4ビットモードに設定するコマンドを送ります。この初期化に関する手順はネット上に色々なパターンがありますが、とりあえずこの手順で出来たので特にこれ以上突き詰めません。
コマンドを送っている関数は、文字列を送る関数にも使用されているのでvoid lcd_str(char *str)の解説のみします。関数は剣菱Pさんの丸パクリです。動画の解説には感謝しかありません。
関数の解説
void lcd_str(char *str) {
while(*str != '¥0') {
lcd_data(*str);
str++;
}
}
*str
に付いている「*」はポインタ変数である事を示しています。私のレベルではポインタについて詳しく説明する事は難しいので何となく説明します。
ポインタ変数にはアドレスが入っています。アドレスというのはデータが保管してある場所の事です。
lcd_str(“Hello World!”)と実行すると”Hello World!”の「H」のアドレスが送られます。
while( )文の( )の中は条件になります。ポインタ変数を普通の変数として使用するには「*」を変数の前に付けます。「*str」にする事でそのアドレスに入っているデータが読み取れます。
while(*str != '¥0')
は「!=」となっているので、2つの値が等しくない場合「真」となり、{ }内を繰り返します。つまり¥0になるまで繰り返します。¥0はヌル文字といって文字列の最後に勝手に付きます。
lcd_data(*str);
の「*str」には「H」が入っているのでvoid lcd_data(char asci)の関数に「H」が送られます。str++
でアドレスに1を足して次の文字へ行きます。
void lcd_data(char asci) {
lcd_out(asci, 1);
lcd_out(asci<<4, 1);
_delay_ms(0.05);
}
lcd_out(asci, 1);
の「asci」の変数に先程の「H」が入っています。「1」はvoid lcd_out(char code, char rs)に「asci」がデータ(文字)である事を指定する為のものです。「0」の場合コマンドとして認識されます。
4ビットモードで使用している為、上位4ビットのみ認識されます。下位4ビットを送る時はビットシフトで下位4ビットを上位に4つビットシフトさせます。このLCDモジュールのキャラクターパターンの表を見ると「Hp:」は「0x48」で二進数だと「01001000」です。ビットシフトで4つシフトすると10000000です。
連続で文字データを送っても処理が追い付かなくなるので_delay_ms(0.05);
で少し待つようになっています。
void lcd_out(char code, char rs) {
LCD_PORT = (code & 0xF0) | (LCD_PORT & 0x0F);
if (rs == 0)
LCD_PORT = LCD_PORT & ~LCD_RS;
else
LCD_PORT = LCD_PORT | LCD_RS;
_delay_ms(1);
LCD_PORT = LCD_PORT | LCD_E;
_delay_ms(1);
LCD_PORT = LCD_PORT & ~LCD_E;
}
「LCD_PORT」は今回PORTDとなっています。最初に送られてくる「char code」は「01001000」です。code & 0xF0
で「01001000」にして、LCD_PORT & 0x0F
で上位4ビットを0にして下位4ビットを維持します。2、3ビット目はLCDに使われていないので他の事に使えます。右端が0ビット目で左端が7ビット目となります。
01001000 | 0000xxxxで0100xxxxとなります。xは0と1どちらかになります。
if (rs == 0)はコマンドだった場合次の行を実行し、そうではない場合elseの次の行を実行します。今回の場合はrsは「1」なのでelseの方を実行します。
LCD_PORT = LCD_PORT | LCD_RS;
で「LCD_PORT」つまりPORTD(0100xxxx)の0ビット目を1にします(0100xxx1)。なぜ0ビット目なのかと言うと#define LCD_RS 0b00000001
としているので0ビット目を1に変更します。LCDモジュールの4番ピンのRSをH(ハイ)にする事でLCDでに文字だと認識させます。
それから同じように「LCD_E」を使って6番ピンのEをH(0100xx11)にしてL(0100xx01)にする事でデータを読み込ませます。
再びvoid lcd_data(char asci)に戻りビットシフトした下位4ビットのデータを送ります。これで「H」が送られました。void lcd_str(char *str)に戻り「e」「l」「l」「o」、、、と繰り返し、LCDに「Hello World!」と表示されます。
見事表示されました( ´ ▽ ` )ノ剣菱Pさんありがとう!
まとめ
今回の実験で私はど壺にハマってしまいました。LCDに文字が表示されず初期化が上手く行っていないのだと思いネットを徘徊しました。
しかし結局の所、半固定抵抗の使い方を間違っていました(~_~;)半固定抵抗をGNDに接続していなかったので分圧出来ていなかった事が問題でした。その為コントラストの調整が出来ておらず何も見えない状態になっていました。
結構な時間を浪費してしまいましたが、1つ勉強になったと思って次に進みたいと思います。