LEDマトリクスの操作
前回、Pro Microとは別にLED制御用のArduino(ATmega328P:以降、328P)を用意することでuniversal16のLEDマトリクスを点灯させることができました。
今回もゆかりさん (https://twitter.com/eucalyn_)の「ねこでも作れる! オリジナルキーボード」とともに、Pro Microからキー入力をシリアル通信で受け取ってバックライトの点灯/消灯を操作してみます。
Pro Microはシリアル通信用のピンがUSBシリアルとは別に用意してあるためTxやRxとあるピンを使いますが、328Pにはシリアル通信用のピンがない(シリアルモニタが使えなくなる)ため、SoftwareSerialを使って通信を行います。今回はTxにA4ピンを、RxにA5ピンを使用し、それぞれUEWで配線してシリアル通信を受け取れるようにしました。
(とても分かりづらい配線の図)
次に、Pro Microから328Pに送るデータを考えます。今回必要なのは押された/離されたキーの座標のみですが、右側のユニバーサル基板部分で2つのロータリーエンコーダを扱いたいので、そのことを念頭に置いて2進数8桁1バイトの中に情報を詰め込みます。
0b0000000 _ matrix(0) or renc(1) *_ press or release **_ master or slave ***__ matrix position (row) *****__ matrix position (col) *******0 blank
少し見づらいかと思いますが、右から
- 1桁目は空白(いつでも0)
- 2~3桁目は列成分
- 4~5桁目は行成分
- 6桁目はmaster/slave判定
- 7桁目は押されたか/離されたか
- 8桁目はmatrixについての通信かロータリーエンコーダについての通信か
について必要な情報を2進数にして押し込んでいます。 master/slave判定は現時点で必要な情報ではないのですが、後々使うことになるかもしれないので、ひと桁分を用意しました。
以上を反映したPro Micro側のプログラムはこのようになりました。
const int Rowpin[] = {4,5,7,8}; const int Colpin[] = {10,15,14,16}; const int Cols = (sizeof(Rowpin)/sizeof(Rowpin[0])); const int Rows = (sizeof(Colpin)/sizeof(Colpin[0])); bool beforeState[Rows][Cols]; bool currentState[Rows][Cols]; byte sendData,readData; /* data description * * 0b0000000 * _ matrix or renc * if matrix(0) * *_ press or release * **_ master or slave * ***__ matrix position (row) * *****__ matrix position (col) * *******0 blank * if renc(1) * *_ renc0 or renc1 * **_ button press or release * ***_ rotate (en/dis)able * ****_ rotate direction * *****000 blank */ void setup(){ Serial.begin(9600); Serial1.begin(115200); Serial1.write("hello!"); // while(!Serial); Serial.println("Started."); for(int i=0;i<Cols;i++){ pinMode(Rowpin[i],OUTPUT); } Serial.print("num of Rowpin: "); Serial.println(Rows); Serial.println("Rowpin initialized."); for(int i=0;i<Rows;i++){ pinMode(Colpin[i],INPUT_PULLUP); } Serial.print("num of Colpin: "); Serial.println(Cols); Serial.println("Colpin initialized."); for(int i=0;i<Rows;i++){ for(int j=0;j<Cols;j++){ currentState[i][j] = HIGH; beforeState[i][j] = HIGH; } } Serial.println("initialize finished"); } void loop(){ int isPress = 0; delay(1); for(int i=0;i<Rows;i++){ digitalWrite(Rowpin[i],LOW); for(int j=0;j<Cols;j++){ currentState[i][j] = digitalRead(Colpin[j]); if (currentState[i][j] != beforeState[i][j]){ Serial.print("key "); Serial.print(i); Serial.print(","); Serial.print(j); if(currentState[i][j] == LOW){ Serial.println(" pressed!"); isPress = 1; } else { Serial.println(" released!"); isPress = 0; } beforeState[i][j] = currentState[i][j]; sendData = isPress << 5 | i << 3 | j << 1 | 0 ; Serial1.write(sendData); Serial.print("sent :"); Serial.println(sendData); } } digitalWrite(Rowpin[i],HIGH); } }
さて、これでPro Microからキー座標を送ることができるようになりました。
次に、328P側での挙動を考えます。水滴が落ちるようなアニメーション動作ができるととてもかっこいいのですが、まずは押したキーのバックライトが点灯するようにプログラムを書いてみました。
#include <SoftwareSerial.h> const int ledcols = 4; const int ledrows = 4; const int ledcol[ledcols] = {A2,A3,4,2}; const int srclk = 7; const int srclr = 8; const int sra = A0; const int srb = A1; //const int renc = 2; //const int rencr[renc] = {11,6}; //const int rencg[renc] = {10,5}; //const int rencb[renc] = {9,3}; const int Tx = A4; const int Rx = A5; SoftwareSerial keyserial(Rx,Tx); byte sendData,readData; //int r[renc] = {0}; //int g[renc] = {0}; //int b[renc] = {0}; int matrixDefault[ledrows][ledcols] = { {1,1,1,1}, {1,1,1,1}, {1,1,1,1}, {1,1,1,1} }; int keyPressed[ledrows][ledcols]={0}; void srclock(){ digitalWrite(srclk,1); digitalWrite(srclk,0); }; void setup(){ Serial.begin(9600); keyserial.begin(115200); while(!Serial); if(keyserial.available()){ readSerial(); } pinMode(srclk,OUTPUT); pinMode(srclr,OUTPUT); pinMode(sra,OUTPUT); pinMode(srb,OUTPUT); for(int i=0;i<4;i++){ pinMode(ledcol[i],OUTPUT); digitalWrite(ledcol[i],0); } Serial.println("initialized"); digitalWrite(srclk,0); digitalWrite(srclr,0); digitalWrite(sra,0); digitalWrite(srb,0); } void loop(){ int *matrix[ledrows]; for(int k=0;k<ledrows;k++){ matrix[k] = matrixDefault[k]; } for(int i=0;i<ledrows;i++){ int swapMatrix = 0; for(int j=0;j<ledcols;j++){ swapMatrix += keyPressed[i][j]; } if(swapMatrix != 0){ for(int k=0;k<ledrows;k++){ matrix[k] = keyPressed[k]; } break; } } for(int i=ledcols;i>=0;i--){ digitalWrite(srclr,1); digitalWrite(srb,1); for(int j=ledrows;j>=0;j--){ digitalWrite(sra,matrix[j][i]); srclock(); digitalWrite(sra,0); } digitalWrite(ledcol[i],1); delay(3); digitalWrite(srclr,0); digitalWrite(ledcol[i],0); } if(keyserial.available()){ readSerial(); } } void readSerial(){ int Row1, Col1, isPress; readData = keyserial.read(); Serial.println(readData); if(readData & 0b00000000){ } else { isPress = readData >> 5; Row1 = (readData & 0b00011000) >> 3; Col1 = (readData & 0b00000110) >> 1; Serial.print("key "); Serial.print(Row1); Serial.print(","); Serial.print(Col1); if(isPress){ keyPressed[Row1][Col1] = 1; Serial.println(" pressed!"); } else { keyPressed[Row1][Col1] = 0; Serial.println(" released!"); } } }
キーを押さない通常時は全点灯の状態にしたかったので、前回のプログラムに
- シリアル通信を受け取る関数を足し、その中で新しい配列
keyPressed
に今押されているキーの座標を追加 void loop()
の最初で押されたキーがあるかどうかを判断して、ひとつでもあるようなら新しい配列keyPressed
から点灯するLEDの座標を読み出す
ように手を加えました。
うまくいった! #universal16 pic.twitter.com/Fmv02tE8MR
— せがた ひろみ (@keyaki_namiki) November 15, 2018
(うごいている様子)
続きます。