ESP32(Arduino)でLEDパネル(128×64)を制御する方法ープログラム編

 皆さんこんにちは。ロクです。

 今回は、少し前に解説した、LEDドットマトリクスの制御の仕組みについての記事の後編ということで書いていこうと思います。

 その時の記事では、制御の仕組みやESP32とLEDパネルの接続方法について解説したものの、具体的なプログラムについては書いていませんでした。

 ですので、今回は、実際にLEDドットマトリクスを動かすときの例として、プログラムをのせていこうと思います。

 前回の記事ではどのようなことを解説していたのか気になるよ~という方は、以下のリンクからぜひご参照ください。

前回のおさらい

 ここで、前回までに解説した内容で、プログラムを書く前に抑えておくべきことがあります。

 それについて少しおさらいしましょう。

制御の流れについて

 制御の流れは以下の通りです。軽く押さえておきましょう。

  1. 全ピンをLOWにする。
  2. R1 G1 B1 R2 G2 B2をLOWかHIGHにする。これで1ピクセルごとの色を指定できる。
  3. CLKをHIGHにする。
  4. CLKをLOWにする。3と4によりそのピクセルは2で指定した色に決定され、次のピクセルの指定に移る
  5. これと同じように、2~4をあと128回繰り返す。これで1セクションのデータ分書き込み終わる。
  6. ABCDEの5bit使って送り込みたいセクションを決める。最初は00000とか。(R1,G1,B1用とR2,G2,B2用でそれぞれ32セクションある)
  7. OEをHIGHにする。画面表示有効のフラグらしい。
  8. LAT(STB)をHIGHにする。これでLEDが光る。
  9. LAT(STB)をLOWにする。
  10. OEをLOWにする。
  11. 以上、2~10をセクションを変えながら32回分繰り返す。これを高速で繰り返すことで画面を全部表示できる。

ESP32とLEDパネルの接続ピンの組み合わせ

 今回僕は、以下のようなピンの組み合わせで行こうと思います。このピンの組み合わせというのは、あらかじめ決まっているものではなく、自分で決めることができます。

  • R1 → 32番ピン
  • G1 → 33番ピン
  • B1 → 27番ピン
  • R2 → 14番ピン
  • G2 → 12番ピン
  • B2 → 13番ピン
  • E  → 23番ピン
  • A  → 22番ピン
  • B  → 21番ピン
  • C  → 19番ピン
  • D  → 18番ピン
  • CLK → 5番ピン
  • LAT → 17番ピン
  • OE  → 16番ピン
今回使うESP32の外観
配線をした時の外観

プログラムの全容

 今回使う開発環境はArduinoIDEです。(ESP32=ArduinoIDEを使って開発:というようにセットで覚えておきましょう。)

 ESP32について詳しく知りたいという方は、ぜひこちらの記事をご参照ください。

 これからプログラムの一例を示しますが、その前に、前提知識として、ファイルを3つに分けているということを抑えておいてください。一つのファイルに抑えようとすると、どうしてもわかりにくいものになってしまうので、その回避策となっています。

 また、ArduinoIDEでは、ファイルの名前によってビルドの順番が決まり、このビルド順によってはエラーが出てしまう恐れがあります。そのため、極力今回僕が紹介するコードのファイル名と同じファイル名を付けるようにしましょう。

 ちなみにビルド順の決まり方は、

  1. 親フォルダと同じ名前のファイル
  2. abc…のようにアルファベットの登場順

 という流れになっています。つまり、この名前の規則さえ押さえておけば、ファイル名は自分の好きなものに変更してもらって構いませんが、不安だという人は、今回はこの記事に沿って名前を付けていきましょう。

 それではプログラムに移りましょう。

“ledmatrix"ファイルのコード内容

 今から書くコードは、“ledmatrix"という名前にしたファイルに書いていってください。名前を変更した状態で保存すると、大元のフォルダ名もledmatrixに変わるはずです。変わらない場合は直接変えましょう。

#define r1 32
#define g1 33
#define b1 27
#define r2 14
#define g2 12
#define b2 13
#define e 23
#define a 22
#define b 21
#define c 19
#define d 18
#define clk 5
#define lat 17
#define oe 16

int pins[]={32,33,27,14,12,13,23,22,21,19,18,5,17,16};
int leng=sizeof(pins)/sizeof(int);
int gamen[128][64];

 ちなみにこのファイルでは、初期設定的なものを書いています。

“agazoudata"ファイルのコード内容

 こちらのコードを書くファイル名は、“agazoudata"にしてください。変な名前だなと思うかもしれませんが、これも、先ほど言いましたビルド順の関係で、先頭にaを持ってきています。

 また、このファイルに書くものは、サンプル的に使用するアニメーションのデータになっているのですが、コードとして書くとあまりにもかさばってしまうので、今回はダウンロードファイルとして置いておくことにします。

 どうぞご自由にダウンロードしてコピペするなりご使用ください。↓

“zmain"ファイルのコード内容

 こちらのコードを書くファイルは“zmain"としてください。

int page=0;//アニメーションページ
int col=0;//色相設定用変数
int time1=0;
int time2=0;
int num=0;//再生するアニメーション選択
int vol=600;//LEDの明るさ 450~800の間を調整しながら使うといい
unsigned int dt=0;//フレーム間の時間
unsigned int bt=0;//前フレーム時間
unsigned int at=0;//現在フレーム時間
//int yoko=0;
int tate=0;
int hubfla=0;

void setup() {
  // put your setup code here, to run once:
  //前フレーム時間の取得
  bt=micros();
  for(int i=0;i<leng;i++)
  {
    pinMode(pins[i],OUTPUT);
    digitalWrite(pins[i],LOW);
  }
  num=1;
  gazouset();
}

void loop() {
  // put your main code here, to run repeatedly:

  
  //前フレームと現在フレームの経過時間(マイクロ秒)を取得する↓
  at=micros();
  
  if(at<bt)//70分経過時のタイマーのオーバーフローを考慮した処理
  {
    dt=0;
  }
  else
  {
    dt=at-bt;
  }
  bt=at;
  //前フレームと現在フレームの経過時間(マイクロ秒)を取得する↑

  //switchによるHUB75Eの制御↓
  switch(hubfla)
  {
    case 0:
      digitalWrite(oe,HIGH);//消えている状態
      digitalWrite(lat,LOW);
      hubfla++;
      break;
    case 1:
      time1+=dt;
      if(time1>=100-vol)
      {
        time1=0;
        hubfla++;
      }
      break;
    case 2:
      for(int i=0;i<128;i++)
      {
        int rgb1;
        int rgb2;
        rgb1=gamen[i][tate];
        rgb2=gamen[i][tate+32];
  
        if(rgb1!=255)
        {
          if(((rgb1&48)>>4)<=col)
          {
            digitalWrite(r1,LOW);
          }
          else
          {
            digitalWrite(r1,HIGH);
          }
          if(((rgb1&12)>>2)<=col)
          {
            digitalWrite(g1,LOW);
          }
          else
          {
            digitalWrite(g1,HIGH);
          }
          if((rgb1&3)<=col)
          {
            digitalWrite(b1,LOW);
          }
          else
          {
            digitalWrite(b1,HIGH);
          }
        }
       if(rgb2!=255)
       {
          if(((rgb2&48)>>4)<=col)
          {
            digitalWrite(r2,LOW);
          }
          else
          {
            digitalWrite(r2,HIGH);
          }
          if(((rgb2&12)>>2)<=col)
          {
            digitalWrite(g2,LOW);
          }
          else
          {
            digitalWrite(g2,HIGH);
          }
          if((rgb2&3)<=col)
          {
            digitalWrite(b2,LOW);
          }
          else
          {
            digitalWrite(b2,HIGH);
          }
        }
      //配色決定↓
        digitalWrite(clk,HIGH);
        digitalWrite(clk,LOW);
      }
      
      //セクション決定↓
       digitalWrite( a, ( tate & 1 ) );
      digitalWrite( b, ( tate & 2 ) >> 1);
      digitalWrite( c, ( tate & 4 ) >> 2);
      digitalWrite( d, ( tate & 8 ) >> 3);
      digitalWrite( e, ( tate & 16 ) >> 4);
      //セクション決定↑

      digitalWrite(lat,HIGH);
      digitalWrite(oe,LOW);//点いている状態
      
      hubfla++;
      break;
    case 3:
      time1+=dt;
      if(time1>=vol)
      {
        time1=0;
        tate++;
        if(tate==32)
        {
          tate=0;
          hubfla++;
        }
        else
        {
          hubfla=0;
        }
      }
      break;
    case 4:
      //PWMによる色の制御のための変数
      col++; 
     // time1=0;
      if(col==3)
      {
        col=0;
      }
      hubfla=0;
      break;
  }
//switchによるHUB75Eの制御↑

//ページ切り替えを行うための時間計測
  time2+=dt;
  if(time2>=100*1000)//100ミリ秒
  {
    page++;
    gazouset();
    time2=0;
  }
}

void gazouset()
{
  for(int i=0;i<128;i++)
  {
    for(int j=0;j<64;j++)
    {
      gamen[i][j]=0;
    }
  }
  
  if(num==0)
  {
    if(page==sizeof(kari)/sizeof(kari[0]))
    {
      page=0;
    }
    for(int i=0;i<sizeof(kari[0])/sizeof(kari[0][0]);i++)
    {
      for(int j=0;j<sizeof(kari[0][0])/sizeof(kari[0][0][0]);j++)
      {
        gamen[i][j]=kari[page][i][j];
      }
    }
  }
  else if(num==1)
  {
    if(page==sizeof(kyoro)/sizeof(kyoro[0]))
    {
      page=0;
    }
    for(int i=0;i<sizeof(kyoro[0])/sizeof(kyoro[0][0]);i++)
    {
      for(int j=0;j<sizeof(kyoro[0][0])/sizeof(kyoro[0][0][0]);j++)
      {
        if(kyoro[page][i][j]!=-1)
        {
          gamen[i][j]=kyoro[page][i][j];
        }
      }
    }
  }
}

 こちらのコードで最後になります。ちなみにこのコードがLEDパネルをコントロールする主要なプログラムとなっています。暇なときに解析してみるといいかもしれません。

実際に動かしてみた

 それでは、実際に動かしてみた時の映像をご覧ください。ちなみに下のアニメーションは、すべて僕の手書きです!

 少し画素が荒くて、ちらつきも半端ないように見えますが、そこは僕のスマホのカメラの問題ですので…許してやってください!本当はもっときれいでちらつきもあまりありません。

 とはいえ、色の階調を多くしようとすると、途端にちらつきが激しくなるので、そこのバランスも考えながらプログラムを組んでいく必要がありそうです。

最後に

 プログラムを載せるだけという少し荒々しいまとめ方になったかもしれませんが、少しでも参考になれば幸いです。

 まずは試しにコピペで動かしてみるのもありだと思います。

 やっぱり電気工作関係は面白いですね。

 それではまた今度~。

電気工作

Posted by ロク