STM32マイコンでUSBデバイス(USB_HSやUSB_FS)に対応している場合、CubeMX上で容易にUSB-CDC(Communications Device Class:CDCもしくはVirtual COM Port:VCP)を利用することができます。CubeMXの設定については今回は割愛しますが、CDCを利用する際に知っておきたい挙動、注意点について下記の3点を紹介します。
@usbd_cdc_if.cの関数ポインタ
Aバッファは割込み専用バッファ
B64byte以上のデータの送受信
@usbd_cdc_if.cの関数ポインタ
CubeMX上で生成されてユーザがCDC機能を利用する場合、usbd_cdc_if.c内の関数を利用することになります。usbd_cdc_if.c内ではCDCの送受信の関数の他にバッファの初期化設定やCDCクラスの要求処理等の関数があります。これらの関数は関数ポインタで呼び出されるため、プロジェクト内で関数をgrepしても個々の関数は見つかりません。USBD_Interface_fops_FSでポインタが管理されています。そのため、usbd_cdc_if.cの関数の初期化や受信処理等の一通りの処理はCubeMX生成時点で既に実装されており、自動的に関数が実行されます。関数ポインタで管理、呼び出されるため、usbd_cdc_if.c内に安易に自作関数を配置することは避けたほうが賢明です。自作関数を置く場合、関数ポインタにも自作関数を加えて、さらに他のUSBライブラリとの整合を取る必要があります。関数ポインタの整合を取らないと関数が正常に呼び出されず、挙動がおかしくなります。
Aバッファは割込み専用バッファ
CubeMX生成時点で自動でバッファUserRxBufferFS、UserTxBufferFSが生成されます。このバッファは割込みorDMAのバッファとして使用されるため、ユーザー用途のデータバッファとして利用するのは避けた方が賢明です。バッファは受信時に読み込み、送信時の書き込みだけで利用します。このバッファは受信時はUserRxBufferFSにデータが入り、次の受信で自動的にクリアされて新しいデータが上書きされます。送信のUserTxBufferFSについても同様です。MX_USB_DEVICE_Init()の初期化の際にバッファの設定が有効化されます。CDC受信時のCDC_Receive_FS関数内のUSBD_CDC_SetRxBufferでバッファを再度設定していますが、例えば受信の都度、バッファをクリアやバッファの位置を変更、他のバッファに設定するといった自動で生成されたバッファに手を加えると2回目以降の送受信ができないといったCDCの不具合が発生しました。
B64byte以上のデータの送受信
USB_FSの場合、USBの仕様上、通信1回のデータサイズが最大64byteに制限されます。通常は相手側(通常はPC)ではデータの分割、結合が自動で行われるため、無理に独自規格で64byte以上に実装し直す必要はありません。一方、STM32マイコン側はデータの分割、結合の処理が記述されていないため、64byte以上の送受信をするとデータが欠落します。Aで少し触れましたが、CDCの通信バッファであるUserRxBufferFSとUserTxBufferFSは毎回の通信で自動的に上書きされます。例えば受信の場合は毎回上書きされるため、64byte以上のデータを受信した場合は64byteの端数(余り)のデータがバッファに残ります。
64byte以上を送信する際にはUSBD_StatusTypeDefを64byte毎にCDC_Transmit_FSを呼び出して、USBの状態を見てバッファが空になってから次の64byteを送信するようにします。
64byte以上を受信する際には下記のようにUserRxBufferFSとは別にバッファ(CDC_RxBuffer、CDC_RxBufferLen)を用意し、上書きされる前にグローバル変数の受信バッファにデータを退避させます。必要に応じてユーザープログラム側でCDC_RxBufferの読み出し、CDC_RxBufferLenのクリアなどの処理を行ってください。
//グローバル変数としての受信バッファ
uint16_t CDC_RxBufferLen=0;
uint8_t CDC_RxBuffer[2048];
uint8_t CDC_RxBuffer[2048];
static int8_t CDC_Receive_FS(uint8_t* Buf, uint32_t *Len)
{
/* USER CODE BEGIN 6 */
USBD_CDC_SetRxBuffer(&hUsbDeviceFS, Buf);
USBD_CDC_ReceivePacket(&hUsbDeviceFS);
{
/* USER CODE BEGIN 6 */
USBD_CDC_SetRxBuffer(&hUsbDeviceFS, Buf);
USBD_CDC_ReceivePacket(&hUsbDeviceFS);
memcpy(CDC_RxBuffer+CDC_RxBufferLen,Buf,*Len);//データ退避処理追加
CDC_RxBufferLen=CDC_RxBufferLen+*Len;//データ長さ処理追加
return (USBD_OK);
/* USER CODE END 6 */
}
CDC_RxBufferLen=CDC_RxBufferLen+*Len;//データ長さ処理追加
return (USBD_OK);
/* USER CODE END 6 */
}
STM32マイコンのCDCに関する記事は多くありますが、CDC機能を応用しようとするとCubeMXで生成された関数の挙動を理解する必要があります。その他にもUSBの接続状態やポートをソフトウェアで開いたタイミングの検知などもできるため、機会があれば紹介したいと思います。