Arduinoの標準ライブラリでLCDに文字を表示する方法

組み込みエンジニア

こんにちは、ENGかぴです。

Arduino環境ではLCDに文字を表示するために標準ライブラリとしてLiquidCrystalがあります。外部機器から取得したデータの情報をLCDに表示したりボタンで表示を切り替えてモードを選択したりと用途は様々です。

Arduino UNOを対象としますが、タイマ管理をしながらボタンで表示を切り替えてLCDに表示します。タイマ管理の方法やDIのチャタリング防止の方法を下記記事にまとめています。

Arduinoのタイマ管理とDIのチャタリング防止の方法

Arduino UNOを使って動作確認を行ったことを下記リンクにまとめています。

Arduinoで学べるマイコンのソフト開発と標準ライブラリの使い方

ArduinoでLCD表示を行う

ArduinoのIDEの初期に搭載されているライブラリである「LiquidCrystal.h」をインクルードして使用します。

ArduinoとLCDの配線

ArduinoとLCDの配線
ArduinoとLCDの配線

ArduinoとLCDの配線例を示しています。最小の組み合わせでは制御線2本とデータ用の4本をつなぐことでLCD表示ができます。4ビットのデータをライブラリで判断して一文字に変換して表示する仕組みになっています。そのほかの配線例は以下の通りです。

配線の本数信号名
最小の組み合わせ(6本)RS, E, D4, D5, D6, D7
制御線を追加(7本)RS, RW, E, D4, D5, D6, D7
データ8ビット(10本)RS, E, D0, D1, D2, D3, D4, D5, D6, D7
最大の組み合わせ(11本)RS, RW, E, D0, D1, D2, D3, D4, D5, D6, D7
LCDの配線の組み合わせ

どの配線でも同じように動作するため使えるピンが少ないArduinoではピンが最小になる組み合わせがよさそうです。

V0は可変抵抗で分圧して入力することでコントラストを変更できますが、電圧が高い方で表示が薄くなり表示が見にくくなるためGND接続としています。LED照明をつけたほうが見やすいので3.3VでLEDを点灯しています。

ボタンは押すと表示を変更できるようにするため準備しています。

ライブラリの使い方

「LiquidCrystal.h」をインクルードするほかにLiquidCrystalの型に従った変数を宣言する必要があります。

#include <LiquidCrystal.h>

#define LCD_RS 2
#define LCD_EN 3
#define LCD_D4 4
#define LCD_D5 5
#define LCD_D6 6
#define LCD_D7 7

LiquidCrystal lcd(LCD_RS, LCD_EN, LCD_D4,LCD_D5, LCD_D6, LCD_D7);

LiquidCrystalの型でlcd()のインスタンス(実体)を宣言しますが、実際に配線するポートを順番に指定していきます。ポート数はLCDの配線の組み合わせで示した数になります。上の例では6本使用する場合の例としています。直接数値を入力してもよいのですが、defineで定義して入力しています。

void setup() {
 
    lcd.begin(16,2); //16×2を表示領域
    lcd.print("LCD-TEST");
    lcd.setCursor(0, 1); //2段目の左端にカーソル
    lcd.print("Ver1.00         ");
    delay(2000); //初期表示を2秒間行う
    lcd.clear(); //初期表示をクリア
}

lcd.begin(16,2)で表示する領域を設定します。カーソル位置は初期状態では左上(0,0)になるのでlcd.print(“LCD-TEST”)によって一段目に文字が表示されます。カーソル位置を2段目の左端にセットしlcd.print(“Ver1.00”)によって2段目に文字が表示されます。2秒表示した後lcd.clear()で表示を消しています。

よく使うコマンド

LCDライブラリに搭載されているコマンドの中でもよく使うものについてまとめました。(私が良く使うものです。)

コマンド使い方
begin()LCDの表示領域を設定します。
lcd.begin(cols, rows)
colsは表示列 rowsは表示行数を指定します。
clear()LCD表示をクリアしてカーソルを1段目の左端にセットします。
lcd.clear()
setCursor()LCDのカーソルの位置を設定します。列は0~15、行は0~1で指定します。
lcd.setCursor(col, row)
write()バイトデータを文字として表示します。1バイトを超えると下位の1バイトが表示されます。
lcd.write(data) data:バイトデータ
print()データを型に応じて表示し、デフォルトでは10進数で表示されます。
lcd.print(data) 文字列はそのまま文字列
lcd.print(data, DEC) DECをHEXにすると16進数、OCTは8進数、BINは2進数となる
sprintf()で文字変換してprint(data)で表示する方法もよく使う
cursor()
noCursor()
次に表示するカーソルの位置を示します。
lcd.cursor() 消すときは lcd.noCursor()
blink() noBlink()カーソルを点滅表示します。
lcd.blink() 点滅を停止するときは lcd.noBlink()
LCDライブラリのコマンド

今回はLCD表示を16×2表示にして、カーソルをセットして表示を切り替えるコマンドを使用しています。文字の表示はprintを使っています。

ボタンで表示を切り替える

LCDは表示したい文字を準備しておきprint()やwrite()を使用することで様々な文字を表示できます。文字はclear()しない限りは残ったままになるため文字を上書きして表示するようにする工夫が必要です。

例えば「ABCDE」と表示した後にカーソルをAの部分にセットし「WXYZ」と書き込んで表示すると「WXYZE」になってしまいます。

文字列を準備して切り替える

ボタンを押したときに表示を切り替える方法はいくつかありますが、あらかじめ表示したい文字があれば定数として宣言しておきボタンを押したときのカウント数で切り替える方法で表示を切り替えることを考えていきます。

const char disptbl[MD_MAX][COL_SZ]={
    "mode1-->cnt DEC ",
    "mode2-->cnt HEX ",
};

この定数をボタンを押した時1段目の表示切替として使用します。

if(difilt.di1 == 0){
    if(btnflg1){
        btnflg1 = false;
    
        if(++dispmd >= MD_MAX ){ //ボタンを押すとモードを変更する
            dispmd = MD_NO1;
        }
    }
}else{
    btnflg1 = true;
}

ボタンを押したときにチャタリングが発生するためチャタリング防止のDIフィルタを実装しています。DIはプルアップしたDIとしているのでボタンを押したときLOWになります。

btnflg1はボタンを押したとき押した状態で1度のみ処理したいからです。ボタンをしたときモード管理している変数dispmdを更新しています。

LCD表示を切り替える

LCD表示のためにDispSet()を準備しています。

void DispSet(){

    lcd.clear();
    lcd.setCursor(0, 0); //1段目の左端にカーソル
    lcd.print(disptbl[dispmd]); //1段目の文字列

    switch( dispmd ){
    case MODE_NO::MD_NO1:
        sprintf(strbuf,"%04d",cnt); //文字を変換4桁の10進数
        lcd.setCursor(0, 1); //2段目の左端にカーソル
        lcd.print(strbuf); //2段目の文字列を表示させる
        break;
    case MODE_NO::MD_NO2:
        sprintf(strbuf,"%04x",cnt); //文字を変換4桁の16進数
        lcd.setCursor(0, 1); //2段目の左端にカーソル
        lcd.print(strbuf); //2段目の文字列を表示される
        break;
    }
}
//10ms毎にカウントアップ(他の個所でカウント)
cnt = ( cnt +1 ) %10000; //4桁以上にならないようにする

一段目の表示はdisptbl[]で準備した文字を表示するようにしています。2段目の表示はcnt値を表示するようにしています。sprintf()で文字変換をしています。MD_NO1では10進数でcnt値を表示しMD_NO2では16進数でcnt値を表示するようにしています。

cntの計算は4桁以上にならないように%(余り)をカウント値とするようにしています。

関数の最初にlcd.clear()をすることで表示をクリアをするようにしています。関数をコールする間隔が短いとチカチカして見えることがあります。LCDを正面から見ると違和感ありませんが角度をつけてみるとチカチカしているように見えます。

Loop関数一周ごとにDispSet()をコールして表示をしていると途中で動作がフリーズする現象が出ました。(5分に一度程度でフリーズ時間が30秒くらい。WDTではなくフリーズ後はカウント値がフリーズ時からスタートする)頻繁に表示を切り替える処理を行うと何かしらの処理が渋滞してしまう可能性があります。

LCDの表示間隔を可能な限り短くならないように工夫するとよいかもしれません。今回は100ms毎に更新するようにしましたが、フリーズする現象はでなくなっています。

またライブラリのバグかもしれませんが、たまに文字化けが発生します。リセットを繰り返すと復帰します。おそらく4ビットデータを1バイトデータとして扱う処理などにおいてビットがこぼれたりしているのかもしれません。

動作確認

電源を入れると最初に「LCD-TEST」とバージョン「Ver1.00」を表示しています。

動作確認(左:文字化け 右:初期画面)
動作確認(左:文字化け 右:初期画面)

左側は文字化けした場合の表示です。文字化けした場合はリセットする以外に復帰する方法がありませんでした。正常に表示できる場合は初期画面として右の画面が2秒間表示されます。

動作確認(左:10進数 右:16進数)
動作確認(左:10進数 右:16進数)

初期表示が終わるとモード1の表示としてカウント値が0~9999までが10進数で表示されます。ボタンを押すとモード2としてカウント値を16進数に変換した値が表示されます。もう一度ボタンを押すとモード1に戻りこれを繰り返します。

ソースコード全体

以下のソースコードはコンパイルして動作確認をしております。チェックはしておりますがコメントなど細かな部分で間違っている可能性があります。参考としてお使いいただければと思います。

#include <MsTimer2.h>
#include <LiquidCrystal.h>

#define LCD_RS 2
#define LCD_EN 3
#define LCD_D4 4
#define LCD_D5 5
#define LCD_D6 6
#define LCD_D7 7

#define TIME_UP 0
#define TIME_OFF -1
#define BASE_CNT 10 //10msがベースタイマとなる
#define LCD_TIM_MAX 10
#define DI_FILT_MAX 4
#define FILT_MIN 1
#define PIN_DI1 10
#define COL_SZ 16

enum MODE_NO{
    MD_NO1 = 0,
    MD_NO2,
    MD_MAX
};

struct DIFILT_TYP{
    uint8_t wp;
    uint8_t buf[DI_FILT_MAX];
    uint8_t di1;
};

const char disptbl[MD_MAX][COL_SZ]={
    "mode1-->cnt DEC ",
    "mode2-->cnt HEX ",
};

// application use
LiquidCrystal lcd(LCD_RS, LCD_EN, LCD_D4,LCD_D5, LCD_D6, LCD_D7);
int16_t timdifilt = TIME_OFF;
int8_t timlcd = TIME_OFF;
int16_t cnt10ms;
bool btnflg1;
DIFILT_TYP difilt;
uint8_t dispmd;
char strbuf[COL_SZ];
uint16_t cnt;

/*** Local function prototypes */
void TimerCnt();
void mainTimer();
void DiFilter();
void DispSet();

void setup() {

    pinMode( PIN_DI1, INPUT_PULLUP );

    MsTimer2::set(1,TimerCnt); //1msごとに関数へ遷移
    MsTimer2::start();

    Serial.begin(115200);
  
    lcd.begin(16,2); //16×2を表示領域
    lcd.print("LCD-TEST");
    lcd.setCursor(0, 1); //2段目の左端にカーソル
    lcd.print("Ver1.00         ");
    delay(2000); //初期表示を2秒間行う
  
    timdifilt = FILT_MIN;
    for( uint8_t i=0; i < 10; i++ ){
        mainTimer();
        DiFilter();
        delay(10);
    }
    cnt = 0; //カウントが進んでいるので0でクリア
    lcd.clear(); //初期表示をクリア
    timlcd = LCD_TIM_MAX;
}

void loop() {

    mainTimer();
    DiFilter();

    if(difilt.di1 == 0){
        if(btnflg1){
            btnflg1 = false;
    
            if(++dispmd >= MD_MAX ){ //ボタンを押すとモードを変更する
                dispmd = MD_NO1;
            }
        }
    }else{
        btnflg1 = true;
    }

    if( timlcd == TIME_UP ){
        timlcd = LCD_TIM_MAX;
        DispSet(); //LCD表示部分をセット
    }
}

/* callback function add */
void TimerCnt(){
    ++cnt10ms;
}
/* Timer Management function add */
void mainTimer(){

    if( cnt10ms >= BASE_CNT ){
        //10msごとにここに遷移する
        cnt10ms -=BASE_CNT;
        if( timdifilt > TIME_UP ){
            timdifilt--;
        }

        if( timlcd > TIME_UP ){
            timlcd--;
        }

        cnt = ( cnt +1 ) %10000; //4桁以上にならないようにする
    }
}
/* DiFilter function add */
void DiFilter(){

    if( timdifilt == TIME_UP ){
        difilt.buf[difilt.wp] = digitalRead(PIN_DI1);
 
        if( difilt.buf[0] == difilt.buf[1] &&
            difilt.buf[1] == difilt.buf[2] &&
            difilt.buf[2] == difilt.buf[3] ){ //4回一致を確認
            difilt.di1 = difilt.buf[0];
        }
    
        if( ++difilt.wp >= DI_FILT_MAX ){
            difilt.wp = 0;
        }

        timdifilt = FILT_MIN;
    }
}
/* DispSet function add */
void DispSet(){

    lcd.clear();
    lcd.setCursor(0, 0); //1段目の左端にカーソル
    lcd.print(disptbl[dispmd]); //1段目の文字列

    switch( dispmd ){
    case MODE_NO::MD_NO1:
        sprintf(strbuf,"%04d",cnt); //文字を変換4桁の10進数
        lcd.setCursor(0, 1); //2段目の左端にカーソル
        lcd.print(strbuf); //2段目の文字列を表示させる
        break;
    case MODE_NO::MD_NO2:
        sprintf(strbuf,"%04x",cnt); //文字を変換4桁の16進数
        lcd.setCursor(0, 1); //2段目の左端にカーソル
        lcd.print(strbuf); //2段目の文字列を表示される
        break;
    }
}

関連リンク

Arduinoのライブラリを使って動作確認を行ったことを下記リンクにまとめています。

Arduinoで学べるマイコンのソフト開発と標準ライブラリの使い方

Seeeduino XIAOで学べるソフト開発と標準ライブラリの使い方

ESP32-WROOM-32Eで学べるソフト開発と標準ライブラリの使い方

あなたの市場価値を見いだす転職サイト【ミイダス】無料会員登録

最後まで、読んでいただきありがとうございました。

タイトルとURLをコピーしました