PICで10キー入力を4bit Binaryに変換する

簡単な入力インターフェースを作ろうと思ったとき、ある程度以上の入力数や、 汎用性を持たせようと思ったときに便利なのが10キーである。
よく見かけるのは、プッシュホン電話と同じキー配列をした12キーのものだが、 0からFまでの16キーのものも売っている。
また、マトリックス上に接線されているものと、それぞれのスイッチにつき1つ づつピンが出ているものもあるようです。
後者のものはBCDに変換する専用のロジックICがあるので、そういったものを 使っても簡単に変換できますが、マトリックス状に接線されたものをPICで簡単に 変換できないかと思って作ったのが今回の回路です。
実際には入力部分だけを転用して他のアプリケーションに利用するために試作した 物で、これだけで何かできるわけではありませんが、練習にはちょうど良いでしょう。

使用したパーツ

10key pin asign キーパッドはピンアサインが不明でしたが、一番安いやつを千石で買ってきました。 16キーのも気になりましたが、とりあえず数値入力できれば良いので、12キーので我慢。 パーツ屋を探せば似たようなのが、\1k位で手に入るでしょう。
分解して調べたところ、案の定マトリックス状に接続されており、3列x4段で計7本、 他にフレームグラウンド1本の計8本のピンが出ていました。
調査結果はイラストのとおり。正面向かって左側から0,1,2,..とピン番号を振って います。
PICは16F84を使おうかと思ったのですが、近頃はPIC16F88の方が安いみたいなのでPIC16F88 を使うことにしました。
おまけに、キークリックの音を出す圧電ブザーをつけてみました。
5Vのデジタル出力でまともな音が出るか疑問でしたが、試したところ1KHz位のの方形波 を加えると、そこそこの音が出ました。裸の状態だと十分かなと思いますが、ケースに 入れたりすると微妙かもしれません。
より大きな音や澄んだ音(正弦波)にしたければ外付け回路が必要でしょう。
実際、ブザーを鳴らす為に結構なステップ数を費やしているので、外部の発振回路出力 をON/OFFする方が賢いかもしれません。

アルゴリズム

アルゴリズムとしては、仕組みは7 Segment LEDのダイナミック点灯と同じで、3列を 順番にドライブしつつ、4段の出力を読み取るという方法です。
出力をプルアップしておき、Lowになった場合入力があったとすると、次のような真理値表 になります。(表は正論理で示したので0がLowです。)
inputoutput
1234567
k
e
y
30110111
60111011
90111101
#0111110
21010111
51011011
81011101
01011110
11100111
41101011
71101101
*1101110
N/Axxx1111
出力するデータは、演算して出しても良いのですが、EEPROMが実装されていますので、 データはROMに入れておきROMのアドレスをキー入力で与えることにしました。このように することで10キー以外や、ボタン数が増えた場合にも最小限の変更で対応することが出来 ます。
キーパッドの出力は4段のうち1つのみONになる(同時押しは考慮しない)ので、4通り、 つまり2bitで表現できるのですが、プログラムを簡単にするため4bitとして扱い、そのまま EEPROMの下位4bitのアドレスとして利用しています。上位4bitもそのまま与えても良いのですが、 プログラムの構造上容易だったのと、ROMデータを作るのが面倒だったので、0,1,2の数値として 与えています。
この結果、ROMのデータとしては0x00-0x2Fまでの48バイトを作ればよいことになります。何も 押されていない場合は0x0Fを出力することとして、重複して押された場合(プログラム側でハン ドリングしていませんが)も0x0Fのダミーデータで埋めておきました。
*は0xa, #は0xbとしています。同じ仕組みで16キーまでサポートすることが可能です。
データ出力が8bitあれば、データにASCIIコードを使うのも良いでしょう。 EEPROMのデータは0x2100番地から書き込めばよいので、アセンブラは次のようになります。
;***** EEPROM Setting
org	0x2100
DE	0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xb, 0xf, 0xf, 0xf, 0x9, 0xf, 0x6, 0x3, 0xf
DE	0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0x0, 0xf, 0xf, 0xf, 0x8, 0xf, 0x5, 0x2, 0xf
DE	0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xa, 0xf, 0xf, 0xf, 0x7, 0xf, 0x4, 0x1, 0xf
さらに、ずっとループで回しておいても良いのですが、何も押されていないときsleepさせて おき、キー入力があった場合に処理させることが出来ないか検討しました。
今回特に必要というわけではないのですが、電池で使う場合などは省電力に出来るので、 ベターです。
PortBは状態変化割り込みが使えるのでこれを利用して実装してみました。状態変化を検出する ためにはキーパッドの1から3pinをLowにしておき、どれかキーが押されたら復帰する方法を 使っています。
割り込みを用いると、割り込みルーチンを書かないとなりませんが、今回割り込みを使うほど でもないのでINTCONレジスタのGIEビットで割り込みを禁止しておき、RBIEをセットして 状態変化割り込みのマスクを外しておくことで、割り込みルーチンに飛ばずsleepから復帰 することが出来ます。
INTCONレジスタのRBIFビットは状態変化が起きた場合にクリアしておかないと、次にsleepした ときに直ぐに復帰してしまいますので、当初はsleepから復帰した直後にクリアする実装にした のですが、クリア直後にセットされてしまいうまく動きませんでした。(よく考えれば当たり前 ですよね)
このため、チャタリングの対策も兼ねて、sleepする前に100ms待ってからRBIFビットをクリアし てsleepするように変更しました。
ブザーを鳴らす部分に関しては殆どが待ち時間を作るためのルーチンです。500usecの待ち時間をつくり、 単にoff/onさせれば1KHzの出力になります。

回路図

以上を踏まえて、組んだ回路がこちらになります。
circuit 実際には、動作確認の為、出力にはLEDを取り付けています。

PICの設定

configレジスタが2つになっており、MPASMの記述方法では次のようにして2つのレジスタを 設定します。
__CONFIG _CONFIG1, _INTRC_IO & _WDT_OFF & _PWRTE_ON &_LVP_OFF & _MCLR_OFF & _CCP1_RB3
__CONFIG _CONFIG2, _IESO_OFF & _FCMEN_OFF
速度は8MHzまでに限定されますが、クロックソースに内部クロックを使うことが 出来るようになっているので、これを利用することにしました。
configレジスタの設定の_INTRC_IOがこの設定ですね。
内臓のプリスケーラーの出力を切り替えることにより、使用する周波数はOSCCONレジスタ の設定で選択できるのですが、デフォルトではOSCCONレジスタは0x0になっており、31.25KHz になってしまうので、OSCCONレジスタの7:5ビットを1にします。これで8MHzになります。
MOVLW	0x70	; set INTOSC to 8Mhz
MOVWF	OSCCON
次はI/O周りの設定です。16F88ではアナログ入力やCCPなどが追加されていますので、この あたりはdisableにする必要があります。特にアナログ入力はデフォルトがenableなので、 忘れると動作しません。
この設定もPIC16F877などでは、ADCON1[3:0]で設定していましたがANSELで設定するように なっています。
CLRF	ANSEL	; Analog input disable
CCPの方はCCP1CONで設定しますが、デフォルトでdisableですので特に設定は必要ありません。
割り込みルーチンは使いませんが、状態変化割り込みをsleepからの復帰に利用するので、 次のようにINTCONレジスタを設定します。
BCF	INTCON,GIE	;Interrupt Disable
BSF	INTCON,RBIE	;PortB Interrupt Enable
デジタル入出力は次のような割り当てにしてみました。
RA[0:3]digital output入力キーのバイナリデータ出力。
RB[0:2]digital outputキーパッドの1から3pinに接続。
RB3digital outputブザーの-側に接続。
RB[4:7]digital inputキーパッドの4から7pinに接続。
CLRF	TRISA
MOVLW	0xf0
MOVWF	TRISB
16f84用に書いたプログラムをシュミレーターで動作確認してあったので、入出力や クロックソースだけ直して焼いてみたのですが、これではぜんぜん動きません。
EEADRやEEDATAがBank2にあるので、切り替え先を変更しなければなりません。
Bank1に切り替え先を決めうちしていたのでは汎用性が無いので、マニュアルにも書いて あるとおりBANKSEL命令を使って記述するように変更しました。
また、手順にEECON1レジスタのEEPGDビットをクリアする手順が追加されていますので、 これもソースに追加しました。
MOVF	daddr,W		;daddrにアドレスが入っていることを想定しています。
BANKSEL	EEADR
MOVWF	EEADR		; set ROM address
BANKSEL EECON1
BCF	EECON1,EEPGD
BSF	EECON1,RD	; RD bit set
BANKSEL	EEDATA
MOVF	EEDATA,W	; read ROM data

完成品

試作品 ソースはこちら
多少余分な記述が見られますが、その辺はご愛嬌ということで。