-->

2024年3月23日土曜日

PCと接続するオリジナルコントローラのインタフェース提案(Raspberry Pi Picoの仮想キーボードとしての使用)

はじめに

当研究所はMakerとしていろいろオリジナルデバイスを作成していますが、最近ではオリジナルのコントローラを使用したゲームの開発に注力を行っています。このようなゲームを作成するには、コントローラデバイスとゲームが動いているPCで通信を行う必要があります。しかし、これが地味に面倒くさく、またこのようなシステムを作成しようとしている初心者に対して高いハードルとなっているかと思います。

本記事では、この通信部分をRaspberry Pi Picoを使用することで容易にする提案をしています(実際に動くサンプルあり)。

概要

オリジナルコントローラを使用したゲームを作成する場合、多くの場合以下のような構成になるかと思います。

既存構成

  • ゲームはPCで動作させる
    Unityなどで作成したゲームを想定しています
  • コントローラデバイス側で入力された情報はシリアル通信でPCに送信する
    物理的にはUSBケーブルで接続します
  • PC側ではシリアル通信で情報を受信してゲームの操作を行う

課題

通信にシリアル通信を使用するということが面倒の元凶となっています。面倒くさい部分を以下に記載します。

  • コントローラデバイス側でシリアル⇔USB変換が必要となる
    デバイス側の構成にもよりますが、たいてい必要です
  • PC側のゲームのインタフェースにシリアル通信の受信機能を実装しなければならない
    COMポートを開くなどの処理の実装が必要です

そもそもシリアル通信はかなり低レベル(単純)な通信方法なので、PCでは逆に扱いにくいのです(その代わり応用が効く)。どうせUSBで接続するならUSBの通信方法でデータ通信ができればいいのに…。ということでRaspberry Pi Picoの登場となります。

Raspberry Pi Pico

Picoシリーズ(RaspberryPi公式HPより引用)

最近、Raspberry Pi Picoを使用しているのですが、かなり便利です。個人的に便利な点を以下に記載します。

  • 1,000円弱で低価格
  • C++で開発可能
    Pythonでも可能だが現時点では一部機能制限がある模様
  • 開発環境はPC1台で完結
    ライタ等不要でWindowsでも開発可能
  • ADCが搭載
    もちろんI2CやSPIといった基本的なペリフェラルも搭載
  • USBによるシリアル通信が標準搭載
    デバッグがしやすい
  • USBデバイスとしての動作が可能
    オープンソースのTinyUSBに対応している

IOピン一覧(RaspberryPi公式HPより引用)

個人的にはADCが搭載されたことで、Raspberry Piシリーズの欠点を完全にカバーできている感じがしています。もうマイコンいらないですね…、安いし…。

提案

さて話を戻して、ここで提案するのは以下のような内容です。

  • コントローラデバイスのインタフェースをRaspberry Pi Picoにする
  • Raspberry Pi Picoを仮想キーボードとして動かし、PCへの情報送信をキーボード入力にしてしまう

提案する構成例を以下に示します。

提案構成

上記の構成ではコントローラデバイスからPC側のゲームへ情報を送信するとキーボードからの入力となります。このときゲーム側では標準のキーボード入力ライブラリが使用できるため、シリアル通信用のインタフェース実装が不要となります。

ただし、これはコントローラデバイス→PCへの単方向のみの送信となりますので、ゲームの操作結果をコントローラデバイスへフィードバックするような必要がある場合には向いていません。この条件が許容できるのであれば実装がかなり容易になるはずです。

動作例

具体的にはどういうことなの?ということで動作例を用意しました。ブレッドボードでRaspberry Pi Picoと2つのボタンを接続した例なのですが、下の回路図と等価の構成になっています。

ブレッドボード実装

等価回路

この回路をPCと接続し、(中央付近の赤い)左のボタンを押すとPC側には"a"が入力されます(キーボードの"a"キーを押したときと同じ動作)。同じく右のボタンを押すと"B"が入力されます。なんとなくわかると思いますが、このボタン入力の部分を好きなメカニズムに変更することでPCにUSBキーボードとして認識されるオリジナルコントローラが作成できます。

ソースコード

上記の動作例で使用しているRaspberry Pi Picoのサンプルをここに置いておきます。ビルド方法などはリンク先のREADMEを参照してください。雛形的なプログラムになっているので、各自で改良することで割と容易に使用できるかと思います。ちなみにRaspberry Pi PicoのUSBサンプルプログラムを改変しているだけのものです。

サンプル解説

このサンプルを雛形に開発を行う場合、修正するのは"main.c"のみでOKです(その他のコードはそのままでOK)。また、"main.c"のすべてを把握する必要はなく、修正する必要のある箇所は主に以下の2点です。

  • main関数
    文字通りのメイン関数で、プログラムはここから開始されます。ここではPCと通信を行うUSBの無限ループ処理が開始されます。そのため、GPIOなどの設定はこのループに入る前に行っておきます。
  • hid_task関数
    この関数の中ではPC側に送信するキーの情報を設定しています。このサンプルではGPIOの状態をリードして送信するキーコードを設定しています。

具体的な処理内容はコード内にコメントを埋めていますので参考にしてください。以下に変更対象の範囲のコードを掲載しておきます。

main関数

/*------------- MAIN -------------*/
int main(void)
{
  board_init();
  tusb_init();

  // **********************************************
  // GPIOの設定
  // - ピンの初期化
  // - 入力/出力の設定
  // - プルアップ設定
  // ※ADCなどの機能を使用する場合もここで設定する
  // **********************************************
  gpio_init(kGPIOpin0);
  gpio_set_dir(kGPIOpin0, GPIO_IN);
  gpio_pull_up(kGPIOpin0);
  gpio_init(kGPIOpin1);
  gpio_set_dir(kGPIOpin1, GPIO_IN);
  gpio_pull_up(kGPIOpin1);

  while (1)
  {
    tud_task(); // tinyusb device task
    led_blinking_task();

    hid_task();
  }
}

hid_task関数

// Every 10ms, we will sent 1 report for each HID profile (keyboard, mouse etc ..)
// tud_hid_report_complete_cb() is used to send the next report after previous one is complete
void hid_task(void)
{
  // Poll every 10ms
  const uint32_t interval_ms = 10;
  static uint32_t start_ms = 0;

  if ( board_millis() - start_ms < interval_ms) return; // not enough time
  start_ms += interval_ms;

  // *******************************************************
  // GPIOのリード
  // プルアップ設定なのでボタンを押していると"0"が取得される
  // *******************************************************
  bool button_0 = !gpio_get(kGPIOpin0);
  bool button_1 = !gpio_get(kGPIOpin1);

  // Remote wakeup
  if ( tud_suspended() && (button_0 || button_1) )
  {
    // Wake up host if we are in suspend mode
    // and REMOTE_WAKEUP feature is enabled by host
    tud_remote_wakeup();
  }
  else
  {
    // skip if hid is not ready yet
    if ( !tud_hid_ready() ) return;

    // use to avoid send multiple consecutive zero report for keyboard
    static bool has_keyboard_key = false;

    if ( button_0 )
    {
      uint8_t keycode[6] = { 0 };
      keycode[0] = HID_KEY_A;  // "a"のキーコードを格納

      tud_hid_keyboard_report(REPORT_ID_KEYBOARD, 0, keycode);  // キーコードを送信
      has_keyboard_key = true;
    }
    else if ( button_1 )
    {
      uint8_t keycode[6] = { 0 };
      keycode[0] = HID_KEY_SHIFT_LEFT;  // shiftキーのキーコードを格納
      keycode[1] = HID_KEY_B;           // "b"のキーコードを格納

      tud_hid_keyboard_report(REPORT_ID_KEYBOARD, 0, keycode);  // キーコードを送信
      has_keyboard_key = true;
    }
    else
    {
      // send empty key report if previously has key pressed
      if (has_keyboard_key) tud_hid_keyboard_report(REPORT_ID_KEYBOARD, 0, NULL);  // 空のキーコードを送信
      has_keyboard_key = false;
    }
  }
}

以下に処理の解説を箇条書きで記載しておきます。

  • hid_task関数は10ms毎の実行(ポーリング)が想定されています。関数の最初にそのための計測処理があります。
  • 使用しているGPIOはプルアップに設定しているので、ボタンを押していない(GNDに接続していない)ときは"1"、ボタンを押している(GNDに接続している)ときは"0"になります。
  • tud_remote_wakeupのような関数がありますが、これはUSBデバイスが(接続しているPCが休止状態などになり)休止状態になっている場合に再開時に実行される処理です(要は休止状態時にボタンを押すと再開するという処理が実装されています)。PCが動きっぱなしであれば休止状態は考慮しなくても良いので、このあたりは無視しても問題ありません。
  • 送信するキーボードのキーコード(HID_KEY_Aのような)の定義はこちらを参照してください。
  • キーボードの同時押し(shiftキー+文字キーのような)は、押したいキーコードを配列に格納して関数に渡します。ただし、格納する順番が重要なようでshiftキーと文字キーを同時押しする場合はshiftキーの方を先に格納する必要があるようです。
  • USBでキーボードの状態を送信する場合はリポートデスクリプタというデータで送信されます。ボタンを押したときは、ボタンを押しているというリポートデスクリプタを送信すればいいのですが、ボタンを離したときは何も押されていないというリポートデスクリプタを送信する必要があります。NULLの情報を送信しているところがあるのはそのためです。

まとめ

Raspberry Pi PicoにはADCだけではなくI2CやSPIも標準で搭載されているので、大抵のセンサが接続できると思います。そして、その値をどのように使用するかの処理を実装すればもうオリジナルコントローラができてしまいます。ADCなどを使用する場合にはCMakeLists.txtの変更が必要ですが、Raspberry Pi Picoにはサンプルが充実しているのでそちらを参照すれば比較的容易に実装できるかと思います。

以上がRaspberry Pi Picoをインタフェースに使用したオリジナルコントローラとPC上のゲームを接続する方法の提案となります。皆さんのアイデアを具体化する手助けになれば幸いです。

その他

  • 使用しているRaspberry Pi Pico
    ここで使用しているのはスイッチサイエンスが卸しているヘッダピン実装済みの「Raspberry Pi Pico H」で、大阪日本橋のシリコンハウスで979円で購入しています。
  • 当研究所の開発環境
    Windows10のWSL2でUbuntu20.04をインストールしてビルドを行っています。
  • 仮想ゲームパッドも実はいける
    Raspberry Pi Picoは仮想ゲームパッドとして動かすこともできます。キーボードかゲームパッドのどちらを採用すべきかというのはコントローラの仕様によるのでなんとも言えませんが、とりあえず今回はキーボードの方を紹介しました。

2024年3月20日水曜日

HP更新

Google Sitesがいつの間にか更新されていたので、これを機にHPを新しくしました。

2022年12月11日日曜日

コインアクセプタの中身はどうなっているのか

コインアクセプタの仕組み

コインアクセプタは投入されたコインをその直径で判断していると思われますが、実際にはどのような仕組みでコインの種類を判断するか知っていますか。解説したページがあったので紹介します。

Coin Acceptors Are Higher-Tech Than You Think

Coin-operated machines have a longer history than you might think. Ancient temples used them to dispense, for example, holy water to the faithful in return for their coins. Old payphones rang a bell when you inserted a coin so the operator knew you paid.

リンク先にある解説動画は以下です。


上記の動画はコインアクセプタをArduinoを使用して実現しているのですが、その過程で実際のコインアクセプタを分解しています。それで明らかになるのですが、意外と高度な仕組みが実装されているのです。

計測しているものは以下の3つです。
  1. スピード
  2. 直径
  3. 材料

スピード

LEDとフォトアクセプタを使用することで、そこをコインが通過するとLEDの光が遮られるのでコインが通過したことがわかります。これを2セット使用すると2点間の移動時間からコインのスピードが判明します。

直径

スピードが判明すると、コイン自体がLEDを遮る時間からコインの直径が計算できます。

材料

さらに驚くことに、内部に2つのコイルがあり、その間にコインを通過させています。その際の電流変化を観測することにより、コインの材質も加味して判断することができます。つまり、直径は同じでも材質が違うコインを認識することができるのです(実際にやったことはありませんが)。

まとめ

意外と身の回りにある機能の仕組みはわかっているようでわかっていないことがたくさんあります。コインアクセプタはコインの種類を判別できますが、自動販売機などではコインを判別した上で分別する必要があります。ではその分別はどのような仕組みで実現しているのでしょうか?そんなことを調べてみるのも面白いかもしれません。





2020年12月17日木曜日

マスクをした顔を認識する(OpenCV)

 Ogaki Mini Maker Faire 2020で顔認識を使用した作品を展示しました。昨今のコロナ状況下で顔認識するためには、マスクをした人の顔を認識する必要があるのですが、ここではどのようにそれを実現したのかを記載しておきます。

使用するシステムはRaspberryPi3B+で、ネットワークなしのローカルで完結する構成にしました。持ち運びや設置などの取り回しが容易になるからです。その代わりRaspberryPiではPCなどに比べ処理性能が制限されます。また、フリーで使用できるライブラリということで顔認識にはOpenCVを使用することにしました。

同様にフリーで使用できる顔認識がdlibというものに含まれており、こちらは68点のランドマークを使用した高性能な顔認識が使用できます。しかし、残念ながらマスクなしが前提となっており、今回は候補から外れます。

OpenCVには"haarcascade"というかの有名なHaar-like特徴量を使用した認識モデルが実装されています。同時に学習済みのデータも含まれているのですぐに認識機能を使用することができます。これら学習済みのデータの種類についてはこちらが参考になります。

この学習済みデータには正面の顔を認識するものがあるのですが、これはマスクをしていない顔を学習したデータなので、マスクをした顔を認識しようとすると性能が低下します。ということで今回は両目の認識をすることで顔認識の代替とすることにしました(マスクをしていても両目は見えるので)。目を認識する際に使用できる学習データは「右目」と「左目」があるのですが、正直目を個別に認識をすると統合処理が面倒なのでどうしたものかと思っていましたが、こちらに"haarcascade"に対応した「両目」の学習済みデータがありました。どうやらこれらの学習済みデータはもうすでに不要と判断されOpenCV本体からは削除されているようです(他にも口や鼻がパーツとして認識できるものもあります)。

OMMF2020(屋内)で両目の認識("haarcascade_mcs_eyepair_big.xml"を使用)を動作させてみた雑感を以下に記載しておきます。

  • 認識しやすい人と認識しにくい人に分かれる
    認識しやすい人は顔の向きに関係なく安定して認識するが、認識しにくい人ははっきり正面を向いた顔を映さないと認識できない傾向がある
  • メガネをしている人が認識しにくい
    おそらく目の部分が隠れるからだと思われる
  • 誤認識が発生する
    遠方に全身が映っている複数の人の足元に誤認識が発生した

"haarcascade"で学習済みデータを使用する場合、手軽に認識機能を実現できるのは良いのですが、精度や誤認識が問題となったときに柔軟な対応ができないということがあります。認識の尤度の閾値設定ができず、設定ができるのは結果の重複数閾値と最小サイズぐらいです。あとは認識結果サイズによる誤認識の切り捨てぐらいしかできません。今回の展示は認識できれば良い程度のものであったため、認識しにくいことや誤認識に関しては特に問題とはなりませんでしたが。

2020年10月25日日曜日

コインアクセプタの解析

 この記事は2012年に別のHPで公開した内容です。今は未公開にしているのですが、たまにアクセスされているようなので、こちらで公開します。ここで解説しているコインアクセプタはAdafruitのものですが、おそらく別のコインアクセプタも仕様は同じかと思われます。参考までに。Amazonだと例えば以下のようなものが該当すると思います。


ここではAdafruitから購入したコインアクセプタの使い方を解説しています。 具体的には以下の商品です。

箱の中にはモジュールと説明が書かれた紙が1枚。紙をスキャンしたのが以下です。

サイトの商品ページに乗っている説明書と大差はないようです。
さて、このモジュールができることは以下のことです。
  • 4種類のコインを登録できる
    コインの直径を認識しているみたいなので、登録できるのは直径の違う4種類のコインということになります。ですが、日本硬貨の5円と50円のような穴の開いたコインはうまく認識できないようです。
  • 登録したコインのみを認識して選り分けることができる
    投入したコインが登録されたものだったら後ろの排出口に、登録されていないものだったら前面の返却口に出力できます。
  • 認識したコインの種類を外部にパルスとして出力できる
    これによって何のコインが投入されたか知ることができます。
次はモジュール周りのインタフェースを見てみます。周辺部の写真を掲載します。
  1. 外部ピン(4本)
    名称説明
    DC12V+12V
    COIN認識したコインの種類をパルスで出力
    パルスの数はプログラム可能(任意数が出力できます)
    パルスはTTL?(5Vレベル)っぽいです
    パルスについては後に掲載あり
    GNDGND
    COUNTER不明
    謎です
    ※接続するコネクタはワイヤ付きで同封されています
  2. 2接点スライドスイッチ
    "NO"と"NC"の記載あり(ノーマリーオープン/コネクタ?)。よくわからないのでデフォルトの"NO"から触らず。
  3. 3接点スライドスイッチ
    - Fast
    - Medium
    - Slow
    上記3つの設定から出力パルスの長さを選択できます(後述)
  4. "SET"ボタン
  5. ”ADD"ボタン
  6. 7セグ
  7. ”MINUS"ボタン
    ※4から7は使用前の設定で使用します
使うにあたってはコインのパラメータ設定とサンプリングをする必要があります。パラメータ設定とは使用するコインの種類の数や出力パルス数の設定で、側面のボタンと7セグで行います。サンプリングは実際に使用するコインを投入してコインの種類を覚えさせる工程です。これらの手順は前述のAdafruitのサイトや同封されているマニュアルに掲載されています。

ここからは使用前の設定を解説していきます。電圧を印加すると1秒ぐらい経過した後、ブザーが鳴ってLED(表示灯)が点灯します(ブザーは結構大きな音ですので、びっくりします)。
まずはパラメータの設定を行います。電源を入れた状態で以下の手順を実行します。
  1. ”ADD”ボタンと”MINUS”ボタンを同時に1秒以上押す
    ボタンを離すと7セグに”A”が表示されます。
  2. ”A"が表示されたら”SET"ボタンを1秒以上押す
    ”SET"ボタンを離すと7セグに”E"が表示されます。
  3. ”E"が表示されたら”ADD"ボタンと”MINUS"ボタンで認識するコインの種類を決め、”SET"ボタンを1秒以上押す
    このモジュールは4種類のコインを認識できるので1から4が設定可能です。”SET"ボタンを離すと”H1"が表示されます。”H1"の”1”はコインの1種類目を意味していると思います。
  4. ”H1"が表示されたら”ADD"ボタンと”MINUS"ボタンでサンプル数を決め、”SET"ボタンを1秒以上押す
    サンプル数とはこのパラメータ設定の後に行うコインのサンプリングを行う回数です。設定できる最大は30で、15以上を設定するとより正確性が増すとマニュアルにあります。 ”SET"ボタンを離すと”P1"が表示されます。”P1"の”1”は"H1”と同様にコインの1種類目を意味していると思います。
  5. ”P1"が表示されたら”ADD"ボタンと”MINUS"ボタンで出力パルス数を決め、”SET"ボタンを1秒以上押す
    このモジュールは認識したコインの種類をパルスの数で出力します。ここではそのパルス数を設定します。マニュアルによると最大は50だそうです。 ”SET"ボタンを離すと”F1"が表示されます。”F1"の”1”は"H1”と同様に (略)。
  6. ”P1"が表示されたら”ADD"ボタンと”MINUS"ボタンで正確度を決め、”SET"ボタンを1秒以上押す
    正確度とは所謂”あそび”に関連しているものだと思われます。1から30まで設定可能で、1であれば最も正確、30であれば曖昧になります。通常は5から10が最適だとマニュアルにあります。似た形状のコインを認識したい場合は小さくしたほうが良いとのこと。 ”SET"ボタンを離すと”H2"が表示されます(2種類以上の設定を行った場合)。
  7. "6"の手順が終わると"4"へ戻るので、"3"で設定したコインの種類の数だけ"4"から"6"の設定を繰り返す
  8. ”A"が表示されたら”SET"ボタンを1秒以上押す
  9. ”E"が表示されたら電源をOFFにする
次は実際にコインを投入するサンプリングの手順です。複数回(同種類の複数枚の)コインを投入する必要がありますが、同一のコインを複数回投入しても大丈夫です(たぶん)。
  1. ”SET”ボタンを1秒以上押す
    ボタンを離すと7セグに”A1”が表示されます。
  2. ”A1"が表示されたら1種類目のコインをパラメータ設定で設定したサンプル数の回数分投入する
    コインを投入すると7セグに投入した回数がカウント表示されます。設定回数分投入すると7セグに”A1"が点滅表示されます。
  3. ”SET”ボタンを1秒以上押す
    ”SET"ボタンを離すと”A2"が表示されます。
  4. "3"の手順が終わると"2"へ戻るので、"2"から"3"の設定を4回繰り返す
    ここではパラメータ設定で設定したコインの種類の数によらず4回行わなければならないようです。ただし、"SET"ボタンを1秒以上押すことでコインのサンプリングをスキップすることが可能です。
    例:2種類のコインを使用したい場合、"A3"と”A4"はスキップします
  5. すべて終わると自動的に再起動します
※設定後、設定したコインを投入すると一瞬だけそのコインの出力パルス数が7セグに表示されるので何を認識したかはモジュール単体でもわかります

実際の日本硬貨を使用する際の設定例を以下に掲載します。残念ながら穴の開いている5円と50円はうまく認識できません(事実サンプリング時に投入するとカウンタが1ではなく2ずつ増えるので認識できていないと考えられます)。ですので使用するのはその2種類を除いた4種類の硬貨です。5円はいいのですが50円が認識できないのは正直微妙なところです。
設定後動かしてみましたが正しく認識できました。
  • 設定値
    10円100円500円1円
    サンプル数15151515
    出力パルス5101520
    正確度5555
注意点としては、モジュールを水平に設置しないとうまく認識しません。コインの直径を見ているとは思うのですが、どのようにセンシングしているのか興味深いです。

では、COINから出力されるパルスを見てみます。パルスはロジアナを使用して観測しました(ZEROPLUSの安いロジアナ使用->これ)。スライドスイッチは"Fast"を選択しています。
  • 出力パルス設定"5"のコインを認識した波形
    プルアップされていてローのパルスが出力される模様。
  • 上の波形を少し拡大して時間を表示したもの
    約30msのパルスが出力されているようです。この出力幅はスイッチで変更できます。
その他のスライドスイッチの設定も試してみました。以下にまとめておきます。
  • スライドスイッチの設定と出力パルス長の関係
    スライドスイッチ設定出力パルス長
    "Fast"約30ms
    "Medium"約50ms
    "Slow"約100ms
解析は以上です。

2020年4月22日水曜日

Air umbrellaの話

以下、HACKADAYより引用(意訳)。

Failed: Air Umbrella | Hackaday
https://hackaday.com/2020/01/25/failed-air-umbrella/
約5年前、kickstarter(クラウドファンディングサイト)で"Air umbrella"というプロジェクトが発足した。当初はクラウドファンディングという形態の目新しさも相まって、プロジェクトは資金を集めることに成功した。しかし、紆余曲折を経て結局はプロジェクトは失敗に終わった。
"Air umbrella"は、頭上に落ちてくる雨を圧縮空気を使って弾き飛ばすことにより、コンパクトな傘を実現しようとした。
雨滴の最終的な落下速度は約時速20マイル(32キロ)と言われる。となると、傘のような広い範囲の雨を防ごうと思うと、少なくとも時速25マイル(40キロ)で空気を噴射する必要がある。
頭のすぐ上でそんな空気の噴射をしたい人がいるのだろうか?このような空気の噴射は持ち運びできる小型バッテリーでどのくらい持つのだろうか?
現実的ではないこのプロジェクトは結局失敗した。投資をした人の中には物理学者はいなかったのだろうか?

このプロジェクトが開始された当時、クラウドファンディングが流行っていたが、思いの外お金が集まっていてびっくりしたことを覚えている。このような空気の噴射は実はものすごくエネルギーを必要とする(身近でいうとドライヤーがいい例)。所長は昔、スカートをめくるためにブロアファンを使用した作品を作ったことがあるが、電力を食う割には弱い風しか発生させられなかった記憶がある。

2019年11月21日木曜日

OpenCVのWindow更新はメインスレッドでしかできない

ということがあったので記載しておく。
割と最近のQ&Aがあったので貼っておこう。

How to update cv::namedwindow in multi-threading environment? - OpenCV Q&A Forum

If 1 callback receives an image and performs some image-processing. How can the output image be shown in multi-threading environment? By multi-threading I mean, if that particular callback(depthCallback) can be invoked by more than one thread. And, one more query: Is using waitkey(1), optimal for real-time application?


ウィンドウの更新はメインスレッドで実施するというのが常識だそうな。