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 |
![]() |
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 | |
![]() |
URL | 実行には、Visual Studio 2012, 2017 の Visual C++ 再頒布可能パッケージが必要です。 |
・PCのアプリケーションプログラムの基本動作は、以下とする。
尚、USBコネクタの脱着を検出し USB接続状態表示(接続完了/未接続 及びランプ表示の灰色/オレンジ色)に反映のこと。
<外観> PIC32MZ評価ボード(→購入方法)を使った実験品の外観です。
汎用モジュール評価ボード(段積みボード)には本テーマと関係ない部品が多々実装されています。
<動作結果>(→ 動画:1080pのHD動画を見ることができます。)
<解説>以下に、記載してある内容は要点だけです。 詳細はプロジェクトファイルを精読願います
(以下は、Harmony v2.04 をもとに作成しています。最新のバージョンとは異なることがあるかもしれませんので注意してください。)
■ MHC設定
■ Options
■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 (青字部がデスクリプタ部分です)