aitendoマトリクスＬＥＤパネルの使い方（２）

#はじめに
共立エレショップで販売販売されている「<a href="https://eleshop.jp/shop/g/gEB8411/">32×16ドットマトリクスLEDパネル</a>」を使ったプログラムを紹介して欲しいとの依頼があり、「<a href="https://qiita.com/Yukiya_Ishioka/items/fd791498bb5c02244546">aitendoマトリクスＬＥＤパネルの使い方（１）</a>」という記事を投稿しました。
この時は**ESP32-DevKitC**を使ってプログラム内に表示したいドットデータを配列定数で用意して表示を行っていましたが、今回はこの時のプログラムを改造して文字列をドットデータへ変換して表示するプログラムを紹介します。
ハードウェアもこの時に紹介した構成で動かすので、以前の記事を参照ください。
![5_all_s.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/370759/77f62328-8308-fafb-58fe-a4985a2237d8.jpeg)
プログラムは前回と同様に **Arduino IDE** を使って作成やビルドを行うので、Arduino IDEとESP32をビルドできる環境を準備しておいてください。
「**arduino ide esp32-devkit**」で検索すると環境の構築方法を説明したページが見つけられると思います。

文字列をドットデータへの変換はドットフォントのデータを用意し、文字コードからドットデータを変換する方法で実現します。
全てのプログラムやデータをここへ記すのは難しいため、ＣＱ出版のInterface誌 <a href="https://interface.cqpub.co.jp/wp-content/uploads/if10_156.pdf">2018年10月号「ESP実験コーナー」</a>で作成したプログラムを流用します。

- **aitendoマトリクスＬＥＤパネルの使い方（１）**
　https://qiita.com/Yukiya_Ishioka/items/fd791498bb5c02244546

- **Interface誌 2018年10月号**
　https://interface.cqpub.co.jp/magazine/201810/
- **「ESP実験コーナー」**
　https://interface.cqpub.co.jp/wp-content/uploads/if10_156.pdf

- **共立エレショップ**の商品ページ
　32×16ドットマトリクスLEDパネル(赤/橙/緑) 32x16DOT-0158-DJK
　https://eleshop.jp/shop/g/gEB8411/

#流用プログラムの取り出し
文字コードからドットフォントへの変換プログラムはInterface誌に執筆した際に利用したプログラムを流用します。プログラムの内容などはInterface誌 2018年10月号「ESP実験コーナー」を参照してください。
ここではダウンロードサービスから必要なプログラムをダウンロードして利用する方法を記します。

まず、Interface誌のダウンロードサイトから <a href="https://www.cqpub.co.jp/interface/download/2018/10/IF1810EE.zip">IF1810EE.zip</a> をダウンロードします。
**IF1810EE.zip**を展開すると複数のディレクトリが出てきますが、**matled64-2**ディレクトリのプログラムを流用します。
**matled64-2**ディレクトリには４つのファイルがありますが、今回は以下の３つのファイルを流用します。これらのファイルを後で作成する **matled_ai.ino**と同じディレクトリへコピーしてください。
ちなみにドットフォントのサイズは全角文字が **横１６ドット×縦１６ドット**、**半角文字が横８ドット×縦１６ドット** です。

| \IF1810EE\matled64-2  |
|:------------------------|
| fx_8x16rk_fnt.c |
| jiskan16_fnt.c |
| Utf8Sjis_tbl.c |

- **Interface誌のダウンロードサイト「2018年」** <BR>
https://www.cqpub.co.jp/interface/download/contents2018.htm
- **10月号　特集　IT農耕実験」連載 定番IoTマイコンESP実験コーナ〈第1回〉** <BR>
IF1810EE.zip <BR>
https://www.cqpub.co.jp/interface/download/2018/10/IF1810EE.zip


#表示プログラムの作成
今回、**matled_ai.ino** とういソースプログラムを作成します。
ベースは前回の記事「<a href="https://qiita.com/Yukiya_Ishioka/items/fd791498bb5c02244546">aitendoマトリクスＬＥＤパネルの使い方（１）</a>」の **matled_ai2.ino** をベースにしています。
matled_ai2.inoから追加・改造した箇所を簡単に説明します。
ソースプログラム **matled_ai.ino** 全体はこの記事の最後に全文を添付してあるので、以下の説明はソースプログラムを参照しながら読んでください。

###プログラムの概要
今回のプログラムは、msg_init1、msg_init2、msg_init3というchar型のポインタ変数に設定された文字列をマトリクスLEDへ表示します。
表示は３秒間隔で３つの表示を切り替えながら繰り返し表示します。
表示色、表示文字は以下のようにしています。

| 変数名 |表示色|表示文字|
|:----------:|:------:|:-----------------:|
| msg_init1 | 赤 | "赤色" |
| msg_init2 | 緑 | "緑色" |
| msg_init3 | 橙 | "橙色" or "Oran" |
![0_red_s.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/370759/2da16e25-9b0f-e868-d368-26d9719f3b88.jpeg) ![1_green_s.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/370759/ddc34ce7-29c6-8972-7db5-bc9727cc52ff.jpeg)
![2_daidai_s.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/370759/a24a8328-a3dc-0166-b71b-cf2d59e51fac.jpeg) ![2_orange_s.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/370759/39457023-7822-a364-d3e3-a7db8782d6dd.jpeg)
データの持ち方や loop()関数内での表示の仕方を工夫すれば文字列をスライドさせて表示することも可能ですが、今回は全角２文字固定での表示としています。
流用元のInterface誌の記事では異なるLEDパネルですが、文字列をスライドさせて表示しているので、そちらの記事やプログラムを参考に改造してみてはいかがでしょうか。

###表示データ
前回はマトリクスLEDへ表示したいデータを配列定数で定義していましたが、今回は文字列で定義します。
msg_init1、msg_init2、msg_init3へ定義している文字列の内容を変更すれば異なる文字を表示させることができます。
なお、プログラムで「**"橙色"**」の行をコメントアウトしていますが、これは次の行の「**"Oran"**」で半角文字が表示できることの確認のためです。
配列変数 dat_buffer1、dat_buffer2、dat_buffer3は３つの文字列をドットへ変換後のデータを保持するためにあります。
前回はこの配列が固定のデータとなっていました。

```c++
const char  *msg_init1 = "赤色";
const char  *msg_init2 = "緑色";
//const char  *msg_init3 = "橙色";
const char  *msg_init3 = "Oran";
  :
unsigned char  dat_buffer1[ DEF_FONT_SIZE ][ DEF_OUTDATA_NUM ];
unsigned char  dat_buffer2[ DEF_FONT_SIZE ][ DEF_OUTDATA_NUM ];
unsigned char  dat_buffer3[ DEF_FONT_SIZE ][ DEF_OUTDATA_NUM ];
``` 

###フォントデータ
以下の変数や定義はフォントデータへアクセスするためのものです。

```c++
extern  const unsigned char  fx_8x16rk_fnt[];
extern  const unsigned char  jiskan16_fnt[];
extern  const unsigned char  Utf8Sjis_tbl[];

#define DEF_FONT_SIZE        16
#define DEF_FONT_A16_VAR     fx_8x16rk_fnt
#define DEF_FONT_K16_VAR     jiskan16_fnt
``` 

###clear_message()
表示用の配列変数の内容をクリアするための関数です。
表示用のドットデータ作成時に呼び出されます。

###set_font()
フォントデータアドレスから表示用配列変数へフォントデータをコピーするための関数です。

###UTF8_To_SJIS_cnv()
文字コードを **UTF8** から **SJIS** へ変換する関数です。
Arduino IDEでは文字コードは UTF8で保持されますが、今回利用するドットフォントデータは SJISで定義されているため、文字コードの変更が必要となります。

###utf8_to_sjis()
UTF8の文字データのアドレスから SJISの文字コードを返す関数です。

###get_fontx2_a()
半角文字用のドットフォントデータ群の先頭アドレスと SJISコードから、該当するフォント単体のデータのアドレスを返します。

###get_fontx2_k()
全角文字用のドットフォントデータ群の先頭アドレスと SJISコードから、該当するフォント単体のデータのアドレスを返します。

###make_message()
文字列から表示用のドットデータの変換を行います。
表示したい文字列が定義された配列の先頭アドレスと文字列のバイト数、出力先の変数配列の番号を指定します。
今回の場合、文字列としては msg_init1、msg_init2、msg_init3 のいずれかが指定されます。
出力先の変数配列の番号は **0**～**2**が有効な番号になります。
また、ここで指定した番号で表示される色が決まります。番号と色の関係は以下になります。

| 番号 |表示色|
|:------:|:------:|
| 0 | 赤 |
| 1 | 緑 |
| 2 | 橙 |

###setup()
文字列から表示用のドットデータへ変換するための処理を追加しています。
以下の行で３つ分のデータが準備されます。


```c++
  led_msg_len = make_message( (unsigned char *)msg_init1, strlen(msg_init1), 0 );
  Serial.printf( "data width = %d\n", led_msg_len );
  led_msg_len = make_message( (unsigned char *)msg_init2, strlen(msg_init2), 1 );
  Serial.printf( "data width = %d\n", led_msg_len );
  led_msg_len = make_message( (unsigned char *)msg_init3, strlen(msg_init3), 2 );
  Serial.printf( "data width = %d\n", led_msg_len );
``` 


###loop()
前回固定のデータとして用意した配列と同じ名前で表示用の配列変数へ改造を行ったので、loop()関数に変更はありません。


#表示プログラム
表示プログラムの **matled_ai.ino** の全文を以下に記します。
表示する文字や順番、種類などの変更は容易と思います。いろいろ試してみてください。
文字のスライドに関しては表示用データの持ち方やデータ表示時の位置指定など工夫が必要になりますが、もともとはInterface誌で紹介したスライドする電光掲示板のプログラムからの改造なので、それほど難しくはないかもしれません。こちらもいろいろ試されてはいかがでしょうか。


```c++:matled_ai.ino 
/*
 *  Copyright(C) by Yukiya Ishioka
 *
 *  http://www.aitendo.com/product/14111
 *  http://aitendo3.sakura.ne.jp/aitendo_data/product_img/LED/HD-0158-RG0019A/HD-0158-RG0019A.rar
 *
 *  https://eleshop.jp/shop/g/gEB8411/
 *  http://www.kyohritsu.jp/eclib/DIGIT/JNK/32x16dot0158.pdf
 *
 *  https://www.cqpub.co.jp/interface/download/2018/10/IF1810EE.zip
 *    fx_8x16rk_fnt.c
 *    jiskan16_fnt.c
 *    Utf8Sjis_tbl.c
 */

#include <stdio.h>
#include <string.h>

           /*  ESP32   PANEL */
#define DRPIN     12  /* 11p */
#define DGPIN     13  /*  8p */
#define A0PIN     32  /*  6p */
#define A1PIN     33  /*  5p */
#define A2PIN     15  /*  4p */
#define A3PIN     19  /*  3p */
#define CLKPIN    25  /*  9p */
#define ALEPIN    26  /* 12p */
#define WEPIN     27  /* 10p */
#define SEPIN     14  /*  1p */
#define ABBPIN    18  /*  2p */

const char  *msg_init1 = "赤色";
const char  *msg_init2 = "緑色";
//const char  *msg_init3 = "橙色";
const char  *msg_init3 = "Oran";

extern  const unsigned char  fx_8x16rk_fnt[];
extern  const unsigned char  jiskan16_fnt[];
extern  const unsigned char  Utf8Sjis_tbl[];

#define DEF_FONT_SIZE        16
#define DEF_FONT_A16_VAR     fx_8x16rk_fnt
#define DEF_FONT_K16_VAR     jiskan16_fnt

#define DEF_DISP_WIDTH       32
#define DEF_DISP_WIDTH_S     0
#define DEF_OUTDATA_NUM      100
#define DEF_INCHAR_NUM       100

unsigned char  dat_buffer1[ DEF_FONT_SIZE ][ DEF_OUTDATA_NUM ];
unsigned char  dat_buffer2[ DEF_FONT_SIZE ][ DEF_OUTDATA_NUM ];
unsigned char  dat_buffer3[ DEF_FONT_SIZE ][ DEF_OUTDATA_NUM ];

int  led_color;
int  led_msg_len ;


/*
 *  clear display buffer
 */
void  clear_message( unsigned char *buff )
{
    int  i, j ;

    for( i=0 ; i<DEF_FONT_SIZE ; i++ ) {
        for( j=0 ; j<DEF_OUTDATA_NUM ; j++ ) {
            *buff++ = 0x00;
        }
    }
}


/*
 *  set font data to display buffer
 */
void  set_font( unsigned char *font, unsigned char *buff, int pos, int width )
{
    int  i, j, k;
    int  row;
    int  w = (width/8);   /* font width byte */
    unsigned char  pat;

    /* row */
    for( i=0 ; i<DEF_FONT_SIZE ; i++ ) {
        row = DEF_OUTDATA_NUM * i;
        /* col */
        for( j=0 ; j<w ; j++ ) {
            pat = 0x80;
            for( k=0 ; k<8 ; k++ ) {
                if( (font[ i * w + j ] & pat) != 0 ) {
                    /*         base up/low  offset */
                    buff[ row + pos + j*8 + k ] = 1;
                }
                pat >>= 1; /* bit shift */
            }
        }
    }
}


void UTF8_To_SJIS_cnv(unsigned char utf8_1, unsigned char utf8_2, unsigned char utf8_3, unsigned int* spiffs_addrs)
{
  unsigned int  UTF8uint = utf8_1*256*256 + utf8_2*256 + utf8_3;
   
  if(utf8_1>=0xC2 && utf8_1<=0xD1){
    *spiffs_addrs = ((utf8_1*256 + utf8_2)-0xC2A2)*2 + 0xB0;
  }else if(utf8_1==0xE2 && utf8_2>=0x80){
    *spiffs_addrs = (UTF8uint-0xE28090)*2 + 0x1EEC;
  }else if(utf8_1==0xE3 && utf8_2>=0x80){
    *spiffs_addrs = (UTF8uint-0xE38080)*2 + 0x9DCC;
  }else if(utf8_1==0xE4 && utf8_2>=0x80){
    *spiffs_addrs = (UTF8uint-0xE4B880)*2 + 0x11CCC;
  }else if(utf8_1==0xE5 && utf8_2>=0x80){
    *spiffs_addrs = (UTF8uint-0xE58085)*2 + 0x12BCC;
  }else if(utf8_1==0xE6 && utf8_2>=0x80){
    *spiffs_addrs = (UTF8uint-0xE6808E)*2 + 0x1AAC2;
  }else if(utf8_1==0xE7 && utf8_2>=0x80){
    *spiffs_addrs = (UTF8uint-0xE78081)*2 + 0x229A6;
  }else if(utf8_1==0xE8 && utf8_2>=0x80){
    *spiffs_addrs = (UTF8uint-0xE88080)*2 + 0x2A8A4;
  }else if(utf8_1==0xE9 && utf8_2>=0x80){
    *spiffs_addrs = (UTF8uint-0xE98080)*2 + 0x327A4;
  }else if(utf8_1>=0xEF && utf8_2>=0xBC){
    *spiffs_addrs = (UTF8uint-0xEFBC81)*2 + 0x3A6A4;
    if(utf8_1==0xEF && utf8_2==0xBD && utf8_3==0x9E){
      *spiffs_addrs = 0x3A8DE;
    }
  }
}


int  utf8_to_sjis( unsigned char *buff, unsigned char *code1, unsigned char *code2 )
{
  unsigned char  utf8_1, utf8_2, utf8_3;
  unsigned char  sjis[2];
  unsigned int  sp_addres;
  int  pos = 0;
  int fnt_cnt = 0;
 
  if(buff[pos]>=0xC2 && buff[pos]<=0xD1){
    /* UTF8 2bytes */
    utf8_1 = buff[pos];
    utf8_2 = buff[pos+1];
    utf8_3 = 0x00;
    fnt_cnt = 2;
  }else if(buff[pos]>=0xE2 && buff[pos]<=0xEF){
    /* UTF8 3bytes */
    utf8_1 = buff[pos];
    utf8_2 = buff[pos+1];
    utf8_3 = buff[pos+2];
    fnt_cnt = 3;
  }else{
    utf8_1 = buff[pos];
    utf8_2 = 0x00;
    utf8_3 = 0x00;
    fnt_cnt = 1;
  }

  /* UTF8 to Sjis change table position */
  UTF8_To_SJIS_cnv( utf8_1, utf8_2, utf8_3, &sp_addres );
  *code1 = Utf8Sjis_tbl[ sp_addres   ];
  *code2 = Utf8Sjis_tbl[ sp_addres+1 ];

  return  fnt_cnt;
}


unsigned char  *get_fontx2_a( unsigned char *font, unsigned int code )
{
    unsigned char  *address = NULL ;
    unsigned int  fontbyte ;

    fontbyte = (font[14] + 7) / 8 * font[15] ;
    address = &font[17] + fontbyte * code ;

    return  address ;
}


unsigned char  *get_fontx2_k( unsigned char *font, unsigned int code )
{
    unsigned char  *address = NULL ;
    unsigned char  *tmp ;
    unsigned int  blknum, i, fontnum ;
    unsigned int  bstart, bend ;
    unsigned int  fontbyte ;

    fontbyte = (font[14] + 7) / 8 * font[15] ;
    fontnum = 0 ;

    blknum = (unsigned int)font[17] * 4 ;
    tmp = &font[18] ;
    for( i=0 ; i<blknum ; i+=4 ) {
        bstart = tmp[i]   + ((unsigned int)tmp[i+1] << 8) ;
        bend   = tmp[i+2] + ((unsigned int)tmp[i+3] << 8) ;
        if( code >= bstart && code <= bend ) {
            address = tmp + (fontnum + (code - bstart)) * fontbyte + blknum ;
            break ;
        }

        fontnum += (bend - bstart) + 1 ;
    }

    return  address ;
}


int  make_message( unsigned char *strbuff, unsigned int size, int line )
{
    int  pos, bitpos;
    int  num;
    unsigned char  *fontdata;
    unsigned int  code;
    unsigned char  code1, code2 ;
    unsigned char  *dat_buffer ;

    switch( line ) {
    case 1:
      dat_buffer = (unsigned char *)dat_buffer2 ;
      break;
    case 2:
      dat_buffer = (unsigned char *)dat_buffer3 ;
      break;
    case 0:
    default:
      dat_buffer = (unsigned char *)dat_buffer1 ;
      break;
    }
    clear_message( (unsigned char *)dat_buffer );

    pos = 0 ;
    bitpos = DEF_DISP_WIDTH_S ;
    while( pos < size ) {
        code = strbuff[ pos ] ;  /* get 1st byte */
        if( code < 0x80 ) {
            /* for ASCII code */
            if( code == 0x0d || code == 0x0a ) {
                code = ' ' ;
            }
            fontdata = get_fontx2_a( (unsigned char *)DEF_FONT_A16_VAR, code );
            set_font( fontdata, (unsigned char *)dat_buffer, bitpos, DEF_FONT_SIZE/2 );
            bitpos += DEF_FONT_SIZE/2 ;
            pos++ ;
        } else {
            /* for KANJI code */
            num = utf8_to_sjis( &strbuff[ pos ], &code1, &code2 );
            code = (code1<<8) + code2 ;  /* get 2nd byte and marge */
            fontdata = get_fontx2_k( (unsigned char *)DEF_FONT_K16_VAR, code );
            set_font( fontdata, (unsigned char *)dat_buffer, bitpos, DEF_FONT_SIZE );
            bitpos += DEF_FONT_SIZE;
            pos += num;
        }

        if( bitpos >= (DEF_OUTDATA_NUM - DEF_FONT_SIZE - DEF_DISP_WIDTH) ) {
            break ;
        }
    }

    return  bitpos;
}


void led_addr_wr( int a0, int a1, int a2, int a3 )
{
  digitalWrite( A0PIN, a0 );
  digitalWrite( A1PIN, a1 );
  digitalWrite( A2PIN, a2 );
  digitalWrite( A3PIN, a3 );
  digitalWrite( ALEPIN, HIGH );
  digitalWrite( WEPIN, HIGH );
  digitalWrite( WEPIN, LOW );
  digitalWrite( ALEPIN, LOW );
}


void setup( void )
{
  int  col, row;
  int  a0, a1, a2, a3;

  /* init serial */
  Serial.begin(115200);
  Serial.println( "call setup()" );

  pinMode( DRPIN,  OUTPUT );
  pinMode( DGPIN,  OUTPUT );
  pinMode( A0PIN,  OUTPUT );
  pinMode( A1PIN,  OUTPUT );
  pinMode( A2PIN,  OUTPUT );
  pinMode( A3PIN,  OUTPUT );
  pinMode( CLKPIN, OUTPUT );
  pinMode( ALEPIN, OUTPUT );
  pinMode( WEPIN,  OUTPUT );
  pinMode( SEPIN,  OUTPUT );
  pinMode( ABBPIN, OUTPUT );
  digitalWrite( CLKPIN, LOW );
  digitalWrite( ALEPIN, LOW );
  digitalWrite( WEPIN, LOW );

  digitalWrite( SEPIN,  LOW );
  digitalWrite( ABBPIN, LOW );

  /* dummy address set */
  led_addr_wr( LOW, LOW, LOW, LOW );

  /* clear MATLED */
  for( row=0 ; row<16; row++ ) {
    for( col=0 ; col<DEF_DISP_WIDTH ; col++ ) {
      digitalWrite( DRPIN, LOW );
      digitalWrite( DGPIN, LOW );
      digitalWrite( CLKPIN, HIGH );
      digitalWrite( CLKPIN, LOW );
    }
    a0 = a1 = a2 = a3 = 0;
    if( row & 0x1 ) a0 = 1;
    if( row & 0x2 ) a1 = 1;
    if( row & 0x4 ) a2 = 1;
    if( row & 0x8 ) a3 = 1;
    led_addr_wr( a0, a1, a2, a3 );
  }

  /* dummy address set */
  led_addr_wr( LOW, LOW, LOW, LOW );

  led_color = 0;
  led_msg_len = make_message( (unsigned char *)msg_init1, strlen(msg_init1), 0 );
  Serial.printf( "data width = %d\n", led_msg_len );
  led_msg_len = make_message( (unsigned char *)msg_init2, strlen(msg_init2), 1 );
  Serial.printf( "data width = %d\n", led_msg_len );
  led_msg_len = make_message( (unsigned char *)msg_init3, strlen(msg_init3), 2 );
  Serial.printf( "data width = %d\n", led_msg_len );
}


void loop( void )
{
  int  a0, a1, a2, a3;
  int  dr, dg;
  int  row, col;
  int  i;
  unsigned char  tmpchar;
  unsigned char  *buff1;
 
  switch( led_color%3 ) {
  case 1:
    dr = 0;
    dg = 1;
    buff1 = (unsigned char *)dat_buffer2 ;
    break;
  case 2:
    dr = 1;
    dg = 1;
    buff1 = (unsigned char *)dat_buffer3 ;
    break;
  case 0:
  default:
    dr = 1;
    dg = 0;
    buff1 = (unsigned char *)dat_buffer1 ;
    break;
  }
  Serial.printf( "color DR=%d  DG=%d\n", dr, dg );

  for( i=0 ; i<16 ; i++ ) {
    row  = DEF_OUTDATA_NUM * i;

    for( col=0 ; col<DEF_DISP_WIDTH ; col++ ) {

      if( buff1[ row + col ] ) {
          digitalWrite( DRPIN, dr );
          digitalWrite( DGPIN, dg );
      } else {
          digitalWrite( DRPIN, LOW );
          digitalWrite( DGPIN, LOW );
      }

      digitalWrite( CLKPIN, HIGH );
      digitalWrite( CLKPIN, LOW );
    }

    /* set access row position */
    a0 = a1 = a2 = a3 = 0;
    if( i & 0x1 ) a0 = 1;
    if( i & 0x2 ) a1 = 1;
    if( i & 0x4 ) a2 = 1;
    if( i & 0x8 ) a3 = 1;
    led_addr_wr( a0, a1, a2, a3 );
  }

  /* dummy address set */
  led_addr_wr( LOW, LOW, LOW, LOW );

  led_color++;
  vTaskDelay( 3000 );  /* delay 3000msec */
}
```  

以上