こんにちは、ENGかぴです。
Arduino環境ではLCDに文字を表示するために標準ライブラリとしてLiquidCrystalがあります。外部機器から取得したデータの情報をLCDに表示したりボタンで表示を切り替えてモードを選択したりと用途は様々です。
LCDはQAPASS1602(スターターキットに付属)を使用しています。
LCDに文字を表示する方法とボタンで表示を切り替える方法についてまとめました。ボタンのチャタリング防止の方法を下記記事にまとめています。
Arduino UNO(以下Arduinoとします。)を対象とします。Arduinoのライブラリを使用して動作確認したことをまとめています。
Arduinoで学べるマイコンのソフト開発と標準ライブラリの使い方
ArduinoでLCD表示を行う
Arduino環境で標準搭載されているLiquidCrystalライブラリを使用する際の配線例とライブラリの使い方を説明します。
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 |
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本使用する場合の例としています。
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(); //初期表示をクリア
}
begin()関数で初期化と表示する領域の設定を行います。第1引数に表示する行の数を指定します。第2引数に列の数を指定します。例ではbegin(16,2)を指定しているので16×2の領域になります。
カーソル位置は初期状態では左上(0,0)になるのでprint()関数で指定した文字列が1段目に表示されます。例ではLCD-TESTが表示されるようにを指定しています。
SetCursor()関数でカーソル位置を変更します。引数に移動するカーソルの位置を指定します。第1引数は行番号を指定します。第2引数は列番号を指定します。例では2段目の左端を指定しています。
カーソルの移動後print()関数で指定した文字列が2段目に表示されます。例ではVer1.00が表示されるように指定しています。
2秒間表示した状態で待機させるためdelay()関数で待機しclear()関数で表示を消しています。
よく使うメンバー関数
LCDライブラリに搭載されているメンバー関数で私がよく使用するものについてまとめました。
関数 | 使い方 |
---|---|
begin() | LCDの表示領域を設定します。 lcd.begin(cols, rows) colsは表示列 rowsは表示行数を指定します。 |
clear() | LCD表示をクリアしてカーソルを1段目の左端にセットします。 lcd.clear() |
setCursor() | LCDのカーソルの位置を設定します。列は0~15、行は2行表示のLCDであれば0~1で指定します。 lcd.setCursor(col, row) |
write() | バイナリデータを文字として表示します。1バイトを超えると下位の1バイトが表示されます。 lcd.write(data) data:1バイトデータ |
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() |
本記事ではbegin()関数でLCD表示を16×2表示を指定し、文字の表示にprint()関数を使用しています。1段目の文字を表示した後でsetCursor()関数でカーソルを切り替えて2段目の文字の表示を行っています。clear()関数はLCDの文字全体を更新する場合に全体の文字をクリアする場合に使用しています。
ボタンで表示を切り替える
LCDは表示する文字をprint()関数やwrite()関数を使用することでアスキーコードに対応した文字を表示することができます。clear()関数で全体をクリアしない場合は文字が残ったままになるため上書きして表示する場合は工夫が必要です。
例えば「ABCDE」と表示した後にカーソルをAの部分にセットし「WXYZ」と書き込んで表示すると「WXYZE」になってしまいます。
文字列を切り替える
1段目に表示する文字列を配列の定数で宣言しボタンを押すと文字列を切り替えて表示します。
const char disptbl[MD_MAX][COL_SZ]={
"mode1-->cnt DEC",
"mode2-->cnt HEX",
};
例ではdisptbl[2][16]の配列に文字列を定数として指定しています。print()関数でdisptbl[0][0]のアドレスを指定すると”mode1–>cnt DEC”の文字列が対象になり、distbl[1][0]のアドレスを指定すると”mode2–>cnt HEX”が対象になります。
ボタンを押したとき表示切替を行いますが表示切替の判断はdispmd変数のカウント数で行います。ボタンを押すとチャタリングが発生するためフィルタによってチャタリングを防止したDI情報を使用して判断を行います。
if(difilt.di1 == 0){
if(btnflg1){
btnflg1 = false;
if(++dispmd >= MD_MAX ){ //ボタンを押すとモードを変更する
dispmd = MD_NO1;
}
}
}
else{
btnflg1 = true;
}
例ではDI1(10ピン)がLOWになったと判断したときにカウント数を更新しています。btnflg1はボタンを押した時に一度だけ処理をするために実装しています。ボタンを押したままにするとbtnflg1がfalseになっているためカウント数の更新が処理されません。
ボタンを離すとbtnflg1がtrueになるため次にボタンを押したときにカウント数の更新が処理されます。
広告
マイベスト3年連続1位を獲得した実績を持つ実践型のプログラミングスクール
LCD表示を行う
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桁以上にならないようにする
自作のDispSet()関数はLCD表示を行うため実装しています。1段目の表示はdisptbl[]配列の文字列を表示します。
2段目の表示は10ms毎にカウントしているcnt変数の値をsprintf()で文字変換をして表示します。MD_NO1では10進数でcnt値を表示し、MD_NO2では16進数でcnt値を表示するようにしています。
cntの計算は4桁以上にならないように10,000で割った余り(%演算)をカウント値とするようにしています。
関数の最初にclear()関数でLCDの表示をクリアをするようにしています。関数をコールする間隔が短いとチカチカして見えることがあります。LCDを正面から見ると違和感ありませんが角度をつけてみるとチカチカしているように見えます。
Loop関数一周ごとにDispSet()をコールして表示をしていると途中で動作がフリーズする現象が出ました。(5分に一度程度でフリーズ時間が30秒くらい。WDTではなくフリーズ後はカウント値がフリーズ時からスタートする)頻繁に表示を切り替える処理を行うと何かしらの処理が渋滞してしまう可能性があります。
LCDの表示間隔を可能な限り短くならないように工夫するとよいかもしれません。今回は100ms毎に更新するようにしましたが、フリーズする現象はでなくなっています。
またライブラリのバグかもしれませんが、たまに文字化けが発生します。リセットを繰り返すと復帰します。おそらく4ビットデータを1バイトデータとして扱う処理でビットがこぼれたりしているのかもしれません。
動作確認
電源を入れると最初に「LCD-TEST」とバージョン「Ver1.00」を表示しています。
左側は文字化けした場合の表示です。文字化けした場合はリセットする以外に復帰する方法がありませんでした。正常に表示できる場合は初期画面として右の画面が2秒間表示されます。
初期表示が終わるとモード1の表示としてカウント値が0~9999までが10進数で表示されます。ボタンを押すとモード2としてカウント値を16進数に変換した値が表示されます。もう一度ボタンを押すとモード1に戻りこれを繰り返します。
ソースコード全体
以下のソースコードはコンパイルして動作確認をしております。コメントなど細かな部分で間違っていたりやライブラリの更新などにより動作しなくなったりする可能性があります。参考としてお使いいただければと思います。
#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);
uint32_t beforetimCnt = millis();
uint16_t cnt;
int16_t timdifilt = TIME_OFF;
int8_t timlcd = TIME_OFF;
uint8_t dispmd;
bool btnflg1;
DIFILT_TYP difilt;
char strbuf[COL_SZ];
/*** Local function prototypes */
void mainTimer(void);
void DiFilter(void);
void DispSet(void);
void setup() {
pinMode( PIN_DI1, INPUT_PULLUP );
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表示部分をセット
}
}
/* Timer Management function add */
void mainTimer(void){
if ( millis() - beforetimCnt >= BASE_CNT ){
//10msごとにここに遷移する
beforetimCnt = millis();
if( timdifilt > TIME_UP ){
timdifilt--;
}
if( timlcd > TIME_UP ){
timlcd--;
}
cnt = ( cnt +1 ) %10000; //4桁以上にならないようにする
}
}
/* DiFilter function add */
void DiFilter(void){
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(void){
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で学べるソフト開発と標準ライブラリの使い方
PR:無料トライアル実施中【PC専用】AIスライド資料作成ツールの利用:イルシル
最後まで、読んでいただきありがとうございました。