前回入手したTFTシールド(下図)には、4線抵抗方式タッチパネルが付いています。今回はタッチパネルを読み取って、タッチ座標の校正と筆圧連動の図形描画を試してみたいと思います。
タッチ座標と筆圧を読み取るのに必要となるので、この方式の動作原理をまとめておきます。
この方式は抵抗分圧を使って、タッチした位置と筆圧を読み取るのが基本原理となっています。下図は「#2-9 抵抗値を測定する」にて使った未知抵抗値Rxの測定回路ですが、この図を使って説明します。
Vccから抵抗R0とRxを通ってGNDに接続されています。この時のA1点の電圧は、オームの法則を使って以下の様に求めることができます。
A1=Rx/(R0+Rx)×(Vcc−GND)
これが抵抗分圧の考え方です。
タッチパネルの場合、2枚の透明導電膜(X平面とY平面)が接触しないように、間に絶縁体のスペーサが挿入された状態で貼り合わされています。指などでタッチすると、その圧力で2枚の透明導電膜同士が接触して、間に電流が流れるようになります。
この時、タッチした点を中心に、X平面、Y平面の両端に取り付けた電極X+,X−,Y+,Y−との間に、抵抗が分割されます(下図)。
各電極間に印可する電圧と、別な電極から読み取った電圧値を使って、タッチ座標と筆圧を以下の様にして読み取ることができます。なお、ここでは、電圧読み取り用の電極を緑文字で示したX−,Y+に設定します。
以上が原理となります。
特に、ステップ4)で筆圧を求める作業が煩雑で難しいのですが、タッチ有無とタッチ座標だけを知りたいケースでは、ステップ1)〜3)の処理だけ実行すれば良いので、一気にハードルは下がります。
ステップ4)では各電極間の抵抗値Rx1+Rx2およびRy1+Ry2をあらかじめ計測しておかなくてはならないことも、実アプリケーションを作る上ではハードルとなるでしょう。次節の実装でもう少しアイディアを絞ってみたいと思います。
前節では動作原理を、理想的な場合について解説しました。しかし実際の回路では、例えばx座標の読み取りですらx=0V〜Vccになることは無くて、恐らくx=数100mV〜(Vcc−数100mV)の範囲でしか読み出すことができないはずです。
同様に「タッチ無し」の判定も、z1−z2=Vccか否かで判定しようとすると、ほぼすべてのケースで「タッチ有り」の判定となってしまうはずです。∵z1=(Vcc−数100mV),z2=数100mVが読み出されて、z1−z2=(Vcc−2×数100mV)となるためです。
※理想的な場合の様な、0VやVccが測定できない理由は、回路に付いた既成抵抗値やAD変換器の変換誤差、ノイズによる影響、等々です。
本節では、実際にC言語のプログラムに落としていく作業を考察して行きます。
電圧値z1およびz2を、ArduinoのanalogRead()関数を用いて読み取ります。よって、z1,z2=0〜1,023[LSB]です。
原理上、z1−z2≧0であり、z1−z2<0となることはありません。
タッチ有りの場合、ある程度接触抵抗Rzが小さければ、z1≒z2ですので、測定値はz1−z2=0付近からブロードに分布するはずです。
タッチ無しの場合、z1=Vcc、z2=GNDレベルが読み出されるので、z1−z2=1,023付近に集中します。
ただしanalogRead()関数は、読み取りに約104μsかかり、かつz1、z2を同時に測定することができません。AD変換器の変換精度やノイズによる影響も加わって、z1−z2=−1,023〜1,023まで、全ての値が出現する可能性があると考えた上で、以下図の様に閾値THSHを用いて判定します。
z1−z2<0のケースはエラーですので、エラーステータスを返します。エラーステータスが返った時にはもう一度読み出す(リトライ)か、もしくはエラーの要因を取り除く必要があります。
// touch
pinMode (pin_XM, INPUT);
pinMode (pin_YP, INPUT);
digitalWrite (pin_XP, HIGH);
pinMode (pin_XP, OUTPUT);
digitalWrite (pin_YM, LOW);
pinMode (pin_YM, OUTPUT);
tz1=analogRead (ana_XM);
tz2=analogRead (ana_YP);
tzi=tz1 - tz2;
if (tzi<0)
{
// ERROR:
return (ErrTouch);
}
else if (tzi>=tft_THSH)
{
// no touch
return (MAX_INT16);
}
後ほど、実回路に付いた既成抵抗成分やAD変換器の変換精度、ノイズによる影響などを除外できる様に、THSHの最適値を実験で決定したいと思います。
x,y座標の読み取りにも、analogRead()関数を使います。従って、x,y=0〜1,023[LSB]です。この0〜1,023が、x,y座標の0〜239ドット(または319ドット)に対応するのですが、こちらも、実回路に付いた既成抵抗成分などの影響で、x,y=min〜max[LSB]の範囲に限定されて読み出されます(0〜1,023にフルスイングしない)。
min,max値は、以下の様な要因でばらつき、一定ではありません。経時変化もあります。
よって、各々の使用状態に合わせて校正(キャリブレーション)して使用してください。
校正は以下の様に行います。
画面の対角線上にある、左上ターゲット(青●)と、右下ターゲット(オレンジ●)の中心点をタッチしてもらい、その時のx,yデータを読み取ります。読み取ったx,yデータから各々min値とmax値を算出して校正します。
一度校正した値は、定数としてプログラムに埋め込んでおくと、毎回電源を投入する度に校正し直さなくて済むので便利です。一方で、経時変化もあるため、プログラム中に埋め込む定数だけでは、将来ペン先とタッチ位置がずれて使えなくなってしまう場合があるので、動的に校正できる仕組みを入れておく必要があるでしょう。
実際のアプリケーションでは、動的に校正したデータをEEPROMの特定アドレスに書き込んでおき、起動時に校正データをEEPROMから読み出すという使い方にするのが良いと思います。
尚、ATmega328 AD変換器の特性なのか、何なのか、普通にanalogRead()したデータを使ってしまうと、ペンでタッチしたまま動かさないようにしているのに、座標がブレて、描画される点が広がってしまう現象が見られました。
前回動かしたサンプルコードのライブラリTouchScreen.cppでは、連続して2回analogRead()して結果が一致しないとエラー扱いになっていました。同様なコードにしたところ、ブレが治まったようです(↓)。
// position x
pinMode (pin_YM, INPUT);
pinMode (pin_YP, INPUT);
digitalWrite (pin_XP, HIGH);
pinMode (pin_XP, OUTPUT);
digitalWrite (pin_XM, LOW);
pinMode (pin_XM, OUTPUT);
(*tx)=analogRead (ana_YP);
if ((*tx)!=analogRead (ana_YP))
{
// ERROR:
return (ErrTouch);
}
理由は良く解りませんm(1__)m。
原理の解説で示した筆圧の計算式をおさらいしておきます。
原理上、「筆圧z=接触抵抗Rzである」と定義しているので、接触抵抗の絶対値[Ω]を求めています。
しかし実は、相対的な筆圧がわかれば良いので絶対値[Ω]は必要なく、定数部分を無視して以下の様に考えることが可能です。
ただしこの場合、困ることがあります。z2≠0の場合のzと、z2=0の場合のzは基準が異なる相対値なので、同じ土俵で大きさを比べることが出来ないのです。もし、z(z2≠0)とz(z2=0)の値を比較したいなら、やっぱり定数部分を無視することは出来なくて、Rzの絶対値[Ω]を求めなければならないことになります。
ぐる、ぐる、ぐる、……、ぐるっと回って元に戻ってしまいました。
ここで、実回路での測定値について考察を広げてみます。前の節で論じた様に、実回路で使う場合には既成抵抗成分などの影響により、本来0と読み出せるケースでも0LSBが読み出せず、1〜数LSBが読み出されてしまうケースがほとんどです。
z2も例外ではなく、確かに原理上z2=0が読み出されるケースはあるのですが、実際の回路ではz2=0になることはまずなくて、z2=1〜数[LSB]が読み出せるはずです。もし、理想的にz2=0が読み出せた場合があったとして、それを無理やり「z2=1だ」と、読み替えたとして、どれほどの誤差が出るでしょうか?
そう、z2→0の場合にz=1として近似計算を行うことで、以下の様にシンプルな方法で筆圧zを求めることができます。
なんと、Rzの絶対値[Ω]の計算を捨てたことで、z2=0の場合分けが不要になったばかりか電極間の抵抗値Rx1+Rx2およびRy1+Ry2を実測する必要もなくなってしまいました。一石二鳥です。C言語の実装では、以下の様になるでしょう。
z=y*(z1 - z2)/((z2==0)? 1: z2);
まじめに実行しようとすると複雑で煩雑だった筆圧の計算が、スッキリ簡単になりました(1^−^)👌。
計算したzは、接触抵抗Rz[Ω]ではなく相対筆圧zとなります。筆圧大でz→小、筆圧小でz→大となる相対値ですので、観測されるz値の範囲min〜maxを判別して、筆圧処理を実行しなければなりません。タッチパネルの校正を実施する時に、座標だけではなく筆圧についても校正をかける必要がある様です。
今回のアプリケーションは、「筆圧連動のメモパッド」です。と、言っても、独立したアプリケーションまでは昇華せずに、実験用プログラムに留めておきます。以下、実験用筆圧連動メモパッドの目標仕様です。
完成したプログラムをリンクしておきます。
完成プログラム:tft_touch.ino
TFTシールドを装着したUNOにコンパイル&書き込んで使います。
実験サンプル:【 】内はシリアルモニタからの送信データ
【cali】
OK+ press white mark on the tft screen
OK+ (1/3) press
OK+ (2/3) press
OK+ touch several times
OK+ (3/3) touch
OK+ (157, 110)-(794, 779) (765, 2820)
OK: calib: 108 73 735 743 765 2055
タッチパネルの校正コマンドを実行しました。目標仕様の【cali】コマンドの項目にて説明した要領で校正してください。最後に表示された6個の数値が定数tft_CALI_*に設定すべき校正値となります。内部処理としては、表示された設定値が適用されていますので、そのまま使い続けることが可能です。
ここで、閾値THSHの値について実験してみました。最初、10ビットAD変換器の測定範囲0〜1,023[LSB]の中央付近である、THSH=512LSBを設定して実験したのですが、これでは画面の右上隅(直角三角形領域)のタッチが認識されませんでした(THSHを超えてしまって、タッチ無しと判定された)。
THSH=550,600,650,……と大きくしていったところ、段々認識されない範囲が狭まって行き、ようやくTHSH=750LSBで消滅したので、現在のプログラムではTHSH=768LSBに設定してあります。
タッチ無し時のz1−z2をシリアルモニタに表示して確かめたところ850LSB程度でしたので、結構シビアな判定値となっています。∵手元のTFTシールドでは、750<THSH<850である必要があったため(100LSB≒0.5V @Vcc=5V)。
※個体差があるかもしれないので、うまく動かなかった場合は、この辺りの情報を参考にデバッグして見てください。
【rgb,32,32,32】
OK: rgb(32,32,32)=0x8410
【init,8410】
OK: initialize done
背景をグレイにして初期化しました。
【rgb,63,32,8】
OK: rgb(63,32,8)=0x241f
ペンの色を橙色に設定しました。
実際にタッチパネルを校正して、筆圧連動で描画しているところを動画に落としておきました(↓)。校正の様子と筆圧連動図形描画の様子をご覧いただけます。
ちょっと、接写した動画(↑)では、微妙過ぎてわかり難いですね……(1x_x)、伝わりにくいのが残念です。ぜひ、ご自身のお手元でご確認ください(UNOと4線抵抗方式タッチパネル付きTFT液晶が必要ですが……)。
4線抵抗方式タッチパネルでは、スマホ(静電容量方式)みたいなマルチタッチ操作ができない代わりに、筆圧の検知が可能なんですね。あと、抵抗方式の方は静電容量方式に比べて、パネルをしっかり強く押さないと検知してくれない傾向があるので、指の腹でタッチするより爪の先でタッチした方が反応が良くなり、タッチ座標も正確にコントロールできるようになります。なので、ペン先が丸いプラスティック製のタッチペンを使うことが多いです(上の動画もタッチペンを使っています)。