PIC24F/PIC32MX USB HUB Driver の製作 2013.5.10 2014.1.13

    [Japanes][English]

    1.概 要

     microchip社から提供されるUSBホストのドライバープログラムは、HUBを使った複数のUSBデバイスの接続には対応していな い。そのため、このドライバープログラムを使う限りにおいては、複数のUSBデバイスをPICに接続することは出来ない。

     USBメモリやWebカメラ、マウス、キーボード、その他自作のUSBデバイスを、HUBで纏めてPICに接続出来れば、もっと面白い 電子工作が楽しめる。そんな思いで、HUBドライバーを内蔵したUSBホストのドライバープログラムを作成した。

     プログラムのベースとなったのは、もちろん microchip社から提供されるプログラム。具体的には、 usb_host.c と そのヘッダファイルの  usb_host.husb_host_local.h の3つのファイルだ。これらに手を加えてHUB対応にした。

    使ったファイルが収納されていたフレームワークのバージョンは、作業開始時点の最新版である、 microchip_solutions_v2013-02-15 である。

     ホストプログラムの中にHUBドライバーを内蔵したので、先の3つのファイルを、この下のリンクで示す同名のファイルに置き 換えるだけでHUBが使えるようになる。

      usb_host.c HUBドライバーを内蔵したマルチデバイス対応のホストドライバー (last update, 2014/1/21)
      usb_host.h アプリケーション用ヘッダファイル (last update, 2013/5/8)
      usb_host_local.h ドライバー内ヘッダファイル (last update, 2014/1/21)


    2.使い方

     HUB対応のホストドライバーだからといって、必ずしもHUBが必要な訳ではない。PICのUSBポートに直接デバイスを 接続すれば、従来通りのシングルデバイス対応のホストとして動作する。

     USB1.1仕様のフルスピードHUBでも、USB2.0仕様のハイスピードHUBでもどちらもでも接続して使える。ただし、 USB2.0仕様のHUBは、PICのUSBモジュールがフルスピードなので、USB1.1仕様のHUBとしてしか動作しない。

     接続可能なHUBのポート数は、デフォルトでは、4ポートとしているが、usb_config.h に、


      #define USB_HUB_NUMBER_OF_DOWN_STREAM_PORTS    6

    と定義すれば、6ポートを持つHUBを使用できる。

     HUBのポートに接続するUSBデバイスは、ロースピード、フルスピードのものなら何でもよい。ハイスピードのデバイスでも フルスピードで動作するものであれば問題ない。ただし、これらを動かすには、このホストドライバーの他に、各デバイス に対応したミドルドライバーが必要なのは言うまでも無い。

     ミドルドライバーを使うには、usb_config.c の中の usbClientDrvTable[] や 各デバイスの情報を usbTPL[] に登録すればよい。HUBに関しては、ドライバーは内蔵されているので、これらのテーブルに登録は一切 不要である。

     HUBドライバーが内蔵されているので、HUBのポートに更にHUBを繋いでもよいが、残念ながら、このカスケードHUBには対応していない。 一応、カスケードHUBもUSBデバイスであるから、ドライバー内部では、エヌメレーション処理までは行うのだが、その後は、ホールド状態にして 管理外のデバイスとして扱うようにプログラムした。

     カスケードHUBが必要なほど、たくさんのデバイスをPICに繋ぐようなことは無いからこれで十分だと思うが、もっと繋ぎたいと 思う人は、この後の解説を読んでドライバーソフトの改造に挑戦して頂ければよい。

       2014.1.13


        HUBドライバーを、カスケード接続した複数のハブが使えるように修正しました。カスケードハブを使いたい方は、次のファイルを使用してください。

        usb_host.c 複数のHUBに対応したHUBドライバーを内蔵したホストドライバー (last update,2014/1/21)
        usb_host.h アプリケーション用ヘッダファイル (last update,2013/5/8)
        usb_host_local.h ドライバー内ヘッダファイル(last update,2014/1/21)

       使う時には、usb_config.h に、それぞれのハブが持っているポートの数の合計値を定義してください。例えば、4つのポートを持つハブを3つ接続する場合には、

        #define USB_HUB_NUMBER_OF_DOWN_STREAM_PORTS    12
      とします。




    3.usb_host.c のHUB対応への改造

     これ以後の話は、C言語やUSB通信の基本、microchip社の提供するUSBプログラムについて、ある程度の知識があり、 また、これらを理解していることを前提にしている。そのため説明不十分な所が多々あると思うが、お許し願いたい。

    3−1 マルチデバイスへの対応

     HUBを使って複数のデバイスを動かすのだから、まずは、ホストプログラムを何とかしてマルチデバイス対応にすることが必要だ。 HUBのドライバーはその後で開発することにする。

     オリジナルの usb_host.c プログラムは、シングルデバイス対応のプログラムである。これをマルチデバイス化するにあたっては、 出来るだけ変更箇所を少なく、かつオリジナルプログラムが実現しているUSB通信の処理メカニズムをそのまま使うようにした方が、 開発スピードも早く、確実なものが作れる。

     全くのスクラッチから作成するのも面白いが、外部インタフェースは、既存のものに合わせないと単純なファイル置き換えが出来 なくなるので、検討事項も多くて大変である。

      そこで、usb_host.c の中で接続デバイスを管理している各変数を配列変数に変更して対処することにした。 配列の要素数は、対応するHUBのポート数+2とした。

     改造したusb_host.cの冒頭の変数宣言部分を次に示す。

      
      // These should all be moved into the USB_DEVICE_INFO structure.
      //static BYTE                          countConfigurations;
      //static BYTE                          numCommandTries; 
      //static BYTE                          numEnumerationTries;
      //static volatile WORD                 numTimerInterrupts;
      //static volatile USB_ENDPOINT_INFO   *pCurrentEndpoint;
      //BYTE                                *pCurrentConfigurationDescriptor    = NULL;
      //BYTE                                *pDeviceDescriptor                  = NULL;
      //static BYTE                         *pEP0Data                           = NULL;
      //static volatile WORD                 usbHostState; 
      //volatile WORD                        usbOverrideHostState;
      
      // this macro defined in usb_config.h
      #ifndef USB_HUB_NUMBER_OF_DOWN_STREAM_PORTS
          // support default Hub it has one up stream port, 4 down stream ports.
          #define USB_HUB_NUMBER_OF_DOWN_STREAM_PORTS 4
      #endif
      
      #define USB_MAX_ADDRESS              (USB_HUB_NUMBER_OF_DOWN_STREAM_PORTS + 1)
      
      static BYTE                          countConfigurations[USB_MAX_ADDRESS+1]; 
      static BYTE                          numCommandTries[USB_MAX_ADDRESS+1];
      static BYTE                          numEnumerationTries[USB_MAX_ADDRESS+1];
      static volatile WORD                 numTimerInterrupts[USB_MAX_ADDRESS+1];
      static volatile USB_ENDPOINT_INFO   *pCurrentEndpoint[USB_MAX_ADDRESS+1];
      BYTE                                *pCurrentConfigurationDescriptor[USB_MAX_ADDRESS+1];
      BYTE                                *pDeviceDescriptor[USB_MAX_ADDRESS+1];
      static volatile WORD                 usbHostState[USB_MAX_ADDRESS+1];
      static BYTE                         *pEP0Data[USB_MAX_ADDRESS+1];
      static USB_DEVICE_INFO               usbDeviceInfo[USB_MAX_ADDRESS+1];
      volatile WORD                        usbOverrideHostState[USB_MAX_ADDRESS+1];
      

     なお、オリジナルのコメント最初に 「These should all be moved into the USB_DEVICE_INFO structure.」 とあるように、countConfigurations から usbOverrideHostState までの変数は、usbDeviceInfo[]構造体変数のメンバーに組み入れた方が、プログラムの見通しも良くなるかと思ったのだが、 そうするとソースの変更が大変なので、単純に今使われている変数をそのまま配列にした。

     これなら、各変数記述の後ろに ”[deviceAddress]” を挿入するだけで済み、 エディタの置換が有効に使えるので変更作業が確実に行える。

     接続デバイスに割り当てるデバイスアドレスは、動的に変化させるのではなく、予め決め打ちにすることにした。

     HUB本体のデバイスアドレスにはを、HUBのポート1に接続したデバイスにはを、ポート2に接続したデバイスにはを、以下ポートn には、アドレス n+1 を割り当てるようにした。

     そして、このデバイスアドレスを配列のインデックスとして使うことにした。ソースファイルでは、配列のインデックス変数は、出来る限り ”deviceAddress” の変数名で 統一することにした。

     HUBを繋ぐと、HUB本体は、usbDeviceInfo[1] で管理され、 ポート4に接続したデバイスは、usbDeviceInfo[5] で管理される。 ポート4にだけデバイスが接続された時は、デバイスアドレスは、1 と 5 が使われる。

     なお、デバイスアドレスは、usbDeviceInfo[].deviceAddress 変数に配列のインデックス値と同じものを設定するのだが、これは、 エヌメレーション時のSET_ADDRESSリクエストを発行した後に設定している。DETACHED状態にある時や、エヌメレーションのアドレス設定の直前までは、ここの値は、0となっている。

     配列のインデックスが1であるデバイスは、HUBかまたは、PICのUSBポートに直接接続したデバイスとなる。

     初期状態では、全デバイスに対するusbHostState[]は、 STATE_DETACHEDが設定されている。USBHostTasks()の処理では、 全デバイスに対して、for()にて処理が一巡するようにした。

      
        // Call Hub Function
        if(flgHubAttached)
        {
            _USB_HubTasks();
        }
      
        for(deviceAddress = 1; deviceAddress <= USB_MAX_ADDRESS; deviceAddress++)
        {
          // See if we got an interrupt to change our state.
          if (usbOverrideHostState[deviceAddress] != NO_STATE)
          {
              usbHostState[deviceAddress] = usbOverrideHostState[deviceAddress];
              usbOverrideHostState[deviceAddress] = NO_STATE;
          }
      
          //-------------------------------------------------------------------------
          // Main State Machine
      
          switch (usbHostState[deviceAddress] & STATE_MASK)
          {
              case STATE_DETACHED:
                  switch (usbHostState[deviceAddress] & SUBSTATE_MASK)
                  {
                      case SUBSTATE_INITIALIZE:
                          // We got here either from initialization or from the user
                          // unplugging the device at any point in time.
                          // Turn off the module and free up memory.
                          USBHostShutdown(deviceAddress);
                          DEBUG_PutString( "HOST: Initializing DETACHED state.\r\n" );
      

     ここまでのソース変更で見かけ上は、マルチデバイス対応となった訳だが、HUBやPICに直接接続されたデバイスの初期設定(ATTACHED検出後の処理) と、HUBのポートに接続されたデバイスの初期設定とでは、異なる動きになる。

     前者の場合には、ホストドライバーの中で、デバイスのリセット処理を行うのだが、後者の場合には、HUBが既にデバイスをリセットしてしまっているのでその必要はない。 つまり、後者の場合には、USBHostTasks()の中で、リセットの処理はスキップするようにしなければならない。

     HUBドライバーは、ポートにデバイスが接続されたことを検出すると、そのポートのステートを、STATE_DETACHED から、STATE_ATTACHED に変更する。 この変更は、 usbHostState[port+1] に STATE_ATTACHED を書き込むことで行っている。

     これにより、USBHostTasks()の中で、STATE_ATTACHED からの処理が、 そのポートに接続したデバイスに対して動作するようになる。

    deviceAddressが2以上のデバイスは、HUBのポートに接続したデバイスだから、SUBSTATE_SETTLE の次のステートは、デバイスのリセット 処理をスキップするように、 usbHostState[deviceAddress]に、SUBSTATE_GET_DEVICE_DESCRIPTOR_SIZE を設定する。 これ以降は、通常のデバイスと同様にエヌメレーション処理が動くことになる。

      
      case STATE_ATTACHED:
          switch (usbHostState[deviceAddress] & SUBSTATE_MASK)
          {
              case SUBSTATE_SETTLE:
                  if(deviceAddress >= 2) // HUB downstream port connect device.
                  {
                      // Initialize the USB Device information
                      usbDeviceInfo[deviceAddress].currentConfiguration      = 0;
                      usbDeviceInfo[deviceAddress].attributesOTG             = 0;
                      usbDeviceInfo[deviceAddress].flags.val                 = 0;
                      _USB_InitErrorCounters(deviceAddress);
                      // See if the device is low speed.
                      if(portDeviceLowSpeed)
                      {
                          DEBUG_PutString( "HOST: HUB Port Device Low Speed!\r\n" );
                          usbDeviceInfo[deviceAddress].flags.bfIsLowSpeed    = 1;
                          usbDeviceInfo[deviceAddress].deviceAddressAndSpeed = 0x80;
                      }
                      // Jump to GET_DESCRIPTOR_SIZE state
                      usbHostState[deviceAddress] = STATE_ATTACHED | SUBSTATE_GET_DEVICE_DESCRIPTOR_SIZE;
                      break;
                 }
      
                 // Wait 100ms for the insertion process to complete and power
                 // at the device to be stable.
                 switch (usbHostState[deviceAddress] & SUBSUBSTATE_MASK)
                 {
                     case SUBSUBSTATE_START_SETTLING_DELAY:
                         DEBUG_PutString( "HOST: Starting settling delay.\r\n" );
      

     その他にも、アドレス1のデバイスとアドレス2以降のデバイスとでは、処理が異なる部分が随所にあるが、 if(deviceAddress >=2)などのように、if 文で場合分けして いるので、そのような観点でソースを眺めてみてほしい。

     なお、BDTのピンポンバッファの管理などは、デバイス単位ではなく、全デバイス共通で処理する必要があり、ピンポンバッファの管理は、 usbDeviceInfo[1] flags.bfPingPongInflags.bfPingPongOutで行っている。

     HUBに接続されたデバイスがポートから抜かれると、HUBドライバーがそれを検出し、該当デバイスの usbHostState[port+1] に STATE_DETACHED を 書き込んでDETACHED状態に変更する。

     ポートに接続されたデバイスのDETACHED処理は、デバイスを管理する usbDeviceInfo[]構造体変数を初期化するだけであり、PICのUSBモジュールの初期化は行わない。

     一方、HUBがPICのUSBポートから抜かれた場合には、HUBのポートにまだデバイスが挿されていてもPICからはアクセスできなくなるので、HUBだけでなく、 ポートに接続されていた全デバイスに対してデタッチ処理を行う必要がある。そこで、PICのUSBポートに直接接続したデバイスのデタッチを検出した際には、 その割り込み処理の中で、全デバイスのステートを STATE_DETACHED に変更するようにしている。

      
           // -----------------------------------------------------------------------
          // Detach ISR
          if (U1IEbits.DETACHIE && U1IRbits.DETACHIF)
          {
          //  DEBUG_PutChar( ']' );
      
              U1IR                    = USB_INTERRUPT_DETACH;
              U1IEbits.DETACHIE       = 0;
      
              for(deviceAddress = 1; deviceAddress <= USB_MAX_ADDRESS; deviceAddress++)
                  usbOverrideHostState[deviceAddress] = STATE_DETACHED;
      
              #ifdef  USB_SUPPORT_OTG
                  //If HNP Related Detach Detected, Process Disconnect Event
                  USB_OTGEventHandler (0, OTG_EVENT_DISCONNECT, 0, 0 );
      
                  //If SRP Related D+ Low and SRP Is Active, Process D+ Low Event
                  USB_OTGEventHandler (0, OTG_EVENT_SRP_DPLUS_LOW, 0, 0 );
      
                  //Disable HNP, Detach Interrupt Could've Triggered From Cable Being Unplugged
                  USBOTGDisableHnp();
              #endif
          }
      


    3−2.ローデバイス対応

     オリジナルのプログラムでは、マウスなどのロースピードデバイスもPICのUSBポートに直接接続することを前提にしており、 HUB経由でロースピードデバイスと接続した場合には、オリジナルのプログラムのままでは、ロースピードデバイスと通信することができない。

     USB1.1仕様のフルスピードHUBを経由してロースピードデバイスと通信するには、まず最初に、HUBに、PREトークンをフルスピードで送信し、これから ロースピードで通信を開始することをHUBに通知しなければならない。その後、ロースピードに切り替えて、SETUPトークンなどを送信すると、HUBは、ロースピードで送られて来た信号を、 ロースピードデバイスが接続されたポートに中継してくれる。

     つまり、ロースピードデバイスとの通信では、PREトークンの処理が必要となるのだが、PIC の ReferenceManual では、トークンは、SETUP/IN/OUTの3つしかサポートされていない。 PREトークンが使えないのでは、マウスやキーボードを動かす事が出来ない。もう少し、ReferenceManual を追いかけてみると、レジスタ U1EP0 のビット7の LSPD ビットの説明に次のような 記述を見つけた。

      ビット 7 LSPD: ロースピード直接接続イネーブルビット(UEP0 のみ)(1,2)
       1 = ロースピードデバイスへの直接接続を有効にする
       0 = ロースピードデバイスへの直接接続を無効にする
      
      Note1:これらのビットは、ホストモードのU1EP0 でのみ利用可能です。これ以外のU1EPn レジスタでは、こ
            れらのビットは実装されておらず、読み出すと「0」となります。
          2:USB モジュールがホストモードで動作しており、ハブ経由でロースピードデバイスと接続している場合
            はこのビットをクリアしてください。
      

     PREトークンについては何も書かれていないが、ロースピードデバイスと通信する際には、 ビット7に1を設定したデバイスアドレスをU1ADDRレジスタに書き込むとともに、このマニュアル記述に従って、U1EP0のビット7(LSPDビット)を0に設定してみたら、 うまく通信することが出来た。どうやら、PREトークンの送出は自動で行ってくれているようだ。

     なお、PIC32の英文のマニュアルを調べてみたら、「0=Direct connection to a low-speed device disabled; hub required with PRE_PID」 と書かれており、HUBが、PREトークンを必要としていることは書かれているが、自動的にPREトークンを送出するとの明確な表現はない。


    3−3.USBバスのリソース割り当て

     複数のデバイスが、それぞれに独立して、コントロール転送やインタラプト転送、アイソクロナス転送、バルク転送の要求を行うので、それに対するバスの調停機能が必要になる。 コントロール転送は、最優先にし、バルク転送は、バスの空いている時だけ実行するようなメカニズムが必要だ。

     オリジナルのプログラムでは、SOFの割り込み処理と、_USB_FinedNextToken() 関数と、転送完了の割り込み処理とが、うまく協調して、そのメカニズムを提供しているのだが、 あくまでも一つのデバイスに対しての処理となっている。

     そこで、全デバイスに対して、同様なメカニズムを提供するよう、次の4つの関数を作成した。(usb_host.c の末尾に配置)

      void _USB_FindService(BYTE transferType);

        transferType で指定された転送を要求しているエンドポイントを全デバイスに対して調べ、結果をグローバル配列 ServiceEndpoint[]に格納する。


      void _USB_FrameFirstTransaction(void);

         この関数は、1msec 毎にSOF割り込みの中から毎回呼ばれる関数であり、呼ばれると、まず最初に、_USB_FindService()を呼び出して、 コントロール転送、インタラプト転送、アイソクロナス転送、バルク転送の順に転送要求のあるエンドポイントを調べる。

         転送要求のエンドポイントが見つからなければ何もしないで終了する。転送要求のエンドポイントがあれば、 usbBusInfo.wBytesSentInFrame 変数をクリアし、_USB_FrameNextTransaction()を呼び出して通信を開始する。


      void _USB_FrameNextTransaction(void)

         ServiceEndpoint[]配列から、転送要求のあるエンドポイントを一つ取り出し、_USB_AdvanceFindNextToken()を呼び出して、 そのエンドポイントに関する通信処理を依頼する。

         _USB_AdvanceFindNextToken()関数は、USB通信が開始されて転送完了の割り込みを待つ場合にはTRUEを返し、そうでない場合(通信終了など)には、 FALSEを返してくるので、TRUEが返ってきた場合には、何もしないで終了し、転送完了の割り込みの中からの呼び出しを待つ。

         FALSEが返ってきた場合には、次のエンドポイントについて、同様の処理を行い、全ての転送要求のあるエンドポイントに対して処理が完了すれば、関数を終了する。


      BOOL _USB_AdvanceFindNextToken(BYTE deviceAddress, BYTE transferType);

         deviceAddressで指定されたデバイスのエンドポイントについて、転送ステートに応じて、通信の開始(トークンの送出)や通信完了の処理を行う。

         転送済みバイト数を管理する変数 usbBusInfo.wBytesSentInFrame の積算処理を行い、1フレーム当たりの転送可能最大バイト USB_FRAME_MAX_TRANSFER_SIZE を越える場合には、次の転送要求は、次のUSBフレームに先送りを行う。

         USB_FRAME_MAX_TRANSFER_SIZE には、オーバーヘッドを考慮し、1フレーム当たりの理論最大転送バイトの90%である1,350バイトを設定している。

         アイソクロナス転送は、USBの仕様上、1フレームに一回の実行に限定されるが、バルク転送については、転送済みバイトが、最大バイト数に達するまで、 1フレーム内で転送を繰り返し実行し、バスを有効活用する。

         何らかの転送が開始され、転送完了の割り込みを待つ場合にはTRUEを返し、そうでない場合(通信終了など)には、FALSEを返す。


    4.HUBドライバーの作成

     HUBもUSBデバイスの一つであるから、他のデバイスと同様、初期化処理を行う _USB_HubInit() 関数と、 HUBの処理を行う _USB_HubTasks() 関数の2つの関数を作成した。(usb_host.c の先頭部分に配置)

    4−1.HUBの初期化

     デバイスのエヌメレーション処理の最終処理の段階で、接続デバイスのクラスをチェックし、HUBクラスのデバイスであれば、_USB_HubInit() 関数を呼び出すようにした。

      
          case SUBSUBSTATE_INIT_CLIENT_DRIVERS:
              DEBUG_PutString( "HOST: Initializing client drivers...\r\n" );
              _USB_SetNextState(deviceAddress);
      
              // if connected first device is Hub, then Hub initialize.
              if(deviceAddress == 1 && pDeviceDescriptor[deviceAddress][4] == USB_HUB_CLASSCODE)
              {
                  if(!_USB_HubInit(deviceAddress))
                  {
                      _USB_SetErrorCode( deviceAddress, USB_HOLDING_CLIENT_INIT_ERROR );
                      _USB_SetHoldState(deviceAddress);
                  }
                  break;
              }
      
              // Initialize client driver(s) for this configuration.
              if (usbDeviceInfo[deviceAddress].flags.bfUseDeviceClientDriver)
              {
                  // We have a device that requires only one client driver.  Make sure
                  // that client driver can initialize this device.  If the client
                  // driver initialization fails, we cannot enumerate this device.
                  DEBUG_PutString( "HOST: Using device client driver.\r\n" );
      
                  temp = usbDeviceInfo[deviceAddress].deviceClientDriver;
                  if (!usbClientDrvTable[temp].Initialize(usbDeviceInfo[deviceAddress].deviceAddress,
                                                                     usbClientDrvTable[temp].flags, temp))
                  {
                      _USB_SetErrorCode( deviceAddress, USB_HOLDING_CLIENT_INIT_ERROR );
                      _USB_SetHoldState(deviceAddress);
                  }
               }
      

     _USB_HubInit()関数での初期化処理では、最初に、HUBから、HUB_DESCRIPTOR を読込んで、HUBのポート数を取得し、続いて、全ポートに対して、PowerON 処理を 行っている。

     最後に、HUBが接続されたことを示すフラグ flgHubAttached をセットして初期化を終了する。


    4−2.HUBの制御

     flgHubAttachedがセットされると、アプリケーションから USBHostTasks()関数が呼ばれる毎に、_USB_HubTasks()関数が呼ばれるようになる。 _USB_HubTasks()関数は、ステートマシンとなっており、HubState 変数に応じて処理が行われる。

     _USB_HubTasks()での主な処理は、インタラプト転送にて、HUBのステータスを監視することと、ステータス変化のあったポートに対する処理である。

     USBデバイスが、HUBのポートに接続されると、HUBステータスにて、変化のあったポートが通知されるので、該当のポートのステータスを読み出し、 変化ビットをクリアした後、デバイスの接続であれば、リセット処理を行い、その後、ポートのイネーブルを確認したあと、デバイスのアタッチ処理を行う。デバイスの抜去であれば、デタッチ処理を行う。

     アタッチ処理もデタッチ処理も、単に、該当ポートの usbHostState[port+1]に STATE_ATTACHED や STATE_DETACHED を書き込むだけの処理である。

     なお、USBバスのパワー管理や、HUBのパワー管理については、特に考慮していないので、バスパワーハブを使う時には、PICのUSBポートのパワーを越えないよう注意して欲しい。



    5.サンプルプログラム(2013.11.22 この項目を追加)

    5−1.usb_hub_demo

    HUBを使って、1つのマウス、1つのWebカメラ、1つのUSBメモリディスクをPIC32MXに接続し、3つのデバイスを動かすサンプルプログラムである。


    5−2.usb_hub_hid_demo

     このサンプルプログラムでは、幾つかの、マウスやキーボード、ジョイスティックなどの HID デバイスをHUBを使ってPIC323MXに接続する。
     少し変更すれば、PIC24でも動かすことが出来る。

     2つ以上のマウスを接続することも出来るし、2つ以上のジョイスティックを接続することもできる。もちろん、1つのマウス、1つのキーボード、1つのジョイスティックをHUBを使って繋ぐことが出来る。