STM32マイコンをUSBキーボードとして認識させる


# はじめに

STMマイコンをマウスの他に，キーボードとして認識させることに成功したためまとめておく．なお，この記事を書いた本人はUSB通信はおろか，マイコンやプログラムに関しても熟知していない初学者なので，間違った内容が含まれている可能性が十分にある．ご容赦いただきたい．

# 環境

- STM32CubeIDE_1.9.0s
- STM32F446RE
- Windows 10

# .iocの設定

.iocの設定は[前回](https://qiita.com/holmes/items/3b3522f41d9547093e39)とほぼ同様なので，異なる点だけを以下に示す．

## Pinout & Configuration

![USB_DEVICE.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2220781/ebd17875-d1f8-cee6-43a8-aff6d9c82ebe.png)

- ```Middcleware->USB_DEVICE->Class For FS IP```から```Custom Human Interface Device Class (HID)```を選択．
- ```Parameter Setting->HID_FS_BINTERVAL```を希望のサンプリングレートにする．(例では，1[ms]おきにマスターから要求が来るようにした．)
- ```Parameter Setting->USBD_CUSTOM_HID_REPORT_DESC_SIZE```をレポートディクリプターのサイズに変更．(今回は37．)
- ```Parameter Setting->USBD_CUSTOMHID_OUTREPORT_BUF_SIZE```を送信するデータのサイズ．(今回は64．)

---

# コード

## コードの編集

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

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

```c
/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */
typedef enum
{
  false,
  true
} bool;
/* USER CODE END PTD */
```

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

```c
/* Private variables ---------------------------------------------------------*/
/* USER CODE BEGIN PV */
// キーボードの状態を保存する構造体
typedef struct keyboardHID_t
{
  uint8_t modifiers;
  uint8_t key[7];
} KeyBoard;
// ここに状態を格納する
KeyBoard kye_board_status = {0};
// ボタンを押した時間を格納する
int push_time[sizeof(KeyBoard) / sizeof(uint8_t)] = {0};
// フラッグ
bool start_flag = false; // 入力開始
bool shift_flag = false; // shiftを押す

/* USER CODE END PV */
```

```c
 /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    // loop();
    // スタートフラッグがtrueのとき,500[ms]間隔で実行される
    if (start_flag == true && HAL_GetTick() - push_time[0] > 500)
    {
      // キーのリリース
      if (kye_board_status.key[0] == 0x04)
      {
        for (int i = 0; i < 7; i++)
        {
          kye_board_status.key[i] = 0;
        }
      } // キーのプッシュ
      else
      {
        for (int i = 0; i < 7; i++)
        {
          kye_board_status.key[i] = 0x04 + i;
        }
      }
      // シフトのプッシュ
      if (shift_flag == true)
      {
        kye_board_status.modifiers = 1 << 1;
      } // シフトのリリース
      else
      {
        kye_board_status.modifiers = 0;
      }
      push_time[0] = HAL_GetTick();
    }
    // 送信
    USBD_CUSTOM_HID_SendReport(&hUsbDeviceFS, (uint8_t *)&kye_board_status, sizeof(KeyBoard));
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
```

```USB_DEVICE\App\usbd_custom_hid_if.c```内の配列，```__ALIGN_BEGIN static uint8_t CUSTOM_HID_ReportDesc_FS[USBD_CUSTOM_HID_REPORT_DESC_SIZE]__ALIGN_END```を以下にように書き換える．

```c
/** Usb HID report descriptor. */
__ALIGN_BEGIN static uint8_t CUSTOM_HID_ReportDesc_FS[USBD_CUSTOM_HID_REPORT_DESC_SIZE] __ALIGN_END =
{
  /* USER CODE BEGIN 0 */
        0x05, 0x01, // USAGE_PAGE (Generic Desktop)
        0x09, 0x06, // USAGE (Keyboard)
        0xa1, 0x01, // COLLECTION (Application)
        0x05, 0x07, // USAGE_PAGE (Keyboard)

        0x19, 0xe0, // USAGE_MINIMUM (Keyboard LeftControl)
        0x29, 0xe7, // USAGE_MAXIMUM (Keyboard Right GUI)
        0x15, 0x00, // LOGICAL_MINIMUM (0)
        0x25, 0x01, // LOGICAL_MAXIMUM (1)
        0x75, 0x01, // REPORT_SIZE (1)
        0x95, 0x08, // REPORT_COUNT (8)
        0x81, 0x02, // INPUT (Data,Var,Abs) //1 byte

        0x19, 0x00, // USAGE_MINIMUM (Reserved (no event indicated))
        0x29, 0x65, // USAGE_MAXIMUM (Keyboard Application)
        0x15, 0x00, // LOGICAL_MINIMUM (0)
        0x25, 0x65, // LOGICAL_MAXIMUM (101)
        0x95, 0x07, // REPORT_COUNT (7)
        0x75, 0x08, // REPORT_SIZE (8)
        0x81, 0x00, // INPUT (Data,Ary,Abs) //7 bytes
  /* USER CODE END 0 */
  0xC0    /*     END_COLLECTION              */
};
```

## 説明

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

### コードの補足

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

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

1. 特殊キー(GUI,Alt,Shift,Ctrl)
2. キーコード

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

2には各ボタンに対応したーキーコードを格納する．対応表は以下を参考にした．
<https://bsakatu.net/doc/usb-hid-to-scancode/>

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

```USB_DEVICE\App\usbd_custom_hid_if.c```内の配列```__ALIGN_BEGIN static uint8_t CUSTOM_HID_ReportDesc_FS[USBD_CUSTOM_HID_REPORT_DESC_SIZE]__ALIGN_END```にユーザー定義のレポートディスクリプターを定義することができる．この配列の要素数は[Pinout & Configuration](#pinout--configuration)で設定した```Parameter Setting->USBD_CUSTOM_HID_REPORT_DESC_SIZE```と一致させる必要がある．
レポートディスクリプターの各項目の**イメージ**を記しておく．

- ```USAGE_PAGE & USAGE_PAGE``` : 2つでデバイスの種類を指定( USAGE　∈　USAGE_PAGE )
- ```COLLECTION & END_COLLECTION``` : 複数のInput,Output,Featureをグループ化
- ```USAGE_MINIMUM & USAGE_MAXIMUM``` : 配列やビットマップのデータに，MINからMAXまでの間の任意のUSAGE_PAGEの要素を割り当て
- ```LOGICAL_MINIMUM & LOGICAL_MAXIMUM```: 送信するデータの最大値及び最小値を指定
- ```REPORT_SIZE``` : １つあたりのデータの大きさを指定( 単位[ bit ] )
- ```REPORT_COUNT``` : 送信するデータの個数
- ```INPUT``` : 送信するデータのフィールド生成

# 実行

作成したコードをマイコンに書き込み&実行しLive Expressionsを起動する．```kye_board_status```,```start_flag```,```shift_flag```を追加すると以下の用になる.
![result.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2220781/36b7e9a0-3560-190d-285d-84095438550b.png)
ここで任意のフラッグを```true```に変更する．```start_flag```をtrueにすると```abcdefg```が500[ms]間隔で入力される．また，```shift_flag```を```true```に変更した後に同様に入力をスタートすると```ABDCDEFG```が入力される．
以上のように動けば成功だ．

# 終わりに

前回に引き続きSTMマイコンでHIDを制作してみた．同じように迷っている方の参考になれば幸いだ．また，もし間違っている箇所があればご指摘いただけるとありがたい．

# 参考文献

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

- [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/3b3522f41d9547093e39)