FPGAとリレーで打鍵音が楽しいキーボードの作り方

# はじめに
入力キーコマンドによりリレーがON/OFFしプロトタイプFPGAをリセットできる仕組みを作っていました。テストでリレーのON/OFFを繰り返してるとカチカチっという音が心地よく、ふと打鍵時にメカニカルな音が鳴りテンションが上がるキーボードを作りたくなりました。

# 開発環境
Windows 11
Vivado 2021.2

# セットアップ
- FPGA評価ボード　Zybo (xc7z010clg400-1)
- リレー　OMRON G2R-1 5VDC
- NチャネルFET　BSS138-7-F
- ショットキバリアダイオード　RB168M-60TFTR
- 抵抗
- UART-USB変換回路

# 製作
## リレーおよび周辺
例えば下図のようにリレー回路を組みます。DC5Vで動作するかっこいいリレーが日本橋で売っていたので使ってみました。今回使ったメカニカルリレーは入力側に一定の電圧を加えると、内部のソレノイドが磁気を発生し出力側のスイッチをON/OFFする部品です。機械的に動作するので、スイッチの開閉時にカチカチっと音がします。リレーはFETのゲートに小信号を与えることで動作させます。リレーに対し3.3Vの評価ボード出力信号を使っています。FET等の特性、定格に合わせて適宜抵抗の定数を調整します。また、ソレノイドはFETがOFFした瞬間は電流を流し続けようとするため、FETのドレイン側電圧が上昇(サージ電圧)し壊れます。そのため保護用にダイオードを挿入し電流を逃がす経路をつくっています。
![Schematic_relay_2022-12-06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2969856/fd04bb26-d64f-641c-aacb-ffb2bfe06d41.png)

今回はリレーを2個使ってます。上の回路が2回路入ってます。
![DSC_3588.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2969856/a50ae682-3ca8-4d06-2798-73cc53fc55d6.jpeg)


UART-USB変換基板はこんな感じです。Zyboでも使えるPmodコネクタに合わせてピンヘッダを出しています。USB typeC、5V半二重通信や電源機能も付いた豪華な基板です。今回はnetPort3、netPort4より右の部分だけあれば問題ありません。
![Schematic_com1_2022-12-06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2969856/e11b5c98-eed5-3618-21f0-9e6c47ede12c.png)

便利なので通信以外の目的でPmodを使う場合でもこの基板を使っています。
![DSC_3589.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2969856/698262fc-ea1d-6f17-0260-d50ce3d3d9d0.jpeg)

## FPGA
基本的にはUARTとステートマシンのみのシンプルな回路です。今回は信号にかかわらず、入力があったかどうかだけ見ればよいので、RDY信号しか見てません。RDYがHIGHになったら、一定期間リレーをONし、その後OFFにします。
```VHDL
--UARTコンポーネントの入出力ポート(内部詳細は割愛)
RS232_2:RStop port map(CLK,RST2,TXD2,RXD2,CTS2,RD2,DATArev2,DATAsend2,F_busy2,RDY2,open);

--リレー制御部
process(CLK)
begin
if(CLK'event and CLK='1')then
    if(RDY2 ='1')then
--RDY2がHIGHになったらreg_keyboardをHIGH(リレーをON)し、タイマーのカウントを0にする。
        reg_keyboard <= '1';
        count_key <= 0;
    elsif(count_key =5000000)then
--タイマーが5000000(CLK周波数が125MHzなので40msec)になったら、reg_keyboardをLOW(リレーをOFF)し、タイマーのカウントを0にする。。
        reg_keyboard <= '0';
        count_key <= 0;
    else
--タイマーのインクリメント
        count_key <=count_key + 1;
    end if;
end if;
end process;
```

## インターフェース
バックグラウンドで動作し、キー入力をUART出力したかったので簡単なプログラムを書きました。この辺はあまり詳しくないのでもっといいやり方があるかもしれません。
```python
from pyhooked import Hook, KeyboardEvent, MouseEvent
import serial

ser = serial.Serial('COM1', 115200) #シリアルポートの設定
oldkey = [] #既に押しているキーを記憶するための空のリストを定義

def handle_events(args):
    global oldkey
    if isinstance(args, KeyboardEvent):
        if args.event_type == 'key down' and not(args.current_key in oldkey): 
            print(args.current_key)
            ser.write(str.encode(args.current_key)) #キーが押されたらそのキーのアスキーコードをシリアルで送る。
            oldkey.append(args.current_key) #送ったキーを記憶する。
        elif args.event_type == 'key up':
            oldkey.remove(args.current_key)  #キーが離されたらそのキーの記憶を消す。

hk = Hook()  # make a new instance of PyHooked
hk.handler = handle_events  # add a new shortcut ctrl+a, or triggered on mouseover of (300,400)
hk.hook()  # hook into the events, and listen to the presses
```

# デモ
こんな感じで動きます。

<iframe width="560" height="315" src="https://www.youtube.com/embed/syfDjWnb-lA" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>

<iframe width="560" height="315" src="https://www.youtube.com/embed/Ci6ChowVZFg" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>


# 参考
https://github.com/ethanhs/pyhooked