喫茶blanc

何か作ったり、何か食べたり

LEDマトリクスの操作

github.com

前回、Pro Microとは別にLED制御用のArduino(ATmega328P:以降、328P)を用意することでuniversal16のLEDマトリクスを点灯させることができました。

今回もゆかりさん (https://twitter.com/eucalyn_)の「ねこでも作れる! オリジナルキーボード」とともに、Pro Microからキー入力をシリアル通信で受け取ってバックライトの点灯/消灯を操作してみます。

eucalyn.booth.pm

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);
    }
}

View code on GitHub.com

さて、これで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!");
        }
    }
}

View code on GitHub.com

キーを押さない通常時は全点灯の状態にしたかったので、前回のプログラムに

  • シリアル通信を受け取る関数を足し、その中で新しい配列keyPressedに今押されているキーの座標を追加
  • void loop()の最初で押されたキーがあるかどうかを判断して、ひとつでもあるようなら新しい配列keyPressedから点灯するLEDの座標を読み出す

ように手を加えました。

(うごいている様子)

続きます。


一連の実験をお手元でお試しいただける基板、universal16 v0.2を少数ですがboothにて販売しています。プレート付属の基板キットを使って、あなただけの左手/右手デバイスを作ってみませんか?

keyaki-namiki.booth.pm