TTP229Bを使った静電容量式キータッチモジュールを使ってみる話の続き。今回は、ジャンパエリアを使った動作設定の変更とArduino PRO MINIのスリープ機能を使ってみた。
ジャンパエリアにピンヘッダを装着
TTP229Bの動作設定を変更するためには、TP0~TP7を設定内容に応じた組合せで1MΩの抵抗を介してGNDに接続しておく。今回使っている基板には、ジャンパエリアが設けられているので、そこにピンヘッダをハンダ着けし、ジャンパピンをさせるようにした。
ピンヘッダ装着前。P1には1~4、P2には8~5という番号が振られており、それぞれはTTP229のTP0~TP3とTP7~TP4 に接続されている。
秋月で買った2列ピンヘッダを4つ分で折り取ってハンダ着けした。最初に載せた写真がジャンパピンをはめたところ。ついでに、基板上部の2つの穴に六角スペーサーを着けて基板の裏側が机に触れないようにした。撮影時にはマクロレンズ(30mm)を使って深度合成した。
基板上に印字されている番号とTTP229のピン名称が一致していないので、ここでは上記のようにICのピン名称(TP0~TP7) と一致した記号で記述することにする。
ジャンパの設定
詳しくはデータシートを参照するとして、今回のスケッチに関連する部分のみをまとめた。出荷時(全ジャンパオープン時)にはすべて論理値1となっている。
- TP0 : OUT1~OUT8の出力方式の設定用。
OPENのまま。 - TP1 : シリアル出力のアクティブレベル。
1 (OPEN) = アクティブL。(SDO, SCLは常時H、検出時Lから開始)。
0 (CLOSE) = アクティブH。(SDO, SCLは常時L、検出時Hから開始)。 - TP2 : シリアル出力可能なキー数の設定。
1 (OPEN) = 8キー。
0 (CLOSE) = 16キー。 - TP3とTP4は以下のように組み合わせて使う。
123456TP3 TP4 動作------------------------------------------------------1 1 単一グループ、すべて単独キー1 0 2グループ。グループ1は単独キー、グループ2も単独キー0 1 2グループ。グループ1は単独キー、グループ2は複数キー0 0 単一グループ、すべて複数キー
単独キー : 1つ目のキータッチ中は以降のタッチを無視。
複数キー : 2つ目以降のタッチも有効。ロールオーバー可能なキー数は不明。
今回は、グループの分割については調査せずに全キー単一グループを使った。 - TP5 : スリープモード(キータッチ待ち)中のサンプリングレート。
1 (OPEN) = 8Hz. (125msecごとにタッチを検出)。
0 (CLOSE) = 64Hz. (約16msecごとにタッチを検出)。 - TP6 : 検出時の長さ、あるいは、検出期間の幅。
1 (OPEN) = 4msec.
0 (CLOSE) = 2msec. - TP7 : 最大キーオン時間。
1 (OPEN) = 無制限。
0 (CLOSE) = 80秒。
常にキーがタッチ状態になってしまうような状況では論理値0としておくことで、80秒後に一旦初期状態に戻る。今回は利用しない。
プログラム
16キーすべてをシリアル通信で得ること、単一キー動作と複数キー動作の違いを知ること、Arduino のスリープモードと組み合わせることを目標とした。スケッチ1は前回と同様の回路を、スケッチ2では、回路上にキーのオン/オフ状態を示すLEDを追加した回路を用いる。
スケッチ1(単一キー)
前回と同じ回路を使い、
- TP1 =CLOSE : シリアル通信のアクティブレベルをHに変更。
- TP2 = CLOSE : すべてのキー(16キー)に対するタッチを、シリアル通信で得られるようにする。
- TP3とTP4 : 全キー単一グループとする。最初のスケッチでは単一キー(TP3 = TP4 = OPEN)とした。
TTP229は基本的にスリープモードにあり、TP5で指定されるインターバルごとにキータッチの検出を行う。今回はTP5 == 1(出荷時設定)なのでインターバルは125msecになる。キーデータを待ち受けるマイコン側もスリープ状態 (SLEEP_MODE_PWR_DOWN) にしておけば、消費電流はずいぶんと小さくなるはず。
TTP229は、キーのタッチを検出してデータ出力の準備ができるとData Valid通知として約90μsecの間SDO = 1 とするので、SDOをINT0 (D2) に接続しておけばマイコンのスリープ状態を解除する割込み源として用いることができる。
このスケッチは、SDOのレベル変化でスリープ状態から復帰し16ビットのキーデータを読み取ってシリアルモニタに出力する。
前回と異なる点として、TP1=0とすることで、シリアル通信時のアクティブレベルを”H”に変更した。これにより、SCLやSDOは常時Lとなり、消費電流もわずかながら抑えることができる。前回載せたシリアル通信のタイミングチャートやスケッチとは論理が逆になっていることに注意。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 |
#include <avr/sleep.h> // アクティブH、16キー、単一キー // TP1 = 0, TP2 = 0, TP3 = 1, TP4 = 1 // #define TTP_SDO 2 #define TTP_SCL 4 #define LED 13 #define READ_SDO() (PIND & B00000100) #define SET_SCL() (PORTD |= B00010000) #define RESET_SCL() (PORTD &= B11101111) #define KEY_COUNT 16 #define SDO_BIT 0x8000 void setup() { delay(100); Serial.begin(115200); Serial.println("start"); pinMode(LED, OUTPUT); pinMode(TTP_SDO, INPUT); pinMode(TTP_SCL, OUTPUT); RESET_SCL(); delay(500); } int read_key(uint16_t& ttp_data) { int key_no = 0; ttp_data = 0; for(int i = 0; i < KEY_COUNT; i++) { ttp_data >>= 1; SET_SCL(); delayMicroseconds(5); RESET_SCL(); bool f = (READ_SDO() == 0); ttp_data |= f ? SDO_BIT : 0; if (!f) key_no = i + 1; delayMicroseconds(5); } return key_no; } void isr() { } void loop() { sleep_enable(); attachInterrupt(0, isr, RISING ); set_sleep_mode(SLEEP_MODE_PWR_DOWN); sleep_mode(); detachInterrupt(0); uint16_t ttp_data = 0; int key_no = read_key(ttp_data); if (ttp_data != 0xffff) { char tmp[100]; sprintf(tmp, "%04X : %d", ~ttp_data, key_no); Serial.println(tmp); } delay(1); } |
void setup()
初期状態で、SCL (D4) == Lとしている。
int read_key(uint16_t& ttp_data)
SCLを16波分駆動(H→Lを繰り返し)して16ビットのキーデータを読む。各ビットは、SCL立下げ直後にSDOの値を読むことで得る。読み取った値が1ならばそのビットに対応するキーがタッチされたことを示す。
ttp_dataには読み取った16ビットをそのまま返し、戻り値としてはLSBから数えて何番目のビットが0だったか、つまり、どのキーが押されたのかをキーの番号で返す。
void isr()
割込みサービスルーチンだが、スリープからの復帰が目的なので関数内ではなにもしない。
void loop()
まずはスリープ状態に入る準備をする。SDO == D2 がLからHに遷移したら割込みがかかるように設定し、 sleep_mode(); によってさっさとスリープしてしまう。割込みがかかると次の行 ( detachInterrupt(0) ) に制御が戻るので割込みを解除する。
次に read_key() を呼び出して16キーの状態とタッチされたキー番号を得、ttp_dataとkey_noをシリアルモニタに出力する。ttp_dataはビット反転して表示することにしたので、タッチされたキーに対応するビットが1になる。最後にdelay(1)を置いているが、これがないとすぐにスリープに入ってしまってシリアルモニタへの出力がうまくない。
スリープと割り込みによる復帰については、Arduino Playgroundの How to let your Arduino go to sleep and wake up on an external event. を参考にした。
シリアルモニタ出力
キー1からキー16までを順にタッチしていくと、シリアルモニタには以下のように表示される。
キーを離したときのデータ(0xffff)については、表示が煩雑になるのでスケッチで抑制している。
気になるのは、 detachInterrupt(0); に要する時間だが、実測で900μsec前後だった。Tw (DVパルスの終了からSCL駆動開始までの時間)の最大値は規定されていないが、あまり長いとTout (2msec. typ.) に達してしまってデータが受信できない可能性があるように思える。しかしながら、上記スケッチの50行目 (detachInterrupt() の次) に、delay(4); とか入れてもタッチしたキーのデータは得られるので、SCLを駆動しなければ待たせても問題ないのかもしれない(SCLの最初のエッジでTTP229の内部状態が遷移するのかも)。
スケッチ2(複数キー動作)
TP1~TP4にジャンパピンをセットすることで、アクティブH、16キー、全キー複数キー動作とし、各キーのタッチ/リリースのタイミングでキーコードを出力させてみる。
複数キー動作とは、あるキーをタッチしたまま次のキーをタッチしたとき、新たなタッチによってデータが出力される動作で、タッチ中のキーすべてがシリアルデータに反映される。
タッチ中のキーが離されたときにも、やはりその時点でタッチ中のキーを反映したシリアルデータが出力される。したがって、前回の状態と比べてみればタッチされたキーやリリースされたキーが分かる。
キーのタッチ状態を示すLEDを追加して動作を確認することにした。
スケッチ2用の回路
いままでの回路に8つのLEDと抵抗を追加した。ミニブレッドボードでは小さかったので、ふつうのブレッドボードに積み替えた。実際の構成は動画を参照。
Arduino PRO MINIのD10~D17につないだ8つのLEDを、キータッチモジュールのキー5~キー12の状態に対応させる。各ポートを出力に設定し、1を与えれば点灯、0を与えれば消灯する。LEDを16個並べるのは面倒になったので8つにしました。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 |
#include <avr/sleep.h> // アクティブH、16キー、複数キー、 // TP1 = 0, TP2 = 0, TP3 = 0, TP4 = 0 // D10~D17にキーオン時に点灯するLEDを配置。 #define TTP_SDO 2 #define TTP_SCL 4 #define LED 13 #define READ_SDO() (PIND & B00000100) #define SET_SCL() (PORTD |= B00010000) #define RESET_SCL() (PORTD &= B11101111) #define KEY_COUNT 16 #define SDO_BIT 0x8000 void setup() { delay(100); Serial.begin(115200); Serial.println("start"); pinMode(TTP_SDO, INPUT); pinMode(TTP_SCL, OUTPUT); for(int i = 10; i <= 17; i++) { pinMode(i, OUTPUT); digitalWrite(i, 0); } RESET_SCL(); delay(500); } int read_key(uint16_t& ttp_data) { int key_no = 0; ttp_data = 0; for(int i = 0; i < KEY_COUNT; i++) { ttp_data >>= 1; SET_SCL(); delayMicroseconds(5); RESET_SCL(); bool f = (READ_SDO() == 0); ttp_data |= f ? SDO_BIT : 0; if (!f) key_no = i + 1; delayMicroseconds(5); } return key_no; } // KEY5~8 → D10~D13 (PB2~PB5) // KEY9~12 → D14~D17 (PC0~PC3) void key_led(uint16_t onkey) { PORTB = ((onkey & 0x00f0) >> 2) | (PINB & 0xc3); PORTC = ((onkey & 0x0f00) >> 8) | (PINC & 0xf0); } void isr() { } static uint16_t last_ttp_data = 0xffff; void loop() { sleep_enable(); attachInterrupt(0, isr, RISING ); set_sleep_mode(SLEEP_MODE_PWR_DOWN); sleep_mode(); detachInterrupt(0); uint16_t ttp_data = 0; read_key(ttp_data); uint16_t changed = ttp_data ^ last_ttp_data; uint16_t now = ~ttp_data; if (changed != 0) { uint16_t mask = 1; bool make = false; byte keyno = 0; for(uint16_t i = 0; i < 16; i++) { if (changed & mask) { keyno = i + 1; keyno |= (now & mask) ? 0 : 0x80; } mask <<= 1; if (keyno) { char tmp[100]; sprintf(tmp, "%04X : %d : %d", ~ttp_data, (keyno & 0x80 ? 1 : 0), keyno & 0x7f); Serial.println(tmp); keyno = 0; last_ttp_data = ttp_data; } } key_led(now); } delay(5); } |
void setup()
スケッチ1とほとんど変わらないが、D10~17の初期設定を追加した。
int read_key(uint16_t& ttp_data)
スケッチ1と同じ。ただ、今回は関数の戻り値は使えない。
void key_led(uint16_t onkey)
引数onkeyには、現在タッチ中のキーに対応するビットが1になった16ビットの値を与える(LSBがキー1、MSBがキー16)。D10~D13がポートBのPB2~PB5、D14~D17がポートCのPC0~PC3に対応するので、直接レジスタを操作することにしてLEDを点灯させる。
void loop()
スリープモードや割込み設定についてはスケッチ1と同様。
今回のスケッチでは、一つ前のキーデータ(last_ttp_data) と新たに得たキーデータ(ttp_data)のXORを得ることで、状態が変化したキーを抽出する。そして、一つでも変化があった場合には、それがオフからオンなのか、あるいは、オンからオフなのかを調べて、全キーの状態と共にキー番号をシリアル出力する。シリアル出力の際には変化の内容 (オン→オフなら1、オフ→オンなら0)も出力している。
最後に、ビット反転したttp_dataを使ってkey_led()を呼び出し、現在オンになっているキー(キー5~キー12に限られるが)を8つのLEDに反映している。
動作状況
シリアルモニタにはこんな具合に表示される(和文の説明はあとで追加)。
1 2 3 4 5 6 7 8 |
0001 : 0 : 1 キー1をタッチ 0003 : 0 : 2 キー2をタッチ 0007 : 0 : 3 キー3をタッチ 000F : 0 : 4 キー4をタッチ 0007 : 1 : 4 キー4をリリース 0003 : 1 : 3 キー3をリリース 0001 : 1 : 2 キー2をリリース 0000 : 1 : 1 キー1をリリース |
左端がオンになっているキー(~ttp_data)、中央がオン/オフフラグ(1 = オフ)、三番目が変化したキー番号である。
今回はLEDがチカチカしてくれるので、動画にした。
連続的なキータッチや、指を滑らせるようなことをしても素早く、正しく応答してくれているように見える。
製品の個体差なのかもしれないが、キー5とキー6を同時に押すと、キー7もオンになってしまうことが多かった。動画を見てもわかるように指の大きさに比べて各キーのタッチエリアが狭いので、実は余計なところを触っているのかもしれないが。
きょうのまとめ
キーのスキャン部分はTTP229に任せ、PC用のキーボードのようにスキャンコードをMAKE/BREAK付で出力するようなスケッチにしてみた。ロールオーバー動作も可能だが、このタッチパッドの大きさからして、単一キー動作の方が押し間違えが少ないような気がする。16個のキーを4つグループに分けて、4キーとして扱った方がいいのかもしれない。
安いテスターを電源側とArduino PRO MINI間にはさんで電流値を読んだところ、キーをタッチしていない間は3.3mA程度流れていたが、TTP229モジュールを外すと0.3mA以下になり、この値は以前測ったスリープ時の電流とほぼ同様だった。TTP229モジュールを接続した状態でキーをタッチすると、点灯中のLED数に応じて電流が増加し、すべて離すと元の3.3mA程度に戻る。
キータッチモジュールは、仕様からしてほとんどスリープしているはずの定常時に3mA程度消費するようだが、キータッチモジュール上には、1KΩの直列抵抗を介して常時点灯の明るいLEDが付いている。読み取り値の3mAという値はほとんどこのLEDによるものだろう。(もうちょっと正確に電流を測りたいとは思っているのだけど)。
キータッチモジュールからLEDを外してしまえば、スタンバイ電流が300μA程度の、キータッチによって復帰して動作が可能な赤外線リモコンなんかもすぐに作れることになる。どうせなら3.3V動作のPRO MINIを使いたいところだが、8MHz動作で果たしてうまくリモコン信号を出せるかな、というのが現在の懸念事項になる。