ゆるく楽しむプログラミング

小型キャラクタLCD AQM0802A

秋月電子通商で販売している小型キャラクタLCD (8桁x2行)です。
バックライト付きの AQM0802A-FLW-GBW と バックライトなしの AQM0802A-RN-GBW
それぞれのDIP化基板のセットが販売されています。

▼ バックライトなしのAQM0802AとDIP化基板 ※背景のグリッドは1cm幅です。


SSD1306の方が安くて高性能なのですが (´・ω・`)
キャラクタLCDだとフォントデータが要らないので、スケッチが小さくて済みます。
ATTinyで使う時などには魅力的な選択肢です。

カタログでは3.3V使用になっていますが、LCDユニット自体は7Vまで動作可能なようです。
コントローラが耐えられない?
Vccが3.3VであればArduino(信号は5V)でも使えました。※5V繋いだらどうなるかは試してません。
RaspberryPi Pico は3.3V駆動なので問題ありません。

AQM0820Aのコマンドと送信手順

※一部、日本語訳された説明書とシーケンス図に食い違いがあるので、試行した結果の推測が入ってます。

コマンドはI2Cの手順に則り 以下のパターンで送信する
書き込みには最大27μ秒を要するので、連続して命令を送信すると命令が受け付けられない事があります。
微妙な時間なので送信終了後にdelay()を入れたた方が良いと思う。



複数命令を送信する命令継続タグ(0x80)は、命令毎に継続か終端(命令タグ0x00)かを付けなければならないので
若干(10μ秒の単位で)スピードは上がるが汎用化はしずらく、初期化などの定型処理かI2Cバスを解放したくない時くらいにしか使い道がないように思える。
※解釈違いだったらすみません。 
※実際、命令タグ(0x00)の後に複数の命令を送信しても動きます。(動きが変になる事もあります)

コマンドの詳細は AQQM0802Aのデータシートを参照
※送信するのは命令かデータのどちらかで、命令とデータを混ぜて送ることはできない。
※DDRAMにデータを設定(LCDに文字を表示)する場合では、書き込み先アドレスの指定は命令、表示する文字はデータ。
  なので、アドレスの指定と表示文字の送信は分けて行わなければならない。


初期化に必要なコマンドを抜粋
 一つの命令にビットごとに意味を割り振っているので、下記表の簡易表現では同時に設定出来ない命令があります。
 詳細はデータシートを参照してください。正しく設定すれば両立可能なはずです。
命令コード  命令内容(設定関連)
0x38 FunctionSet :IS=0 / 2行表示 1行表示と倍サイズ表示の組み合わせで、8桁1行の表示になる
0x3C(1行表示だが倍サイズではない)の場合、2行目が表示されない
0x34 FunctionSet :IS=0 / 1行表示
0x40~0x6F 【ISモード=0の時】 CG-RAMのアドレス指定  キャラクタデータ設定時に使用
0x39 FunctionSet :ISモード=1
0x14 【ISモード=1の時】 OSC周波数 1/4bias 
0x70~0x7F 【ISモード=1の時】 コントラスト(0x70~0x73で良いと思う、0x75位で黒くなる)
0x56 【ISモード=1の時】 ブースターオン  スペックシートの初期化フローに書いてあるから入れてるけど
何を意味しているのか分かってない 
0x6C 【ISモード=1の時】 flowwer circuit ON
0x0D Display ON これは一つの命令に、それぞれのビットが割り当てられているので
DisplayとCurcorの組み合わせはスペックシートのビット情報を参照
0X80 Display OFF
0x0A Cursor ON(カーソルは_)
0x0B Cursor Position ON(カーソルは■)
命令コード  命令内容(表示操作関連)
0x01 Display Clear : Displayを0x20(ブランク)で埋める
0x80~0x87 表示開始位置を1行目の1文字目~7文字目に設定 0x80が命令、下位7Bitの0x00~0x07がDDRAMのアドレス 
0xC~0xC7  表示開始位置を1行目の1文字目~7文字目に設定 0x80が命令、下位7Bitの0x40~0x47がDDRAMのアドレス
0x14 【ISモード=0の時】 カーソルを右に移動  これは同じ命令にそれぞれのビットが割り充てられているので
カーソルと表示の組み合わせはスペックシートのビット情報を参照

表示のシフトは1行目と2行目が同時にシフトします。
0x10 【ISモード=0の時】 カーソルを左に移動 
0x1C 【ISモード=0の時】 表示を左にシフト
0x18 【ISモード=0の時】 表示を右にシフト
0x02 リターンホーム:カーソルを1行目の1桁目(アドレス0x00)に移動します

 2行表示(通常) 1行表示(倍サイズ)

-- LIST --
文字表示サンプル:流石に今回はベタ書きはツラいので関数化してます

#include <Wire.h>
#define LCDADR 0x3E

// ----- LCD初期化コマンド
static byte initCmd[] = {
  0x80, 0x38, //  FunctionSet : 2行表示
  0x80, 0x39, //  FunctionSet : ISモード = 1 
  0x80, 0x14, //  IS=1:OSC周波数 1/4bias
  0x80, 0x70, //  コントラスト
  0x80, 0x56, //  booster-ON , Contrast-2
  0x80, 0x6C, //  Follower control
  0x80, 0x38, //  FunctionSet : ISモード = 0 0x38 // 1行表示 = 0x34
  0x80, 0x0C, //  Display ON , Cursor ON = 0x0D // Cursor OFF = 0x0C
  0x00, 0x01, //  Clear Display
} ;

// ----- LCD 初期化
void initialComand(int cmdsLen , byte *cmds) {
  Wire.beginTransmission(LCDADR);
  for (int i=0 ; i<cmdsLen ;i++) {
    Wire.write(cmds[i]);
  }
  Wire.endTransmission();
  delay(1) ;
}

// ----- CLS
// 画面消去
void CLS() {
  Wire.beginTransmission(LCDADR);
  Wire.write(0x00);
  Wire.write(0x01); // CLS
  Wire.endTransmission();
  delay(1) ;
}

// ----- CONSOLE
// 表示行数 lines = 1 or 2
void CONSOLE(int lines) {
  byte cmd = (lines == 1) ? 0x34 : 0x38 ;
  Wire.beginTransmission(LCDADR);
  Wire.write(0x00);
  Wire.write(cmd);
  Wire.endTransmission();
  delay(1) ;
}

// ----- CURSOR
// 表示位置 x=0~7 y=0~1
void CURSOR(int x,int y) {
  byte yAdr = (y==0) ? 0x00 : 0x40 ;
  byte xAdr = x & 0x07 ;
  Wire.beginTransmission(LCDADR);
  Wire.write(0x00);
  Wire.write(0x80 | yAdr | xAdr );
  Wire.endTransmission();
  delay(1) ;
}

// ----- DISP
// 表示文字列 NULL Term
void DISP(unsigned char str[]) {
  Wire.beginTransmission(LCDADR);
  Wire.write(0x40);
  for (int i=0;str[i] != NULL;i++) {
    Wire.write(str[i]);
  }
  Wire.endTransmission();
  delay(1) ;
}

// ----- セットアップ
void setup() {
  Wire.begin();
  delay(40) ; // 電源ON後 40m秒はLCDが安定しないらしい

  // LCD設定コマンド
  initialComand(sizeof(initCmd) , initCmd) ;
}

// ----- ループ
void loop() {
  // ----- Character Disp -----
  CONSOLE(2);
  CLS();
  CURSOR(0,0);
  DISP("ABCDEFGH") ;
  CURSOR(0,1);
  DISP("01234567") ;
  delay(10000) ;

  // ----- Character Disp -----
  CONSOLE(1);
  CLS();
  CURSOR(0,0);
  DISP("ABCDEFGH") ;
  delay(10000) ;
}

CG-RAM(キャラクタ定義)

AQM0802Aには定義可能なキャラクタが6文字分あります。

キャラクタの定義は▼のように、5x8ドットで定義できます。
一番下はカーソル表示用に開けているようです、埋めても構いませんが他のキャラクタに合わせるなら空けた方が良いと思います。



CG-RAMへの書き込みは アドレス指定とデータ でワンセットで、複数バイトを一度に書き込むことは出来ないようです。
なので、1キャラ分の定義に アドレス指定、データ送信 のセットを8回繰り返す事になります。
 2行表示(通常) 1行表示(倍サイズ)
※流石に5x8ドットで平仮名はムリだったので、二文字分使って10x8ドットで平仮名一文字を定義してます

-- LIST --
文字表示サンプル:流石に今回はベタ書きはツラいので関数化してます

#include <Wire.h>
#define LCDADR 0x3E

// ----- LCD初期化コマンド
static byte initCmd[] = {
  0x80, 0x38, //  FunctionSet : 2行表示
  0x80, 0x39, //  FunctionSet : ISモード = 1 
  0x80, 0x14, //  IS=1:OSC周波数 1/4bias
  0x80, 0x70, //  コントラスト
  0x80, 0x56, //  booster-ON , Contrast-2
  0x80, 0x6C, //  Follower control
  0x80, 0x38, //  FunctionSet : ISモード = 0 0x38 // 1行表示 = 0x34
  0x80, 0x0C, //  Display ON , Cursor ON = 0x0D // Cursor OFF = 0x0C
  0x00, 0x01, //  Clear Display
} ;

// ----- CG-RAM DATA(文字フォント)
static byte bitMapYU1[] = { 0x00,0x05,0x06,0x04,0x05,0x00,0x01,0x00} ;
static byte bitMapYU2[] = { 0x10,0x1C,0x12,0x12,0x1C,0x10,0x00,0x00} ;
static byte bitMapRU1[] = { 0x03,0x00,0x01,0x03,0x04,0x03,0x03,0x00} ;
static byte bitMapRU2[] = { 0x18,0x10,0x00,0x18,0x04,0x04,0x18,0x00} ;
static byte bitMapPU1[] = { 0x00,0x07,0x00,0x00,0x00,0x00,0x03,0x00} ;
static byte bitMapPU2[] = { 0x08,0x14,0x0C,0x04,0x08,0x10,0x00,0x00} ;
static byte bitMapRO1[] = { 0x00,0x07,0x04,0x04,0x04,0x04,0x07,0x00} ;
static byte bitMapRO2[] = { 0x00,0x1C,0x04,0x04,0x04,0x04,0x1C,0x00} ;

// ----- LCD 初期化
void initialComand(int cmdsLen , byte *cmds) {
  Wire.beginTransmission(LCDADR);
  for (int i=0 ; i<cmdsLen ;i++) {
    Wire.write(cmds[i]);
  }
  Wire.endTransmission();
  delay(1) ;
}

// SET CG-RAM
void setCGRAM(int charNo , byte bitMap[]) {
  byte adr = charNo * 8 ;
  Wire.beginTransmission(LCDADR);
  Wire.write(0x00);
  Wire.write(0x38); // ISモード=0に設定
  Wire.endTransmission();
  delay(1) ;
  for (int j=0;j<8;j++) {
    Wire.beginTransmission(LCDADR);
    Wire.write(0x00);
    Wire.write(0x40 | (adr + j) ); // 【ISモード=0の時】CG-RAM アドレス指定
    Wire.endTransmission();
    delay(1) ;

    Wire.beginTransmission(LCDADR);
    Wire.write(0x40);
    Wire.write(bitMap[j]);   // CG-RAMに書き込み
    Wire.endTransmission();
    delay(1) ;
  }
}

// ----- CLS
// 画面消去
void CLS() {
  Wire.beginTransmission(LCDADR);
  Wire.write(0x00);
  Wire.write(0x01); // CLS
  Wire.endTransmission();
  delay(1) ;
}

// ----- CONSOLE
// 表示行数 lines = 1 or 2
void CONSOLE(int lines) {
  byte cmd = (lines == 1) ? 0x34 : 0x38 ;
  Wire.beginTransmission(LCDADR);
  Wire.write(0x00);
  Wire.write(cmd);
  Wire.endTransmission();
  delay(1) ;
}

// ----- CURSOR
// 表示位置 x=0~7 y=0~1
void CURSOR(int x,int y) {
  byte yAdr = (y==0) ? 0x00 : 0x40 ;
  byte xAdr = x & 0x07 ;
  Wire.beginTransmission(LCDADR);
  Wire.write(0x00);
  Wire.write(0x80 | yAdr | xAdr );
  Wire.endTransmission();
  delay(1) ;
}

// ----- DISP
// 表示文字列 NULL Term
void DISPN(int len,unsigned char str[]) {
  Wire.beginTransmission(LCDADR);
  Wire.write(0x40);
  for (int i=0;i<len;i++) {
    Wire.write(str[i]);
  }
  Wire.endTransmission();
  delay(1) ;
}

// ----- セットアップ
void setup() {
  Wire.begin();
  delay(40) ; // 電源ON後 40m秒はLCDが安定しないらしい

  // LCD設定コマンド
  initialComand(sizeof(initCmd) , initCmd) ;

  // ----- Set Character BitMap -----
  setCGRAM(0,bitMapYU1) ;
  setCGRAM(1,bitMapYU2) ;
  setCGRAM(2,bitMapRU1) ;
  setCGRAM(3,bitMapRU2) ;
  setCGRAM(4,bitMapPU1) ;
  setCGRAM(5,bitMapPU2) ;
}

// ----- ループ
void loop() {
  // ----- Character Disp -----
  CONSOLE(2);
  CLS();
  CURSOR(0,0);
  DISPN(7,"\x00\x01\x02\x03\x04\x05\xDB") ;
  CURSOR(0,1);
  DISPN(8,"01234567") ;
  delay(10000) ;

  // ----- Character Disp -----
  CONSOLE(1);
  CLS();
  CURSOR(0,0);
  DISPN(7,"\x00\x01\x02\x03\x04\x05\xDB") ;
  delay(10000) ;
}

解説動画