YS電子工作ラボ

USB(デバイスモード)CDCクラス
PIC-PC間 文字列送受信


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



・ USB (デバイスモード)のCDCクラスを用いて PICとPC間で文字列の送受信を行う例を紹介します。 PCのアプリケーションは VC# 2017 及びVC++2017で作成しています。

・ 本サンプルプログラムはマイクロチップの以下2つのプログラムを参考にして作成しています。
 ① Harmonyのcdc_com_port_single
     "C:\microchip\harmony\v2_04\apps\usb\device\cdc_com_port_single"
 ② MLAのDevice - CDC - Basic Demo
     "C:\microchip_solutions_v2013-06-15\USB\Device - CDC - Basic Demo\PC Software Example\VCsharp 2005"
 
・ USB(デバイスモード)CDCクラスを使ったプログラムを実装したPICでWindows PCにアクセスする場合、USBドライバーはWindowsの標準ドライバーが使えます。 しかし 全くなにもせずにそのままつかえるわけではなく 最初のアクセス1回目だけ INFファイル(場所: "C:\microchip\harmony\v2_04\apps\usb\device\cdc_msd_basic\inf\mchpcdc.inf" )に記載されているベンダーID、プロダクトIDなど各種の設定値をつかってドライバーをインストールする必要があります。 したがって、Windowsからドライバーインストールを要求され、ドライバーの所在場所を要求されたらINFファイルがあるフォルダを指定します。

・PC側プログラムは以下を参照してください。

PC開発環境      参照URL
 VC#(Visual Studio 2017) URL 
VC++(Visual Studio 2017)  URL




<仕様>
 ・PCとPIC32MZ間でUSB接続による文字列の送受信をおこなう。
 ・USBのライブラリはHarmonyのCDCクラスをつかうこと
 ・PC側から文字列に対して PIC側からそれぞれに対応した文字列を返信すること
  送受信の文字列は 以下とする

      PCからの送信文字列 PICからの返信文字列
Japan\r Tokyo 
America\r Washington
U.K.\r  London
France\r  Paris
How are you?\ Pardon?

 ・PICのキャラくタ液晶はI2Cインターフェースのキャラクタ液晶とし、以下を表示のこと。
   1行目 …… PCからの受信文字列
   2行目 …… PCへの送信文字列

 ・PC側のアプリケーションは マイクロソフトのVC# またはVC++で作成のこと。  実行ファイルを以下からダウンロードできます。

PC開発環境    実行ファイル  備考
 VC#(Visual Studio 2017) URL   
VC++(Visual Studio 2017)  URL  実行には、Visual Studio 2012, 2017 の Visual C++ 再頒布可能パッケージが必要です。


 ・PCのアプリケーションプログラムの基本動作は、以下とする。
   尚、USBコネクタの脱着を検出し USB接続状態表示(接続完了/未接続 及びランプ表示の灰色/オレンジ色)に反映のこと。

操作/動作 ①プログラム立ち上がり後の初期状態
  USB未接続状態、 
②接続ボタンをPC使用可能COMポートを検索し、使用COMポートのコンボボックスにセットする。セレクトされたCOMポートがPIC側とUSB接続できない場合は COMポート変更等を促すメッセージを表示する。 ③コンボボックスから別のCOMポートを選択する。
PC画面
操作/動作 ④PIC側と接続できたCOMポートを選択できた場合は準備完了"I am Ready"のメッセージを表示する。 またこれと同時に送信ボタン、送信データ用のコンボボックス、"接続完了"の文字 及びオレンジ色のランプ表示も行う。 ⑤送信データをコンボボックスから選択後、送信ボタンをクリックしてPIC側に送信する。PIC側からの返信データを受信データ用のリッチテキストボックスに表示する。
 尚、表示された受信データはクリアボタンで消去できること。
⑥停止ボタンをクリックするとUSB接続は遮断されること。 これに伴い使用COMポートは空欄となり、"未接続"の文字が表示されランプ表示は灰色となること。
PC画面




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


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




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

操作/動作 PC画面 & PIC32MZ評価ボード 備考
①プログラム立ち上がり後の初期状態
  USB未接続状態
②接続ボタンをPC使用可能COMポートを検索し、使用COMポートのコンボボックスにセットする。セレクトされたCOMポートがPIC側とUSB接続できない場合は COMポート変更等を促すメッセージを表示する。
③コンボボックスから別のCOMポートを選択する。
④PIC側と接続できたCOMポートを選択できた場合は準備完了"I am Ready"のメッセージを表示する。 またこれと同時に送信ボタン、送信データ用のコンボボックス、"接続完了"の文字 及びオレンジ色のランプ表示も行う。
⑤送信データをコンボボックスから選択後、送信ボタンをクリックしてPIC側に送信する。PIC側からの返信データを受信データ用のリッチテキストボックスに表示する。
 尚、表示された受信データはクリアボタンで消去できること。
⑥停止ボタンをクリックするとUSB接続は遮断されること。 これに伴い使用COMポートは空欄となり、"未接続"の文字が表示されランプ表示は灰色となること。



<解説>以下に、記載してある内容は要点だけです。 詳細はプロジェクトファイルを精読願います
       (以下は、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
: 3
 Function1 > Device Class: CDC

 Product ID Selection :cdc_com_port_single_demo
 □Enable SOF Events: チェック追加
 
項目 ④I2C設定
Harmony Framework Configuration
 > Drivers
   
MHC    
備考 デフォルトからの変更要領:
 □USE I2C Driver? チェック追加
 I2C Module ID: I2C_ID_2
 I2C CLOCK FREQUENCY(Hz): 100000
   



  ■Pin Settings

項目 ポート設定(その1) 
MHC  
備考 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's state machine's initial state. */
    APP_STATE_INIT=0,

    /* Application waits for device configuration*/
    APP_STATE_WAIT_FOR_CONFIGURATION,

    /* Wait for a character receive */
    APP_STATE_SCHEDULE_READ,

    /* A character is received from host */
    APP_STATE_WAIT_FOR_READ_COMPLETE,

    /* Wait for the TX to get completed */
    APP_STATE_SCHEDULE_WRITE,

    /* Wait for the write to complete */
    APP_STATE_WAIT_FOR_WRITE_COMPLETE,

    /* Application Error state*/
    APP_STATE_ERROR


    // /* Application's state machine's initial state. */
    // APP_STATE_INIT=0,
    // APP_STATE_SERVICE_TASKS,
    //
    // /* TODO: Define states used by the application state machine. */
  } APP_STATES;



 ②構造体 APP_DATAのメンバーを既存のメンバーをコメントアウト後、以下のように変更し、宣言します。
  typedef struct
  {
    /* Device layer handle returned by device layer open function */
    USB_DEVICE_HANDLE deviceHandle;

    /* Application's current state*/
    APP_STATES state;

    /* Set Line Coding Data */
    USB_CDC_LINE_CODING setLineCodingData;

    /* Device configured state */
    bool isConfigured;

    /* Get Line Coding Data */
    USB_CDC_LINE_CODING getLineCodingData;

    /* Control Line State */
    USB_CDC_CONTROL_LINE_STATE controlLineStateData;

    /* Read transfer handle */
    USB_DEVICE_CDC_TRANSFER_HANDLE readTransferHandle;

    /* Write transfer handle */
    USB_DEVICE_CDC_TRANSFER_HANDLE writeTransferHandle;

    /* True if a character was read */
    bool isReadComplete;

    /* True if a character was written*/
    bool isWriteComplete;

    /* Flag determines SOF event occurrence */
    bool sofEventHasOccurred;

    /* Break data */
    uint16_t breakData;

    /* Application CDC read buffer */
    uint8_t * readBuffer;


    // /* The application's current state */
    // APP_STATES state;
    //
    // /* TODO: Define any additional data used by the application. */

    } APP_DATA;



以下、app.h





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

  ① MHCが作成したapp.cを修正するより、マイクロチップのサンプルソフトcdc_com_port_singleのapp.c
   ("C:\microchip\harmony\v2_04\apps\usb\device\cdc_com_port_single\firmware\src\app.c")を修正した方が
   簡単なので app.cを入れ替えます。

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


  ④USB制御の読込バッファーのメモリ変数を 16バイト単位で KSEG1(カーネルセグメント1)領域に確保して
   キャッシュ化してはいけない旨定義します。
  /* Macro defines USB internal DMA Buffer criteria*/

  #define APP_MAKE_BUFFER_DMA_READY __attribute__((coherent)) __attribute__((aligned(16)))
           //coherent: キャッシュ化不可、指定メモリ領域…KSEG1(カーネルセグメント1)
           //aligned(16):16バイト単位で要メモリ確保
  #define APP_READ_BUFFER_SIZE 512
  uint8_t APP_MAKE_BUFFER_DMA_READY readBuffer[APP_READ_BUFFER_SIZE];


  ⑤プログラムで使用される変数、受信文字列 及び返信文字列などの宣言、定義を行います。
    尚、uint8_t APP_MAKE_BUFFER_DMA_READY _IamReadyは、charとは C言語上かなり異なった挙動をするので
   返信用文字列は同様の内容を別々に定義します。
  int delay_Clock = 200000000; //システムクロック:200MHz
  char Buf[32];

  int ix = 0;
  char* str;
  char* str2;

  char Ready[] = "Are you Ready ?\r";
  char Japan[] = "Japan\r";
  char America[] = "America\r";
  char UK[] = "U.K.\r";
  char France[] = "France\r";
  char IamFine[] = "I am fine\r";

  char IamReady[] = "I am Ready";

  //返信用 "Tokyo","Washington","London","Paris","Pardon ?"には、
  //char と uint8_t APP_MAKE_BUFFER_DMA_READYの2つを定義
  char Tokyo[] = "Tokyo";
  char Washington[] = "Washington";
  char London[] = "London";
  char Paris[] = "Paris";
  char Pardon[] = "Pardon ?";

  uint8_t APP_MAKE_BUFFER_DMA_READY _IamReady[] = "I am Ready\r\n";
  uint8_t APP_MAKE_BUFFER_DMA_READY _Tokyo[] = "Tokyo";
  uint8_t APP_MAKE_BUFFER_DMA_READY _Washington[] = "Washington";
  uint8_t APP_MAKE_BUFFER_DMA_READY _London[] = "London";
  uint8_t APP_MAKE_BUFFER_DMA_READY _Paris[] = "Paris";
  uint8_t APP_MAKE_BUFFER_DMA_READY _Pardon[] = "Pardon ?";



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

    count = (int)(delay_Clock/20000000)*usec;
    do //実測 at 200MH (Clock=200000000)
    { //delay_us(1000):1000.4μsec delay_us(100):100.6μsec delay_us(10):10.5μsec  delay_us(1):1.5μsec
      asm("NOP"); asm("NOP"); asm("NOP"); asm("NOP"); asm("NOP");asm("NOP");
  ……
  ……



  ⑦USB初期化に係る関数です。APP_USBDeviceEventHandler ( )のUSB_DEVICE_CDC_EventHandlerSet( ):の中で呼び出されています。
  USB_DEVICE_CDC_EVENT_RESPONSE APP_USBDeviceCDCEventHandler
  (
    USB_DEVICE_CDC_INDEX index ,
    USB_DEVICE_CDC_EVENT event ,
    void * pData,
    uintptr_t userData
  )

    ……
    ……

    return USB_DEVICE_CDC_EVENT_RESPONSE_NONE;

  }


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

  ……
  ……

      case USB_DEVICE_EVENT_ERROR:
       default:
       break;
    }
  }



  ⑨USB制御がリセットする場合に機能する関数です。USBコネクタを抜き差しした場合などに機能しています。
  bool APP_StateReset(void) //USBコネクタを抜き差しした場合などに機能する
  {
    /* This function returns true if the device
     * was reset */

    bool retVal;

  ……
  ……

    return(retVal);
  }


  ⑩APP_Initialize( )関数の中で USBのデバイスハンドル、仮想UARTの通信条件(通信速度:9600bps、データ長:8ビット)、
    USB受信バッファなど いろいろな変数を初期化しています。
  /* Device Layer Handle */
  appData.deviceHandle = USB_DEVICE_HANDLE_INVALID ;

  /* Device configured status */
  appData.isConfigured = false;

  /* Initial get line coding state */
  appData.getLineCodingData.dwDTERate = 9600;
  appData.getLineCodingData.bParityType = 0;
  appData.getLineCodingData.bParityType = 0;
  appData.getLineCodingData.bDataBits = 8;


  /* Set up the read buffer */
  appData.readBuffer = &readBuffer[0];


  ⑪I2Cインターフェースのキャラクタ液晶を初期化して、 液晶に "USB device CDC Start !!"と表示しています。
  lcd_ACM1602_init_i2c(); //I2Cインターフェース式液晶初期化
  lcd_ACM1602_cmd_i2c(0x0C); //カーソル:0FF、ブリンク:0FF

  lcd_ACM1602_cmd_i2c(0x80); //1行目の先頭へ
  lcd_ACM1602_str_i2c("USB device CDC ");

  delay_ms(1000);


  lcd_ACM1602_cmd_i2c(0xC0); //2行目の先頭へ
  lcd_ACM1602_str_i2c(" Start !! ");
  delay_ms(2000);



  ⑫USBを初期化して、システムの構築をしています。電源投入の起動時 及びUSBコネクタ抜き差しなどが発生した場合に
    このステートが機能します。 通常のPC-PIC間のデータ読み書きに係る送受信にはこのステートは関与しません。
  switch(appData.state)
  {
    case APP_STATE_INIT:

      /* Open the device layer */
      appData.deviceHandle = USB_DEVICE_Open( USB_DEVICE_INDEX_0, DRV_IO_INTENT_READWRITE );

  ……
  ……

      /* If the device is configured then lets start reading */
      appData.state = APP_STATE_SCHEDULE_READ;
      }
      break;


  ⑬PCからのデータを読込バッファーに読み込むステートです。system_tasks( )から呼ばれるサイクルで繰り返し呼び
   出されています。
  case APP_STATE_WAIT_FOR_READ_COMPLETE:

    if(APP_StateReset())
    {
      break;
    }

    /* Check if a character was received or a switch was pressed.
    * The isReadComplete flag gets updated in the CDC event handler. */

    if(appData.isReadComplete)
    
  ……
  ……

      appData.state = APP_STATE_SCHEDULE_WRITE;
    }

    break;


   ⑭PCへデータを送信するステートです。 読み込みバッファーの文字列を判別して 対応する文字列を選び返信しています。
     尚、リセット信号が入った場合は 送信処理はしないでこのステートを終了するようになっています。
  case APP_STATE_SCHEDULE_WRITE:

    if(APP_StateReset()) //リセットが発生した場合
    {
      break; //(送信処理をしないで)このステート終了
    }

    /* Setup the write */

    appData.writeTransferHandle = USB_DEVICE_CDC_TRANSFER_HANDLE_INVALID;
    appData.isWriteComplete = false;
    appData.state = APP_STATE_WAIT_FOR_WRITE_COMPLETE;


    if(strcmp(readBuffer,Ready) == 0)
    { //下記2つの文字列は分けて処理のこと
      str = _IamReady; //返信用文字列
      str2 = &IamReady[0]; //表示用文字列
    }
    else if(strcmp(readBuffer,Japan) == 0) //Japanの場合 → Tokyo
    {
      str = _Tokyo;
      str2 = &Tokyo[0];
    }
    else if(strcmp(readBuffer,America) == 0) //Americaの場合 → Washinton
    {
      str = _Washington;
      str2 = &Washington[0];
    }
    else if(strcmp(readBuffer,UK) == 0) // U.K.の場合 → London
    {
      str = _London;
      str2 = &London[0];
    }
    else if(strcmp(readBuffer,France) == 0) //Franceの場合 → Paris
    {
      str = _Paris;
      str2 = &Paris[0];
    }
    else //その他の場合 → Pardon ?
    {
      str = _Pardon;
      str2 = &Pardon[0];
    }


    //I2C 制御キャラクタ液晶表示
    lcd_ACM1602_cmd_i2c(0x80); //1行目の先頭へ
    sprintf(Buf,"%s ",readBuffer);//
    lcd_ACM1602_str_i2c(Buf); //液晶表示

    for(ix = 0; ix < 20; ix++)Buf[ix] = 0x20; //バッファークリア //必須


    lcd_ACM1602_cmd_i2c(0xC0); //2行目の先頭へ
    sprintf(Buf,"%s ",str2); //
    lcd_ACM1602_str_i2c(Buf); // 開始メッセージ1行目表示

    for(ix = 0; ix < 20; ix++)readBuffer[ix] = '\0'; //バッファークリア //必須


    //文字列を返信
    appData.readBuffer[0] = appData.readBuffer[0] + 1;
    USB_DEVICE_CDC_Write(USB_DEVICE_CDC_INDEX_0,
    &appData.writeTransferHandle,
    str, 12, //12バイト: 最大送信データサイズ //12バイト: _IamReady[] = "I am Ready\r\n";
    //strのサイズが12バイトより小さい場合 unknownデータが送られるので要注意
    USB_DEVICE_CDC_TRANSFER_FLAGS_DATA_COMPLETE);

    break;



以下、app.c





 <注記> デスクリプタについて
  USBの内部構成を決めるデスクリプタはMHCで設定すればMHCが自動的に system_init.cの中に作成してくれます。 ソースコードレベルで
 自分で作成するする部分は何もありません。 

以下、sytem_init.c (青字部がデスクリプタ部分です)