CalicoCPU でキースキャン (Pmod KYPD 読み取り)

[自作CPU「CalicoCPU」](https://elchika.com/article/f38275fc-1f46-4e77-bc30-c172971ecf0f/)で、[Pmod KYPD](https://digilent.com/shop/pmod-kypd-16-button-keypad/) 上で押されたキーの認識をしてみた。

# Pmod KYPD

Pmod KYPD は、列に対応する4本の入力端子と、行に対応する4本の出力端子を持つ。
そして、4×4で16個のキーがある。
出力端子はプルアップされており、LOWが入力された列のキーが押されるとLOWになる。
そのため、キーは一度に1個しか押されないと仮定すれば、1列ずつLOWを入力し、出力がLOWになる位置を探すことで、どのキーが押されたかを判定できる。

# プログラム

ポート1に接続された Pmod KYPD で押されたキーの番号を、ポート0の下位4ビットに出力する。

以下のプログラムは、[MikeAssembler](https://mikecat.github.io/mike_assembler/) でアセンブルできる。

```
target calico

; PORT0 lower : read result
; PORT1 : Pmod KYPD

	MOVI A, 0xf
	DRIVE 0, A
	DRIVE 1, A
main_loop:
	; スキャンを行うキーの番号
	MOVI A, 0
	; スキャンを行う列を表すビット
	MOVI B, 8
scan_loop:
	; 列のビットを反転して、出力する
	MOV C, B
	NOT C
	OUT 1, C
	NOP
	; 行のスキャン結果を読み込み、反転する
	IN C, 1
	NOT C
	SHR C, 4
	; 押されているボタンがあれば、判別処理に進む
	MOVI D, key_pressed
	JNZ C, D
	; 次の列のスキャンに進む
	ADDI A, 4
	SHR B, 1
	MOVI D, scan_loop
	JNZ B, D
	; 全ての列のスキャンが終わったので、最初に戻る
	MOVI D, main_loop
	JNZ D, D
	; 行のどのビットが1かを調べる
key_pressed:
	; 1のビットが無くなるまでシフトする
	SHR C, 1
	MOVI D, find_row_loop
	JNZ C, D
	MOVI D, key_concluded
	JNZ D, D
find_row_loop:
	; シフトした回数を数える
	ADDI A, 1
	MOVI D, key_pressed
	JNZ D, D
	; どのキーが押されたのかの調査が完了した
key_concluded:
	; テーブルを引いて押されたキーに対応する数値を求める
	MOVI3 C, key_table
	ADD C, A
	ADD C, A
	MOVI D, key_out
	JNZ C, C
key_table:
	MOVI C, 0
	JNZ D, D
	MOVI C, 7
	JNZ D, D
	MOVI C, 4
	JNZ D, D
	MOVI C, 1
	JNZ D, D

	MOVI C, 0xF
	JNZ D, D
	MOVI C, 8
	JNZ D, D
	MOVI C, 5
	JNZ D, D
	MOVI C, 2
	JNZ D, D

	MOVI C, 0xE
	JNZ D, D
	MOVI C, 9
	JNZ D, D
	MOVI C, 6
	JNZ D, D
	MOVI C, 3
	JNZ D, D

	MOVI C, 0xD
	JNZ D, D
	MOVI C, 0xC
	JNZ D, D
	MOVI C, 0xB
	JNZ D, D
	MOVI C, 0xA
	JNZ D, D

key_out:
	OUT 0, C
	MOVI D, main_loop
	JNZ D, D
```

以下は、このプログラムをHEX形式で表したものである。

```
:100000002F161E2068848A9A00988A8F8FEBCD9FC6
:10001000344BE55FE3DF8BE2C7FF9FE2C7F3DF31DD
:10002000EBCDDFAB83008181E5C7FC9BA0DFA7DFC1
:10003000A4DFA1DFAFDFA8DFA5DFA2DFAEDFA9DF8E
:0F004000A6DFA3DFADDFACDFABDFAADF92E3DF2C
:00000001FF
```

# 解説

Pmod KYPD の入力や出力は「1ビットをLOWにする」が基本であるが、
「0になったかどうか」でループの終了を判定するため1ビットをHIGHにする形でデータを持ち、入出力時に反転する形にした。
また、右シフトで下位へ抜けて0になるようにするため、上位ビットから下位ビットの順で判定する。

まず、キーの番号を4ずつ増やしながら、列のスキャンを行う。
よって、Pmod KYPD の列は左ほど上位のビットになっているので、左ほど若い番号になる。
なお、CalicoCPU では入力を一旦 D-FF で受けているため、`OUT` 命令の直後に `IN` 命令を実行すると、`OUT` 命令の結果が反映される前の状態が読み込まれてしまい、スキャンがうまくいかない。
そこで、`OUT` と `IN` の間に `NOP` を挟んだ。
ある列についていずれかの行のキーが押されていることがわかったら、キーの番号を1ずつ増やしながら右シフトを行い、行のどのキーが押されているかを調べる。
このときは、下位ビットから先に抜けるので、同じ列の中では下位ビットほど若い番号になる。
すなわち、Pmod KYPD の行は下ほど下位のビットになってるので、下ほど若い番号になる。

まとめると、キーに対して以下のように番号を割り当てたことになる。

```
 3   7  11  15
 2   6  10  14
 1   5   9  13
 0   4   8  12
```

一方、Pmod KYPD のキーは以下のように配置されている。

```
 1   2   3   A
 4   5   6   B
 7   8   9   C
 0   F   E   D   
```

単純な計算式で変換するのは難しそうなので、変換テーブルを用意することにした。
変換テーブルの1項目は、「変換結果の値を格納する命令」と「次の処理に移るためのジャンプ命令」で構成する。
そして、「変換テーブルの最初の項目のアドレス」に「変換テーブルの1項目のサイズ×変換する値」を足したアドレスにジャンプすることで、テーブルを引く。

最後に、変換したキーの番号を出力し、次のスキャンに移る。

# 実行結果

ポート0の下位ビットに [1-Digit 7seg Board](https://elchika.com/article/fb155241-6490-4cbf-ae85-c166c3743874/#h_1-Digit%207seg%20Board) を、ポート1に Pmod KYPD を接続して実行した。

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

押したキーの番号が正しく表示に反映されている。