少ないポートでキー入力
マイコンを使って、ユーザー入力があるアプリを作っていると、ポートが足りないことが時々起こります。 これに対応する方法を考えます。
ポートが足りない時にとるべき措置
マイコンを使っていると、ポートが足りないという事態によく陥ります。その場合、さまざまな対応方法がありますが、当方でやったことのある方法をご紹介します。
ロジックICでIOを拡張する
入力/出力がきっちり8ビット足りないとか、8ビットでデータを渡す(=データバス的な)処理がある場合には、ロジックICを使ってIOを拡張しています。条件が合えば、最も安い拡張だと思います。74HC595や、74HC165あたりがターゲットです。
SPIで、IOエキスパンダを接続する
拡張するIOが、入力出力で数ビットづつだとか、拡張した入力ポートからの信号で割り込み駆動を行う必要があるとか、32ビット以上の大量拡張の必要が生じた場合などは、SPI接続でIOエキスパンダを使用します。MCP23S08が当方の定番です。4個(32ビット)までなら、チップ側のアドレス選択を使って振り分けできるので、チップセレクトを分ける必要もありません。
SPIで、ATTINY2313を接続する
拡張するIOでPWMを行いたいとか、シリアル回線も必要とかいう需要の場合、SPI接続でマイコンをもうひとつ増設します。当方定番サブマイコンは、アトメルのATTINY2313です。何といっても100円ですし。
ハードとソフトで何とかする
これが、今回のテーマなのですが、キー入力などの場合でマイコンにADコンバータを搭載している場合、うまく回路を作れば、複数キーを1つのポートで受信できます。
ADコンバータでキー入力
ネットで検索してみると、上のような回路図が出てきました。たった1ビットのアナログ入力に7個のキーを割り当てています。ただし、この回路では、複数キーを同時に押した場合、右側の(スイッチ番号が小さいほうの)キーが優先されます。このことが問題にならない場合、非常に有効な方法です。実際、MP3のポータブルプレーヤーや、デジタルフォトフレームなんかの製品にも、この手の回路があるようです。
スイッチ番号 | 下側抵抗値[Ω] | AD電圧[V] | 取込値(10bit) |
1 | 680 | 0.63 | 129 |
2 | 1590 | 1.26 | 259 |
3 | 2790 | 1.86 | 381 |
4 | 4790 | 2.52 | 517 |
5 | 8090 | 3.16 | 648 |
6 | 14290 | 3.76 | 771 |
7 | 34290 | 4.40 | 901 |
マイコン内臓ADCで一般的解像度は10ビットなので、それに合わせて計算してみました。
また、VCCの電圧は5Vとしています。(最近では異論もありましょうが)
まず、何も押されていなければ、R1によるプルアップで、ADCの電圧はVCCになります。10ビットのADCであれば、上限値である1023が返って来るハズです。
キー1が押されていれば、電圧は0.63Vとなり、取込値は129に、キー2であれば取込値は259になりそうです。もちろん、抵抗の誤差が1%とか、5%とかありますが、この129と259の中点である、194より、小さいかどうかを判定すれば、キー1が押されているということが言えそうです。
// 判定ルーチン1 // (効率わるい) int adv2key(int adcval) { if (adcval < 194) return 1; else if (adcval < 320) return 2; else if (adcval < 449) return 3; else if (adcval < 582) return 4; else if (adcval < 709) return 5; else if (adcval < 836) return 6; else if (adcval < 962) return 7; return 0; }
何も考えずに判定ルーチンを書くと、こんな感じになりました。でもちょっと考えると、このコードは、7回if文を通過するので、効率が悪いと言えます。せっかく不等号で判定するので、いじってみます。
// 判定ルーチン2 // (ソフト的には効率よさそうだけど・・・) int adv2key(int adcval) { if (adcval < 582) // key 1〜4 { if (adcval < 320) // key 1 or 2 { if (adcval < 194) return 1; else return 2; } else // key 3 or 4 { if (adcval < 449) return 3; else return 4; } } else // key 5〜7,0 { if (adcval < 836) // key 5 or 6 { if (adcval < 709) return 5; else return 6; } else // key 7 or 0 { if (adcval < 962) return 7; else return 0; } } return 0; //ここには来ない }
この形だと、どんな場合でも、if文3回通過で判定できるので、効率が良いように見えます。パソコンのプログラムで、あるデータ範囲を判定する方法であれば、このように、まず大きく2つに分けて、その中をさらに2つに・・・というアプローチは、効率的です。ところが、マイコンのキー判定ルーチンとしては、非効率なのです。
マイコンシステムにキーが7個あるとして、マイコン動作時間の全てのうち、キーが押されている時間は、僅かなものであると言えましょう。つまり、ADコンバータから取得されるデータは圧倒的に1023近辺の値に偏っていることになります。そこで、この部分を先に判定することにより、キーが押されていないケースの場合、if文一回で戻れるように変更しました。
// 判定ルーチン3 // (この場合、これがいいと思う) int adv2key(int adcval) { if (adcval >= 962) return 0; if (adcval < 582) // key 1〜4 { if (adcval < 320) // key 1 or 2 { if (adcval < 194) return 1; else return 2; } else // key 3 or 4 { if (adcval < 449) return 3; else return 4; } } else // key 5〜7,0 { if (adcval < 836) // key 5 or 6 { if (adcval < 709) return 5; else return 6; } else // key7 { return 7; } } return 0; // ここにはこない }
無作為研究所トップページに戻る