さくらのジャンク箱ロゴ

Sakura87がほぼ月刊でお届けするPCや電子工作と写真のブログ @なんと20周年

Raspberry Pi 2でグラフィック液晶の制御(2) 制御編

この記事は9年ほど前に投稿されました。内容が古くなっている可能性がありますので更新日時にご注意ください。

IMG_4713_20150708

※前回の続きです。

前回、グラフィック液晶のざっくりとした説明と接続をやりました。
今回はその液晶を実際に動かしてみたいと思います。

関連記事リンク

制御方法

制御信号6本+8bitコードなので基本的にはキャラクター液晶とそんなに変わりません。
レジスタ選択信号を切り替えて、イネーブルにした後データを転送するという方法にコントローラを選択する信号を追加するだけです。

初期化方法

この液晶を初期化する手順は以下のとおりです。

tg12864init

電源をGPIOからとった場合、Raspberry Piの電源入力と同時にGPIOの5Vから電源が取れるので、OSが起動している間に30ミリ秒は経つので気にしなくてOK。

後はリセットコネクタを1にしてその後0に、ちょっと待ってまた1に戻せば液晶がリセットされます。
その後にディスプレイスタートラインを0に設定、そしてディスプレイの表示をONにすることで初期化完了。
あとはページと行を指定してデータを送ってあげれば液晶を使うことができます。

コーディング

まず必要なライブラリと定数を定義します。

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

// データ線以外のポート定義
// データ線はdigitalWriteByteコマンドを使いたいので
// GPIOの0~7番に接続する。
// ※ R/WはL時書き込みモードなので接続しない。
//   もし万一読み込みモードに設定してしまった場合
//   パイが焼けてしまうので絶対に繋がない。
#define LCD_RS 13 // データ・コマンド切り替え信号
#define LCD_EN 12 // イネーブル信号
#define LCD_C1 10 // コントローラ切り替え信号(右半分)
#define LCD_C2 11 // コントローラ切り替え信号(左半分)
#define LCD_RE 14 // リセット信号

前回で決定したとおり、データコマンド切り替え(レジスタ選択)を13、イネーブル12 コントローラ切り替えを10/11 リセット信号14としました。

main関数を定義します。

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

※あとでBMPファイルを読み込む際に引数をつけてファイルを指定するので引数を受け取れるようにしておきます。

次にライブラリと液晶を初期化します。

	if(wiringPiSetup() < 0) printf("GPIO Err...");
	int i=0;
	
	//データbit用ポートを出力に設定
	for(i=0;i<8;i++){pinMode(i,OUTPUT);delay(2);} 
	
	
	// データ以外のポートを出力に設定
	pinMode(LCD_RS,OUTPUT);
	pinMode(LCD_EN,OUTPUT);
	pinMode(LCD_C1,OUTPUT);
	pinMode(LCD_C2,OUTPUT);
	pinMode(LCD_RE,OUTPUT);

	digitalWrite(LCD_RE,1);
	delay(50);
	digitalWrite(LCD_RE,0);
	delay(50);
	digitalWrite(LCD_RE,1);

wiringPi初期化→データbit用ポート初期化→それ以外のポート初期化→リセット信号送信

という手順で初期化を行います。
データ用のbitはGPIO.0~7固定なのでループ関数でループさせています。
ただしループが早過ぎるのかdelayを指定しないと初期化がうまくいかない場合がありました。

次に液晶にデータを転送する関数を作ります。

// LCD制御命令
// CntSel=コントローラセレクト
//  1=CS1  2=CS2
// rs=データ・コマンド切り替え
//  0=コマンド 1=データ
// Data = 8bit制御データ
int LCD_SET(int CntSel,int rs,int Data){
	int chip_sel[2]={LCD_C1,LCD_C2};
	digitalWrite(chip_sel[0],0);digitalWrite(chip_sel[1],0);
	digitalWriteByte(0);
	digitalWrite(LCD_EN,0);delayMicroseconds(0.45);
	digitalWrite(LCD_RS,rs);delayMicroseconds(2);
	
	digitalWrite(chip_sel[CntSel-1],1);
	digitalWrite(LCD_EN,1);delayMicroseconds(0.45);
	
	digitalWriteByte(Data);
	delayMicroseconds(2);
	digitalWrite(LCD_EN,0);
	digitalWrite(LCD_RS,0);
	digitalWriteByte(0);
	digitalWrite(chip_sel[0],0);digitalWrite(chip_sel[1],0);

	}

この関数は

コントローラ選択解除(CS1/CS2に0)→イネーブル信号=0 →コントローラ選択→
イネーブル信号=1 →データ転送→ 今使った全部のポートに0を設定

という制御を指定ます。この関数を使用することによって液晶を

LCD_SET(コントローラ選択,レジスタ選択,データ(8bit));

で制御できます。
例えばCS1に画像データ0xFF(11111111)を転送する場合は以下のように指定します。

LCD_SET(1,1,0xFF);  ※一部記号がみにくいので全角にしています。

この関数を利用してmain関数にて液晶の初期設定を行います。

	// 液晶初期化
	LCD_SET(1,0,0xC0);LCD_SET(1,0,0x3F);
	LCD_SET(2,0,0xC0);LCD_SET(2,0,0x3F);

それぞれのコントローラにスタートライン0指定(0xC0)と表示ON(0x3F)を送信しています。
なお、16進数は分かりづらいという方は以下のとおりに指定することもできます。

	// 0b…で2進数表記になる CS1での例
	LCD_SET(1,0,0b11000000);
	LCD_SET(1,0,0b00111111);

main関数にBMP読み込み部分を実装します。

	// モノクロ2値 1bit 64x128サイズのBMPファイル読み込み
	// 液晶の入力端子がある方を「下」にした場合
	// Windows標準のペイントで「右90度回転」させればOK。
	// 入力端子を「上」にした場合は「左90度回転」
	unsigned char bhd[62],BMPData[128][8];

	FILE *bmpr;
	bmpr=fopen(argv[1],"rb");
	
	// 読み込み ファイル形式が(1bit BMP 64x128px)だと前提して
	// 色々と端折ってデータ読み込み。↑の形式じゃないとエラー
	fread(bhd,1,62,bmpr);fread(BMPData,1,64*128,bmpr);
	fclose(bmpr);

先ほど引数を読み込めるようにしたのでファイルに引数を代入します。
※この代入方法はあまりお勧めできません。きちんとその文字列がディレクトリ名なのかを確認しましょう。今回はpi上の決まった形式で作成したデータを利用するだけですので色々端折っています。

BMPデータを転送する関数を実装します。

//  BMPデータ転送関数
//  128x8バイトの配列を「末尾 ex: array[127][7]」から転送する
int LCD_BMP(unsigned char BMPData[128][8]){
	int d=0,n=0;
	LCD_SET(1,0,0x3E);LCD_SET(2,0,0x3E);
	// 転送 BMPファイルは左下から記録されているので後ろから読み込む
	for(d=0;d<128;d++){for(n=0;n<8;n++){LCD_SET(1+(d>=64),0,0x40+(d%64));LCD_SET(1+(d>=64),0,0xB8+n);LCD_SET(1+(d>=64),1,BMPData[127-d][7-n]);}}
	LCD_SET(1,0,0x3F);LCD_SET(2,0,0x3F);
	}

この関数は受け渡された[128][8]の2次元配列を後ろから読み込んでLCDにセットします。やっていることは

LCDの表示をオフ→ [横ドットアドレス指定→
[ページアドレスしてい→ページx(0~7)に縦1行分データ転送]x8]x128
→LCD表示オン

という感じです。この液晶は横64ドットでコントローラがわかれていますので、
横ドットのアドレスは63までしかありませんが、ループは128回しています。これでどうやってコントローラをまたいで転送しているのかというと、「0x40+(d%64)」の部分で0x40(横ドットアドレス0)に、繰り返し回数を64で割ったあまり(d%64)を足しています。 そしてコントローラの選択は「1+(d>=64)」の部分で行っています。

この「1+(d>=64)」の(d>=64)の部分は比較演算子で値が64以上なら真になるようになっています。つまりBoolean型のTRUEが数値だと「1」であることを利用して、ループ回数64回以上なら1+TRUE(1)=2になって、CS2が選択されるというわけです。

この関数を使うには…は単に配列をLCD_BMP();の()の中に指定するだけですので省略

以上のコードをコンパイルします。コンパイルはいつもどおり -lwiringPi を指定するだけです。

例えばこのコードを「glcd」というファイル名でコンパイルした場合以下のとおりにすることで使えます。

root@raspberrypi:~# ./glcd ./gazou.bmp

「./gazou.bmp」が以下の画像だった場合、液晶には以下のように表示されます。

kanji

IMG_4733

以上、グラフィック液晶に画像を表示する事が出来ました。

今回は画像ファイルを読み込んでそれを表示しましたが、この液晶を制御しているコンピュータはRaspberry Pi 2。PICやAVRなんか目じゃないくらいの広大なメモリとストレージ。そして超高速な演算性能を持っていますし、基本LinuxなのでLinuxで出来る処理はすべてこの液晶に適応できるということです。フォントも自在に変更できますし、フレームバッファを利用すればコンソールやGUIを表示することさえできます。(コンソールはともかくGUIは需要あるのかしらないけど)

考えられる使い方は無限大ですね。

ソースコード

上記のコードを完成させたものを以下に貼っておきます。

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

// データ線以外のポート定義
// データ線はdigitalWriteByteコマンドを使いたいので
// GPIOの0~7番に接続する。
// ※ R/WはL時書き込みモードなので接続しない。
//   もし万一読み込みモードに設定してしまった場合
//   パイが焼けてしまうので絶対に繋がない。
#define LCD_RS 13 // データ・コマンド切り替え信号
#define LCD_EN 12 // イネーブル信号
#define LCD_C1 10 // コントローラ切り替え信号(右半分)
#define LCD_C2 11 // コントローラ切り替え信号(左半分)
#define LCD_RE 14 // リセット信号



int main(int argc,char *argv[]){
	if(wiringPiSetup() < 0) printf("GPIO Err...");
	int i=0;
	
	//データbit用ポートを出力に設定
	for(i=0;i<8;i++){pinMode(i,OUTPUT);delay(2);} 
	
	
	// データ以外のポートを出力に設定
	pinMode(LCD_RS,OUTPUT);
	pinMode(LCD_EN,OUTPUT);
	pinMode(LCD_C1,OUTPUT);
	pinMode(LCD_C2,OUTPUT);
	pinMode(LCD_RE,OUTPUT);

	digitalWrite(LCD_RE,1);
	delay(50);
	digitalWrite(LCD_RE,0);
	delay(50);
	digitalWrite(LCD_RE,1);
	// 液晶初期化
	LCD_SET(1,0,0xC0);LCD_SET(1,0,0x3F);
	LCD_SET(2,0,0xC0);LCD_SET(2,0,0x3F);
	
	
	// モノクロ2値 1bit 64x128サイズのBMPファイル読み込み
	// 液晶の入力端子がある方を「下」にした場合
	// Windows標準のペイントで「右90度回転」させればOK。
	// 入力端子を「上」にした場合は「左90度回転」
	unsigned char bhd[62],BMPData[128][8];

	FILE *bmpr;
	bmpr=fopen(argv[1],"rb");
	
	// 読み込み ファイル形式が(1bit BMP 64x128px)だと前提して
	// 色々と端折ってデータ読み込み。↑の形式じゃないとエラー
	fread(bhd,1,62,bmpr);fread(BMPData,1,64*128,bmpr);
	fclose(bmpr);
	// 読み込んだデータを転送
	LCD_BMP(BMPData);
	

	
}




//  BMPデータ転送関数
//  128x8バイトの配列を「末尾 ex: array[127][7]」から転送する
int LCD_BMP(unsigned char BMPData[128][8]){
	int d=0,n=0;
	LCD_SET(1,0,0x3E);LCD_SET(2,0,0x3E);
	// 転送 BMPファイルは左下から記録されているので後ろから読み込む
	for(d=0;d<128;d++){for(n=0;n<8;n++){LCD_SET(1+(d>=64),0,0x40+(d%64));LCD_SET(1+(d>=64),0,0xB8+n);LCD_SET(1+(d>=64),1,BMPData[127-d][7-n]);}}
	LCD_SET(1,0,0x3F);LCD_SET(2,0,0x3F);
	}
		

// LCD制御命令
// CntSel=コントローラセレクト
//  1 =CS1  2 =CS2
// rs=データ・コマンド切り替え
//  0 =コマンド 1 =データ
// Data = 8bit制御データ
int LCD_SET(int CntSel,int rs,int Data){
	int chip_sel[2]={LCD_C1,LCD_C2};
	digitalWrite(chip_sel[0],0);digitalWrite(chip_sel[1],0);
	digitalWriteByte(0);
	digitalWrite(LCD_EN,0);delayMicroseconds(0.45);
	digitalWrite(LCD_RS,rs);delayMicroseconds(2);
	
	digitalWrite(chip_sel[CntSel-1],1);
	digitalWrite(LCD_EN,1);delayMicroseconds(0.45);
	
	digitalWriteByte(Data);
	delayMicroseconds(2);
	digitalWrite(LCD_EN,0);
	digitalWrite(LCD_RS,0);
	digitalWriteByte(0);
	digitalWrite(chip_sel[0],0);digitalWrite(chip_sel[1],0);

	}


※液晶の個体によってはこのコードでは表示が乱れることがあります。
表示が乱れる場合で、たまに正常に表示できる場合は「LCD_SET」関数内にある2つの

delayMicroseconds(2)

の数字を大きくしてみてください。 (2015年7月18日追記)

余談

ちなみにこの液晶、結構描画速度が高速なので。例えばGIFアニメみたいなパラパラ漫画を表示することも可能です。試してみた感じ15fps程度なら問題なく表示できますし、なんとか30fpsくらいならいけるみたいなので「動画」を表示しても問題ないくらいの描画性能があるみたいです。白黒ですが。

しかし、残像がひどい。むしろその応答速度の遅さを利用してもっと高速に切り替えて階調表示という手も…。

う~ん…。

改版履歴

2015年7月11日:初版
2015年7月18日:ソースコードの部分に注意書きを追加

総閲覧数:240 PV

関連記事

“Raspberry Pi 2でグラフィック液晶の制御(2) 制御編” への17件のフィードバック

  1. さねすけ より:

    こんばんは。

    まったく同じ液晶を所持しているので、真似してみたのですが、、
    sudo gcc glcd.c -o glcd -lwiringPi
    でコンパイルして
    sudo ./glcd ./gazou.bmp
    で実行しても何も動きません。エラーもでません。
    画像はちゃんとありますし、液晶は配線を済ませたときから明るくはなっています。

    コードは恥ずかしながらコピーさせていただきました。

    考えられる原因は配線でしょうか?
    同じように配線しましたが、RW信号は未接続です。
    ご意見いただきたいです。よろしくお願いします。

    • Sakura87 より:

      コメントありがとうございます。

      このコードで全く動かないという事でしたら、配線ミスか液晶の不良が考えられると思いますが。
      他の方が作成されたプログラムなどは動作しますでしょうか。

      > 同じように配線しましたが、RW信号は未接続です。
      RW信号については準備編(https://sakura87.net/archives/2116)の記事に
      『先ほど注意したRW信号ですが。これは未接続が一番なのですが、未接続だと液晶の動作が不安定になってしまうので、保険として抵抗を挟んでGNDに接続しました。』
      とありますように、抵抗を介してGNDに接続したほうが安定します。

      以上のことから

       ・ほかのプログラムは動くか
       ・配線ミスはないか
       ・RW信号をGNDに接続したらどうなるか

      という事を確認してください。
      また、それでも動かない場合は考えにくいのですが白黒BMP形式で作成されているかをもう一度確認してください。

      • さねすけ より:

        素早い返信ありがとうございます。

        RW信号は抵抗を介してGNDに接続しました。

        http://putix.hatenablog.jp/entry/20141022/1413965553
        上記のサイトのコードの数字を変えて試してみたのですが、、やはりエラーも出ず、動かずでした。

        試した画像もペイントで作成したモノクロビットマップファイルです。

        液晶は光っていますので、配線ミスでしょうか、、、。
        ブレッドボードに液晶のピンを直接差し、RaspberryPiからメスオスピンで繋いでいます。
        恥ずかしいことに初心者なのですが、配線の注意点等ありますか?

        • Sakura87 より:

          コメントありがとうございます。

          デジタル信号の配線が問題ないのであればアナログ側の問題でしょうか。

          コントラスト調整関係の配線はどうしてます?
          準備編では私の環境で最良のコントラストになるように抵抗分圧で調整を行いましたが。もしかすると液晶の個体によっては準備編の抵抗値では見えないかもしれません。可変抵抗器があるならそちらをつないでコントラストを調整してみてください。

  2. 匿名 より:

    こんにちは。

    自分もこの液晶を使って階調処理をやってみたくて色々やってみたのですが、どうにもちらつくばかりでうまく濃淡が出ません。

    Gistにテストコードを上げましたので何かご助言が有れば伺いたいです。
    因みに下のコードでは8階調をやろうとしてますが、4階調で試した時も結果はそうかわりませんでした。
    やってる事としては、LEDのPWM制御の理屈でできるのかな?と思いドットの点滅を繰り替えしてみてるだけです。

    https://gist.github.com/moribudenhome/48e87d0b90e39e5698bde1d5b5f9267d

    よろしくお願いします。

    • Sakura87 より:

      コメントありがとうございます。

      コードを拝見しましたところ、メインのループ内にいくつか変数の定義を行っている箇所があります。
      ここの変数の定義をループ外に出してみると改善がみられるかもしれません。

      この後どのように発展させていく予定なのか存じませんが、パターンを生成しているところが少し煩雑に見えます。私が試したパターンでは、BMPファイルを事前に作成しているため、実際の転送部分は単純に変数データの読出しのみを行っているので、このパターンですと、パターン生成の処理を事前にやっておくことで高速化を図ることができると思います。

      ただ、正直なところ、写真ではそこそこ諧調表現ができているように見えますが、実際見てみるとそんなに良いものではありません。過度な期待はせずおとなしくモノクロ液晶として使った方が無難だと思います。
      念のため動画を表示したときに使用したプログラムのソースコードとそのファイルをアップロードしておきますので確認してください。

      https://sakura87.net/wp-content/uploads/2017/02/glcd0-1.zip

      ↑ファイル名を変更しないと動かないかもしれないです。

      • moribuden より:

        ご回答ありがとうございます。
        返事が遅くなってしまいました、すみません。

        頂いたコード動かしてみましたが、なるほど確かに階調表現という感じでは無いですね…

        示しましたコードをご指摘頂いた通り、高速化の為リファクタリングもしてみましたが、あまり効果は見込めずでした…

        というわけでこの液晶では一旦階調は諦めてモノクロ液晶として使用する事にします。
        諦めの踏ん切りがつきました。ありがとうございます!

        ですが、レトロな4階調表示というのに憧れは捨て切れていませんので、今度機会が有れば別の液晶で再挑戦したいと思います!

        • Sakura87 より:

          コメントありがとうございます。

          この液晶でも例えば表示領域を絞ったりすればどうにかなりそうですけどね。
          ただ、趣味ではなく実用性を考えますと最初からグレースケール対応している液晶やカラー液晶使った方がやっぱり手っ取り早くいろいろできますから便利だと思いますね。

          それでは。

  3. ふじみ野 より:

    Sakura87 さん
    素早い回答をありがとうございました。
    みごとに表示されました。
    ただ表示がされただけで喜んでいます。
    申し訳ない事ですが中身が何をしているのかは段々に勉強します。

  4. ふじみ野 より:

    Sakura87 さん
    「Raspberry Pi 2でグラフィック液晶の制御(1) 前準備」での質問に回答を頂きありがとうございました。
    何度も結線を確認して実行しました結果、
      横に4列縦に8行に分かれそれぞれのブロックにバラバラの点模様が表示されました。
    各ブロックは濃淡があり32あるブロックは全て模様が異なります。
    何かを描こうとしていると思います。表示は安定しています。

    使用したBMPファイルは貴兄のHPの「./gazou.bmp」を単純に保存したものです。
    この画像をMSペイントの消しゴムで消してモノクロビットマップとして保存して
      sudo ./glcd ./kara.bmp
    で表示させるとLCDは前面が白く表示されました。

    main関数のBMP読み込み部分に
    >// モノクロ2値 1bit 64×128サイズのBMPファイル読み込み
    >// 読み込み ファイル形式が(2bit BMP 64x128px)だと前提して
    と記述がある事に気づきましたがこの意味が分かりません。
    インターネットチェックで調べてみましたが理解ができませんでした。
    (2bit BMP 64x128px)ファイルをどの様に作れば良いのか教えて頂けませんか。

    • Sakura87 より:

      こん○○は。

      > この画像をMSペイントの消しゴムで消してモノクロビットマップとして保存して
      >  sudo ./glcd ./kara.bmp
      > で表示させるとLCDは前面が白く表示されました。

      とありますので、おそらく画像データがおかしいのではと思います。
      ブログにある画像を使わずにそちらで用意したものを試してみて結果がどう変わるかお試しいただけないでしょうか。

      > (2bit BMP 64x128px)ファイルをどの様に作れば良いのか教えて頂けませんか。
      貴方がペイントの消しゴムで消して作ったファイルがその形式のファイルです。
      ペイントでモノクロビットマップとして保存すれば作れます。
      (2bitの部分は1bitの誤りです。訂正しておきます。)

  5. […] 基本的には前回のコードがベースです。ここからBMP制御命令と読み込み命令を削除したものを用意してください。 […]

  6. じゅん より:

    本ページを拝見させていただき、全く同じ構成、部品でグラフィックLCDを組んでみました。
    描画自体はできるのですが、なぜか表示が乱れるときがかなりあります。毎回きれいに表示されていますか?プログラムや回路で何か改善点はありますでしょうか?ご教授いただければ幸いです。

    • Sakura87 より:

      こんにちは。

      私がこの記事を書くまで一週間程度同じ回路で使って動作させていましたが特に表示が乱れるということはありませんでした。

      ただ、表示実験中。以下の様な場合に表示が乱れることがありました。

       ・イネーブル信号を立ち上げてからデータ転送までが早過ぎる
       ・データ転送関数(LCD_SET)を実行する間隔が早すぎる
       ・RESET信号の制御し忘れ *
       ・RW信号のをGNDに接続していない *
       ・Raspberry Piの調子が悪い ←再起動で回復する

         *は記事内に書いてあるもの

      貴方が実際に作成した回路・コードを見てみないとわかりませんが、以上の点が私がこの液晶を買ってから正常に動作できるようになるまでに起きた不具合です。特にこのコードのデータ転送間隔は結構シビアで、転送ディレイ(LCD_SETの delayMicroseconds(2) のところ)は「私が入手した個体で正常に動作する」ギリギリの時間に設定しているので、表示が成功する場合がある場合はおそらくデータを転送している間隔の問題だと思われます。

      試しにこのページ最後にあるコードで90行目と96行目のdelayMicrosecondsの数値を大きくしてみて状況が改善するか確認してみてください。

      • じゅん より:

        いきなりの質問にご丁寧にご回答いただき、誠にありがとうございます。
        ご指摘いただきました90行目と96行目のdelayMicrosecondsの数値を変化させてみたところ、乱れることなく、正常表示が確認できました。(自分の所持している固体はどうやら転送ディレイは4が最適なようです…)
        最近ラズパイを購入し、Sakura87様のHPは非常に丁寧で分かりやすく、いつも参考にさせていただいております。今後も頑張ってください!

じゅん へ返信する コメントをキャンセル

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください