RaspberryPi 3からキャラクタディスプレイ(LCD)を制御する

RaspberryPi 3のGPIOピンをうまく使ってやろうと思った。いずれ、自分でロボットを作りたいという気持ちもあるので。さしあたっての試みだ。

LCDは、秋月電子で500円で売っていたACM0802C-NLW-BBH、8文字2行のちっちゃいやつだ。

最終的にうまくいったので、記録しておく。

(1)ハード作り
R7に付いている100オームの抵抗をつける。R8は何もしない。R9は、抵抗の切った線を使って直結する。抵抗は小さくつけたが、もう少し脚長でつけてもよかった。
右側に、ピンがさせるようにコネクタをつける。この写真では裏側になって半田付けしか見えないが。
10Kの半固定ボリュームも必需品だ(別売、秋月電子で買っておく)。つけないと表示されない。足三本が、三角に配置されたものを買った。足は三角の右から1,2,3となっている。半固定ボリュームのピン1をLCDのピンの1,2をピン3、3をピンの2に配置する感じになるはず(自分で確認すること)。足が短かったので、抵抗の線をつぎはぎして半田付けした。ショートに気をつける。

(2)結線
ピンを節約するために、4ビットモードにする。文字は、1バイトで作られているので、データを4ビットずつ2度送らないという面倒さはあるが、コンピュータ側のピンの節約の方が大事である。
結線は複雑にしない方が良いので、電源関係以外は、LCDのピン番号を、RaspberryPiのgpio番号に対応させるというポリシーにする。
電源:
LCD1→RSP2 (5.5V)
LCD2→RSP6 (GRD)
LCD3→半固定ボリューム2
制御:
LCD4→GPIO4 (RS) データ、コマンドの切り替え
LCD5→GPIO5 (RWは使わないので、GRD:どれでも良いがピン25にした)
LCD6→GPIO6 (E)
LCD11→GPIO11 (DATA)
LCD12→GPIO12 (DATA)
LCD13→GPIO13 (DATA)
LCD14→GPIO14 (DATA)
これで結線終わり。バックライトが光るはずだ。
(3)ソフトウェア
まず、以下のページを参考にして、WiringPiを使えるようにする。
https://tool-lab.com/2013/12/raspi-gpio-controlling-command-2/
このCライブラリを使わせていただき制御する。
基本的に、WiringPiを初期化し、LCDを初期化し、書き込む。ただし、パルスを送るタイミングが複雑で微妙なのだ。そこで、
http://amahime.main.jp/lcd/main.php?name=lcd
にPICのCでACM0802C-NLW-BBHを制御したプログラムが記載されているので、それを参考にさせていただきながらRaspberryPi用に書き換える。
まず、ライブラリ的な、liblcd.c  である。

/*
キャラクタディスプレイACM0802C-NLW-BBHをRaspberryPi 3 から制御する
liblcd.c
2017年4月19日
*/

#include <wiringPi.h>
#include <stdio.h>

//LCD関数プロトタイピング

void lcd_init(void);
void lcd_str(char* str);
void lcd_data(char data, int cmd);
void lcd_out(char code, int flag);

// LCD 初期化
void lcd_init(void){
//
lcd_out(0x00, 1); // Displayをクリアしたい
//
delay(30);  // 30ms
lcd_out(0x02, 1); // Function set
lcd_out(0x02, 1);
lcd_out(0x08, 1);
delayMicroseconds(39);// 関数作成する必要あり
lcd_out(0x00, 1); // Display ON/OFF
lcd_out(0x0C, 1); //
delayMicroseconds(39);
lcd_out(0x00, 1); // Display Clear
lcd_out(0x01, 1); //
delayMicroseconds(1530);
lcd_out(0x00, 1); // Entry Mode Set
lcd_out(0x06, 1); //
}

// 文字列出力
void lcd_str(char *str){
while(*str != 0x00){
lcd_data(*str,0); //文字出力
str++;
}
}

// dataは送信内容 cmd は[0] ならデータ、[1]ならコマンド
void lcd_data(char data, int cmd){
//printf("lcd_data 1 [ %c ] の書き込み開始 \n",data);
//printf("lcd_data 2 上位ビットの書き込み開始 \n");
lcd_out(data>>4,cmd);// 上位4ビット出力
//printf("lcd_data 3 下位ビットの書き込み開始 \n");
lcd_out(data, cmd);// 下位4ビット出力

if(cmd == 1){
if((cmd & 0x03) != 0) delayMicroseconds(50);
}else{
delayMicroseconds(50);
}
}

// ビットチェンジ及び信号制御
void lcd_out(char code, int flag){

int gpio11 = (code & 0x01);
int gpio12 = (code & 0x02);
int gpio13 = (code & 0x04);
int gpio14 = (code & 0x08);

//printf("lcd_out 1 [ %c ] の書き込み開始 \n",code);
digitalWrite(11, gpio11);
digitalWrite(12, gpio12);
digitalWrite(13, gpio13);
digitalWrite(14, gpio14);

//printf("lcd_out 2 データORコマンド\n");
if(flag == 0){
digitalWrite(4, 1);// データ
}else{
digitalWrite(4, 0);// コマンド
}

//printf("lcd_out 3 書き込み\n");
delayMicroseconds(50);
digitalWrite(6, 1);
//nanodelay(220); // 0 でいい
digitalWrite(6, 0);
//printf("lcd_out 4 書き込み終了\n");
}
次にメインモジュールである。

/*
キャラクタディスプレイACM0802C-NLW-BBHをRaspberryPi 3 から制御する
lcdtest.c
2017年4月19日
*/

#include <stdio.h>
#include <wiringPi.h>
#include <string.h>

char sendChar[] = "My n";

//char sendChar0[] = "LCD TEST";
//char sendChar1[] = "WASSIISG";

void lcd_init(void);
void lcd_str(char* str);
void lcd_data(char data, int cmd);
void lcd_out(char code, int flag);

int main(int argc, char** argv){

if(argc <= 1){
printf("表示すべき文字が指定されていません!\n");
return 1;
}
strcpy(sendChar, argv[1]);

printf("gpioを初期化します\n");
// Initialize WiringPi
if(wiringPiSetupGpio() == -1) return 1;

printf("ポートを出力モードにします\n");
// Set GPIO pin 4-14to output mode
pinMode(4, OUTPUT);
pinMode(5, OUTPUT);
pinMode(6, OUTPUT);
pinMode(11, OUTPUT);
pinMode(12, OUTPUT);
pinMode(13, OUTPUT);
pinMode(14, OUTPUT);

printf("少し待ちます\n");
delayMicroseconds(250);
printf("lcdを初期化します\n");
lcd_init();

printf("データを書き込みます\n");

int len = strlen(sendChar);
int i;
if(len > 8){
// 8文字より長い場合は
// 2行にまたがって記述する
for(i=0;i<8;i++){
lcd_data(0x80+i,1);
lcd_data(sendChar[i],0);
}
for(i=8;i<len;i++){
lcd_data(0xc0+i-8,1);
lcd_data(sendChar[i],0);
}
for(i=len;i<16;i++){
lcd_data(0xc0+i-8,1);
lcd_data(0x20,0);
}
}else{
for(i=0;i<len;i++){
lcd_data(0x80+i,1);
lcd_data(sendChar[i],0);
}
for(i=len;i<8;i++){
lcd_data(0x80+i,1);
lcd_data(0x20,0);
}
for(i=0;i<8;i++){
lcd_data(0xc0+i,1);
lcd_data(0x20,0);
}
}

printf("表示を終了しました\n");

//while(1){
//}

return 0;
}

色々苦労したが、最も苦労したのは、タイミングの管理である。なの秒単位の管理があるのだが、cのnanosleepだったかを使ったら、ものすごく時間がかかる表示になった。
delayMicroseconds
を使って治った。即表示するようになった。1μs以下のタイミングがあるのだが。0タイミングにした。