こんにちは。18のHitomosiです。この記事は [rogy Advent Calendar 2018]の20日目の記事です。
マウスとかキーボードが作れるってよくないですか?PCに接続するだけで動くだけでも正直面白いんですが、作ったものでPCが操作できます!
マウスとかキーボードを作るといっても、今回はいわゆるそういうものを作るというわけではなくて、USBでPCに接続するとマウスやらキーボードとして使える何かみたいなものを作ります。要するにHIDデバイスを作るわけですね。ヒューマンインタフェースデバイスデバイス。
今回はボタン入力のものを作っていきますが、様々なセンサーの値を反映させるみたいなこともできるので、いろいろ応用が利くかと思います。
最低限必要な部品
- STM32のうち、USBが使えるもの
今回は、F042K6T6を使います。このマイコンは秋月で250円で売っているうえにUSB通信に水晶発振子がいらないのがメリットです。逆に言うとこれ以外のSTM32を使う場合は通信に必要な48MHzを得るために水晶発振子が必要です。STM32はPLLを内蔵しているので、12MHzあたりの水晶発振子を用意すればいいかと思います。 - 3.3Vレギュレータ
STM32は電源としてUSBのVBUSの5Vを使うと死ぬので用意しましょう。 - USBコネクタ
今回はUSBで接続するので必要です。
回路図
ポリスイッチはDIP化キットについていたものなんですが、たぶんあったほうがいいです。
余っているIOポートにスイッチとかLEDとかセンサとかを付けます。今回はそういう基板があったのでPA8にスイッチを付けていますが、別に何でもいいです。
JTAGは書き込みとデバッグに使っているだけなのでSTLinkとかでもいいです。
今回の環境
種類 | 環境 |
---|---|
マイコン | STM32F042K6T6 |
OS | Windows10 |
IDE | Atollic TrueSTUDIO 9.0.1 |
コードジェネレーター | STM32CubeMX |
デバッガ | SEGGER J-Link EDU Mini |
CubeMXの設定
Pinout
Peripherals
- SYS
今回はSWDで書き込むので以下のようにします。
- USB
以下のように設定します。
- RCC
以下のように設定します。
- TIM14
タイマ割り込みを使うのでActivateしておきます。
MiddleWares - USB_DEVICE
以下のように設定します。
あとはPA8をGPIO_Inputにしておしまいです。
こんな感じになります。
Clock configuration
Pinoutの設定をしてから開くと以下のようなダイアログが出てくるかと思うのでYesを押します。自動でエラーが起こらないように設定を変えてくれます。
エラーが起こらないようになのでメインのクロックなどが変な値になっていることがあります。そうなっていたら修正したい箇所をいじってからAlt+Fを押すともう一度自動修正してくれます。
Configration
まずは、PA8をプルアップしておきます。
GPIOの設定からできます。
TIM14の設定をします。
今回は1msごとにタイマ割り込みをしようと考えてるので、こんな感じに設定します。
割り込み許可をしておきます。
終わったら、Project→Generate CodeからIDEとしてTrueStudioを選択してOKを押します。
TruestudioのProjectが生成されるので、そのProjectを開きます。
TrueStudioでの操作
自動で生成されているSrc/main.cを開き、
タイマ割り込みをするため、/USER CODE BEGIN X/と/USER CODE END X/(Xには数字とかが入る)に以下のようなコードを書きます
/* USER CODE BEGIN PV */
/* Private variables ---------------------------------------------------------*/
static volatile uint8_t interrupt;
/* USER CODE END PV */
/* USER CODE BEGIN 0 */
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) {
if (htim->Instance == TIM14) {
interrupt = 1;
}
}
/* USER CODE END 0 */
/* USER CODE BEGIN 1 */
interrupt = 0
/* USER CODE END 1 */
/* USER CODE BEGIN 2 */
HAL_TIM_Base_Start_IT(&htim14);
/* USER CODE END 2 */
ここからはマウスとキーボードで違います。
https://notes.iopush.net/stm32-custom-usb-hid-step-by-step-2/
https://damogranlabs.com/2018/02/stm32-usb-hid-mouse-keyboard/
をめちゃくちゃ参考にしているので分からなかったらそっちを見たほうが早いかもしれません。
マウス
ライブラリを読み込んだ時点でだいたい設定が終わっているのでほとんど操作はいりません。
今回はボタンを押したら(押している間)カーソルが右に動くやつを作っていきます。
/* USER CODE BEGIN 1 */ interrupt = 0; struct mouseHID_t { uint8_t buttons; int8_t x; int8_t y; int8_t wheel; }; struct mouseHID_t mouseHID; mouseHID.buttons = 0; mouseHID.x = 0; mouseHID.y = 0; mouseHID.wheel = 0; /* USER CODE END 1 */
こんな感じで構造体を定義しておきます。
※https://notes.iopush.net/stm32-custom-usb-hid-step-by-step-2/からの引用です。
この構造体に値を入れて、
USBD_HID_SendReport(&hUsbDeviceFS, &mouseHID, sizeof(struct mouseHID_t));
という関数を呼び出すと値が送られます。
じゃあどれにどんな値を入れるとどうなるんだという話なんですが、
- mouseHID.buttons
ボタンの状態をビット単位で入力します。
1になっていると押している扱いになります。
7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
---|---|---|---|---|---|---|---|
– | – | – | – | – | ホイールクリック | 右クリック | 左クリック |
要するに、
mouseHID.buttons = 0b001; //左クリック
USBD_HID_SendReport(&hUsbDeviceFS, &mouseHID, sizeof(struct mouseHID_t));
と書くと左クリックしていることになります。
- mouseHID.x , mouseHID.y
これの値の相対座標の位置にカーソルが移動します。ベクトル的な感じです。int8_t(-127~127)の範囲であることに注意。 - mouseHID.wheel
正の値が入っていると、上方向にスクロールします。負の値だと逆です。やたらすごい速度が出ます。
あとはこんな感じに書いてあげればボタンを押している間カーソルが右に動いてくれるかと思います。
/* USER CODE BEGIN WHILE */
while (1)
{
while(interrupt == 0);
interrupt = 0;
if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0) == 0){
mouseHID.x = 5;
}
USBD_HID_SendReport(&hUsbDeviceFS, &mouseHID, sizeof(struct mouseHID_t));
mouseHID.x = 0;
/* USER CODE END WHILE */
本来USBD_HID_SendReportの2つ目の引数にはuint8_tのものを入れないといけないんですが、int8_tを入れてしまっています。受け取った側がint8_tとして解釈するんですよね……動くのでおそらく大丈夫です。
キーボード
キーボードはライブラリをいろいろ書き換えなきゃいけません。ここで注意なんですが、CubeMXでコードを再生成するとライブラリも再生成されます。
要するにいじったところが全部戻されてしまうので注意してください。私は解決法が見つからなかったためライブラリを/Srcに避難させたうえで再生成されたものを消しています。解決法を知っている方がいたら教えていただきたいです。
ライブラリのうち書き換えないといけない部分はMiddlewares/ST/STM32_USB_Device_Library/Class/HIDの/Inc/usbd_hid.hと/Src/usbd_hid.cです。
usbd_hid.c
レポートディスクリプタを書き換えます。
HID_MOUSE_ReportDesc[HID_MOUSE_REPORT_DESC_SIZE]を、
__ALIGN_BEGIN static uint8_t HID_MOUSE_ReportDesc[HID_MOUSE_REPORT_DESC_SIZE] __ALIGN_END ={ //45 bytes 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 0x95, 0x01, // REPORT_COUNT (1) 0x75, 0x08, // REPORT_SIZE (8) 0x81, 0x03, // INPUT (Cnst,Var,Abs) //1 byte 0x95, 0x06, // REPORT_COUNT (6) 0x75, 0x08, // REPORT_SIZE (8) 0x15, 0x00, // LOGICAL_MINIMUM (0) 0x25, 0x65, // LOGICAL_MAXIMUM (101) 0x05, 0x07, // USAGE_PAGE (Keyboard) 0x19, 0x00, // USAGE_MINIMUM (Reserved (no event indicated)) 0x29, 0x65, // USAGE_MAXIMUM (Keyboard Application) 0x81, 0x00, // INPUT (Data,Ary,Abs) //6 bytes 0xc0 // END_COLLECTION };
のように書き換えます。
※https://damogranlabs.com/2018/02/stm32-usb-hid-mouse-keyboard/ から引用したものです。
キーボードなのにHID_MOUSE_ReportDescじゃ気持ち悪いと言われたらそうなんですが、ちょっと大変なので今回はそのまま使います。
次に、USBD_HID_CfgDescを書き換えます。 nInterfaceProtocolを1(keyboard)としてください。
usbd_hid.h
HID_EPIN_SIZEを0x08にしてください。 HID_MOUSE_REPORT_DESC_SIZEをレポートディスクリプタのbyte数と同じ値(今回は45)にしてください。
これで書き換えはおしまいです。
main.c
/* USER CODE BEGIN 1 */
interrupt = 0;
struct keyboardHID_t {
uint8_t modifiers;
uint8_t reserved;
uint8_t key[6];
};
struct keyboardHID_t keyboardHID;
keyboardHID.modifiers = 0;
keyboardHID.reserved = 0;
for(int i = 0 ;i < 6; i++){
keyboardHID.key[i] = 0;
}
/* USER CODE END 1 */
今度はこんな感じの構造体を定義します。
今回は分かりやすいですが、各変数について説明すると以下のようになります。
- modifiers
修飾キーの動作をビット単位で入力します。1にしていると押してることになります。
GUIはWindowsだとWin、MacだとCommandになります。
7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
---|---|---|---|---|---|---|---|
右GUI | 右Alt | 右Shift | 右Ctrl | 左GUI | 左Alt | 左Shift | 左Ctrl |
- reserved
予約されている部分です。要するに使いません。 - key[6]
キーコードが6つまで入れられる配列です。
キーに割り当てられているコードを入れると押していることになります。つまり、通常のキーの同時押しは6つが限界です。
キーコードは以下のサイトを参考にするといいです。
http://www2d.biglobe.ne.jp/~msyk/keyboard/layout/usbkeycode.html
Usage IDがコードになります。Aだったら0x04といった具合です。
あとはこんな具合に書けば、ボタンを押すごとにasdfghが表示されるかと思います。
/* USER CODE BEGIN WHILE */
while (1)
{
while(interrupt == 0);
interrupt = 0;
if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_8) == 0){
keyboardHID.key[0] = 0x04; //a
keyboardHID.key[1] = 0x16; //s
keyboardHID.key[2] = 0x07; //d
keyboardHID.key[3] = 0x09; //f
keyboardHID.key[4] = 0x0A; //g
keyboardHID.key[5] = 0x0B; //h
}
USBD_HID_SendReport(&hUsbDeviceFS, &keyboardHID, sizeof(struct keyboardHID_t));
for(int i = 0 ;i < 6; i++){
keyboardHID.key[i] = 0;
}
/* USER CODE END WHILE */
これでキーボードも完成です。
まとめ
マウスとキーボード(という名の1ボタンの何か)ができました。ハードと操作するソフトウェアの組み合わせで様々なことができるので、ぜひ作ってみてください!