STM32マイコンをUSBマウスとして認識させる


# はじめに

STMマイコンを用いて，HIDを自作している人が少なかったので試してみた．情報が少なくかなり苦労したが，一応動くところまではできたのでまとめておく．なお，この記事を書いた本人はUSB通信はおろか，マイコンやプログラムに関しても熟知していない初学者なので，間違った内容が含まれている可能性が十分にある．ご容赦いただきたい．

# 環境

- STM32CubeIDE_1.9.0
- STM32F446RE
- Windows 10

# .iocの設定

まずはお馴染みのiocの設定からする．

## 1.Clock Configuration

![ClockConfig.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2220781/dd1b904e-e48d-35f4-10ba-6e787f9365fa.png)
Clock Configurationの設定は以上のようにした．丸をつけた場所がUSBに関わるところなので，そこを合わせればうまくいくはず．Input Frequencyは各自が使用している発振子の値に合わせ，最終的にTo USBが48[Mhz]になるようにうまく設定する．To USBの項目がグレーアウトされている場合は先に```Pinout & Configuration```タブから```Connectivity->USB_OTG_FS->Mode```を```Device_Only```にしておく．

## 2.Pinout & Configuration

![USB_OTG_FS.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2220781/ee96e707-729a-dd8b-62e7-1a8c51b055dc.png)
```Conectivity->USB_OTG_FS->Mode```から```Device_Only```を選択．

---

![USB_DEVICE.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2220781/a289f68d-b786-b3ef-652f-f5e40a8b4822.png)
```Middcleware->USB_DEVICE->Class For FS IP```から```Human Interface Device Class (HID)```を選択．
同タブの```Parameter Setting->HID_FS_BINTERVAL```を希望のサンプリングレートにする．(例では，1[ms]おきにマスターから要求が来るようにした．)

---

![USB_DEVICE_device_descripter.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2220781/dc0fe067-52a2-125c-70e3-859930b66ae0.png)
同タブの```Device Descripter```でデバイスディスクリプターの設定をする．
全て初期値の状態で良いが，せっかくなので```PRODUCT_STRING```でデバイス名をつけてみる．(例では,OriginalMouseとしてみた．)

# コード

## コードの編集

```main.c```の中に以下を追加する．

```c
/* USER CODE BEGIN Includes */
//warningが出ないように
#include "usbd_hid.h"
/* USER CODE END Includes */
```

```c
/* USER CODE BEGIN PV */
//マウスの状態を保存する構造体
typedef struct
{
  int8_t buttons;
  int8_t x;
  int8_t y;
  int8_t wheel;
} Mouse;
//ここに状態を入れる
Mouse mouse_status = {0};
//ボタンを押した時間を格納する
uint32_t before_time[sizeof(Mouse) / sizeof(int8_t)] = {0};
/* USER CODE END PV */
```

```c
/* USER CODE BEGIN 0 */
//どこにプロトタイプ宣言してよいのかわからないのでここに宣言しておく
extern USBD_HandleTypeDef hUsbDeviceFS;
/* USER CODE END 0 */
```

```c
 while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
    //10[ms]ボタンが押されていた場合離す
    if (mouse_status.buttons != 0)
    {
      if (HAL_GetTick() - before_time[0] > 10)
      {
        mouse_status.buttons = 0;
      }
    }
    else
    {
      before_time[0] = HAL_GetTick();
    }
    //1[ms]ホイールが回転していたら止める
    if (mouse_status.wheel != 0)
    {
      if (HAL_GetTick() - before_time[3] > 1)
      {
        mouse_status.wheel = 0;
      }
    }
    else
    {
      before_time[3] = HAL_GetTick();
    }
    //送信
    USBD_HID_SendReport(&hUsbDeviceFS, (uint8_t *)&mouse_status, sizeof(Mouse));
  }
  /* USER CODE END 3 */
  ```

## 説明

以下は読み飛ばし,実行まで進んで頂いても構わない．

### コードの補足

コードのコメントに書いてあることに加えて少し補足する．(なお，```while```内の処理はだいぶおそまつなので適宜変えていだきたい.)
送信する関数の定義を見に行くと

```c
uint8_t USBD_HID_SendReport(USBD_HandleTypeDef *pdev, uint8_t *report, uint16_t len)
```

となっている．
レポートは```uint8_t```となっているので，一見正の値しか送信できなさそうだが，負の値を入れてもしっかり動く(なんでや).

送信するデータの中身は以下の4つのデータで構成されており，以下のデータを配列に入れて渡してあげれば良い．今回は扱いやすいように構造体に入れて渡した．

1. ボタンの状態(右クリック，左クリック，ホイールクリック)
2. X軸方向の変位
3. Y軸方向の変位
4. ホイールの回転

1はデータの各ビットがそれぞれのボタンの状態に対応しており，1だと押していることになる．各ビットとボタンの対応は以下のようになっている.
| bit    | 7   | 6   | 5   | 4   | 3   | 2        | 1   | 0   |
| ------ | --- | --- | --- | --- | --- | -------- | --- | --- |
| button | -   | -   | -   | -   | -   | ホイール | 右  | 左  |

2,3は127~-127の値を入れることができる.
4はホイールの回転を127~-127の値で入れる事ができる．ボタンと異なり，即座に0に戻してあげないとすごい速度でスクロールしてしまう．

### レポートディスクリプター

今回は書き換えなかったが```\Middlewares\ST\STM32_USB_Device_Library\Class\HID\Src\usbd_hid.c```内にある以下のレポートデスクリプタを書き換えてあげれば他のデバイスも作れそうだ．各項目の詳細については[次回](https://qiita.com/holmes/items/2453f7d57efe9a4a66f2#レポートディスクリプター)の記事にまとめた．

```c
__ALIGN_BEGIN static uint8_t HID_MOUSE_ReportDesc[HID_MOUSE_REPORT_DESC_SIZE] __ALIGN_END =
{
  0x05, 0x01,        /* Usage Page (Generic Desktop Ctrls)     */
  0x09, 0x02,        /* Usage (Mouse)                          */
  0xA1, 0x01,        /* Collection (Application)               */
  0x09, 0x01,        /*   Usage (Pointer)                      */
  0xA1, 0x00,        /*   Collection (Physical)                */
  0x05, 0x09,        /*     Usage Page (Button)                */
  0x19, 0x01,        /*     Usage Minimum (0x01)               */
  0x29, 0x03,        /*     Usage Maximum (0x03)               */
  0x15, 0x00,        /*     Logical Minimum (0)                */
  0x25, 0x01,        /*     Logical Maximum (1)                */
  0x95, 0x03,        /*     Report Count (3)                   */
  0x75, 0x01,        /*     Report Size (1)                    */
  0x81, 0x02,        /*     Input (Data,Var,Abs)               */
  0x95, 0x01,        /*     Report Count (1)                   */
  0x75, 0x05,        /*     Report Size (5)                    */
  0x81, 0x01,        /*     Input (Const,Array,Abs)            */
  0x05, 0x01,        /*     Usage Page (Generic Desktop Ctrls) */
  0x09, 0x30,        /*     Usage (X)                          */
  0x09, 0x31,        /*     Usage (Y)                          */
  0x09, 0x38,        /*     Usage (Wheel)                      */
  0x15, 0x81,        /*     Logical Minimum (-127)             */
  0x25, 0x7F,        /*     Logical Maximum (127)              */
  0x75, 0x08,        /*     Report Size (8)                    */
  0x95, 0x03,        /*     Report Count (3)                   */
  0x81, 0x06,        /*     Input (Data,Var,Rel)               */
  0xC0,              /*   End Collection                       */
  0x09, 0x3C,        /*   Usage (Motion Wakeup)                */
  0x05, 0xFF,        /*   Usage Page (Reserved 0xFF)           */
  0x09, 0x01,        /*   Usage (0x01)                         */
  0x15, 0x00,        /*   Logical Minimum (0)                  */
  0x25, 0x01,        /*   Logical Maximum (1)                  */
  0x75, 0x01,        /*   Report Size (1)                      */
  0x95, 0x02,        /*   Report Count (2)                     */
  0xB1, 0x22,        /*   Feature (Data,Var,Abs,NoWrp)         */
  0x75, 0x06,        /*   Report Size (6)                      */
  0x95, 0x01,        /*   Report Count (1)                     */
  0xB1, 0x01,        /*   Feature (Const,Array,Abs,NoWrp)      */
  0xC0               /* End Collection                         */
};
```

# 実行

作成したコードをマイコンに書き込み&実行しLive Expressionsを起動する．```mouse_status```を追加すると以下の用になる.
![Live_Expressions.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2220781/4b3b1132-6222-12fd-37d4-b990be637a45.png)
ここで各データを書き換えてみる．
例えば，```x```に1を入れるとカーソルが右に動き続け,```buttons```の値を(00000010)<sub>2</sub>=(2)<sub>10</sub>にすると右クリックされるはずだ．
以上のように動けば成功だ．

# 終わりに

STMマイコンに関する情報が少なく，ここまでたどり着くのに時間がかかってしまった．同じように迷っている方の参考になれば幸いだ．また，もし間違っている箇所があればご指摘いただけるとありがたい．

# 参考文献

参考にさせていただいたサイト．

- [STM32でマウスとキーボードを作る](https://blog.rogiken.org/blog/2018/12/20/stm32%E3%81%A7%E3%83%9E%E3%82%A6%E3%82%B9%E3%81%A8%E3%82%AD%E3%83%BC%E3%83%9C%E3%83%BC%E3%83%89%E3%82%92%E4%BD%9C%E3%82%8B/)
- [USB HID クラス](https://www.wdic.org/w/TECH/USB%20HID%20%E3%82%AF%E3%83%A9%E3%82%B9)
- [STM32でUSBをベアメタル(8) - HIDを構成](https://blueeyes.sakura.ne.jp/2020/04/25/3108/)

# 次回

[STM32マイコンをUSBキーボードとして認識させる](https://qiita.com/holmes/items/2453f7d57efe9a4a66f2)