ESP32でボタンを使用した制御を行いたいと思います。
ボタンを押した確認として、前回使用したLCDディスプレイモジュールに押した回数を表示するようにします。
ESP32とボタンを接続
ボタンにはタクトスイッチを使用します。
タクトスイッチは、ボタンを押すと①と②、③と④が通電します。ボタンを離すと遮断されます。
ESP32、タクトスイッチ、LCDディスプレイモジュールを接続します。
ボタン制御 プログラム作成
ボタンを押した回数をLCDディスプレイモジュールに表示するプログラムの作成を行います。
前回のLCDディスプレイモジュール制御プログラムに、ボタン制御プログラムを追加していきます。
ボタンが押された場合の検知は、ピンの入力をCPUが検知して即座に処理を行う割り込み処理を使用します。
通常動作しているプログラムを一時停止し、割り込んで処理を行うため、短時間で終わる処理のみ行うように注意が必要です。
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
const unsigned int ADDRESS = 0x27;
const int CHARS_NUM = 16;
const int LINES_NUM = 2;
const int BTN1 = 15; // ボタン入力ピン
LiquidCrystal_I2C lcd(ADDRESS, CHARS_NUM, LINES_NUM);
int btnPushCnt = 0; // カウント数
// ボタン押下時割り込み処理
void IRAM_ATTR btn1Pushed() {
btnPushCnt++;
}
void setup() {
lcd.init();
lcd.backlight();
pinMode(BTN1, INPUT_PULLUP); // プルアップ設定
attachInterrupt(BTN1, btn1Pushed, FALLING); // 割り込み処理設定
}
void loop() {
lcd.setCursor(0, 0);
lcd.print("Hello World!");
lcd.setCursor(0, 1);
lcd.print("COUNT:");
lcd.print(btnPushCnt); // カウント表示
}
追加した部分のプログラムについて簡単に説明いたします。
8行目で、ボタン入力のピン番号を設定しています。
12行目で、ボタンを押した回数をカウントする変数を設定しています。
15~17行目で、ボタンが押された場合に割り込み処理を行う関数を設定しています。処理内容は、ボタンが押された場合に、「btnPushCnt」変数をインクリメント(+1)しています。
23行目で、BTN1に指定したピンを入力設定、かつ、内部プルアップ抵抗を有効に設定しています。
24行目で、割り込みが発生した場合に処理する関数の設定を行っています。設定内容は、以下になります。
- 第1引数:割り込み番号(ボタン入力ピン)
- 第2引数:割り込み発生時に呼び出す関数
- 第3引数:ピンの状態がHIGHからLOWに変わったときに発生
32、33行目で、ボタンを押した回数「btnPushCnt」をLCDディスプレイモジュールの2行目に表示しています。
ESP32とパソコンをUSBケーブルで接続して、「マイコンボードに書き込み」を行ってください。
ボタンを押すと2行目に表示されている数字が増えていくと思います。
これでも一応は動作はするのですが、1回ボタンを押しただけで、回数が2、3増えてしまうことがあります。
電子工作をしていると聞くことになる「チャタリング」によるものです。チャタリングは、Wikiペディアによると「可動接点などが接触状態になる際に、微細な非常に速い機械的振動を起こす現象のことである。」とのことです。
チャタリングは、ボタンを押した後に「HIGH」と「LOW」が複数回パタパタと入れ替わる状態で、定規を机に押し付けた状態で、定規の先端を持ち上げ、手を離したときビヨヨヨヨーンとなる感じです(机にぶつかった時がON、離れた時がOFFのイメージです)。
ボタンの入力検知方法を「loop()」関数で定期的に監視、サンプリングするポーリング処理に変更して、チャタリングの除去をしていきます。
ボタンが「ON」になった後に一定時間 間隔をおいて、再度ボタンの状態を確認し、「ON」だったら処理を行うといった流れです。
プログラムを以下に書いていきます。
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
const unsigned int ADDRESS = 0x27;
const int CHARS_NUM = 16;
const int LINES_NUM = 2;
const int BTN1 = 15; // ボタン入力ピン
unsigned char lastBtnSt = 0; // 前回ボタン状態
unsigned char fixedBtnSt = 0; // 確定ボタン状態
int btnPushCnt = 0; // カウント数
unsigned long smpltmr = 0; // サンプル時間
unsigned char lcdRefresh = 0; // リフレッシュ
LiquidCrystal_I2C lcd(ADDRESS, CHARS_NUM, LINES_NUM);
void setup() {
lcd.init();
lcd.backlight();
pinMode(BTN1, INPUT_PULLUP); // プルアップ設定
lcdRefresh = 1;
}
void loop() {
btnEvent(); // ボタンイベント
if(lcdRefresh){
lcd.setCursor(0, 0);
lcd.print("Hello World!");
lcd.setCursor(0, 1);
lcd.print("COUNT:");
lcd.print(btnPushCnt); // カウント表示
lcdRefresh = 0;
}
}
void btnEvent() {
if(millis() - smpltmr < 40) return;
smpltmr = millis();
int btnSt = digitalRead(BTN1);
int cmp = (btnSt == lastBtnSt);
lastBtnSt = btnSt;
if(!cmp) return;
if(!btnSt && (btnSt != fixedBtnSt)){
btnPushCnt++;
fixedBtnSt = btnSt;
lcdRefresh = 1;
}
if(btnSt){
fixedBtnSt = btnSt;
}
}
プログラムについて簡単に説明いたします。
9行目で、前回押したボタンの状態を記憶しておく変数を設定します。
10行目で、チャタリング除去後の確定したボタン状態を記憶しておく変数を設定します。
13行目で、ボタンのIO入力のサンプリング間隔を測定するための変数を設定します。
14行目で、LCDディスプレイモジュールに表示する内容を更新するときに使用する変数を設定します。
26~39行目の「loop()」関数では、ボタンのIO入力のサンプリング処理と、LCDディスプレイの表示更新を行っています。「lcdRefresh」変数が、1の時にLCDディスプレイの表示更新を行います。
41~60行目の「btnEvent()」関数で、ボタン状態を監視して、ONだったら「btnPushCnt」をインクリメントして、LCDディスプレイの表示を更新します。
42行目で、前回サンプリングを行った時間から 40ms 経過した場合のみ再度サンプリング処理を行うようにしています。「40ms」という時間はボタンを押した感じで適当に設定したものなので、随時変更してみてください。
45行目で、ボタンのIO入力値を取得しています。
46行目で、現在のボタン入力値と前回のボタン入力値を比較した結果を「cmp」変数に格納しています。一致していると「0」、不一致だと「1」が設定されます。
47行目で、前回ボタン入力値を更新しています。
49行目で、現在のボタン入力値と前回のボタン入力値が不一致の場合は、処理を終了します。
51行目で、ボタン入力値が「LOW(ボタンON)」かつ、現在ボタン状態と確定ボタン状態が不一致の場合に、押した回数をインクリメントして、LCDディスプレイの表示を更新します。
53行目で、確定ボタン状態に現在ボタン状態を設定しています。
57~59行目で、ボタンが押されていない場合は、確定ボタン状態に現在ボタン状態を設定します(ボタンOFF状態にしておきます)。
チャタリングが起こったり、押しても数が増えないといった場合は、サンプリング時間の「40ms」を変更して調整して下さい。
【参考図書】