STM32 NUCLEO-F446REでGamepadを読む方法

STM32 NUCLEO-F446REにはUSBのホスト機能がありますが、HIDデバイスは標準でマウスとキーボードにしか対応していません。それを有線コントローラーに対応させる方法について書きました(この方法でコントローラー以外のHIDデバイスも対応できます)。
# 必要なもの
- STM32 NUCLEO-F446RE
:::note warn
STM32 NUCLEO-F446REでUSB機能を使うには外部クロックが必要です。
各自用意してください。
:::
- HID Gamepad(有線)
  自分はELECOMのJC-GP20SBKを使いました。おすすめです。
:::note warn
XInputは基本的に読めないので、DirectInputモードで読みましょう。
LogicoolのF310など、DirectInputでも読めない機種もあります。
(恐らくHIDのプロトコルに完全に準拠しているわけではないことが原因)
:::
- USB Type-Aメス端子
- STM32 CubeIDE
# 設定
まずCubeIDEを開いて新しくF446REのプロジェクトを作成してください。
iocファイルで以下の設定をしましょう。
![1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3843466/3c68b0fa-9a7f-6632-1abb-50a466c658db.png)
ConnectivityのUSB_OTG_FSタブを開いてModeをHost_Onlyにしましょう。

![2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3843466/624d87d6-7a72-c6ea-e2a9-6fc09d11afd1.png)
SpeedをHost Low Speed 1.5MBit/sにしましょう。(全然この速度でも高速で読めるのでLow Speedにしました。)

![5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3843466/3b69d505-d047-813b-643e-b7e4b8a8092f.png)
クロックの設定はこんな感じです。これは外部クロックに8MHzを使った場合です。各自で外部クロックに合わせて、To USBに48MHzが供給されるように設定しましょう。

設定は以上です。
コードをジェネレートしてください。
# プログラム
![6.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3843466/4e4b56f8-ad98-61de-b639-b3b77a98329f.png)
新しくソースフォルダ「usbh_hid_unknown」を作成してください。

![7.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3843466/a0878af1-6373-a21b-5f49-1d83595520c0.png)
このフォルダを右クリックしてAdd/remove include pathを押してそのままOKしてください。

https://github.com/Doraemonjayo/usbh_hid_unknown

そして上のリンクのusbh_hid_unknown.cとusbh_hid_unknown.hをこのフォルダに入れてください。

次にプロジェクト内の`Middlewares\ST\STM32_USB_Host_Library\Class\HID`のIncとSrcを開き、usbh_hid.hとusbh_hid.cを編集します。

:::note alert
iocファイルを変更するとusbh_hid.hとusbh_hid.cの中身が元に戻ってしまうため、その都度編集してください。
:::

usbh_hid.hの31行目付近
```diff_c:usbh_hid.h
#include "usbh_hid_mouse.h"
#include "usbh_hid_keybd.h"
+ #include "usbh_hid_unknown.h"

/** @addtogroup USBH_LIB
```
usbh_hid.hの252行目付近
```diff_c:usbh_hid.h

/* Interface Descriptor field values for HID Boot Protocol */
- #define HID_BOOT_CODE                                 0x01U
+ #define HID_BOOT_CODE                                 0xFFU
#define HID_KEYBRD_BOOT_CODE                          0x01U
#define HID_MOUSE_BOOT_CODE                           0x02U
```
usbh_hid.cの109行目付近

```diff_c:usbh_hid.c
extern USBH_StatusTypeDef USBH_HID_MouseInit(USBH_HandleTypeDef *phost);
extern USBH_StatusTypeDef USBH_HID_KeybdInit(USBH_HandleTypeDef *phost);
+ extern USBH_StatusTypeDef USBH_HID_UnknownInit(USBH_HandleTypeDef *phost);

USBH_ClassTypeDef  HID_Class =
```
usbh_hid.cの189行目付近

```diff_c:usbh_hid.c
  else
  {
-     USBH_UsrLog("Protocol not supported.");
-     return USBH_FAIL;
+ 	  USBH_UsrLog("Unknown device found!");
+ 	  HID_Handle->Init = USBH_HID_UnknownInit;
  }

```
main.cで
```c:main.c
#include "usb_host.h"
#include "usbh_core.h"
#include "usbh_hid.h"
```
上記をインクルードしてUSER CODE BEGIN 0に
```c:main.c
uint8_t gamepad_data[USBH_HID_UNKNOWN_REPORT_SIZE];
void USBH_HID_EventCallback(USBH_HandleTypeDef *phost)
{
    HID_TypeTypeDef hidType;
    uint8_t* unknown_info;

    hidType = USBH_HID_GetDeviceType(phost);
    if(hidType == HID_UNKNOWN){
    	unknown_info = USBH_HID_GetUnknownInfo(phost);
    	for(int i = 0; i < sizeof(gamepad_data); i ++){
    		gamepad_data[i] = unknown_info[i];
    	}
    }
}
```
を書いてください。
# Let's read ゲームパッド
### 配線
| F446RE | Type-A |
|:-:|:-:|
| VDD | VCC |
| PA12 | D+ |
| PA11 | D- |
| GND | GND |

![Image.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3843466/b9853b79-8fb0-d64c-8bf5-e2e456dbbdfc.jpeg)
こんな感じ

![Image (1).jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3843466/ba525142-9bb9-4b09-b706-82fda3650e53.jpeg)
DirectInputにするのを忘れずに

### 実行
デバッグを開始し、Live Expressionに`gamepad_data`と入力して中身を見ると、ゲームパッドの各データをあります。比較的わかりやすいデータ構造なので各自で解読して使ってください。
![スクリーンショット 2024-10-09 164954.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3843466/d651bf02-4c18-bf56-15ac-8f23a81d0f13.png)