目的

単純なスイッチ(ON/OFF)を判定する際に、できるだけマイコンのIOポートを節約するための方法を考える

なぜ単純なスイッチでは駄目なのか

単純なON/OFFであれば下記の様な単純なスイッチでも問題ないです。

※GPIO23は入力ポートとする

単純なスイッチ(1スイッチ/1ポート) 

GPIO23はプルアップされていて、常にHIGHとなっている状態です。

ここでスイッチが押されるとGNDに接続されてLOWになる感じですね。

個人的にこの設計はシンプルで好きです。迷うこともないし。

しかしこれで例えばキーボードなんて実装しようとしたらIOポートの数の壁にぶち当たるのは想像できますね。

同時押しを考えないでいいのならアナログ入力

少し複雑になりますが、原理はわかりやすい。

アナログ入力で判定する(nスイッチ/1ポート)

GPIO23をアナログ入力とすると、プルアップされているので常に3.3V付近です。

SW1が押されるとGNDに落ちて0V, SW2が押されると分圧回路が成立して半分の1.65V付近になります。(誤差や熱による変化が考えられるため)

なのでアナログ入力で電圧値を読み取ってやれば1ポートで複数のスイッチの状態が得られるというわけです。

ただし、この方法だと同時押しに対応できません。

対応できないというか、上記の場合は同時押しなのかSW1なのかを区別できません。

同時押しを考えなくてよいのであればアナログ回路でもよいかもしれませんが、例のごとくキーボード(ry

あとはマージンを適切に設計しないとノイズや誤差で誤作動するかもしれません。

これが本命、スイッチマトリクス

さて、本題ですが下記の回路は押されたキーを行列でスキャンして判定する回路です。

スイッチマトリックス回路(ROW*COLスイッチ/ROW+COLポート)

ここでCOL(GPIO16, GPIO17)は出力ポート、ROW(GPIO18, GPIO23)は入力ポートです。

スイッチマトリクスでは対象の座標のスイッチが押されているかどうかを、スキャンして判定します。

例えばSW1が押されているかを判定するにはCOL0をLOWに設定します。

次にROW0(GPIO23)がHかLかを判定します。

ここで対象のスイッチが押されている状態とは、0V(LOW)を指します。

そのままROW1の状態を見ればSW2の状態が判定できます。

その後、COL0をHにしてCOL1をLOWに設定し、同じことを繰り返します。

(つまり、スキャンしたいCOLの出力をLに設定し、他のCOLをHにしてから入力ポートをチェックする)

行列を全捜査(スキャン)する必要があるため厳密には同時押しではない(並列ではない)かもしれませんが、比較的単純に少ないポートで多くのスイッチを扱えます。

出力ポートをLOWにすると何が起こるのか?

COL(GPIO16, GPIO17)は出力ポートなのですが、これをLに設定するとマイコンの中で何が起きるのか?です。

出力ポートに設定しているということは、一見すると出力ポートからGND方向に向かってのみ出力できる、と考えがちですが(私はそうだった)実は出力ポートは入力ポートのように電力を引き込むこともできます。

ちなみに前者をソース電流、後者をシンク電流とかいうらしいです。

他にはN-chオープン・ドレーン出力とかありますが、たぶんシンク電流と同じだと思います()

つまり上の回路は出力ポートをLにしてやることで出力ポート内部のGNDに接続して電力を引き込んでるんですね。ちなプルアップ抵抗がないとショートしてたぶん壊れます。

あと流していい最大電流も決まっているのでデータシートを確認する必要があります。

ダイオードの必要性

ダイオードの必要性ですが、SW1とSW3が同時に押された場合に何が起こるか考えてみます。

まずスキャンの際にCOL0をL, COL1をHにしますがここでダイオードがないと引き込んだ電力とCOL1(HIGH)から出力された電力(VCCからのソース電流)がCOL0へ向かって流れてしまいますのでショートしてしまいます。

なので個人的には必須だと思います。

参考

抵抗分圧器を使った、多くのスイッチのセンシング

マイコンでのキー・スイッチ入力

N-chオープン・ドレーン出力について