ESP32でタッチセンサ

ESP32

ESP32でタッチセンサ

タッチセンサのハードウェア

ESP32のGPIO(汎用入出力端子)のうちのいくつかは、タッチセンサとしても使用できます。使用できる端子は以下の通りです。

タッチ入力としての名前GPIOとしての名前
T0GIPO 4
T1GIPO 0
T2GIPO 2
T3GIPO 15
T4GIPO 13
T5GIPO 12
T6GIPO 14
T7GIPO 27
T8GIPO 33
T9GIPO 32

『タッチ入力としての名前』は、プログラム中でタッチセンサとして使用したい端子を指定する場合に使用します。上の表の通り、digitalWrite()などで指定するピン番号とはまったく異なります。

なお、特に必要な外付け部品というものはなく、タッチ入力対応端子から配線を引き出すだけでタッチセンサとして使用可能です。

タッチセンサのソフトウェア

Arduino IDE から利用できる Arduino core for ESP32 ライブラリで定義されている関数のうち、タッチセンサ用としてよく使用されるのは次の2つです。

touchRead()関数

touchRead()関数は、タッチセンサの値を読み取ります。

C++
touch_value_t touchRead(uint8_t pin);
引数
名前意味
pinuint8_tタッチセンサとして値を読み取る端子。

値を読み取る端子は、digitalRead()などGPIO用の関数で使用する端子番号とは異なるT0~T9というラベルで指定可能です。

返却値

返却値は16bit長の整数値で、『タッチされている/いない』の真偽値ではありません。その値も『タッチされていないとき』より『タッチされているとき』の方が小さな値となる、という相対的なものです。

なお、返却値の型は touch_value_t となっていますが、これは uint16_t を typedef したものです。

touchAttachInterruput()関数

touchAttachInterrupu()関数は、指定されたタッチセンサの読取値が閾値を下回ったときに割り込みを発生させコールバック関数を呼び出します。touchRead()関数とは異なり、setup()関数内などで一度実行しておけば、繰り返し値を読み取る必要がありません。

C++
void touchAttachInterrupt(uint8_t pin, void (*userFunc)(void), touch_value_t threshold);
引数
名前意味
pinuint8_tタッチセンサとして値を読み取る端子。
userfuncvoid*タッチされたときにコールバックする関数。
thresholdtouch_value_tタッチされたことを判定する閾値。

値を読み取る端子は、digitalRead()などGPIO用の関数で使用する端子番号とは異なるT0~T9というラベルで指定可能です。

コールバック関数 userfunc は引数なし・返却値なしの関数として定義しておきます。タッチセンサ端子 pin の読取値が 閾値 threshold 以下となったときにコールバック関数が呼び出されます。

返却値

なし

まずは実験

閾値としてどんな値を設定したらいいか手がかりがありませんので、まずはタッチセンサの読取値について調べる予備実験を行います。

回路

回路は非常に簡単です。タッチセンサとして使用する端子にジャンパ線を取り付けるだけです。

配線

一方に何も接続されていないジャンパ線の、露出しているピン部分がセンサ代わりです。銅板などをハンダ付けするともっと使いやすいと思いますが、今回は簡単にこのようにしています。

露出しているピンが、なにかの拍子に ESP32-DevkitC の基板上の部品や他の端子に接触してしまわないように注意してください。故障の原因となります。

プログラム

ソースコード

C++
// 定数の設定
#define LED_PIN 12
#define TOUCH_PIN T4  // GPIO13

void setup() {
  // シリアルポートの設定
  Serial.begin(115200);
  delay(1000);
}

void loop() {
  // タッチセンサの読取値を表示する
  Serial.println(touchRead(TOUCH_PIN));
  delay(100);
}

プログラム解説

定数の設定
C++
// 定数の設定
#define LED_PIN 12
#define TOUCH_PIN T4  // GPIO13

以下の2つの定数を設定しています。

名前意味
LED_PINLEDを接続している出力端子
TOUCH_PINタッチセンサとして使用する端子
setup()
C++
void setup() {
  // シリアルポートの設定
  Serial.begin(115200);
  delay(1000);
}

この実験ではタッチセンサ端子の読取値を人間が確認するため、まず Serial.begin()関数でシリアル通信の初期化をしています。この例では速度を115200bpsに設定しているので、Arduino IDEなど通信に使うアプリケーションもこれに合わせて速度を設定しておきます。

Serial.begin()の直後に通信しようとすると不具合が起こることがあるのでdelay()で1秒だけウェイトしています。

loop()関数
C++
void loop() {
  // タッチセンサの読取値を表示する
  Serial.println(touchRead(TOUCH_PIN));
  delay(100);
}

touchRead()関数でタッチセンサーの値を読み取り、Serial.println()でシリアル送信しています。これを100msのウェイトを入れて繰り返します。

実験結果

製作例どおりの配線+手元の環境では、

状態
タッチしていないとき53 ~ 54
ピンの先端部に軽くタッチ20 ~ 23
ピンをしっかりつまむ11 ~ 13

となりました。この値は、配線の長さやタッチセンサとして使う導体の面積・形状、周囲の環境(湿度?)、タッチしているときなら肌の状態によっても大きな差が出ると思いますので、あくまで参考程度に。

タッチスイッチ

回路

LED点滅のための回路に、タッチセンサ(実際はただのジャンパ線)を接続するだけです。

配線

Lチカの実験の時と同じ要因 LED と抵抗器を接続し、先の実験と同様にセンサがわりとなるジャンパ線を接続します。

先の実験と同様、露出しているピンが、なにかの拍子に ESP32-DevkitC の基板上の部品や他の端子、ブレッドボード上の抵抗器やLEDの足に接触してしまわないように注意してください。故障の原因となります。

プログラム

ソースコード

C++
// 定数の設定
#define LED_PIN 12
#define TOUCH_PIN T4  // GPIO13
#define THRESHOLD 35

void setup() {
  // LED 端子の初期設定
  pinMode(LED_PIN, OUTPUT);
  digitalWrite(LED_PIN, LOW);
}

void loop() {
  // タッチセンサの値を確認してLEDを点灯/消灯
  if(touchRead(TOUCH_PIN) < THRESHOLD){
    digitalWrite(LED_PIN, HIGH);
  }else{
    digitalWrite(LED_PIN, LOW);
  }
  delay(100);
}

プログラム解説

定数の設定
C++
// 定数の設定
#define LED_PIN 12
#define TOUCH_PIN T4  // GPIO13
#define THRESHOLD 35

3つの定数を定義しています。それぞれ意味は以下の通りです。

名前意味
LED_PINLEDを接続している出力端子
TOUCH_PINタッチセンサとして使用する端子
THRESHOLDタッチしている/していないの閾値

閾値は『この値よりtouchRead()の読取値が小さければタッチしている』と判断するためのものです。先の実験の結果から、タッチしていないときの最小値53とタッチしているときの最大値23の中間付近の値35としていますが、環境によっては異なる値になるかもしれません。

setup()関数
C++
void setup() {
  // LED 端子の初期設定
  pinMode(LED_PIN, OUTPUT);
  digitalWrite(LED_PIN, LOW);
}

setup()関数内では、LEDを接続した端子を出力に設定しています。

loop()関数
C++
void loop() {
  // タッチセンサの値を確認してLEDを点灯/消灯
  if(touchRead(TOUCH_PIN) < THRESHOLD){
    digitalWrite(LED_PIN, HIGH);
  }else{
    digitalWrite(LED_PIN, LOW);
  }
  delay(100);
}

touchRead()関数でタッチセンサの値を読み取り、その値が THRESHOLD 以下であれば LED接続端子の出力を HIGH (LED点灯)/そうでなければ LOW(LED消灯)します。

loop()関数なのでこの処理がずっと繰り返されますが、あまり早く繰り返しても意味がないので delay(100) で 100ms のウェイトを入れています。

実行

こんな簡単なハードウェア&ソフトウェアでもちゃんと動作します。

割り込みを用いたタッチスイッチ

今度はtouchAttachIntrrupt()関数を利用して、loop()関数の実行状況とは無関係にタッチによる操作が行えるようなプログラムを作ってみましょう。

回路・配線

回路・配線は前節のものをそのまま流用します。

プログラム

ソースコード

C++
// 定数の設定
#define LED_PIN 12
#define TOUCH_PIN T4  // GPIO13
#define THRESHOLD 35

// 変数
int count=0;
bool flag=false;

void setup() {
  // シリアル通信の初期化
  Serial.begin(115200);
  delay(1000);
  // LED 端子の初期設定
  pinMode(LED_PIN, OUTPUT);
  digitalWrite(LED_PIN, LOW);
  // タッチ割り込みの設定
  touchAttachInterrupt(TOUCH_PIN, onTouch, THRESHOLD);
}

void loop() {
  count = (count+1) % 10;
  Serial.println(count);
  delay(1000);
}

// タッチされた場合のコールバック関数
void onTouch(){
  flag=!flag;
  if(flag){
    digitalWrite(LED_PIN, HIGH);
  }else{
    digitalWrite(LED_PIN, LOW);
  }
}

プログラムの解説

1つ前のプログラムから追加/変更された部分のみ解説します。

グローバル変数の定義
C++
// 変数
int count=0;
bool flag=false;

変数countはloop()関数内でタッチセンサとは独立した動作のために使用します。
変数flagはLEDの点滅を切り替えるために使用します。

タッチ割り込みの登録
C++
  // タッチ割り込みの設定
  touchAttachInterrupt(TOUCH_PIN, onTouch, THRESHOLD);

タッチセンサー端子 TOUCH_PIN の読取値が THRESHOLD 以下となったとき、onTouch()関数を呼び出すように設定します。第二引数の『onTouch』は後で定義している関数名そのものです。()がつかないことに注意してください。

loop()
C++
void loop() {
  count = (count+1) % 10;
  Serial.println(count);
  delay(1000);
}

loop()関数内はタッチセンサとはまったく関係なく、1秒ごとに『1、2、3、…』とカウントアップしてシリアル送信するだけです。『この処理とは独立して割り込みがかかる』ことを確認するためもののです。

コールバック関数 onTouch()
C++
// タッチされた場合のコールバック関数
void onTouch(){
  flag=!flag;
  if(flag){
    digitalWrite(LED_PIN, HIGH);
  }else{
    digitalWrite(LED_PIN, LOW);
  }
}

onTouch()関数は、タッチセンサにタッチされたときにloop()関数で実行中の処理とは独立して呼び出されます。内容は、まず変数flagの真偽値を反転し、その後のflagの値によってLEDを点灯または消灯します。

つまり、タッチするたびにLEDが点灯したり消灯したりすることを意図しています。

実行

プログラムは意図通りに動きませんでした。よく見るとタッチセンサ代わりのジャンパ線に触っている間、LEDが高速で点滅を繰り返しています。

どうやらtouchAttachInterrupt()関数は他の言語のイベント処理と異なり、『タッチしている間何度も割り込みをかけ続ける』という仕様のようです。

プログラム改良版

ソースコード

C++
// 定数の設定
#define LED_PIN 12
#define TOUCH_PIN T4  // GPIO13
#define THRESHOLD 35

// 変数
int count=0;
bool flag=false;

void setup() {
  // シリアル通信の初期化
  Serial.begin(115200);
  delay(1000);
  // LED 端子の初期設定
  pinMode(LED_PIN, OUTPUT);
  digitalWrite(LED_PIN, LOW);
  // タッチ割り込みの設定
  touchAttachInterrupt(TOUCH_PIN, onTouch, THRESHOLD);
}

void loop() {
  count = (count+1) % 10;
  Serial.println(count);
  delay(1000);
}

// タッチされた場合のコールバック関数
void onTouch(){
  static unsigned long prevTime=0;
  unsigned long currentTime=millis();
  if(currentTime-prevTime>=500){  // 前回の割り込みから500ms以上経過した場合のみ
    prevTime=currentTime;
    flag=!flag;
    if(flag){
      digitalWrite(LED_PIN, HIGH);
    }else{
      digitalWrite(LED_PIN, LOW);
    }
  }
}

プログラム解説

onTouch()関数
C++
// タッチされた場合のコールバック関数
void onTouch(){
  static unsigned long prevTime=0;
  unsigned long currentTime=millis();
  if(currentTime-prevTime>=500){  // 前回の割り込みから500ms以上経過した場合のみ
    prevTime=currentTime;
    flag=!flag;
    if(flag){
      digitalWrite(LED_PIN, HIGH);
    }else{
      digitalWrite(LED_PIN, LOW);
    }
  }
}

millis()関数を用いて時刻(マイコンの起動からの経過時間)を求め、前回onTouch()関数を実行した時刻prevTimeとの差を求めることで、一度onTouch()関数を実行したら500ms経過するまではなにもしないように処理を追加しました。これにより、短い時間でLEDが点滅してしまうことを防ぐことが出来ます。

実行

今度はタッチするたびにLEDが点灯/消灯するようになりました。ただし、タッチセンサ代わりのピンをずっとつまんだままにすると、500ms間隔でLEDが点滅を繰り返します。

まとめ

  • ESP32はタッチセンサを簡単に使うことが出来る
  • タッチセンサ機能を利用するためtouchRead()関数とtouchAttachInterrupt()関数が用意されている

コメント

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