前回はシフトレジスタを使用して4つの7セグメントディスプレイを制御する方法を学びました。
今回は同じ考え方を使用して、マトリックスLEDを制御してみましょう。
ドットマトリクスLEDについて
LEDマトリックスは、LEDを規則的に並べた四角形のディスプレイモジュールです。以下は、64個のLED (8行8列) で構成されたモノクロ (1色) LEDマトリックスの例です。
このコンポーネントの操作を容易にし、駆動に必要なポート数を減らすために、内部では各行のLEDのアノード(正極)と各列のLEDのカソード(負極)がそれぞれ接続されています。これを共通アノード方式と呼びます。また、別の接続方法として、各行のLEDのカソードと各列のLEDのアノードをそれぞれ接続した、共通カソード方式もあります。このプロジェクトで使用するLEDマトリックスは、共通アノード型のLEDマトリックスです。内部の回路図は以下のとおりです。
まず初めに、ESP32-S3ボード上の16個のGPIOポートを、LEDマトリックスの16個の端子に接続します。その上で、列側のGPIOポートを1本LOWレベルに設定することで、その列を選択状態にします。その後、行側の8個のポートを使って、選択した列に表示したい内容を出力します。遅延を挟み、次の列を選択して、それに対応する内容を出力します。このように列単位で点灯を制御することを、スキャンと呼びます。
例えば、以下のニコニコマークの画像を表示したい場合、8列として表示することができ、1列につき1バイトで表します。
まず、1列目だけを表示させます。その後、1列目を消して2列目だけを表示させます。同様に、7列目を消して8列目だけを表示させてから、バーLEDアレイのプロジェクトと同じように制御し、最初から繰り返します。この一連の処理は高速に繰り返されます。人間の目に残像が残ること、また人間の目が視覚的な刺激を連続したものとして認識する「視覚の残像効果」により、私たちは個々の列が順番に点灯していくのではなく、ニコニコマークの像が同時に点灯しているように見えます。(実際には各列が順番に点灯しているのですが、人間の目には認識できないということです。)
次に、GPIOの数を節約するために74HC595を使用します。1列目を点灯させる場合、1列目で表示させたいLEDは「1」、それ以外は「0」に設定します。上の例では、1列目の値は0x1cとなります。この値を74HC595に送信してLEDマトリックスの1列目表示を制御します。この考え方に従って、1列目の表示を消した後、2列目を点灯させて、2列目の値を74HC595に送信します… というように、各列の表示が1回ずつされるまで繰り返し、再度1列目からLEDマトリックス全体を表示します。
回路図
接続図
では実際に接続してみましょう!・・・と言うにはちょっとヘビーな回路ですね。今回はコードの机上検証だけでも良いかもしれません。
コード
/**********************************************************************
Filename : LED Matrix Display
Description : Use 2 74HC595 to drive the LED Matrix display
Auther : www.freenove.com
Modification: 2022/10/24
**********************************************************************/
int latchPin = 39; // Pin connected to ST_CP of 74HC595(Pin12)
int clockPin = 40; // Pin connected to SH_CP of 74HC595(Pin11)
int dataPin = 38; // Pin connected to DS of 74HC595(Pin14)
// Define the pattern data for a smiling face
const int smilingFace[] = { //"^ⅴ^"
0x1C, 0x22, 0x51, 0x45, 0x45, 0x51, 0x22, 0x14
};
// Define the data of numbers and letters, and save them in flash area
const int data[] PROGMEM = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // " "
0x00, 0x00, 0x21, 0x7F, 0x01, 0x00, 0x00, 0x00, // "1"
0x00, 0x00, 0x23, 0x45, 0x49, 0x31, 0x00, 0x00, // "2"
0x00, 0x00, 0x22, 0x49, 0x49, 0x36, 0x00, 0x00, // "3"
0x00, 0x00, 0x0E, 0x32, 0x7F, 0x02, 0x00, 0x00, // "4"
0x00, 0x00, 0x79, 0x49, 0x49, 0x46, 0x00, 0x00, // "5"
0x00, 0x00, 0x3E, 0x49, 0x49, 0x26, 0x00, 0x00, // "6"
0x00, 0x00, 0x60, 0x47, 0x48, 0x70, 0x00, 0x00, // "7"
0x00, 0x00, 0x36, 0x49, 0x49, 0x36, 0x00, 0x00, // "8"
0x00, 0x00, 0x32, 0x49, 0x49, 0x3E, 0x00, 0x00, // "9"
0x00, 0x00, 0x3E, 0x41, 0x41, 0x3E, 0x00, 0x00, // "0"
0x00, 0x00, 0x3F, 0x44, 0x44, 0x3F, 0x00, 0x00, // "A"
0x00, 0x00, 0x7F, 0x49, 0x49, 0x36, 0x00, 0x00, // "B"
0x00, 0x00, 0x3E, 0x41, 0x41, 0x22, 0x00, 0x00, // "C"
0x00, 0x00, 0x7F, 0x41, 0x41, 0x3E, 0x00, 0x00, // "D"
0x00, 0x00, 0x7F, 0x49, 0x49, 0x41, 0x00, 0x00, // "E"
0x00, 0x00, 0x7F, 0x48, 0x48, 0x40, 0x00, 0x00 // "F"
};
void setup() {
// set pins to output
pinMode(latchPin, OUTPUT);
pinMode(clockPin, OUTPUT);
pinMode(dataPin, OUTPUT);
}
void loop() {
// Define a one-byte variable (8 bits) which is used to represent the selected state of 8 column.
int cols;
// Display the static smiling pattern
for (int j = 0; j < 500; j++ ) { // repeat 500 times
cols = 0x01;
for (int i = 0; i < 8; i++) { // display 8 column data by scaning
matrixRowsVal(smilingFace[i]);// display the data in this column
matrixColsVal(~cols); // select this column
delay(1); // display them for a period of time
matrixRowsVal(0x00); // clear the data of this column
cols <<= 1; // shift"cols" 1 bit left to select the next column
}
}
// Display the dynamic patterns of numbers and letters
for (int i = 0; i < 128; i++) {
for (int k = 0; k < 10; k++) { // repeat image of each frame 10 times.
cols = 0x01; // Assign binary 00000001. Means the first column is selected.
for (int j = i; j < 8 + i; j++) { // display image of each frame
matrixRowsVal(pgm_read_word_near(data + j));// display the data in this column
matrixColsVal(~cols); // select this column
delay(1); // display them for a period of time
matrixRowsVal(0x00); // close the data of this column
cols <<= 1; // shift"cols" 1 bit left to select the next column
}
}
}
}
void matrixRowsVal(int value) {
// make latchPin output low level
digitalWrite(latchPin, LOW);
// Send serial data to 74HC595
shiftOut(dataPin, clockPin, LSBFIRST, value);
// make latchPin output high level, then 74HC595 will update the data to parallel output
digitalWrite(latchPin, HIGH);
}
void matrixColsVal(int value) {
// make latchPin output low level
digitalWrite(latchPin, LOW);
// Send serial data to 74HC595
shiftOut(dataPin, clockPin, MSBFIRST, value);
// make latchPin output high level, then 74HC595 will update the data to parallel output
digitalWrite(latchPin, HIGH);
}
さて、実行してみるとどうなったでしょうか?
行がズレて表示されていたり、上下反転して表示されたでしょうか?
これはfreenoveさんのテキストに誤りがあると思われます。
では、何が正解なのか、バグを修正(デバッグ)してみましょう!デバッグもプログラミングの大切なスキルです!
ちょっと長いですが、順に見ていきましょう。
まずは『smilingFace』定数です。定数は変数と異なり、値の変更をしないものです。定数はconstと定義します。少し上の方にも出てきましたが、各列(列は縦です!)の値を16進数に変換した値です。
const int smilingFace[] = { //"^ⅴ^"
0x1C, 0x22, 0x51, 0x45, 0x45, 0x51, 0x22, 0x14
};
次にdata定数です。PROGMEMというのはメモリの確保方法ですが、さほど気にしなくて良いです。定数の中身は0~Fのパターンです。こちらも各列の値を順に格納している状態ですね。
const int data[] PROGMEM = {
次にメインループの1つ目のforループです。
500回の意味は500ミリ秒+α(処理時間)というような意味合いですね。
2つ目のforで8回繰り返しています。この繰り返しの中で『cols <<= 1;』としているので、各列に対して処理をしているのがわかると思います。そのため、『i』は列番号に相当します。
『smilingFace[i]』は各列の値が入っていますので、ここまでは合っているようです。
for (int j = 0; j < 500; j++ ) { // repeat 500 times
cols = 0x01;
for (int i = 0; i < 8; i++) { // display 8 column data by scaning
matrixRowsVal(smilingFace[i]);// display the data in this column
matrixColsVal(~cols); // select this column
delay(1); // display them for a period of time
matrixRowsVal(0x00); // clear the data of this column
cols <<= 1; // shift"cols" 1 bit left to select the next column
}
}
matrixRowsVal()関数の中身を見てみましょう。
まずはラッチピンをLOWにして、シフトアウトして、ラッチピンをHIGHにしています。大きな流れとしては合っていますね。ただ、列の上下が反転しています。シフトレジスタを最初に学習したときに、LSBFIRSTは『最下位ビットから送る』と学びました。ですのでまずはここを逆にする必要がありそうです。『最上位から送る』はMSBFIRSTでしたね。
void matrixRowsVal(int value) {
// make latchPin output low level
digitalWrite(latchPin, LOW);
// Send serial data to 74HC595
shiftOut(dataPin, clockPin, LSBFIRST, value);
// make latchPin output high level, then 74HC595 will update the data to parallel output
digitalWrite(latchPin, HIGH);
}
これで上下反転が直りました。あとは少し行がズレている状態ですね。
上の方に記載した、内部の回路図を見ながら結線を組み替えましょう。
正しい接続図は以下のとおりです。
正解は・・・
点線のジャンパー線が組み替えたジャンパー線です。
いかがでしょうか?正しく表示されるようになったでしょうか?
バグに遭遇した瞬間は絶望感がありますが、正しく動作させられるようになった瞬間になんとも言えない爽快感がありますよね!プログラミングをしていて一番楽しい瞬間です。
次回はリレーでモーターをコントロールしてみましょう。
コメント