秋月電子通商で購入した「7 セグメント用 LED ドライバー IC TM1630」、LED 表示がうまくいきましたので、今回はキースキャン読み取りの動作を確認してみたいと思います。
キースキャン読み取りを含めた回路図
回路図です。
前回の LED 表示回路に、押しボタンスイッチを追加しています。表示まわりに変更はありません。
まずスイッチの取り付けですが、マトリクス用にスイッチングダイオードを追加しています。これがないと、複数のボタンを同時に押したときに、SEG ピンがショートしてしまい、LED 表示が狂ってしまいます。
前回、SEG ピンの出力波形の説明でも書きましたが、キースキャン読み取りモードでは、SEG ピンから定期的にパルスを出して K2 ピンで検出し、そのタイミングによってどのキーが押されたかを判断しています。したがって、SEG から K2 へ向けてダイオードを配置します。
DIO ピンは、読み取りモードではデータ出力としても働きます。データシートには、出力はオープンドレインなのでプルアップ抵抗をつけろ、と書かれています。
でも実測してみると、書き込みモード (入力) でも読み取りモード (出力) でも、DIO ピンには 5V 出ているんですよね。10KΩ の抵抗で GND に引っ張り込むと電圧が 2V に下がるんで、内部プルアップしているんじゃないかと。
そんなわけで、プルアップ抵抗はつけませんでした。もちろん、問題なく動作しています。
CLK 、STB は Arduino からの入力ですので、プルアップする必要はありません。もっとも、これらのピンも内部プルアップされてるようで、常に 5V が出ています。
100pF コンデンサは部品箱に、ないので (^_^;) つけてません。
キースキャン読み取りのスケッチ
前回の LED 表示のスケッチに、キースキャン読み取りの機能をプラスしました。
- // LED Driver TM1630 v.1.2a 2021.03.01 by meyon
- #include <MsTimer2.h>
- #define DISPLAY_MODE 0x00
- #define WRITE_REG_AUTO_ADDR 0x40
- #define WRITE_REG_FIXED_ADDR 0x44
- #define READ_KEY_SCAN 0x42
- #define SET_DISPLAY_ADDR 0xC0
- #define DISPLAY_OFF 0x80
- #define DISPLAY_ON 0x88 | 0x84
- #define STB_ENABLE LOW
- #define STB_DISABLE HIGH
- #define DOT_POSITION -1
キースキャンを時間割り込みするために MsTimer2 をインクルードしています。
データシートのフローチャートにあるように、表示処理のあとに続けてキースキャンをやってもいいと思います。目的にあわせて、より良い方法で。
- const int dioPin = 5;
- const int clkPin = 6;
- const int stbPin = 7;
- const int numberOfDigits = DISPLAY_MODE & 1 ? 5 : 4;
- int gridData[numberOfDigits] = {0};
- byte buttonState = 0;
- const int digit[] = {
- 0b01111110, // 0
- 0b00001100, // 1
- 0b10110110, // 2
- 0b10011110, // 3
- 0b11001100, // 4
- 0b11011010, // 5
- 0b11111010, // 6
- 0b01001110, // 7
- 0b11111110, // 8
- 0b11011110, // 9
- 0b00000000, // 10:blank
- 0b00100000, // 11:dot
- };
- const int blank = 10;
- const int dot = 11;
buttonState という変数を追加しています。ここに各ボタンの状態を格納します。
- void displayNumbers(int n) {
- noInterrupts();
-
- for (int i = 0; i < numberOfDigits; i++) {
- int exponentialInDecimal = pow(10, i) + 0.5;
- bool zeroSuppression = (0 != i) && (DOT_POSITION < i) && (exponentialInDecimal > n);
- gridData[i] = zeroSuppression ? blank : n / exponentialInDecimal % 10 ;
- }
- digitalWrite(stbPin, STB_ENABLE);
- shiftOut(dioPin, clkPin, LSBFIRST, DISPLAY_MODE);
- digitalWrite(stbPin, STB_DISABLE);
- digitalWrite(stbPin, STB_ENABLE);
- shiftOut(dioPin, clkPin, LSBFIRST, WRITE_REG_AUTO_ADDR);
- digitalWrite(stbPin, STB_DISABLE);
- digitalWrite(stbPin,STB_ENABLE);
- shiftOut(dioPin, clkPin, LSBFIRST, SET_DISPLAY_ADDR | 0x00);
- for (int i = 0; i < numberOfDigits; i++) {
- shiftOut(dioPin, clkPin, LSBFIRST, digit[gridData[i]]);
- shiftOut(dioPin, clkPin, LSBFIRST, DOT_POSITION == i ? digit[dot] : 0);
- }
- digitalWrite(stbPin, STB_DISABLE);
- digitalWrite(stbPin, STB_ENABLE);
- shiftOut(dioPin, clkPin, LSBFIRST, DISPLAY_ON);
- digitalWrite(stbPin, STB_DISABLE);
- interrupts();
- }
LED 表示の部分です。この処理中には割り込みを行わないようにしています。それ以外は、変更ありません。
- void keyboardScan() {
- static byte scanData[4] = {0};
- digitalWrite(stbPin, STB_ENABLE);
- shiftOut(dioPin, clkPin, LSBFIRST, READ_KEY_SCAN);
-
- pinMode(dioPin, INPUT);
- for (int i = 0; i < 4; i++) {
- scanData[i] = shiftIn(dioPin, clkPin, LSBFIRST);
- }
- pinMode(dioPin, OUTPUT);
- digitalWrite(stbPin, STB_DISABLE);
- buttonState = 0;
- for (int i = 0; i < 8; i++) {
- buttonState |= (scanData[i / 2] >> ((i % 2) ? 4 : 1) & 1) << i;
- }
- }
今回追加したキースキャン読み取りの部分。
まず、STB を有効にして、読み取りモードのコマンドを送ります。
次に Arduino 側の DIO (D5) ピンのモードを INPUT に変更し、shiftIn() で 4byte 受け取ります。
コマンドからデータの出力までの間に 1μs 待つようにデータシートには書かれていますが、ピンモードを変更したりしてる間にそんな時間は過ぎちゃいます。気にする必要はありません。
データを受け取ったらピンモードを OUTPUT に戻し、STB を無効にして終了です。
87~89 行目は、受け取ったデータを buttonState にビット列として格納しています。
KS1 ってキーは存在しませんがあるものとして、キーのデータは BYTE1~BYTE4 の B1 、B4 ビットにあります。それをひとつずつとりだしていきます。
KS1 = (BYTE1 >> 1) & 1 KS2 = (BYTE1 >> 4) & 1 KS3 = (BYTE2 >> 1) & 1 : KS8 = (BYTE4 >> 4) & 1
で、これらを buttonState にビット列として並べます。
buttonState = (BYTE1 >> 1) & 1 buttonState |= ((BYTE1 >> 4) & 1) << 1 buttonState |= ((BYTE2 >> 1) & 1) << 2 : buttonState |= ((BYTE4 >> 4) & 1) << 7
これが 89 行目のわけわからん部分です。
したがって、buttonState は LSB から KS1 、KS2 、KS3 … KS8 の 8 ビットのデータで、オン時 1 、オフ時 0 となります。KS1 は存在しませんから、常に 0 です。
この処理は、単にデータが 1byte に並んでいたら扱いやすいかなと思って作っただけなので、目的によってお好きなように。
- void setup() {
- pinMode(dioPin, OUTPUT);
- pinMode(clkPin, OUTPUT);
- pinMode(stbPin, OUTPUT);
- MsTimer2::set(1, keyboardScan);
- MsTimer2::start();
- }
割り込み用の MsTimer2 の初期設定をしています。
- void loop() {
- switch (buttonState) {
- case 0x02:
- displayNumbers(1);
- break;
- case 0x04:
- displayNumbers(2);
- break;
- case 0x08:
- displayNumbers(3);
- break;
- case 0x10:
- displayNumbers(4);
- break;
- case 0x20:
- displayNumbers(5);
- break;
- case 0x40:
- displayNumbers(6);
- break;
- case 0x80:
- displayNumbers(7);
- break;
- default:
- displayNumbers(0);
- }
- }
押したボタンに応じた数を表示させる確認用のスケッチです。
ボタンの状態を読み込めれば、あとはもうどうにでもしてください。
ブレッドボード
ブレッドボードです。
下にタクトスイッチを 7 個つないでいます。タクトスイッチはそれぞれダイオードを介して K2 ピンへつながります。
オシロスコープは、「8888」を表示しているときの SEG2 / KS2 ピンの波形です。これはセグメント a につながっていて 4 桁とも点灯していますから、4 個のパルスが繰り返し出ています。
キーの読み取り方法
キースキャンでどのようにキーを読み取っているのか、波形をみることでわかってきました。
波形 1 は SEG2 / KS2 の出力です。
幅 0.5ms のパルスが 4 個ずつ並んでいますが、これがセグメントをオンにする信号です。間に 0.5ms の LOW の期間がありますが、その期間の 2 回に 1 回、幅の小さなパルスが出ているのがわかります。これが、キースキャン用の信号です。
SEG2 / KS2 では、0.5ms の期間の初めのほうで出力されています。
波形 2 は SEG5 / KS5 の出力です。
同様にセグメントの信号が 4 個ずつ並んでいて、間に 0.5ms の LOW の期間があります。そこにキースキャン用の細いパルスが出ています。
SEG5 / KS5 では、キースキャン用の信号は、0.5ms の期間の中央付近で出力されます。
波形 3 は SEG8 / KS8 の出力で、キースキャン用信号は、0.5ms の期間の最後のほうで出力されています。
このように時間差を持った信号を出力することで、どのキーが押されているかを判別しているようです。
ちなみに、K2 ピンに 5V を放り込むと、BYTE1~4 のすべてのビットが立ちます。なにか裏技に使えるかも (^_^;)
とゆーわけで。
LED 表示を 4 桁の数値表示させてみましたけど、固定アドレスを使って 4 桁を独立して制御することも可能です。時計のように時分を 2 桁ずつわけて表示することも簡単にできそう。
ボタンは、たとえば LED の輝度調整とかオンオフとかもできますねぇ。ボタン入力だけでもいいわけで、7 個のボタン入力を 3 線でできるのは、Arduino のピン節約に大いに役立ちます。
今回は動作確認の実験だけでしたが、いろいろ使えそうな気がしています。