アプリケーションノート 5267

空気にタッチ:簡単なジェスチャによる機器のウェイクアップ

筆者: Ilya Veygman

要約: このアプリケーションノートでは、タブレットなどのタッチスクリーンベースの機器を、非常に基本的な形式のジェスチャ認識を使用してウェイクアップするという考えについて解説します。近接センサーのみを使用した実装について、いくつかの考えを検討します。これには、物理的レイアウト、速度の制限、検出スレッショルド、およびコンテキストなどの高レベルのシステム統合の概要が含まれます。ソフトウェアの実装を示すコード例も提供します。

この記事と同様の記事が「Electronic Products」のサイトに2012年6月1日付けで掲載されています。

はじめに

このアプリケーションノートでは、タブレットなどのタッチスクリーンベースの機器を、タッチせずにウェイクアップさせる方法について検討します。タッチの代わりに、非常に基本的な形式のジェスチャ認識と、新しい近接センサーを使用します。取り上げる話題としては、物理的レイアウト、速度の制限、検出スレッショルド、高レベルのシステム統合、および「ヒューマン」ファクタが含まれます。コード例でソフトウェアの実装を示します。

料理中の思いつき

料理中にタッチスクリーン機器を使用したことがある人は、たぶんそれらの機器からレシピを追うのが思ったほど簡単ではないことに気づいたはずです。私のようなハイテクマニアの料理人は、夕食を作りながらタブレットコンピュータやスマートフォンを使ってレシピを参照したいと考えます。「なるほど、でもそれのどこが難しいの?」とあなたは思うでしょう。画面がオンのままではかなりの電力を消費するため、ハンドヘルド機器は通常は1、2分後にスリープに移行します。その結果、何かを調べたいときには機器がスリープ状態になっているため、強制的に画面を常時オンのままにしておくか、または食材の付いた手で画面を汚す危険を冒すかの、二者択一を迫られます。何かを調べる必要が生じるたびに手を洗うという方法もありますが、手を洗って拭くという作業を繰り返すのは面倒な上に、水の無駄遣いにもなります。
ここで私はこう自問します。「画面を常にオンにしておくことなく、しかも機器を汚す危険を避けることはできないだろうか?」と。実際に、その2つを両立させる方法は存在します。ジェスチャによって、実際にタッチしなくても画面をオンに戻すことが可能なのです。複雑そうな話だと思っていますね?幸いなことに、思っているよりは簡単です。

近接センサーへの接近

多くのタッチスクリーン機器(特にスマートフォン)は、赤外線(IR)近接センサーを内蔵しています。これらのセンサーは、電話機への不要な入力を防止するために、一般的に通話中の画面のオン/オフに使用されます。手を振ることによるタッチなしのウェイクアップ動作に必要なのは、このセンサーと、ある種の巧妙なソフトウェア設計のみです。
基本的な考えとしては、機器がスリープ状態のとき、すなわちタッチスクリーンがオフでアプリケーションプロセッサが低電力モードのとき、近接センサーにバックグランドの読み値からの十分大きい変化を「見張らせて」、適切に対応させます。これは、通話中に画面をオフにするために近接センサーがすでに行っている動作とほぼ同じです。我々のアプリケーションでは、単にデータの認識方法が少し異なるに過ぎません。
最初に、センサーによって読み取られる「通常の」バックグランドのカウント量を調べます。これはゼロカウントの場合もありますが、必要に応じてシステム的なオフセット(たとえばバックスキャッターやクロストーク)を計算に入れるために役立ちます。次に、設定されたスレッショルドを超えて信号が増大した場合に割込みをトリガするかまたはアプリケーションプロセッサに信号を送信するようにセットアップします。これによってシステムがオンライン状態に復帰し、画面がオンになります。全体として非常に明快な仕組みであり、環境光およびIR近接センサーを使用して実装可能です。
このデモで使用しているMAX44000は、1.56msに1回または(環境光センサーとのインターリーブ動作の場合に)最長で100msに1回の割合で近接読み値を取得します。最大検出距離を10cmと仮定し、LEDの照射角を±15°とした場合、検出領域は約22cm²または幅約5.35cmになります。目標物が検出されるためには、この画面領域を通過する間に少なくとも1回サンプリングされる必要があります。したがって、最も低速かつ最も低電力のサンプリング速度において高信頼性で検出(または「ピックアップ」)することができる最も高速なジェスチャは、約0.53mpsです。またここでは、検出領域内を通過する単純な目標物を認識するために、センサーはスレッショルドを上回るサンプルを1つ検出すれば良いということも仮定しています。

手首がポイント...

理論上の方式としては、これは非常に単純です。機器がスリープモードに移行するとき、環境をスキャンして目標物を検出した場合に割込みを送信するように近接センサーを設定します(目標物は、あらかじめ設定されたスレッショルドを信号が上回ることによって示されます)。これは、単純にI²Cインタフェース上でセンサーのポーリングを繰り返すことでも実現可能です。残念ながら、それはほとんどの運用者が望むよりも大きい電力の消費にもつながります。
ここで最も重要になるのが、使用する近接センサーが備える固有の機能です。MAX44000センサーは、よりアプリケーションプロセッサの介入が少ない(および低電力の)方式を使用することができるように設計されています。
MAX44000の内部近接割込み(レジスタ0x01のビット1)をイネーブルすることによって、内部レジスタ(0x0Bおよび0x0C)にウェイクアップスレッショルドを書き込むことができます。近接読み値がこのスレッショルドを超えた場合、割込みフラグがセットされます。これによってMAX44000のアクティブローのINT端子がローに駆動され、外部ライン上に割込みを通知します。このラインがローに駆動されたことを検出したアプリケーションプロセッサは、ウェイクアップして機器の低電力状態を終了させて画面をオンライン状態に戻すか、またはその他の必要な処理を行うことができます。

...手を振り続けなくて済むように

多くの場合と同様、実用的なアプリケーションは理論上のものほど簡単ではありません。残念ながら、ハンズフリーウェイクアップは、スレッショルド以上のサンプルを1つ探すという単純な話ではないのです。代わりに、この設計を実装しようとする場合には、検討すべき要素がいくつか存在します。

信号レベルとレイアウト

おそらく最も重要な検討事項は、ウェイクアップ条件をトリガする信号レベルの選択です。システムの応答性と誤検出への耐性の間には、重要なトレードオフが存在します。スレッショルドを低く設定した場合、入力(たとえば、手の振りなど)の検出は容易になりますが、過渡ノイズや偶発的な動きによる誤検出の危険が増大します。逆に、スレッショルドが高すぎる場合、誤検出の可能性はほぼ確実にゼロに減少しますが、同時にシステムは非常に近くの目標物のみを検出するようになり、ほとんどの入力に(たとえば激しく手を振っても)反応しなくなる可能性さえあります。
これに対処する最良の方法は、第1に、システム内のノイズ量を低減することです。これは、光学的ソリューションまたは注意深い電気的配線と部品配置によって実現可能です。ノイズフロアを低下させることによって、誤検出の可能性が減少します。次に、「平均的な」検出距離(たとえば4cm~5cm)を選択し、基準となる目標物による信号を測定します。18%グレーカードが最適です。当然と思われるかも知れませんが、センサー前面に暗色ガラスを使用するアプリケーションの場合は、そのガラスを装着した状態で測定を行ってください。こうして測定した信号レベルは、スレッショルドを設定するための最良の目安として使用することができます。レベルは場合によって異なりますが、フルスケールの8%~15%にレベルを設定するのが良いでしょう。
MAX44000センサーのProximity Thresholdレジスタを使用して、上記の実験に基づいてウェイクアップのスレッショルドを設定することができます。図1は、18%グレーカードと100mAの駆動電流を使用し、センサー前面にガラスがない場合の、信号と距離の関係を示したグラフです。青い線は、ウェイクアップのスレッショルドとして選択可能な値の例です。
図1. MAX44000近接センサーを使用した場合の距離と信号強度の関係(18%グレーカード、電流100mA、ガラスなし)
図1. MAX44000近接センサーを使用した場合の距離と信号強度の関係(18%グレーカード、電流100mA、ガラスなし)

ノイズとローパスフィルタ

それでもノイズが問題になる場合は、ローパスフィルタを実装して信号をきれいにすることができます。MAX44000は、割込みフラグをセットする前のスレッショルドの持続時間をイネーブルするビットも備えているため、これを行う方法が2種類あります。この設定は、近接読み値が一定のサンプル数にわたってスレッショルドの範囲外のままであることを要求するもので、これを使ってノイズの影響を低減することができます。
もう少し複雑な方法としては、センサーからの読み値をデータキューに保存し、カスタマイズされたFIRフィルタをソフトウェアでそれに適用します。残念ながら、この方式には欠点があります。近接センサーのサンプリングレートを増大させることができない場合、センサーの視界を通るように手を振るときの検出可能な速度が低下します。実効サンプリングレートが100msの場合は、特にこの影響が大きくなります。持続時間のスレッショルドを使用する場合、この検出の低下が最大16倍になる可能性があります(実際には4倍の持続で十分です)。

手を振る速度

ここから、次の検討事項として手を振る速度が問題になります。最大速度を決定するのは、第1にセンサーの視界、第2にセンサーから手までの距離、第3にサンプリングレート、そして第4にスレッショルドレベルです。最初の2つの基準の決定方法は明白です。センサーが検出可能な角度と、センサーから目標物までの距離を組み合わせ、基本的な三角法を使用することにより、センサーが検出を試みている間に目標物が移動可能な距離を計算することができます。たとえば、センサーの視野角が合計30°で、有効範囲が最大10cmの場合、目標物がセンサーの前を5.35cm移動しても検出可能です。これは、約78cm²の検出領域になります。この直線距離と、サンプリングレートの組合せによって、速度の上限が示されます。具体的には、サンプリングレートがTの場合、目標物が可視範囲を通過する時間がT以下であってはなりません。たとえば、Tが100ms (MAX44000の最も低いサンプリングレート)の場合、先ほどの例によると、理論上の許容最大速度は1mpsになります(これは実際にはかなり高速です)。多くの場合、複数のサンプルを捕捉して評価を確認する必要があるため、この制限速度はさらに低下します。
検出スレッショルドも許容最大速度に影響します。一般に、スレッショルドが低いほど、高速なジェスチャを検出可能になります。前述のように、誤検出を防止する必要上、この低いスレッショルドは注意して選択してください。

「ヒューマン」ファクタ

このアプリケーションでは、人間の手およびそれを振る人間の一貫性のなさが不可避です。したがって、設計に当たっては、標準的なユーザーが画面の前で手を動かす速度はどの程度か、画面からの距離はどのくらいか、手袋をしているかどうかなどを、実際の用途に基づいて決定する必要があります。機器とのこの相互作用は、スマートフォン、タブレット、または自動車のダッシュボード上の何かなど、アプリケーションによって大きく異なる可能性があります。結局は、アプリケーションごとに設計プロセスの中でユーザーインタフェースおよび使い勝手に関するこれらの変数に対処する必要があります。
最後に1つ検討する必要があるのは、手の振りと思われたものが手の振りではない場合についてです。具体的には、受信した信号が手の振りによるものなのか、ケース/ポケット/バックパックなどに入れられたり、画面を下にして何かの表面に置かれたときのような、他の単純な動きによるものなのかを、機器はどうやって判断することができるでしょう?ここで我々は、真実に向き合う必要があります。機器にコンテキストが与えられない限り、この問題に対する簡単な答は存在しません。それをどのように行うかは、まったく別の問題です。
最終的には、タッチスクリーン機器が特定のアプリケーションを実行する場合にのみこのウェイクアップソリューションを実装したり、ユーザーにそのイネーブルを行わせるという選択も可能です。さらに、これらの機器の多くは加速度センサーを備えているため、画面を下にして機器が置かれていることを判断可能です。また、ユーザーが手動で機器をスリープ状態にした場合(たとえば、機器をオフにした場合)、この機能をディセーブルすることもできます。

実際に試してください

読者の便宜を図って、このアプリケーションノートには3つのコード例が添付されています。第1のコードは、MAX44000の近接読み値の手動チェックを使用してこのウェイクアップソリューションを実装した、概念的に単純なバージョンです。第2のコードは、第1のコードを拡張して、前述のフィルタの概念を実装したものです。最後のコードは、MAX44000の割込み機能を使用してこのウェイクアップを行うための簡単な方法を示しています。

コード例1

__interrupt void TimedInterrupt( void ) 
{

  uint8 proximity_counts;
  ....
  .... 


  if ( device_status == SLEEP_MODE )
  {
    // read one byte from register 0x16
    proximity_counts = read_i2c_register(MAX44000_ADDR,0x16,1);
    if (proximity_counts > WAKEUP_THRESHOLD)
    {
      device_status = WAKE_MODE;
      ...
    }
    else
    {
      // do whatever it is you need to in sleep mode
      ...
      ...
    }
  }

  ...
  ...

}

コード例2

// example interrupt function where this might be implemented
__interrupt void TimedInterrupt( void ) 
{

  uint8 proximity_counts;
  uint8 filtered_counts;
  ....
  .... 


  if ( device_status == SLEEP_MODE )
  {
    // read one byte from register 0x16
    proximity_counts = read_i2c_register(MAX44000_ADDR,0x16,1);

    // weights[QUEUE_SIZE] contains the filter weights for the FIR filter
    // data_queue[QUEUE_SIZE] is a FIFO queue meant to be the input to the filter
    filtered_counts = fir_filter(proximity_counts,weights,data_queue);
    
    if (filtered_counts > WAKEUP_THRESHOLD)
    {
      device_status = WAKE_MODE;
      ...
    }
    else
    {
      // do whatever it is you need to in sleep mode
      ...
      ...
    }
  }

  ...
  ...

}

/**
 * fir_filter()
 *
 * Implements an FIR filter in the form
 *   y = w[0]*x[0] + w[1]*x[1] + ... + w[QUEUE_SIZE]*x[QUEUE_SIZE]
 *
 * Arguments:
 *	uint8 input - newest datapoint taken (that is, x[0])
 *	uint8 *weights - w[0]...w[QUEUE_SIZE]
 *	uint8 *queue - the discrete sequence x[0]...x[QUEUE_SIZE]
 * 
 * Returns: 
 *	The FIR-filtered output, y
 */
uint8 fir_filter(uint8 input, uint8 *weights, uint8 *queue)
{

  uint8 i;
  int sum = 0;

  // pop first entry in the queue, then
  // push new data into the last position
  push_into_queue(queue,input);

  // input is now x[0]
  for (i=0; i<QUEUE_SIZE; ++i)
  {
    sum += weights[i]*queue[i];
  }

  return (sum/QUEUE_SIZE);
}

コード例3

// this handles hardware-level interrupts on the micro
__interrupt void irq_handler( void )
{
  ...
  
  // if the hardware interrupt came from the MAX44000 sensor
  // pulling its \INT pin low
  if ( irq_source == MAX44000 )
  {
    // if the device is in sleep mode
    if (device_status == SLEEP_MODE)
    {
      device_status = WAKE_MODE;	// wake up the device
      ...
      // reconfigure whatever else you need here as the system wakes up
    }
    // otherwise, handle it however it is you wish
    else
    {
      ...
    }
  }
  
  ...
}

/**
 *	configure_max44000_for_sleep_mode()
 *
 *	Sets up the MAX44000 to trigger a hardware interrupt when the proximity
 *	counts go above some set threshold.
 *
 *	Arguments:
 *		uint8 upper_threshold - the set threshold (8-bit mode)
 *
 *	Returns:
 *		n/a
 */
void configure_max44000_for_sleep_mode(uint8 upper_threshold)
{
  uint8 max44000_thresh_registers[] = {0x0B,0x0C};
  uint8 max44000_upper_thresh[] = {0x40,0};
  
  max44000_upper_thresh[1] = upper_threshold;
  
  // do a consecutive write of 0 followed by upper_threshold to 
  // registers 0xB and 0xC, respectively
  //	MAX44000_ADDR is usually 0x94
  // interrupt will trigger only if proximity value is above the threshold
  write_i2c_register(MAX44000_ADDR,max44000_thresh_registers,
				max44000_upper_thresh,2);
				
  
  // write to bits 2 and 3 of register 0x0A here if you wish to set the
  // persist time to anything other than one sample
  
  // writes to register 0x01 to enable interrupts on the MAX44000
  max44000_enable_interrupt();
  
  return;
}