M5Paperをトラックパッドにしてみる

M5Paperの電子ペーパー、表面の触りごごち良すぎて無駄に触っちゃいませんか?
用もなく触ってたらサイズ的にトラックパッドに見えてきたのでBLEマウスにして遊んでみます。

f:id:hollyhockberry:20210927203131j:plain

キーボードと並べるとやたらおさまりがいいですね!

タッチの検出

まずはタッチの検出です。

M5Stackのドキュメントによると、2点のタッチが検出できるようです。

The display is a GT911 capacitive touch screen,which supports two point touch and a variety of gesture controls . 

M5EPDのサンプルスケッチTOUCH.inoを参考にタッチされたポイントの数をシリアルポートに出力させてみました。

GT911::update()レジスタを読んでメンバの更新を行うスタイルですね。GT911::isFingerUp()は一度コールするとフラグが落ちる実装になっていましたので連続してコールする場合は注意です。

ちなみにサンプルのスケッチはif(!M5.TP.isFingerUp())時のみGT911::update()をコールしてますが、これだとタッチ状態変化後に1回誤検出をしちゃう気がするんですけど、どうなんでしょう・・?

タッチジェスチャの判定

タッチジェスチャを判定します。

スワイプ(タッチしたまま移動)、タップ(短押し)、ドラッグ(一度単押ししてすぐスワイプ動作)、ダブルタップ(短押し2回)の状態変化を図にしてみました。

f:id:hollyhockberry:20210929182655p:plain

一定時間状態変化がなかった時(図中✅)にジェスチャが確定しています。
変化した履歴を保存しておけば実現できそうですね。
ちょっとコードが長めですが判定自体は単純です。

タッチ座標の取得

タッチしている指の情報は下記のメソッドか、readFinger(uint8_t num)を使ってtp_finger_t構造体で一気に取得できます。

uint16_t readFingerX(uint8_t num);
uint16_t readFingerY(uint8_t num);
uint16_t readFingerID(uint8_t num);
uint16_t readFingerSize(uint8_t num);

これをタッチの検出のサンプルと同様に、M5.TP.isFingerUp() == falseの時に取得すればOKです。

表示される座標を見る限り、RotationがデフォルトだとUSB端子を右側にして置いた状態のようですね。

BLEマウス

BLEマウスのライブラリは ESP32-BLE-Mouseを利用します。

github.com

このライブラリも非常にわかりやすいです。
BleMouseインスタンスを生成してsetup()(じゃなくてもいいけど)でbegin()をして、click() move() press() release()をコールしてマウスの動きをPCへ送信できます。

バイス名やManufacturerはコンストラクタで設定できます。デフォルトのデバイス名はESP32 Bluetooth Mouseですね。begin()さえコールすればデバイス一覧に出てくるはずです。

ノイズ対策

今まで実験したことを組み合わせればトラックパッドになるはず!と思ってBleMouseのインスタンスを生成して実装していたらどうにも動きがおかしい。

具体的な現象としては、タップし続けているのにたまにアップ状態になってドラッグが途切れてしまいます。色々実験しているとBleMouseのbegin()を呼ぶとM5.TP.avaliable()が不正にtrueになっていることがわかりました。
そうなると不用意にupdate()してしまうわけで、変な値が取得されてしまうのも納得ですね。

#include <M5EPD.h>
#include <BleMouse.h>

BleMouse bleMouse;

void setup() {
  M5.begin();
  bleMouse.begin();  // ← このメソッドの有無で挙動が変わる・・
}

void loop() {
  if (!M5.TP.avaliable()) {
    return;
  }

  M5.TP.update();
  if (!M5.TP.isFingerUp()) {
    Serial.println("down");
  } else {
    Serial.println("up");
  }
}

納得したところでどうにもならないので、思い切ってチャタリング除去みたいな読み捨て処理で誤魔化すか?と思っていたところまたも救いの手が。ありがたい・・

無線を有効にするとGPIO36と39に余計なパルスが入るそうです。
GT911のbeginを見るとバッチリGPIO36を使ってますね。第3引数はavaliable()がtrueになるフラグをセットする割り込みのポートですね。思いっきり現象どおりでした。

if(TP.begin(21, 22, 36) != ESP_OK)
{
    log_e("Touch pad initialization failed.");
}

というわけで、setup()adc_power_acquire();をコールしてADCを有効にします。それだけで期待通りの動作になりました。よかった・・。

完成!

さて、今までの成果を組み合わせてどうにかこうにか完成しました。 スワイプ、タップ、ドラッグ、タブルタップ、ホイール全ての想定したジェスチャが実現できました。

実はこの動画はちょっと前に作ったバージョンで、ちゃんとデバッグしてないのでGPIO36のノイズの影響に気づいていない状態ですね。
見た目同じ感じなので動画上げ直すのを横着しちゃいました。

例によってソースコードGithubで公開していますので興味があれば覗いてみてください。

github.com