静電容量式のセンサ(タッチセンサ)は物理的なスイッチが必要ありません。
そのため作品のデザインに影響を与えませんし自由度も上がりますので、よく使っています。
Arduinoだとライブラリもあって簡単に使えますしね。
ただ、離散値としてデータが入ってくるのでスイッチとして使えるようにするには大変で、そのあたりの解説も乏しい気がします。
そこで1回タッチするごとにON/OFFの切り替えができるスイッチとして使えるようなプログラムを紹介をしようと思います。
解説が多めですので、プログラムだけ見たい方は一番下に移動してください。
スポンサーリンク
ライブラリは上記リンクのものを使います。
これはarduino.ccで紹介されている公式のライブラリです。
もう何年も更新されていませんが、完成されているプログラムだと思います。
目次
使用する回路・簡単な原理
回路としては送信ピンと受信ピンの間に10MΩの抵抗器、受信ピンにアルミホイルや銅箔などを使ったタッチ電極をつけます。
タッチ電極はプラスチックや木材で覆っても構いません。
確認用にArduino Unoの13番ピンについているLEDを使います。
送信ピンから送信されるパルスが、電極に触れることによってRCローパスフィルタを形成し、パルスの立ち上がりが鈍り、検出が若干遅れます。
ヒトは若干ですがC(静電容量)を持っています。
この時間差を検出しているわけです。
これはArduinoと人がアースでつながっているため成立するセンサです。
ArduinoはパソコンのUSBやACアダプタを介してアースに繋がります。(実際はトランスなどがあるので物理的に繋がっているわけではないです)
そのため電池では検出が難しくなります。
ライブラリのサンプルプログラム
まずはライブラリに入っているサンプルプログラム(の縮小版)を見てみましょう。
#include <CapacitiveSensor.h> // 10M resistor between pins 4 & 2, pin 2 is sensor pin, add a wire and or foil if desired CapacitiveSensor touchPad = CapacitiveSensor(4, 2); void setup() { pinMode(LED_BUILTIN, OUTPUT); touchPad.set_CS_AutocaL_Millis(0xFFFFFFFF); Serial.begin(115200); } void loop() { long val = touchPad.capacitiveSensor(30); Serial.println(val); delay(10); }
上記のプログラムであれば、4番ピンと2番ピンの間に10MΩ程度の抵抗と2番ピンにタッチ電極をつけます。
データが入ってくるだけですのでこのままではスイッチとしては使えません。
サンプルだけを使うと「タッチセンサに触ると数値が大きくなるんだな」ということしか分かりません。
しきい値と比較する
では、これをスイッチとして使うにはどうすればよいでしょうか。
すぐに考えられるのが、しきい値と比較するプログラムです。
といってもただのif文なので非常に簡単ですけどね。
また、「state」はスイッチの状態データのboolean型の変数とします。
void loop() { // get value long val = touchPad.capacitiveSensor(30); Serial.println(val); // comparing: alternating state static boolean state = false; if (val > 1000) { state = !state; } digitalWrite(LED, state); delay(10); }
こうすればタッチセンサの値(valの値)が1000(しきい値)より大きいときに「state」はON/OFFを切り替えることができます。
しきい値の1000は適当ですので実際はやってみて決めるしかないです。
もうこれでスイッチとして完成しましたね!
動作が不安定
ただし動作としては非常に不安定です。
触っている間にLEDが点滅したり、上手に入り切りすることはできないでしょう。
これはタッチセンサの値が1000より大きくなるタイミングがタッチしている間に何回もあるからです。
「1000より大きければ状態を切り替える」プログラムですので、上の状況では6回状態を切り替えたことになります。
「タッチ1回ごとに切り替える」という仕様を満たしていません。
だったら「絶対に1回しか判定されない数値」や「[タッチしている間]が必ず1回だけになるようなサンプリング間隔」にするのはどうでしょうか。
これは運が良ければ動作しますが、ダメな場合が多いです。
前者は「絶対にピークが1回」になる保証はないですし、とても大きな数値にすれば反応しないかもしれません。
後者はタッチしている間を制限しなければなりませんし、間隔を大きくしすぎると反応が遅くなります。
「1秒以上はタッチしないで」というような「おかしな仕様」も後付しけなければなりません。
エッジ検出
切り替えるタイミングを「1000より大きくなった瞬間」にすればよいのです。
これだと上図のシリアルモニタの状況であれば「168→17988」になったときだけです。
エッジ検出をするプログラムは1つ前の状態を記憶しておいて前回と不一致、かつ現在の状態が"1"ならONにします。
void loop() { boolean touch_state = false; static boolean last_touch_state = false; // get value long val = touchPad.capacitiveSensor(30); Serial.println(val); // comparing if (val > 1000) { touch_state = true; } else { touch_state = false; } // edge detection static boolean led_state; if (touch_state != last_touch_state) { if (touch_state) { led_state = !led_state; } } digitalWrite(LED_BUILTIN, led_state); last_touch_state = touch_state; delay(10); }
コンパレータはエッジ検出をするため、状態を保持しないようにしました。
ON/OFF切り替え用の変数はエッジ検出部の「led_state」に変わりました。
時々不安定になる
これで仕様を満たすことができましたので、タッチスイッチの完成と言っていいでしょう。
しかし実際はまだ不安定な場合があるんです・・・
数値が大きくなっているところがタッチセンサに触れているところです。
2回の立ち上がりが理想ですね。
1回目と3回目は立ち上がりは少しゆっくりなのもあると思いますが、丸印のところで何回もしきい値である"1000"を行ったり来たりしている状態(チャタリング)があるので状態が不安定になってしまうのです。
チャタリング処理(ヒステリシスコンパレータ)
そこで、しきい値の比較に「ヒステリシス」特性を持たせます。
ロジックICには入力にヒステリシス特性を持たせたものがありますので聞いたことがあるかもしれません。(74HC14などのシュミットトリガ入力)
ヒステリシス特性は「一度状態が変化すると、しきい値が変化してさらに大きな変化をしないと状態は変化しない」という特性です。
とりあえずプログラムを見ていただいたほうが良いと思います。
void loop() { static boolean touch_state = false; static boolean last_touch_state = false; // get value long val = touchPad.capacitiveSensor(30); Serial.println(val); // hysteresis comparator if (val > 1000) { touch_state = true; } else if (val < 500) { touch_state = false; } // edge detection static boolean led_state; if (touch_state != last_touch_state) { if (touch_state) { led_state = !led_state; } } digitalWrite(LED_BUILTIN, led_state); last_touch_state = touch_state; delay(10); }
プログラムは「touch_state」にstatic修飾子がつき、値が保持されるようになりました。
「touch_state」はタッチセンサの値(valの値)が「1000より大きくなると"1"」ですが、「"1"になると500以下になるまで"1"のまま」です。
これでヒステリシス特性をもたせることができました。
ここまでしてようやく「ちゃんとしたスイッチ」のできあがりです。
より安定したチャタリング処理
実は先ほど紹介したヒステリシスコンパレータでは対応できない場合があります。
こういう場合はヒステリシス特性をもってしても対応できませんね。
スポンサーリンク
この場合にも対応するには「ローパスフィルタを実装して波形をなめらかにする」か、「スイッチ入力に使われるチャタリング処理を実装する」のがよいでしょう。
今回は「スイッチ入力に使われるチャタリング処理」を実装してみます。
ローパスフィルタ(概略)
ローパスフィルタに関しては touchPad.capacitiveSensor(30) の引数("30"の部分)の値を大きくするとなめらかになるでしょうし、それでも無理な場合は別の式で実装したほうが良いでしょう。
別の式に関しては以前の記事を参考にしてください。
ソフトウェアによるチャタリング処理
機械式スイッチの入力によく用いられるチャタリング処理です。
それをタッチスイッチに適用します。
原理は簡単で、状態が変化したときにまたすぐに変化したときは無効、安定した後に判定するというものです。
ここまでくるとちょっと長く感じますね・・・
コンパレータはヒステリシス特性を持たせなくてもチャタリング処理だけでも大丈夫かもしれませんが、念には念を入れています。
void loop() { static boolean touch_state = false; static boolean last_touch_state = false; static boolean led_state = false; // get value long val = touchPad.capacitiveSensor(30); Serial.println(val); // hysteresis comparator if (val > 1000) { touch_state = true; } else if (val < 700) { touch_state = false; } // edge detection & debounce static unsigned long last_time = 0; static boolean debounce_state = false; if (touch_state != last_touch_state) { last_time = millis(); } if (millis() - last_time > 100) { // debounce delay if (touch_state != debounce_state) { debounce_state = touch_state; if (debounce_state) { led_state = !led_state; } } } digitalWrite(LED_BUILTIN, led_state); last_touch_state = touch_state; delay(10); }
ここまでできれば、十分に実用的で安定しているタッチスイッチになります。
もっとスマートなプログラムに
タッチセンサのプログラムとは関係ありませんが、まだ改良の余地はあります。
loop関数内にdelayを使わないようなプログラムの方がよりスマートです。
delayはプログラムを停止させます。
別の処理を並行して動かすことができません。
例えば「タッチスイッチをON/OFFするたびにLEDが1秒ごとに点滅する」というようなプログラムは、今回書いたプログラムを基にしてつくるのは面倒でしょう。
そこで時間を測定する関数 millis() を使って「処理した時間を記憶して、現在と以前の差が一定以上であれば再度処理を行う」といったプログラムを書きます。
このあたりは割愛しますが、Arduinoのサンプルプログラム[02. Digital]→[BlinkWithoutDelay]を一度見てみてください。
日本語解説もあります。
またそれを実装したSimpleTimerライブラリを使うのもありです。
似たようなものがいくつかあるので自分に合うものを選びましょう。
今回は解説をかなり詳しくしてみましたが、タッチスイッチ1つだけ取っても思ったより奥が深いものです。
文中で言ったように電池では難しいですし、ノイズが多い場合も難しいかもしれません。
「スイッチをつける穴をあけたくない」「スイッチはダサい」となるのであればタッチスイッチを一度試してみてください。
スポンサーリンク
最後のLOOP関数を代入すると以下のメッセージが出てきてしまいます。
教えていただけないでしょうか?Arduino:1.8.7 (Windows Store 1.8.15.0) (Windows 10), ボード:"
seiden3:37:8: error: 'led_state' was not declared in this scope
led_state = !led_state;
^
seiden3:41:29: error: 'led_state' was not declared in this scope
digitalWrite(LED_BUILTIN, led_state);
^
exit status 1
'led_state' was not declared in this scope
コメント頂きありがとうございます。
エラー部分を修正いたしました。
掲載していたプログラムにled_stateの変数宣言が抜けておりました。