M5Stack で LEDマトリクスパネル (HUB-75) を光らせる

いわゆる HUB75 という規格のLEDパネルを M5Stack で光らせます。光らせて何かをするというより、LED Matrix Panel の光る仕組みについて実験してみたという主旨です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/266247/6140d957-1f27-840e-36bf-7b7775aac777.png)

# 参考

- [LEDドットマトリクスパネル HUB75規格について調べてみた](https://qiita.com/onokatio/items/1b99ae9475b6a9fc2f15)
- [ESP32で64x32 LEDマトリクスパネルを制御](https://neocat.hatenablog.com/entry/2018/05/02/213331)

# HUB75 LED Matrix Panel

屋外の高いところとかに、輝度がやや高めの LED ディスプレイがあったら、だいたいこの規格の LED パネルが使われています（多分）。最近は国内でも簡単に購入できるようになりました。そこそこの輝度があって、制御に必要な機器もそれなりの値段で入手できることから、業務用としてはかなり広く使われているという印象です。

HUB75 の仕様については、下記の記事で詳しく書かれているので、ここでは要点だけ書きます。

- [PIC32のDMA機能を使ったLEDディスプレイ表示実験](http://www.ze.em-net.ne.jp/~kenken/leddisplay/index.html)

なお、64x64 のパネルの場合は HUB75E という、HUB75 を拡張した規格が使われていることが多いようです。HUB75E では GND ピンをひとつ潰して、代わりに列を指定するための E ピンが追加されています。ABCDE の 5bit で指定することで、32列 (x2=64列) 指定できます。

# 使ったもの

- M5Stsck
- 32x64 LED Matrix Panel (HUB75)
- 30A/5V 安定化電源

LED Matrix Panel は、同じ見た目をしていても、信号回りの仕様(ピン配置、HIGH/LOWの解釈)が異なることがあるようです。

- 参考： [Adafruit Learning System: 32x16 and 32x32 RGB LED Matrix](https://cdn-learn.adafruit.com/downloads/pdf/32x16-32x32-rgb-led-matrix.pdf)

# 結線

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/266247/9def880d-45a9-bcce-0a54-ed779a2b1ed5.png)

- LED Matrix のピン名: M5Stack の GPIO
- A: 18
- B: 19
- C:   21
- D:   22
- R1:  16
- G1:  26
- B1:  17
- CLK: 2
- OE:  23
- LAT: 5

実際に結線しようとしてから気がつきましたが、M5Stack のデフォルトで出ているピンだけだと、微妙にピン数が足りなくて R2,G2,B2 に結線できませんでした。無理矢理出して足りるようにすることもできますが、実験なのでとりあえず今回は R2,G2,B2 は無しでやりました。このため、パネルの上半分しか光りません。

M5Stack の 18 と 19 のピンは LCD に使われているため、LED パネルを光らせている間に LCD を動かすとバグります。1, 3 のピンはシリアル通信に使われており、35, 36 は input only なので今回の用途には使えません。

なお LED Matrix Panel は 5V 動作ですが、M5Stack の 3.3V の出力を直接入れても問題なく動作するようです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/266247/2380d833-96e1-4edd-34ba-dd6a67d5fdba.png)

だいぶん粗いですが、こんな結線でも光りました。ただし、ちょっとでも触るとノイズがのってちらつきます。

# 全点灯プログラム

実験なので、いろいろしやすいように Arduino で書いてみました。PIN_R1, PIN_G1, PIN_B1 を HIGH にしたり LOW にしたりすると、色を変えられます。

```.c

#define PIN_A   18
#define PIN_B   19
#define PIN_C   21
#define PIN_D   22
#define PIN_R1  16
#define PIN_G1  26
#define PIN_B1  17
#define PIN_CLK 2
#define PIN_OE  23
#define PIN_LAT 5

void setup() 
{

  pinMode( PIN_A, OUTPUT );
  pinMode( PIN_B, OUTPUT );
  pinMode( PIN_C, OUTPUT );
  pinMode( PIN_D, OUTPUT );
  pinMode( PIN_R1, OUTPUT );
  pinMode( PIN_G1, OUTPUT );
  pinMode( PIN_B1, OUTPUT );
  pinMode( PIN_CLK, OUTPUT );
  pinMode( PIN_OE, OUTPUT );
  pinMode( PIN_LAT, OUTPUT );

  digitalWrite( PIN_A, LOW );
  digitalWrite( PIN_B, LOW );
  digitalWrite( PIN_C, LOW );
  digitalWrite( PIN_D, LOW );
  digitalWrite( PIN_R1, LOW );
  digitalWrite( PIN_G1, LOW );
  digitalWrite( PIN_B1, LOW );
  digitalWrite( PIN_LAT, LOW );
  
  // OE のみ HIGH で OFF になる
  digitalWrite( PIN_OE,  HIGH );
  digitalWrite( PIN_CLK, LOW );
}

void loop() 
{
  int i = 0;
  char d = 0;

  for ( d = 0; d < 16; d++ ){

    // ラッチはこのタイミングで HIGH にする必要があるぽい
    digitalWrite( PIN_LAT, HIGH ); 

    // (R=1,G=1,B=1) 
    digitalWrite( PIN_R1, HIGH );
    digitalWrite( PIN_G1, HIGH );
    digitalWrite( PIN_B1, HIGH );

    // R2,G2,B2 も結線できている場合
    // digitalWrite( PIN_R2, HIGH );
    // digitalWrite( PIN_G2, HIGH );
    // digitalWrite( PIN_B2, HIGH );

    // RGB データ (R=1,G=1,B=1) を 64ibt 分転送する
    for ( i = 0; i < 64; i++ ){
      digitalWrite( PIN_CLK, HIGH );
      digitalWrite( PIN_CLK, LOW );
    }

    // LED を消灯する (データ転送時のちらつきを防ぐ)
    digitalWrite( PIN_OE, HIGH );

    // LED パネルの光らせる列を 4bit で指定する
    digitalWrite( PIN_A, ( d & 1 ) );
    digitalWrite( PIN_B, ( d & 2 ) >> 1);
    digitalWrite( PIN_C, ( d & 4 ) >> 2);
    digitalWrite( PIN_D, ( d & 8 ) >> 3);

    // 4bit で指定した列に RGB データを転送する
    digitalWrite( PIN_LAT, LOW );

    // LED を点灯する
    digitalWrite( PIN_OE,  LOW );

  }
}
```

# 中間色点灯プログラム

LED が点灯する時間の長さを調整することで、中間色を表現してみるテストです。

```.c
// setup() と #define は上のプログラムと同じなので省略

// 色調(階調)の設定
inline void SET_RGB( char r, char g, char b, int j ){
    digitalWrite( PIN_R1, r > j ? HIGH : LOW );
    digitalWrite( PIN_G1, g > j ? HIGH : LOW );
    digitalWrite( PIN_B1, b > j ? HIGH : LOW );
}

char d = 0;
void loop()
{
  int i = 0;
  int j = 0;

  for ( j = 0; j < 16; j++ ){ // RGB 16 階調 (16x16x16=4096色)
    for ( d = 0; d < 16; d++ ){
      digitalWrite( PIN_LAT, HIGH );

      for ( i = 0; i < 64; i++ ){
        SET_RGB( d, i/4, 15-d, j );
        digitalWrite( PIN_CLK, HIGH );
        digitalWrite( PIN_CLK, LOW );
      }

      digitalWrite( PIN_OE, HIGH );

      digitalWrite( PIN_A, ( d & 1 ) );
      digitalWrite( PIN_B, ( d & 2 ) >> 1);
      digitalWrite( PIN_C, ( d & 4 ) >> 2);
      digitalWrite( PIN_D, ( d & 8 ) >> 3);

      digitalWrite( PIN_LAT, LOW );

      digitalWrite( PIN_OE,  LOW );

    }
  }
}
```

240MHz 動作なら 256 階調でもできそうなのですが、上記のプログラムで 256 階調にするとちらつきました。16 階調くらいにして、ようやくちらつかなくなりました。真面目に 256 階調やるなら、やはり DMA などを使う必要がありそうです。

# 結論

いろいろ中途半端になってしまったので、ESP32 でもうちょっと真面目に書いて情報追記しときます(いつ?)。