今回のテーマは、DUE+TFTシールドを使ってタッチパネルのセンシングを行う事です。
UNO+TFTシールドでは、「#10-10 タッチパネル(座標・筆圧)」にて、筆圧連動メモパッドを実装済みです。
TFTシールドのタッチパネルは4線抵抗方式でした。マルチタッチができない代わりに、筆圧を検知することが可能です。
UNOで作ったプログラム(tft_touch.ino)は、タッチパネルのセンシングをコアライブラリ(※)で実装したので、DUEでも基本的にはそのまま動作させることが可能です(以下、一部抜粋)。
※コアライブラリ:本HPでは、Arduino IDEプログラミングに於いて、デフォルトで使用可能なdigitalWrite()関数、analogRead()関数などの収められたライブラリの事を指します。
// 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);
}
この辺は、Arduino環境のありがたさを感じられる良いところですね。
プログラム的にはそのまま流用可能なのですが、UNOとDUEの違いと言えば、analogRead()関数から制御されているAD変換器が全く異なるユニットなので、その差分をしっかりと押さえておきたいと思います。
DUEにLogiscopeを移植した時には、AD変換器のレジスタを直接叩いて制御したのですが、今回はコアライブラリのanalogRead()関数にフォーカスを当てます。一番気になっているのは、変換時間です。AD変換時間は、恐らく今後のアプリケーションでanalogRead()関数を使う時、一々利いて来るでしょうから。
次のようなコードをDUEで実行して、色々な条件でanalogRead()関数の実行時間を実測してみました。
// adc conversion speed measurement
#define ADC_1CH
//#define ADC_4CH
//#define ADC_1RG
//#define ADC_MCK_4
//#define ADC_12BIT
//#define ADC_SLEEP
int idx;
int16 anlg;
uint32 usec_sta;
uint32 usec_end;
uint32 msec_sta;
uint32 msec_end;
const int NN=100000;
anlg=analogRead (0); // 1st conversion
#ifdef ADC_MCK_4
REG_ADC_MR=(REG_ADC_MR&(~ADC_MR_PRESCAL_Msk)) | ADC_MR_PRESCAL (1);
#endif // ADC_MCK_4
#ifdef ADC_12BIT
REG_ADC_MR=(REG_ADC_MR&(~ADC_MR_LOWRES));
#endif // ADC_12BIT
#ifdef ADC_SLEEP
REG_ADC_MR=(REG_ADC_MR | ADC_MR_SLEEP);
#endif // ADC_SLEEP
anlg=anlg;
usec_sta=micros ();
for (idx=0; idx<NN; idx++)
{
#ifdef ADC_1CH
anlg=analogRead (0);
#endif // ADC_1CH
#ifdef ADC_4CH
anlg=analogRead (idx&0x03);
#endif // ADC_4CH
#ifdef ADC_1RG
// start adc
REG_ADC_CR=ADC_CR_START;
// polling until ADC_ISR_DRDY==1
while (!(REG_ADC_ISR&ADC_ISR_DRDY))
;
anlg=REG_ADC_LCDR;
#endif // ADC_1RG
}
usec_end=micros ();
sprintf (lbuf,"DBG: %ld[ns]/conversion",
((usec_end - usec_sta)*1000 + NN/2)/NN);
Serial.println (lbuf);
だらだら長いコードですみません。単に、analogRead()関数をNN回繰り返し実行してトータルの時間を測り、最後にNNで除すことで1回当たりの実行時間を出しています。AD変換器の変換時間だけでなく、計測に使ったループやanalogRead()関数内の色々な処理(オーバヘッド)分も含んだ、実効的な実行時間です。
以下の#defineで場合分けして、様々な条件で計測できるようになっています。
測定した変換時間を以下表に示します。
mode: | normal | SLEEP | |||
bit: | 10ビット | 12ビット | 10ビット | ||
clock: | MCK/6 | MCK/4 | MCK/6 | MCK/4 | MCK/6 |
ADC_1CH | 4.167μs | 3.704μs | 4.193μs | 3.669μs | 40.853μs |
ADC_4CH | 4.873μs | 3.992μs | 4.968μs | 4.132μs | (未測定) |
ADC_1RG | 1.964μs | 1.370μs | 2.000μs | 1.513μs | (未測定) |
この測定はそもそもUNOとの比較が目的だったので、UNOとの比較をしてみましょう。UNOのanalogRead()関数は、約104μsを要します。UNOのコアライブラリは、AD変換の精度向上に重きを置いていたようで、だいぶ遅いです。
それに比べると、DUEのanalogRead()関数は4〜5[μs]と言う事でかなり速いので、処理速度が問題になることは少ないでしょう。レジスタを直接叩いても、1.5〜2[μs]程度までしか向上しないので、まあ、よほどのことが無い限り、DUEではanalogRead()関数を使っておいて良い様です。
※UNOのAD変換器も、レジスタを直接叩いて変換クロックなどを最適化すると、最速で変換時間1μsまで高速化できましたので、こちらもDUEと「どっこいどっこい」と言ったところですね。この様に、ロジックの性能(16MHz→84MHz)は上がっても、アナログの性能はほとんど変わらないのが一般的です。
SLEEPモードに設定した時の変換時間約40μsも、UNOのデフォルト変換時間約104μsよりは高速です。そう思うと、消費電力を下げたい場面ではSLEEPモードを積極的に使っても良い様に思います。電気的特性の規定によると、通常9mA(MAX)のAD変換電流値が、SLEEPモードで1μA(MAX)になるみたいです。
ここまで、ほぼ、UNOで作成したコードのままでしたので、少し筆圧連動メモパッドの機能を拡張しておこうと思います。
筆圧に応じた描画モードの設定ができるように、シリアルモニタから受け付けるコマンドに【mod】コマンドを追加します。以下、拡張機能の仕様です。
シリアルモニタから受け付ける拡張コマンド仕様:
モード1だとブレンドするので、高い筆圧で描くとほぼ描画色pix_drawの線が描かれます。一方、モード2だと、現在の色にどんどん加算して行くので、色々な色を重ねて行くと最後は白になります(加法混色)。絵の具で色を重ねて行くイメージだと減法混色の方がピッタリマッチしますが、必要なら、皆様の手で拡張してください。
※このモード、UNOでもそのまま使用可能となっていますが、筆者はUNOでは試してみていないので注意してください。UNO @16MHzだと、ちょっと処理量が多い気がします。……遅くて使い物にならないかも……?
特定座標(ix,iy)の現在表示色を読み取るためのtft_getpix()関数は、「#11-3 ゲームで遊ぼう」で作成した物を流用しました。UNO版プログラムでは、AVRのインラインアセンブラで高速化してありましたが、DUEではCコードのままで十分だと思います。
読み取ったピクセル値からRGB値への変換は、以下の様なコードとなります。TFTシールドのRGB値はR:G:B=5:6:5ビットで、ピクセル内の並び順は上位側からB→G→Rの、変則的な値なのでちょっと癖があります。
// pixel to rgb
pix=tft_getpix (ix,iy);
ir=(pix<<1)&0x3e;
ig=(pix>>5)&0x3f;
ib=(pix>>10)&0x3e;
ir += (ir&0x20)? 1: 0;
ib += (ib&0x20)? 1: 0;
最終的に、ir,ig,ibには0〜63の数値が格納されます。
最後の2行はオプションです。厳密に計算する場合は必要です。速度を優先する場合、端折っても大きな誤差にはなりません。その場合、ir,ibのみ、0〜62の偶数値のみを取ります。
DUEに対応して機能拡張したプログラムをリンクしておきます。
DUE対応版プログラム:tft_touch.ino
いつもの様に、オリジナルを差し替えてあるので、どこからダウンロードしても同じ最新バージョンです。使い方については、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+ (210, 133)-(835, 813) (795, 2060)
OK: calib: 162 96 721 755 795 1265
校正を実施して、タッチ座標や筆圧検知の最適パラメータを計測しました。
校正機能も、UNOで作り込んだままのコードだったのですが、何ら問題なく動作できたようです。
UNOであれば、校正した結果を不揮発メモリEEPROM等に書き込んでおけば、次回起動時にEEPROMから校正データを読み出して即座に使用可能にすることが出来たのですが、DUEには不揮発メモリが搭載されていないため、その様なアプリケーションにすることはできません。
【init】
OK: initialize done
画面を初期化しました。筆圧連動のメモパッドとして機能していますので、タッチペンなどを使って、何か文字や記号などを描画して見てください。
【rgb,32,0,63】
OK: rgb(32,0,63)=0xf810
ペンの色は、【rgb】コマンドで変えることが出来ます。
【mod,1】
OK: mode 1 (change size and color by pressure)
追加した新しい描画モードについても、試してみてください。
良い様です。
UNOでのタッチx,y座標検出時、2回analogRead()関数を実行して、2回とも同じデータが読めなかったらエラーにしないと、うまくセンスできなかった記憶があったのだけど、そのコードも含めて、DUEでも問題なく動作しています。
全体的に処理が高速化(fcpu=16MHz(UNO) → 84MHz(DUE)、analogRead()関数実行時間=104μs(UNO) → 5μs(DUE)等)しているためか、UNOの時より滑らかに筆圧連動メモパッドが機能しているように思えます(個人の感想です(1^^;))。
あらら、終わっちゃった……。もう少し手古摺らされるかと思っていたのですが、難なく通過してしまいました。短めですが今回はここまで。たまにはこんなこともあって良い(1^−^)。次回への英気を養う時間に充てます(1−o−)💤。