1. はじめに
このドキュメントでは、抵抗膜式のタッチスクリーンをマイコンから制御する方法を解説するものです。
タッチスクリーンにはいろんな種類があるのですが、このドキュメントで使う技術は抵抗膜式向けに限っています。タッチスクリーンの種類などに興味のある方は、Wikipedia のタッチパネルの項を参照してみてください。
マイコンはAVRを前提としていますが、もろもろのセットアップがシンプルなほうが制御内容に集中できるのでこのドキュメントではプラットフォームを Arduino としています。
2. まずは簡単にタッチスクリーンの原理
タッチスクリーンは、指やペンで押したところを検出できる板ですが、ここで抵抗膜式タッチスクリーンがどういうわけで位置を検出できるのかを解説します。
タッチスクリーンは、横から見ると以下のような構造をしています。
ようは、二枚の抵抗膜が二枚向かい合っていて、間を浮かしてあります。おのおのの膜の両端には電極がついています。これを指やペンで押すと
このように上下の膜が接触するわけですが、その位置から決まる電極からの抵抗値によって位置を割り出します。電極は、下図のようについています。
つまり、X軸用の膜は、左右両端に電極がついており、Y軸用の膜は、上下両端に電極がついています。このパネルの一箇所を押すと、上下の膜が接触するわけですが、それを下図のように読み取ります。
左図では、X軸方向にパネル膜に電圧をかけ、接触した位置の分圧をY膜経由で拾って読み取ります。読み取った電圧からX軸上の位置がわかります。
右図では、Y軸方向にパネル膜に電圧をかけ、接触した位置の分圧をX膜経由で拾って読み取ります。読み取った電圧からY軸上の位置がわかります。
左右の測定を交互に繰り返すことにより、タッチパネルが押された座標を読み取ります。
3. X, Y軸の読み取り
それでは早速タッチスクリーンを読み取ってみましょう。抵抗膜式のタッチスクリーンは、上図のとおり、4つの電極を持っています。この記事の例で使ったタッチパネルは、左から X+, Y+, X-, Y- の順番で電極が並んでいました。
これを Arduino に接続した回路図は以下のとおりです。回路図にちゃんと書いてなくてすみませんが analog 入力の 2, 3, 4, 5 ポートに、それぞれ X+, Y+, X-, Y- がつながります。上から何かデジタルアウトが出てますが動作確認のためのものなので今回のお話とは関係ありません。気にしないでください。
3.1 やり方
タッチスクリーンについないだ4本のアナログピンの役割と出力値を変えることによって、X軸、Y軸の接続状態を制御し、測定を行います。つまり、先ほどの図Aと図Bの回路への組み換えは、アナログピンのロジックと役割を切り替えることによって行うことができます。プログラミングだけで回路が組み変わるなんて、便利ですねー。ではどうやってやるかというと...
の、前に、測定前の初期状態について説明します。
タッチスクリーンは、内部抵抗がたかだか数百Ω、小型のスクリーンにいたっては、50Ω程度しか内部抵抗がありません。これを常時通電状態にすることは、電源に負担を与え、系を不安定にするので、測定に必要な最低限の時間だけスクリーンに電流を流すことにします。つまり、タッチスクリーンは、通常は非測定状態にあって、測定をするときだけ測定状態に遷移し、測定が終了したら速やかに非測定状態に戻ります。
非測定状態:
- X+, Y+, X-, Y- 全てのピンが OUTPUT モード
- X+, Y+, X-, Y- 全てのピンが LOW 出力
と、こう書くとなにかいかめしいですが、ようは全電極をアースに落としているだけです。
X軸測定状態:
X軸を測定する状態へは、以下の手順で移行します。
- X+ を HIGH 出力にする
- X+ 端子が立ち上がりきるまでの短時間待つ
- Y+, Y- を INPUTモードに切り替える
- Y+ 端子に接触点からの分圧が反映されるまでの短時間待つ
- Y+ 端子を測定可の状態への移行完了
測定が終わったら速やかに非測定状態に戻ります。
Y軸測定状態:
Y軸を測定する状態へは、以下の手順で移行します。
- Y+ を HIGH 出力にする
- Y+ 端子が立ち上がりきるまでの短時間待つ
- X+, X- を INPUTモードに切り替える
- X+ 端子に接触点からの分圧が反映されるまでの短時間待つ
- X+ 端子を測定可の状態への移行完了
測定が終わったら速やかに非測定状態に戻ります。
3.2 プログラム
以上の動きを Arduino スケッチで実装すると、例えば以下のようになります。
// Assign analog pins to the touch screen terminals
//
#define xPositive_A 2
#define yPositive_A 3
#define xNegative_A 4
#define yNegative_A 5
// We also digitalWrite on above pins
//
#define xPositive_D A2
#define yPositive_D A3
#define xNegative_D A4
#define yNegative_D A5
void setup()
{
ts_Reset();
}
void loop()
{
int xSensorValue, ySensorValue;
delay(100); // sleep
xSensorValue = ts_ReadXPosition(); // eat
delay(4); // sleep
ySensorValue = ts_ReadYPosition(); // eat
// ここには本当は xSensorValue, ySensorValue を使った処理が入るはず
}
/**
* Set the touch screen to non-measuring state
*/
void ts_Reset()
{
// change all pins to output mode
pinMode( xPositive_D, OUTPUT );
pinMode( xNegative_D, OUTPUT );
pinMode( yPositive_D, OUTPUT );
pinMode( yNegative_D, OUTPUT );
// set low to all pins
digitalWrite( xPositive_D, LOW );
digitalWrite( xNegative_D, LOW );
digitalWrite( yPositive_D, LOW );
digitalWrite( yNegative_D, LOW );
}
/**
* Shift the touch screen reading state, followed by measurement.
* This routine sets the state back to non-measuring at the final step.
*/
void ts_ReadPosition(uint8_t resist_Positive_D, uint8_t resist_Negative_D,
uint8_t probe_Positive_D, uint8_t probe_Negative_D,
uint8_t probe1_A, uint8_t probe2_A,
int* value1, int* value2)
{
digitalWrite( resist_Positive_D, HIGH ); // Set the positive side of resistor terminals.
delayMicroseconds(100); // Wait a while for onset of the terminal
pinMode(probe_Positive_D, INPUT); // Change probe terminals to input mode
pinMode(probe_Negative_D, INPUT);
delayMicroseconds(100); // Wait a while for the probe terminal onsets at the measuring voltage
*value1 = analogRead( probe1_A ); // OK, here we go
// this is optional read for Z-axis // We may sometimes want to read at opposite side of the probes as well.
if (value2 != NULL) {
*value2 = analogRead( probe2_A );
}
ts_Reset(); // The measurement finished. Getting back to normal state.
}
/**
* Read X Position
*/
int ts_ReadXPosition()
{
int value;
ts_ReadPosition( xPositive_D, xNegative_D,
yPositive_D, yNegative_D,
yPositive_A, 0,
&value, NULL );
return value;
}
/**
* Read Y Position
*/
int ts_ReadYPosition()
{
int value;
ts_ReadPosition( yPositive_D, yNegative_D,
xPositive_D, xNegative_D,
xPositive_A, 0,
&value, NULL );
return value;
}
C3.3 注意点
先ほどのプログラムにあるように、測定状態への遷移は、以下の順序で行われます。
digitalWrite( resist_Positive_D, HIGH ); // 抵抗側の +端子を立ち上げる
delayMicroseconds(100); // 端子が立ち上がるのを待つ
pinMode(probe_Positive_D, INPUT); // +測定端子を INPUT モードに切り替え
pinMode(probe_Negative_D, INPUT); // -測定端子を INPUT モードに切り替え
delayMicroseconds(100); // 測定端子の電圧が安定するのを待つ
*value1 = analogRead( probe1_A ); // OK, here we go
Cこれを、例えば以下のような順序に変えてしまうと、あまり具合がよくありません。
pinMode(probe_Positive_D, INPUT); // +測定端子を INPUT モードに切り替え
pinMode(probe_Negative_D, INPUT); // -測定端子を INPUT モードに切り替え
digitalWrite( resist_Positive_D, HIGH ); // 抵抗側の +端子を立ち上げる
delayMicroseconds(100); // 測定端子の電圧が安定するのを待ちたい、が...
*value1 = analogRead( probe1_A ); // まだ安定してないよ
Cタッチパネルは、大きな抵抗膜が二枚並行している構造をしていますから、コンデンサとしても振舞います。つまり、片側の端子の電圧を急激に動かすと、信号は反対側の端子に飛び移ります。
二番目のリストのような順序で状態を遷移すると、抵抗側+の端子を立ち上げるときに、測定側端子はすでにINPUTモードに切り替わっています。INPUTモードでは、測定端子はハイインピーダンス状態になっていますから、この状態で抵抗側+の立ち上げ信号が飛び移ってくると、ノイズを受けてしまいます。しかもそのノイズはなかなかきえません。したがって、100マイクロ秒後になってもプローブ電圧はまだ落ち着いておらず正確な測定ができません。
最初のリストでは、抵抗側端子の立ち上げ時には、プローブ端子はまだグランドされていますから、受けたノイズは速やかにアースに落とされます。したがって測定端子にノイズが残留することなく、すばやく測定状態に切り替わることができます。
4. 圧力の読み取り
実は、抵抗膜式のタッチパネルは、感圧することもできます。このサイトでタッチパネルといえば、当然楽器用途を意識しているので、感圧できることは大きな魅力です。
ということでやってみました。
4.1 やり方
いくつかやり方があるのですが、ここでは、シンプルで実装しやすい方法を紹介します(というか他の方法はまだ試していません)
やり方は、TSC2046 のデータシートを参考にしました。上の図のように、抵抗膜と抵抗膜の間の接触抵抗 Touch を測定します。
Touch の値を知るには、三箇所の測定が必要です。
- X+ から X- に電圧をかけ、Y+ で測定をする(つまり普通のX軸測定) X-Position
- Y+ から X- に電圧をかけ、X+で測定をする .. Z1-Position
- Y+ から X- に電圧をかけ、Y- で測定をする .. Z2-Position
これらの三点の測定結果から、抵抗値 Touch を以下のように計算できます。
R_touch = Rx-plate * (X-Position / X-Position_max) / ( Z2/Z1 – 1)
ここで Rx-plate は、X軸の抵抗膜の両端の抵抗値、X-Position_max は、X軸の測定値の最大値です。
ここで得られる値は、抵抗値で、しかも圧力に反して値が小さくなってゆくわけですから、この値のままでは、圧力として使うには少し使いづらいです。そこで、以下のようなアッテネータ回路を考えます。
R2は、適当に選んだ抵抗値です。このようにすると、圧力が最小の時に出力はほぼゼロで、圧力が上がるにつれ出力が上がってゆきます。
こういった回路を実際に組む必要は全くなく計算によるシミュレーションで十分です。この回路の出力をまずは感圧出力として得ます。
感圧のカーブはこのままでは最適ではないかもしれません。アプリケーションにあわせてカーブは変形しないといけないかもしれません。
4.2 プログラム
先ほどのプログラムを拡張することで実装できます。追加分の関数だけ記述します。
抵抗値の計算は割り算を使うため精度が必要です。float の演算を使っています。float 演算に対応できないプロセッサを使う場合は注意してください。
Arduino 環境では大丈夫でした。
出力値のスケールは上の数式に忠実というわけではなく、適当に端折って計算してから、使いやすいようにスケールを直しています。
int ts_ReadZPosition()
{
int pos_X, pos_Z1, pos_Z2;
float value;
pos_X = ts_ReadXPosition();
if (pos_X < 10) {
return 0;
}
ts_ReadPosition( yPositive_D, xNegative_D,
xPositive_D, yNegative_D,
xPositive_A, yNegative_A,
&pos_Z1, &pos_Z2 );
value = pos_Z2;
value /= pos_Z1;
value -= 1;
value *= pos_X;
#define R2 100.0
value += R2;
value = 1.0 / value;
value *= R2 * 4096;
return value;
}
C