YS電子工作ラボ

USB(デバイスモード) HIDクラス
PIC-PC間 IO制御

(LEDのOnOff、SW・VR電圧読込)


(→プロジェクトファイル(Harmony Ver.2.04版 ) ダウンロード


   ・ USB (デバイスモード)のHIDクラスを用いて PICとPC間でI/Oを制御する例を紹介します。 I/O制御は要約すると以下の3つです。
 ① PC側のボタンスイッチをクリックして PIC側のLEDをON/OFFする。
 ② PIC側スイッチのON/OFFの状態をリアルタイムでPC側のウィンドウに表示する。
 ③ PIC側の可変抵抗器による可変電圧値をリアルタイムでPC側の制御ウィンドウに表示する。
  尚、PC側のアプリケーションソフトはVC++(Visual Studio 2012) 及びVC#(Visual Studio 2017)で作成しています。

・ 本サンプルプログラムはマイクロチップの以下2つのプログラムを参考にして作成しています。
 ① Harmonyのhid_basic
     "C:\microchip\harmony\v2_04\apps\usb\device\hid_basic"
 ② MLAのDevice - HID - Custom Demos
     "C:\microchip_solutions_v2013-06-15\USB\Device - HID - Custom Demos\Simple Demo - Windows Software\Microsoft Visual C++ 2005 Express"
 
・ USB(デバイスモード)HIDクラスを使ったプログラムを実装したPICでWindows PCにアクセスする場合、USBドライバーは特に作成する必要はありません。 Windowsがキーボードやマウス用に使用しているUSB(HID)ドライバーがそのまま使えます。

・ PC側プログラムは を参照してください。
PC開発環境   参照URL 
 VC++(Visual Studio 2012)  URL
VC#(Visual Studio 2017) URL
   

<仕様>
  ・PCとPIC間のUSB通信はHIDクラスとする。
  ・PICはPIC32MZ2048EFH100を使用のこと。
  ・PC側のプログラムは、マイクロソフトのVC++で作成のこと。
  ・PC側はダイアログウィンドウを作成して、以下を実施できること。             
PC開発環境  PC側サンプル 
実行ファイル
備考
VC++(Visual Studio 2012) ダウンロード 実行には、Visual Studio 2012 の Visual C++ 再頒布可能パッケージが必要です。
VC#(Visual Studio 2017) ダウンロード  


     ① PIC側のLEDのON/OFF制御
     ② PIC側のスイッチの状態のモニタ
     ③ PIC側の可変抵抗器の電圧もモニタ
  ・ダイアログウィンドウのボタンスイッチ(コントロール)のクリックにより、PIC側のLEDがON/OFFできること。
  ・ボタンスイッチの背景色はLEDがONの場合は赤色、LEDがOFFの場合は灰色とする。
  ・ボタンコントロールはLEDがONの場合は赤色に、OFFの場合は灰色とする。
  ・LEDがONの場合は画像を またOFFの場合は画像を表示のこと。
  ・PIC側のスイッチのON/OFF状態が分かるモニターとして 円形のオーバルシェープコントロールを使用のこと。
   尚、色はONの場合はオレンジ色、OFFの場合は灰色とする。
  ・スイッチがONの場合は画像を またOFFの場合は画像を表示のこと。画像は重ねて配置のこと。
  ・PIC側の可変抵抗器の電圧値に表示のこと。値は四捨五入して小数点以下2桁を表示のこと。
  ・入力電圧値がビジュアルにわかるようにプログレスバーでも表示のこと。
  ・途中切断も含め、USB通信がOFFの場合はLED,、スイッチ、可変抵抗器などに係る表示は行わないようにすること。
  ・PCのアプリケーションは100msec毎にPIC側の状態をサンプリングのこと。
  ・ PC側のダイアログウィンドウ例を以下に示す。      
    
   <USB通信OFFの場合>  <USB通信がONの場合>

    ・PIC側のモニタ用液晶として、I2C制御のキャラクタ液晶を用いること。
    ・PICが起動する時、キャラクタ液晶には以下を表示のこと。 
      1行目: USB device HID
      2行目: start !!
    ・USB通信が始まったら液晶に可変抵抗器の入力電圧をモニタする。
      1行目: AdValue= ◯◯◯◯   
      2行目: Volt= ◯◯◯◯[V]
     AdValueの〇〇〇〇は内臓ADコンバータの読み込み整数値、Voltは電圧換算値で小数点以下3桁を表示のこと。
    ・可変抵抗器の電圧のサンプリングタイムは50msecとする。
    ・USBケーブルが送受信中に抜かれたりして通信が切断された場合は LED、スイッチ、可変抵抗器のPictureBoxはDisableとなり ダイアログウィンドウ画面は 上記の <USB通信OFFの場合>の画面に戻ること。
   



  

<回路図> (→ PDFファイル

     


  


<外観> PIC32MZ評価ボード(→購入方法)を使った実験品の外観です。
     汎用モジュール評価ボード(段積みボード)には本テーマと関係ない部品が多々実装されています。

     


 
<動作結果> (→ 動画:1080pのHD動画を見ることができます。)

 

 モード PC画面 & PIC32MZ評価ボード 備考(部分拡大、詳細)
① PCの後、PICを起動した状態の写真です。
 USBは未接続の状態です。
② 接続ボタンをクリックしてUSB通信が確立した状態です。
 スイッチのON/OFF状態や可変抵抗器の電圧はリアルタイムモニタリングされています。
   
③ 接続ボタンをクリックして LEDのON/OFFボタンをクリックすることによって、PIC側のLEDを点灯させた状態です。 ON/OFFボタンスイッチが灰色→赤色に変化して、ランプの画像がOFFの画像からONの画像に変わっています。
 尚、スイッチはOFF、可変抵抗器の電圧は0vであることをモニタリングしています。
 
④ PIC側のスイッチを押した状態です。シェープの色がオレンジ色になって、スイッチがONの画像が表示されています。  
⑤ PIC側の可変抵抗器を回して、入力電圧が約1.2vの状態の写真です。 入力電圧のテキストボックスには1.21 [V]が表示され、またプログレスバーは、 36.7 (= 1.21/3.3) %を示しています。
  
 
⑥ PICの電源をOFFにした状態です。 USB通信が遮断されたことを検出して PCのダイアログウィンドウはUSB通信が確立していない状態になっています。  


<解説> 以下に、記載してある内容は要点だけです。 詳細はプロジェクトファイルを精読願います
   (以下は、Harmony v2.04 をもとに作成しています。最新のバージョンとは異なることがあるかもしれませんので注意してください。)


■ MHC設定
  Options 

項目  ①Config設定
Device & Project Configuration
  > PIC32MZ2048 Device Configuration 
②USB Library設定(その1)
Harmony Framework Configuration
 > USB Library 
③USB Library設定(その2)
Harmony Framework Configuration
 > USB Library  
MHC
備考 デフォルトからの変更要領:
 FPLLIDIV: DIV3
 FPLLICLK: PLL_POSC
 POSCMOD: EC

システムクロック周波数: 200MHz
外部
 主発振器: 24MHz

#pragma config FNOSC = SPLL
#pragma config POSCMOD = EC
#pragma config FPLLIDIV = DIV_3
#pragma config FPLLICLK = PLL_POSC
#pragma config FPLLMULT = MUL_50
#pragma config FPLLODIV = DIV_2
デフォルトからの変更要領:
 □USE USB Stack? チェック追加
 Number of Endpoints Used: 2
 Device Class: HID
 Product ID Selection: hid_basic_Demo
 左同
項目 ④Timer設定
Harmony Framework Configuration
 > Drivers
⑤I2C設定
Harmony Framework Configuration
 > Drivers
⑥ADCの設定(その1)
Harmony Framework Configuration
 > Drivers
MHC
備考 デフォルトからの変更要領:
 □USE Timer Driver? チェック追加
 Number of Driver Instance: 2
 TMR Driver Instance0 >
  Timer Module ID: TMR_ID_2
 同 Interrupt Priority: INT_PRIORITY_LEVEL4
デフォルトからの変更要領:
 □USE I2C Driver? チェック追加
 I2C Module ID: I2C_ID_2
 I2C CLOCK FREQUENCY(Hz): 100000
デフォルトからの変更要領:
 USE ADC Driver? チェック追加
 Channel Set Up >
   □Channel Instance 0: チェックを入れる
 Channel-0 Analogue...... Selection:
      ADCH_ALTERNATE_CLASS1_AN45
項目 ⑦ADCの設定(その2)
Harmony Framework Configuration
 > Drivers
   
MHC    
備考      



  Pin Settings

項目 ポート設定(その1)  ポート設定(その2) ポート設定(その3)
MHC
備考  RG15: 出力モード設定
 RB5: アナログ入力モード
RB0: 入力モード、プルアップ設定 I2Cポート設定
RA2: SCL2
RA3: SCLA



■ I2Cインターフェースのキャラクタ液晶表示ライブラリの追加要領
  キャラクタ液晶表示のライブラリ lcd_ACM1602_lib_i2c.h と lcd_ACM1602_lib_i2c.cを main.cと同じフォルダにコピーして、
 プロジェクトに追加します。
                                       (使い方 → I2Cインターフェースキャラクタ液晶表示制御 参照

  プロジェクトへの追加は以下のようにします。



■app.h を以下の要領で変更します。

 ①列挙型変数 APP_STATES の定義を以下のように変更し、所要の変数を定義ます。
  typedef enum
  {
    /* Application is initializing */
    APP_STATE_INIT,

    /* Application is waiting for configuration */
    APP_STATE_WAIT_FOR_CONFIGURATION,

    /* Application is running the main tasks */
    APP_STATE_MAIN_TASK,

    /* Application is in an error state */
    APP_STATE_ERROR

  } APP_STATES;




 ②構造体 APP_DATAのメンバーを以下のように変更し、宣言します。
  typedef struct
  {

    /* The application's current state */
    APP_STATES state;

    /* Device layer handle returned by device layer open function */
    USB_DEVICE_HANDLE usbDevHandle;

    /* Recieve data buffer */
    uint8_t * receiveDataBuffer;

    /* Transmit data buffer */
    uint8_t * transmitDataBuffer;

    /* Device configured */
    bool deviceConfigured;

    /* Send report transfer handle*/
    USB_DEVICE_HID_TRANSFER_HANDLE txTransferHandle;

    /* Receive report transfer handle */
    USB_DEVICE_HID_TRANSFER_HANDLE rxTransferHandle;

    /* Configuration value selected by the host*/
    uint8_t configurationValue;

    /* HID data received flag*/
    bool hidDataReceived;

    /* HID data transmitted flag */
    bool hidDataTransmitted;

    /* USB HID current Idle */
    uint8_t idleRate;


  } APP_DATA;



以下、app.h



■ app.cに、青字部の修正、追加をします。 

  
  ①インクルードファイルを追加します。stdio.hがないと、sprintf( )がコンパイラのバージョンによってコンパイルでエラーが
    でることがあります。
  #include "stdio.h"

  ②USB送受信バッファーや液晶表示のバッファー、また制御用の各種変数などを定義します。 USBの送受信バッファーメモリの
    配置は16バイト単位となっています。
  APP_DATA appData;
  char Buf[32];

  #define APP_MAKE_BUFFER_DMA_READY __attribute__ ((coherent, aligned(16))) //16バイト単位で配置
  uint8_t receiveDataBuffer[64] APP_MAKE_BUFFER_DMA_READY; //受信バッファー
  uint8_t transmitDataBuffer[64] APP_MAKE_BUFFER_DMA_READY; //送信バッファー
  ……
  ……


  ③NOPによる遅延関数delay_us(), delay_ms()を定義します。
  void delay_us(volatile unsigned int usec) //1μsec遅延
  {
  volatile int count;

  count = (int)(delay_Clock/20000000)*usec;
  ……
  ……


  ④DRV_TMR_AlarmRegister ( )から50msec毎に呼び出され、入力電圧のAD変換を行うコールバック関数を定義します。
  void tmrISR(uintptr_t context, uint32_t alarmCount ) //タイマ1コールバック関数
  {  
    //AD変換開始 Trigger a conversion
    PLIB_ADCHS_GlobalSoftwareTriggerEnable(ADCHS_ID_0);//変換開始(グローバルソフトウェアエッジトリガの場合)
    ……
    ……


  ⑤USBデータ送受信バッファーの補助関数です。
    データ送信済みフラグを偽として、ホストへの送信データをバッファーにセットしてホストへ送信要求を出します。 
    また、データ受信済みフラグを偽として、受信バッファを空にしてホストへ受信要求をだします。
  void BufferRW(void)
  {
    appData.hidDataTransmitted = false;
    /* Prepare the USB module to send the data packet to the host */
    USB_DEVICE_HID_ReportSend (USB_DEVICE_HID_INDEX_0,
    &appData.txTransferHandle, appData.transmitDataBuffer, 64 );

    appData.hidDataReceived = false;
    /* Place a new read request. */
    USB_DEVICE_HID_ReportReceive (USB_DEVICE_HID_INDEX_0,
    &appData.rxTransferHandle, appData.receiveDataBuffer, 64 );
  }  


  ⑥USB接続チェック用の関数です。 USBの送信バッファーに"Ready"の5バイトを書き込んで送信要求を
    出します。PC側で100msec毎に "Ready"受信できるかチェックします。"Ready"が受信できない場合は
    USB接続がされていないと判断します。
  void ConnectCheck(void) //接続確認応答
  {
    appData.transmitDataBuffer[2] = 'R';
    appData.transmitDataBuffer[3] = 'e';
    appData.transmitDataBuffer[4] = 'a';
    appData.transmitDataBuffer[5] = 'd';
    appData.transmitDataBuffer[6] = 'y';

    BufferRW();
  }


  ⑦RG15に接続されているLED の点灯/消灯を制御している関数です。USB受信信号の中にあるLED点灯/消灯制御信号
   (appData.receiveDataBuffer[1])を判別して制御しています。 点灯/消灯制御の結果を送信バッファーに
   セットして送信要求を出しています。
  void LedOnOff(void) //LED点灯/消灯制御
  {
    switch(appData.receiveDataBuffer[1])
    {
      case 0x30: //RG15 //LED1
        if(appData.receiveDataBuffer[2] == 0x30)
        {
          LATGbits.LATG15 = 0; //RG15 消灯
          appData.transmitDataBuffer[2] = 0x30;
        }
        else
        {
          LATGbits.LATG15 = 1; //RG15 点灯
          appData.transmitDataBuffer[2] = 0x31;
        }
        BufferRW();
        break;

     default :
        break;
    }
  }


  ⑧スイッチRB0の状態を検出している関数です。 スイッチの状態を検出後に結果を送信バッファーにセットして
    送信要求を出しています。
  void SwDetect(void) //スイッチの状態検出
  {
    if(appData.receiveDataBuffer[1] == 0x31) //RB0
    {
      if(PORTBbits.RB0 == 0)appData.transmitDataBuffer[2] = 0x30;
      else appData.transmitDataBuffer[2] = 0x31;
    }
    BufferRW();
  }


  ⑨AD変換された可変抵抗器の電圧を送信バッファーにセットして送信要求を出す関数です。
    また、AD変換値AdcValue 及びAD変換値の電圧換算値を I2Cインターフェースのキャラクタ液晶に
    表示も行っています。
  void VR_Detect(void) //可変抵抗器VRの電圧検出
  {
    float Volt;
    appData.transmitDataBuffer[2] = AdcValue; //下位8ビット at 12ビット
    appData.transmitDataBuffer[3] = ((AdcValue >> 8) & 0b00001111); //上位4ビット摘出 at 12ビット
    BufferRW();

    Volt = (float)AdcValue/4096*3.3;
    cd_ACM1602_cmd_i2c(0x80); //1行目の先頭へ
    sprintf(Buf,"AdcValue=%d ",AdcValue);
    lcd_ACM1602_str_i2c(Buf);
    lcd_ACM1602_cmd_i2c(0xC0); //2行目の先頭へ
    sprintf(Buf,"Volt=%.3f ",Volt);
    lcd_ACM1602_str_i2c(Buf);
  }


  ⑩USB初期化に係る関数です。 APP_USBDeviceEventHandler( )関数の中で呼び出されています。
  USB_DEVICE_HID_EVENT_RESPONSE APP_USBDeviceHIDEventHandler
    (
      USB_DEVICE_HID_INDEX iHID,
      USB_DEVICE_HID_EVENT event,
      void * eventData,
      uintptr_t userData
    )
  {

   ……
   ……

    return USB_DEVICE_HID_EVENT_RESPONSE_NONE;
  }



  ⑪USB初期化に係る関数です。APP_Tasks ( )のcase APP_STATE_INIT:の中で呼び出されています。
  void APP_USBDeviceEventHandler(USB_DEVICE_EVENT event, void * eventData, uintptr_t context)
  {
    switch(event)
    {

      ……
      ……

      default:
       break;
    }
  }

  ⑫ 変数の初期設定をします。
   appData.state = APP_STATE_INIT;
  appData.usbDevHandle = USB_DEVICE_HANDLE_INVALID;
  appData.deviceConfigured = false;
  ……
  ……


  ⑬ 専用(Dedicated) ADCのADC0入力ポートをデフォルトのAN0からAN45に変更して、ADCをスタートさせます。
  ADCTRGMODEbits.SH0ALT = 1; //入力ポート変更: AN0 --> AN45

  DRV_ADC0_Open();
  DRV_ADC_Start();


  ⑭ I2Cインターフェースのキャラクタ液晶を初期化して立ち上がり時に表示する文字を設定します。
  lcd_ACM1602_init_i2c(); //I2Cインターフェース式液晶初期化
  lcd_ACM1602_cmd_i2c(0x0C); //カーソル:0FF、ブリンク:0FF

  lcd_ACM1602_cmd_i2c(0x80); //1行目の先頭へ
  ……
  ……


  ⑮ タイマ1の割り込み諸元やコールバック関数をセットし、タイマをスタートさせます。
  uint32_t divider = 19532; //周期レジスタ初期値 //最初の割り込みまでの時間をセット
  //10 nsec x 19532 x 256 = 50.00192msec = 50mec //PBCLK3のデフォルトは100MHz

  DRV_TMR_AlarmRegister ( myHandle, divider, true, 0, tmrISR ); //繰り返し呼び出し
  ……
  ……


  ⑯ USBを初期化します。
  case APP_STATE_INIT:
  ……
  ……
   break;


  ⑰ USBの初期化完了を待ちます
  case APP_STATE_WAIT_FOR_CONFIGURATION:
  ……
  ……
   break;


  ⑱ 通常のUSBによる送受信を繰り返し行います。appData.receiveDataBuffer[0]の値を判別して
    接続確認応答、LED点灯/消灯制御、スイッチの状態検出、VR電圧検出などの処理を行います。
  case APP_STATE_MAIN_TASK:
  ……
  ……
    switch(appData.receiveDataBuffer[0]) //コマンドの種類判定
    {
      case 0x30: //接続確認応答
        ConnectCheck();
        break;

      case 0x80: //LED点灯/消灯制御
        LedOnOff();
        break;

      case 0x81: //スイッチの状態検出
        SwDetect();
        break;

      case 0x82: //VR電圧検出
        VR_Detect();
        break;

      ……
      ……




以下、app.c