stm32:usb

stm32

USB

D+,D-どちらが1.5kΩでプルアップされているかを検出してLS,HSを切り替える.
stm32なら基本的にD+をプルアップしとけば良さそう。
デバイスの型番によって1.5kΩのプルアップ抵抗が内蔵されているものとそうでないものがあるので注意する。参考文献のAN4879に記載あり。
なお,このプルアップ抵抗を外付けする場合,GPIOでプルアップを切り替えられるようにしておくと良い.
プルアップしただけにしておくと,stm32のリセットをかけても接続したOS側はデバイスが切り離されたとは認識してくれない, GPIOで制御するにようにしておけば,リセットがかかったときにプルアップも切断される.
Nucleoに実装されているST-Linkの回路図を真似すれば良い。

Blue pill, black pill and RadioHead library
Configuring a micro-controller for low-speed USB communication
LTM2884

USBデバイスと言ってもいろいろある.
USBのパケットの処理 一回の転送での最大データ量は64バイト(アイソクロナス転送以外) このため、CDC_Receive_FS関数においてLenの最大値は64となりそう。
CubeMXから出力すると

USBD_CDC_SetRxBuffer(&hUsbDeviceFS, &Buf[0]);

が呼ばれている。これはどうやら次受信するデータを入れるバッファの先頭アドレスを入れる。 これはhUsbDeviceFS.pClassData->RxBufferに記憶される。 しかし、&Buf[0]から次に入れるバッファの場所をずらさないのであればこれは初期化の時に指定していたuint8_t UserRxBufferFS[APP_RX_DATA_SIZE];を再び入れているだけなので、毎回呼び出す必要ななさそう。 もしもUSBからのデータを受け取るバッファをリングバッファとかで構成し、次に入れる場所のアドレスをずらしたいならここを逐一ずらしていばいいだろう。

USBD_CDC_ReceivePacket(&hUsbDeviceFS);

が呼ばれると次のデータの受信準備をする。データが受信されると再びCDC_Receive_FSが呼び出される。 注意点として、次に受信するデータを入れるバッファの場所を変えていない場合、そのバッファの場所のデータをそのまま上書きしてしまうので、先にバッファから受信したデータを回収し、その後USBD_CDC_ReceivePacketを呼び出したほうがいいと思われる。

STM32のUSB使う際の注意
エンドポイント0は64バイトまでしか一回に送信できない。(これはUSBエンドポイントの仕様?) なので送受信はそのあたりを配慮し、細切れにするのがいい? しかしSTのUSBデバイスライブラリはその辺をよしなにやってくれているのだろうか
STM32 USB CDC注意点
秋月電子通商で売られているSTM32F042K6T6を使ってUSBシリアルデバイスを作る
[STM32メモ] USBでCDCを2つ持つ複合デバイスを作る ディスクリプタ編(IADとか)

stm32のmainstreamシリーズは(ここではstm32f1を指す)、CANとUSBのSRAMが同じバッファを分断して共有するので、両方を使う場合には注意が必要。(AN4879参照)

CubeMXでUSBデバイスをしてコード生成しても、WindowsではどうやらすぐにUSBデバイスとして認識してくれない模様。この理由として、ホストがデバイスに対してデバイス記述子というものを要求するのだが、これに失敗するための模様。
調査の結果、usbd_desc.c内のGet_SerialNum()周りが原因だった模様。stm32でもUSBが動作するコードを眺めていたところ

/**
  * @brief  Create the serial number string descriptor
  * @param  None
  * @retval None
  */
static void Get_SerialNum(void)
{
  uint32_t deviceserial0;
  uint32_t deviceserial1;
  uint32_t deviceserial2;
 
  deviceserial0 = *(uint32_t *) DEVICE_ID1;
  deviceserial1 = *(uint32_t *) DEVICE_ID2;
  deviceserial2 = *(uint32_t *) DEVICE_ID3;
 
  deviceserial0 += deviceserial2;
 
  if (deviceserial0 != 0)
  {
    IntToUnicode(deviceserial0, &USBD_StringSerial[2], 8);
    IntToUnicode(deviceserial1, &USBD_StringSerial[18], 4);
  }
}

と実装されているものがあった。stm32のチップごとに割り当てられている96bitのユニークID(UID)を利用してUSBデバイスとしてのシリアル番号記述子を生成している。
しかし、CubeMXですぐに出力されたコードの場合、

/**
  * @brief  Create the serial number string descriptor
  * @param  None
  * @retval None
  */
static void Get_SerialNum(void)
{
  uint32_t deviceserial0;
  uint32_t deviceserial1;
  uint32_t deviceserial2;
 
  deviceserial0 += deviceserial2;
 
  if (deviceserial0 != 0)
  {
    IntToUnicode(deviceserial0, &USBD_StringSerial[2], 8);
    IntToUnicode(deviceserial1, &USBD_StringSerial[18], 4);
  }
}

となっていた、stm32デバイスのヘッダファイルに定義されているDEVICE_ID1~DEVICE_ID3を代入する処理が消えていることがわかる。
この結果USBD_StringSerialにUSBデバイスのシリアル番号記述子が定義されていないためホストから見てデバイス記述子の読み取りに失敗している模様。
USBD_FS_SerialStrDescriptorという関数がGet_SerialNum()を呼び出しており、ここの実装を見てみる。

uint8_t * USBD_FS_SerialStrDescriptor(USBD_SpeedTypeDef speed, uint16_t *length)
{
  UNUSED(speed);
  *length = USB_SIZ_STRING_SERIAL;
 
  /* Update the serial number string descriptor with the data from the unique
   * ID */
  Get_SerialNum();
  /* USER CODE BEGIN USBD_FS_SerialStrDescriptor */
 
  /* USER CODE END USBD_FS_SerialStrDescriptor */
  return (uint8_t *) USBD_StringSerial;
}

となっており/* USER CODE BEGIN USBDFSSerialStrDescriptor */のコメントアウトがある。CubeMXはBEGIN~ENDの間に書いたコードはCubeMXの生成で上書きされない。
どうやらここに自分の思うようなデバイスシリアル番号記述子を実装してね、ということの模様。気づくまでに数日を要した。
USB VCP Get_SerialNum
STM32F103ZE - USB - Login to Windows with the same ComPort
STM32CubeMX H7x5/H7x7 USB CDC bug

PCと通信できる.要はFT232とか使わなくてもシリアル通信で送受信可能
現状ではLLAPIの関数は用意されていない。HALのSysTickを使っているからだろうか

参考文献

  • stm32/usb.txt
  • 最終更新: 2024/06/29
  • by yuqlid