ArduinoでLED Matrixモジュールを使う

Arduino
DSC_2394.JPG

LED Matrixモジュールを使う

前回はダイナミックドライブという手法でArduinoの出力端子の数を超える LED を点灯させる方法について実験しましたが、今回は多数の LED を制御する専用のチップを搭載した LED Matrix モジュールを使用してみます。

LEDディスプレイドライバ MAX7219 の概要

今回は、LED制御チップMAX7219 を使用したLED Matrixモジュールを使用します。

MAX7219はコモンカソードの7セグメントLED×8桁を直結(トランジスタ等のスイッチング素子も電流制限抵抗も不要)で点灯させることが可能なLEDディスプレイドライバチップです。7セグメントLEDは1桁あたり数字を表すのに7つ、小数点に1つの合計8つのLEDを使用します。これを8桁分制御するため、MAX7219は8ビット×8のRAMを内蔵しています。7セグメントLED×8桁の代わりに8×8のLEDマトリクスを制御することも可能です。

SPI通信の基本

MAX7219は、SPIという形式の通信によってコマンドやデータを受信します。これはシリアル通信の一種で、クロックにあわせてデータを送受信します。SPIによる一般的な接続例は下図のようになっています。

SPIでは、通信主体が MasterSlave にわかれています。通常は PC や Arduino などのマイコンがMaster、センサーや表示機器などの各種デバイスが Slave となります。

V+GND は電源です。Masterから電源を供給する必要がない場合もGNDの接続は必要です。

MOSIMaster Out Slave In、つまり Master から Slave にデータを送信する場合の信号です。

MISOMaster In Slave Out、つまり Slave から Master にデータを送信する場合の信号です。

CLKクロック信号、データ送受信のタイミングを合わせるための信号です。

CSChip Select、1台の Master に複数の Slave が接続されている場合にどの Slave に対するデータかを指定するために使われますが、Slave が1台だけの場合も接続します。

MAX7219の接続

MAX7219 を Arduino などのマイコンと接続する場合、マイコン側が Master、MAX7219 が Slave となります。また、MAX7219 からマイコンにデータを送信することはないため、MISO を配線する必要がありません(そもそもMISOに相当する端子がありません)

MAX7219側のピン名称では MOSI が DIN/CS が LOAD となっていますので、回路例は下図のようになります(データシートに掲載されている図とほぼ同じです)。

LEDの配線

MAX7219は、カソードコモンの7セグメントLED用のドライバです。よって、LEDを点灯させるときにはSEG a~gがHIGH、DIG 0~7がLOWとなります。

LED Matrix の場合はどちらの接続でも使えると言えば使えます。ダイナミックドライブの例で使用した 1088AS のような『ROW側がカソード/COL側がアノード』の素子の場合は、LED の ROW 0~7 を MAX7219 の DIG 0~7 に、LED の COL 0~7 を MAX7219 の SEG a~g+dp に接続することになります。

1088B のように接続が逆な素子ならば ROW を SEG に、COL を DIG に接続することになります。この場合は当然、後のサンプルプログラムでは文字が横倒し(かつ裏返し?)に表示されることになります。

制御データ

MAX7219は、一回に16ビットのデータを送信することで動作を制御します。16ビットデータの各ビットを D0D15 としたとき、

D0 ~ D7データ
D8 ~ D11制御レジスタ

を表しています。D12 ~ D15 は使用されていませんので、値は動作に影響を与えません。
D0~D7に代入したい値、D8~D11に代入先の制御レジスタを入れて送信していくことで動作を制御できます。

DIN端子から入力されたデータは、内部のシフトレジスタに一時保存され、LOAD(CS)がLOWからHIGHになるタイミングでD8~D11ビットで示される制御レジスタにデータが反映されます。

制御レジスタを制御用の命令だと考え、

『8ビットの制御命令 + 8ビットのデータで16ビット』

だと思ってもいいでしょう。

制御レジスタ

制御レジスタには以下の種類があります。

D11 D10 D9 D8 制御レジスタ
0000(0x0)No-OP
0001(0x1)Digit 0
0010(0x2)Digit 1
0011(0x3)Digit 2
0100(0x4)Digit 3
0101(0x5)Digit 4
0110(0x6)Digit 5
0111(0x7)Digit 6
1000(0x8)Digit 7
1001(0x9)Decode Mode
1010(0xA)Intensity
1011(0xB)Scan Limit
1100(0xC)Shutdown
1101(0xD)未使用
1110(0xE)未使用
1111(0xF)Display test

次に、各制御レジスタについて解説します。

No-OP

制御レジスタにNo-OPが指定されている場合、下位8bitのデータによらずMAX7219は何も動作しません。

『何もしない命令』になんの意味があるのか?と思うかもしれませんが、これはカスケード接続をする場合に重要な役割をもちます。詳細は次の記事で。

Digit 0~Digit 7

制御レジスタ Digit 0~Digit 7 は、LEDの各桁に対応しています。下位8bitのデータが各桁の表示データを表します。

Decode Mode

Decode Modeは、各桁の表示データの使用法を表します。各桁のデータには2つのモードがあります。

No Decode

No Decodeモードでは、データバイトの各ビットがそれぞれ8個のLEDに対応しています。
つまり各ビットがそのままLEDの点滅を表しています。

Code B Decode

7セグメントLEDが接続されている場合に使用するモードです。データバイトのうち下位4ビット(D3-D0)の値を文字コードとして、MAX7219内蔵のフォントを表示します。データバイト各ビット(下位4ビット)の値と LED A~LED G の表示内容の対応は下図の通りです。

D3 D2 D1 D0文字ABCDEFG表示
0000(0x0)01111110
0001(0x1)10110000
0010(0x2)21101101
0011(0x3)31111001
0100(0x4)40110011
0101(0x5)51011011
0110(0x6)61011111
0111(0x7)71110000
1000(0x8)81111111
1001(0x9)91111011
1010(0xA)
(ハイフン)
0000001
1011(0xB)H1001111
1100(0xC)E0110111
1101(0xD)L0001110
1110(0xE)P1100111
1111(0xF)(空白)0000000

なお、下位4ビット D0~D3 とは関係なく、D7 が 1 だと LED DP が点灯、0 だと LED DP が消灯します。また、D6~D4 の値は表示に影響を与えません。

Intensity

LED の輝度を表します。下位4ビット(D3-D0)のみ有効で、輝度を16段階に設定できます。

D3 D2 D1 D0Duty Cycle(輝度)
0000(0x0)1/32
0001(0x1)3/32
0010(0x2)5/32
0011(0x3)7/32
0100(0x4)9/32
0101(0x5)11/32
0110(0x6)13/32
0111(0x7)15/32
1000(0x8)17/32
1001(0x9)19/32
1010(0xA)21/32
1011(0xB)23/32
1100(0xC)25/32
1101(0xD)27/32
1110(0xE)29/32
1111(0xF)31/32
Scan Limit

ダイナミック点灯時、7セグメントLED何桁(またはLED×8の列を何列)スキャンするかを設定します。下位3ビットのみ有効です。

D2 D1 D0スキャン対象
000(0x0)Digit 0 のみ
001(0x1)Digit 0~1
010(0x2)Digit 0~2
011(0x3)Digit 0~3
100(0x4)Digit 0~4
101(0x5)Digit 0~5
110(0x6)Digit 0~6
111(0x7)Digit 0~7

Scan Limit の設定値は LED の明るさに影響を与えます。ダイナミック点灯の桁数が減ると、Intensity の設定が同じでも単位時間あたりの LED の点灯時間が増すため、明るくなります。カスケード接続で複数のMAX7219を同時に使用する場合は注意してください。

Shutdown

データバイトのD0のみが使用され、Shutdown mode か否かを設定します。

D0モード
0Shutdown mode
1Normal Operation

Shutdown mode では、ダイナミック点灯のための桁スキャンが行われず、すべてのLEDが消灯します。

Normal Operation では他の各制御レジスタの値に従ってLEDが点灯します。通常の使用時はこちらを選びます。

Display Test

データバイトのD0のみが使用され、Display Test Mode か否かを設定します。

D0モード
0Normal Operation
1Display Test Mode

Display Test Mode では、

Digit 0~Digit 7の全ビットは1
Intensity の Duty Cycle は 31/32
Scan Limit は Digit 0~Digit 7

として扱われます。つまり、最高輝度ですべてのLEDが点灯します。
ただし Display Test Mode になる前に各制御レジスタに設定された値は保存されており、Normal Operationに切り替えると保存された値が復帰します。

Normal Operation では他の各制御レジスタの値に従ってLEDが点灯します。通常の使用時はこちらを選びます。

初期設定例

電源投入時、MAX7219は Shutdown mode で起動します。

Display Test レジスタを Normal Operation に
Decode Mode レジスタを適切な値に
Intensity レジスタを適切な値に
Scan Limit レジスタを適切な値に
Shutdown レジスタを Normal Operation に

という順に設定していくとよいと思われます。

製作&実験

回路

今回はモジュール化された製品を使用するので正確な回路は不明ですが、だいたい下図のような感じだと思います。実際にはArduino と LED Matrixモジュール間を、電源を入れて5本のジャンパ線で接続するだけです。

配線

配線は非常にシンプルです。LED MatrixモジュールとArduinoをオス-メスのジャンパ線で接続するだけです。

LED Matrixモジュール側の端子配置が写真のものとは異なっている製品がありますので、よく確認してください(CLKとCSの並び順が逆になっているものを見たことがあります)。

接続は以下の通りです。

ArduinoLED Matrixモジュール
D12DIN
D11CS
D10CLK
5VVCC
GNDGND

実物はこんな感じです。前回のArduinoから直接ダイナミックドライブするものに較べて配線がずっとスッキリしています。

プログラム

C++
// 定数の定義
#define DATAPIN 12
#define CSPIN 11
#define CLKPIN 10

// 文字パターン設定
byte pattern[8][8]={
  {0B00111000,0B01000100,0B10000010,0B10000010,0B11111110,0B10000010,0B10000010,0B00000000},  // A
  {0B00000000,0B00000000,0B10011100,0B10100000,0B11000000,0B10000000,0B10000000,0B00000000},  // r
  {0B00000100,0B00000100,0B00000100,0B00111100,0B01000100,0B01000100,0B00111100,0B00000000},  // d
  {0B00000000,0B00000000,0B10000100,0B10000100,0B10000100,0B10001100,0B01110100,0B00000000},  // u
  {0B00001000,0B00000000,0B00011000,0B00001000,0B00001000,0B00001000,0B00011100,0B00000000},  // i
  {0B00000000,0B00000000,0B10111000,0B11000100,0B10000100,0B10000100,0B10000100,0B00000000},  // n
  {0B00000000,0B00000000,0B01111000,0B10000100,0B10000100,0B10000100,0B01111000,0B00000000},  // o
  {0B00000000,0B00000000,0B00000000,0B00000000,0B00000000,0B00000000,0B00000000,0B00000000}   // 空白
};

// 16ビットのデータをSPI送信
void sendSPIData(uint16_t data){
  byte highByte=data>>8, lowByte=data&0xFF;
  digitalWrite(CSPIN, LOW);                       // CSはLOWの時セレクト
  shiftOut(DATAPIN, CLKPIN, MSBFIRST, highByte);  // データをシリアル送信
  shiftOut(DATAPIN, CLKPIN, MSBFIRST, lowByte);   // (MAX7219は上位ビットを先に受信するのでMSBFIRSTを指定)
  digitalWrite(CSPIN, HIGH);                      // CSをHIGHに戻しておく
}

// 行digに値:dataをセットする
void setDigitData(byte dig, byte data){
  uint16_t sendData = (dig+1)*256|data;
  sendSPIData(sendData);
}

void setup() {
  // 関係する端子を出力にセット
  pinMode(DATAPIN, OUTPUT);
  pinMode(CLKPIN, OUTPUT);
  pinMode(CSPIN, OUTPUT);

  // とりあえずCSは非選択にセット
  digitalWrite(CSPIN, HIGH);

  // 各レジスタに初期値をセット
  sendSPIData(0x0F00);    // ディスプレイテスト;normal operation
  sendSPIData(0x0900);    // デコードモード:no decode
  sendSPIData(0x0A0F);    // 輝度最大
  sendSPIData(0x0B07);    // 全桁使用

  // 点灯データクリア
  for(int i=0; i<8; i++){
    setDigitData(i, 0);
  }

  // 初期設定が終わったらシャットダウン解除
  sendSPIData(0x0C01);
}

void loop() {
  for(int i=0; i<8; i++){
    for(int row=0; row<8; row++){
      setDigitData((byte)row, pattern[i][row]);
    }
    delay(500);
  }
}

プログラムの解説

定数の定義
C++
// 定数の定義
#define DATAPIN 12
#define CSPIN 11
#define CLKPIN 10

まず、通信に使う端子を定数として定義しています。

D12 が MOSI(LED Matrixモジュール側のDIN)
D11 が CS
D10 が CLK

です。

表示パターンの定義
C++
// 文字パターン設定
byte pattern[8][8]={
  {0B00111000,0B01000100,0B10000010,0B10000010,0B11111110,0B10000010,0B10000010,0B00000000},  // A
  {0B00000000,0B00000000,0B10011100,0B10100000,0B11000000,0B10000000,0B10000000,0B00000000},  // r
  {0B00000100,0B00000100,0B00000100,0B00111100,0B01000100,0B01000100,0B00111100,0B00000000},  // d
  {0B00000000,0B00000000,0B10000100,0B10000100,0B10000100,0B10001100,0B01110100,0B00000000},  // u
  {0B00001000,0B00000000,0B00011000,0B00001000,0B00001000,0B00001000,0B00011100,0B00000000},  // i
  {0B00000000,0B00000000,0B10111000,0B11000100,0B10000100,0B10000100,0B10000100,0B00000000},  // n
  {0B00000000,0B00000000,0B01111000,0B10000100,0B10000100,0B10000100,0B01111000,0B00000000},  // o
  {0B00000000,0B00000000,0B00000000,0B00000000,0B00000000,0B00000000,0B00000000,0B00000000}   // 空白
};

“A”、”r”、”d”、”u”、”i”、”n”、”o”、” “の8文字の点灯パターンを配列に定義しています。各バイトが横1ライン分の点灯に対応します。

16ビット分のデータを送信
C++
// 16ビットのデータをSPI送信
void sendSPIData(uint16_t data){
  byte highByte=data>>8, lowByte=data&0xFF;
  digitalWrite(CSPIN, LOW);                       // CSはLOWの時セレクト
  shiftOut(DATAPIN, CLKPIN, MSBFIRST, highByte);  // データをシリアル送信
  shiftOut(DATAPIN, CLKPIN, MSBFIRST, lowByte);   // (MAX7219は上位ビットを先に受信するのでMSBFIRSTを指定)
  digitalWrite(CSPIN, HIGH);                      // CSをHIGHに戻しておく
}

ここでは、16ビットのデータを送信する sendSPIData()関数を定義しています。

実際の送信は shiftOut()関数 を使用します。この関数は一度に8ビット分のデータを送信するので、まず uint16_t型の引数 data を上位バイトと下位バイトに分解し、それぞれ変数 highBytelowByte に代入しています。

データの送信手順は以下の通りです。

digitalWrite()関数でCSをLOWにします。これによりMAX7219にこれからデータを送信することを通知します。

shiftOut()関数でデータを送信します。この関数だけで自動的にクロックに併せて1ビットずつデータを送信するので、複雑な信号の制御は不要です。

書式
C++
void shiftOut(int dataPin, int clkPin, int bitOrder, byte data)
引数
名前意味
dataPinintArduinoからデータを送出する端子番号
clkPinintArduinoからクロックを送出する端子番号
bitOrderintシリアル送出するときに上位ビットか下位ビットのどちらを先にするか
databyte送出するデータ

bitOrderは、上位ビットから送信するときは MSBFIRST、下位ビットから送信するときは LSBFIRST を指定します。

返却値

返却値はありません。

16ビットの上位ビットから順に送信するので、bitOrder に MSBFIRST を指定して、まず highByte を送信し、続けて lowByte を送信します。

送信後に digitalWrite()関数でCSをHIGHにします。このタイミングで送信したデータが制御レジスタに反映されます。

LEDの点灯データを設定する
C++
// 桁:digに値:dataをセットする
void setDigitData(byte dig, byte data){
  uint16_t sendData = (dig+1)*256|data;
  sendSPIData(sendData);
}

引数digがLED MatrixのROW(dig)、dataが1ROW分の点灯データを表しています。

dig 0~dig 7が上位8ビット(制御レジスタ)の0x01~0x08に対応していますので、digに1を加えて上位8ビット、点灯データdataを下位8ビットとした16ビット数をつくり、先に定義した sendSPIData()関数を呼び出しています。

初期設定
C++
void setup() {
  // 関係する端子を出力にセット
  pinMode(DATAPIN, OUTPUT);
  pinMode(CLKPIN, OUTPUT);
  pinMode(CSPIN, OUTPUT);

  // とりあえずCSは非選択にセット
  digitalWrite(CSPIN, HIGH);

  // 各レジスタに初期値をセット
  sendSPIData(0x0F00);    // ディスプレイテスト;normal operation
  sendSPIData(0x0900);    // デコードモード:no decode
  sendSPIData(0x0A0F);    // 輝度最大
  sendSPIData(0x0B07);    // 全桁使用

  // 点灯データクリア
  for(int i=0; i<8; i++){
    setDigitData(i, 0);
  }

  // 初期設定が終わったらシャットダウン解除
  sendSPIData(0x0C01);
}
出力ピンの設定
C++
  // 関係する端子を出力にセット
  pinMode(DATAPIN, OUTPUT);
  pinMode(CLKPIN, OUTPUT);
  pinMode(CSPIN, OUTPUT);

LED Matrixモジュールと接続した Arduino の端子をすべて出力に設定します。

CSを非選択に
C++
  // とりあえずCSは非選択にセット
  digitalWrite(CSPIN, HIGH);

CS(LOAD)端子をLOWにします。これでいったん、LED Matrixモジュールは非選択の状態になります。

各制御レジスタに値を設定
C++
  // 各レジスタに初期値をセット
  sendSPIData(0x0F00);    // ディスプレイテスト;normal operation
  sendSPIData(0x0900);    // デコードモード:no decode
  sendSPIData(0x0A0F);    // 輝度最大
  sendSPIData(0x0B07);    // 全桁使用

各レジスタに値をセットします。この例では下の表のように設定しています。

制御レジスタ名意味
Display Test0x00normal operation
(通常の動作状態)
Decode Mode0x00no decode
(データのビットパターンがそのままLEDの点滅パターンを表す)
Intensity0x0Fduty cycle = 31/32
(輝度最大)
Scan Limit0x07dig 0-7
(8桁/8行すべてを使用)
点灯データのクリア
C++
  // 点灯データクリア
  for(int i=0; i<8; i++){
    setDigitData(i, 0);
  }

各桁(各行)の点灯パターンをすべて0にセットします。MAX7219の場合、電源投入直後は自動的に点灯パターンがクリアされるのですが、通電したまま Arduinoのプログラムのみ再起動した場合には前の点灯パターンが残ったままになってしまうため、ここでクリアしています。

シャットダウンを解除
C++
  // 初期設定が終わったらシャットダウン解除
  sendSPIData(0x0C01);

すべての設定が終わったら、shutdownレジスタに1(normal operation)をセットします。これでMAX7219が通常動作モードとなり、LED Matrixのスキャンが開始されます。

loop()関数
C++
void loop() {
  for(int i=0; i<8; i++){
    for(int row=0; row<8; row++){
      setDigitData((byte)row, pattern[i][row]);
    }
    delay(500);
  }
}

内側のループ(変数 row のループ)は、LED Matrixの行数分のデータをセットしています。setDigitalData()関数で、LED Matrix の row 行目に i 番目の文字の row 行目の点灯パターンを送信しています。このループが終了した時点で8×8の1画面分の点灯データを送信したことになります。delay()で500msの間、その画面を保ちます。

外側のループ(変数 i のループ)は、表示する文字を順に切り替えています。表示する文字は “A”、”r”、”d”、”u”、”i”、”n”、”o”、” “の8文字なので、i=0~7の8回のループになっています。

実行結果

このようにArduinoの文字を繰り返し表示します。直接Arduinoがダイナミックドライブをする場合に較べて配線がずっと少ないのが特徴です。

まとめ

  • MAX7219などシリアル通信可能なLEDドライバを使用すると少ない配線で多数のLEDを制御できる

コメント

タイトルとURLをコピーしました