LED方向幕を再現する#02(ソフト編1)

# まえおき

様々な方がソースコード付きで制作例を公開されており、コピペ可の方もちらほらいらっしゃいます。
でもやっぱり自分の手でプログラム組みたいじゃん？
世間はpythonが主流のようですが、今回は例の黒魔術ライブラリのC言語APIを叩いて動かします。
~~構想時はpython習得してなかったのよ~~


# 準備するもの
### rpi-rgb-led-matrix
主役の黒魔術ライブラリ。パネルの制御を担当。

https://github.com/hzeller/rpi-rgb-led-matrix

### wiringPi

delay()だけのために必要。~~ぶっちゃけ要らん。~~
案の定プログラム改良時にsleep()に置き換えられた。

https://github.com/WiringPi

### stb

画像を読み込んでくれるライブラリ。この辺はお好みで。
~~解説は過去記事へどうぞ。~~(広告)

https://github.com/nothings/stb

https://qiita.com/DozyouMetoro/items/2a0e74ab39517801bad6

### Remote-SSH(VScode拡張機能)

SSH経由でいろいろできる。
ファイルのやり取りもコマンド要らずでできちゃいます。
開発もMicrosoftで安心。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2944355/d33b39e5-8da1-e7f1-7d94-59cea19d6277.png)

# フォルダに入ってる画像を垂れ流すサンプル
**コピペしても動かないので注意**
パネルの設定、画像を入れるディレクトリを環境に合わせて設定すること。
```C
#define STB_IMAGE_IMPLEMENTATION //#include <stb_image.h>に必要

#include <stdlib.h>       //メモリ関係
#include <stdio.h>        //標準入出力
#include <string.h>       //文字列関係
#include <dirent.h>       //ディレクトリ関係
#include <stb_image.h>    //画像を読み込む
#include <led-matrix-c.h> //パネル制御
#include <wiringPi.h>     //delay()用
#include <errno.h>        //エラー関係

char ext[] = ".png";//画像の拡張子
char path[] = "/home/hogehoge/hogehoge";//画像を入れるディレクトリ

char **get_filename(char *, int *);

int main(void)
{
    char **list = NULL;                                 // ファイルのパスを入れる配列
    unsigned char *pixel = NULL;                        // 画像のデータを入れる配列
    int image_x = 0, image_y = 0, image_bpp = 0, n = 0; // 画像の幅、高さ、ピクセルあたりサイズ、処理用変数
    int file_num = 0;                                   // 見つかった画像の数
    struct RGBLedMatrixOptions options;                 // 設定を入れる構造体その1
    struct RGBLedRuntimeOptions options_run;            // 設定を入れる構造体その2
    struct RGBLedMatrix *matrix_options;                // 関数がパネルの設定を入れる構造体
    struct LedCanvas *offscreen_canvas;                 // キャンバス
    int led_width = 0, led_height = 0;                  //パネルのサイズ

    memset(&options, 0, sizeof(options));//設定をとりあえずすべてデフォルトに設定
    memset(&options_run, 0, sizeof(options_run));//設定をとりあえずすべてデフォルトに設定

    list = get_filename(path, &file_num); // 指定ディレクトリから画像のパスを取得

    if (list == NULL)
    {
        printf("画像もしくはディレクトリがありません\n");
        return 0;
    }

    /*-----初期設定-----*/
    options.rows = 32;                // パネルの行(デフォルト32)
    options.cols = 64;                // パネルの列
    options.chain_length = 3;         // パネルの連結数
    options.pwm_bits = 4;             // PWMのビット(高くすると表現できる色の種類が、低くすると動作の安定度が上がる)
    options_run.drop_privileges = -1; // root権限で動作
    options_run.gpio_slowdown = 2;    // GPIO速度を設定

    matrix_options = led_matrix_create_from_options_and_rt_options(&options, &options_run); // 関数に設定を渡して構造体に格納してもらう

    if (matrix_options == NULL)
    {
        exit(1);
    }
    /*-----キャンバスの準備-----*/
    offscreen_canvas = led_matrix_create_offscreen_canvas(matrix_options); // 描画用キャンバスを用意する
    led_canvas_get_size(offscreen_canvas, &led_width, &led_height);        // キャンバスの大きさを返す

    /*-----設定を構造体に格納-----*/
    while (1)
    {
        for (int i = 0; i < file_num; i++)
        {
            pixel = stbi_load(list[i], &image_x, &image_y, &image_bpp, 3); // 画像を読み込み
            if (pixel == NULL)
            {
                exit(10);
            }

            for (int y = 0; y < led_height; y++)
            {
                for (int x = 0; x < led_width; x++)
                {
                    led_canvas_set_pixel(offscreen_canvas, x, y, 0, 0, 0); // キャンバスの座標x,yの色を(0,0,0)に設定
                }
            }

            for (int y = 0; y < image_y; y++)
            {
                for (int x = 0; x < image_x; x++)
                {
                    n = (y * image_x + x) * 3;
                    led_canvas_set_pixel(offscreen_canvas, x, y, pixel[n], pixel[n + 1], pixel[n + 2]); // 座標(x,y)のrgb値を設定
                }
            }
            offscreen_canvas = led_matrix_swap_on_vsync(matrix_options, offscreen_canvas); // キャンバスを渡してパネルに反映(返してくるキャンバスのポインタを保持すること)

            delay(3000);
        }
    }
}





char **get_filename(char *path, int *n)//ディレクトリを読み込んで条件に合うファイルのパスを返す
{
    /*-----変数の宣言-----*/
    DIR *dir = NULL;
    struct dirent *entry = NULL;
    char **filename_list = NULL, **buf = NULL;

    dir = opendir(path); // ディレクトリを開く
    if (dir == NULL)
    {
        if (errno == ENOENT)
        {
            printf("ディレクトリが存在しません。\n");
        }
        else if (errno == EACCES)
        {
            printf("ディレクトリにアクセスできません。\n");
        }
        else
        {
            printf("ディレクトリのオープンに失敗しました。エラーコード: %d\n", errno);
        }
        fflush(stdout);
        exit(9);
    }

    /*-----指定した拡張子のファイルを抜粋-----*/
    while ((entry = readdir(dir)) != NULL)
    {
        if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0)
        { //.を無視
            continue;
        }

        if (strstr(entry->d_name, ext) == NULL)
        {
            continue;
        }

        *n = *n + 1; // 見つかったファイルの数を+1
        buf = (char **)realloc(filename_list, *n * sizeof(char *));
        if (buf == NULL)
        {
            exit(1);
        }
        filename_list = buf;
        buf = NULL;

        filename_list[(*n) - 1] = (char *)calloc(257, sizeof(char));
        if ((filename_list[(*n) - 1]) == NULL)
        {
            exit(2);
        }

        snprintf(filename_list[(*n) - 1], 257, "%s/%s", path, entry->d_name);
    }

    return filename_list;
}


```

# コンパイル

**C言語APIとか言っているけど裏で動いているのはC++なのでg++でコンパイルすること**

`rpi-rgb-led-matrix`と`stb`はヘッダファイルの場所を教えてあげる必要がある。
`rpi-rgb-led-matrix`と`wiringPi`はリンクが必要。
これらをコマンドに羅列する。


```shell
＜オプションの記述例（これを必要な数羅列する）＞
・ヘッダファイルの場所を教える
-I <フォルダのパス>
・リンク
-L <リンクする.aファイルなどのあるフォルダのパス> -l<リンクするファイル名>

```

コマンド例
最適化はかけない方が無難か。(-O3かけたら動かなかった)
`-g`付けとくとgdbが使えるのでデバッグが楽。
```shell
g++ -o hoge hoge.c -Wall -I /home/hoge/rpi-rgb-led-matrix/include -L /home/hoge/rpi-rgb-led-matrix/lib -lrgbmatrix -I /home/hogeo/stb -O0 -g

```

なぜか`wiringPi`は-Lなくても行ける。


# 使用する画像のフォーマット

1ピクセルがLED1個に対応。192x32のパネルなら192x32ピクセルの画像を用意すること。小さくても動くが左右どちらかに寄ってしまうため、ぴったりサイズ推奨。まあ実装次第でどうにでもなるけど。

こんな感じ。
![5050快急小川町.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2944355/75a8a55d-7c88-eea0-e5fb-d338a479dee9.png)

VScodeのRemote-SSHで指定したディレクトリに送りましょう。
ドラッグアンドドロップで送れるので非常に楽。



# 起動
コード中に指定したディレクトリに画像を入れてroot権限で起動。

# ライブラリの解説

## #define

`stb_image.h`のコメントをGoogle翻訳にかけましょう。
もしくは過去記事に…

https://qiita.com/DozyouMetoro/items/2a0e74ab39517801bad6

## #include

何をするかコメントに書いといたんでそれ読んでちょ

## 構造体(RGBなんちゃら等)関連

`led-matrix-c.h`にて定義されている構造体となります。


### ユーザーが中身をいじらない構造体

ヘッダファイルを覗くと定義こそされているものの、中身が空っぽのため関数などに渡そうとするとコンパイルエラーを吐かれる。細かく関数化したい人はグローバル変数で置いとくのが安定。

`struct RGBLedMatrix`
ライブラリがパネルなどの設定を保存する構造体

`struct LedCanvas`
描画につかうキャンバス。ユーザーは関数を通じて操作する。

### ユーザーが中身をいじる構造体

`struct RGBLedMatrixOptions`

中身の変数をいじることでオプションをいろいろ設定できる。
**かならず宣言後にmemset()ですべて0に初期化すること。**
設定項目はライブラリのReadmeにいろいろ書いてあるので翻訳機能の力を借りて一度読んでおこう。
また、どの変数がどのオプションに対応するかはヘッダファイルに書かれているので、定義に移動する機能あたりを使って読んでおこう。

`struct RGBLedRuntimeOptions`
中身の変数をいじることでオプションをいろいろ設定できる。~~なんで分けたのか~~
注意点などは上に同じ。

### 重要な設定項目
**RGBLedMatrixOptionsの設定項目**
`cols`
パネル1枚の幅。

`rows`
パネル1枚の高さ

`chain_length`
パネルの連結数
192x32と設定したい場合、幅64,連結数3と設定しても、幅192,連結数1と設定してもよい。
ただし、複数枚に同じ画像を表示するからと言って実際より小さく表示するとちらつきや表示崩れの原因になるので非推奨。

`pwm_bits`
PWMのbit数を設定。
大きくするほど微妙な色の違いを表現できるが重くなり、ちらつきが増える。
当環境がGUI版Ubuntuを使っているのもあり、軽量化を重視した。

**RGBLedRuntimeOptionsの設定項目**

`gpio_slowdown`
ラズパイ2以降は性能が良すぎてパネル側が追い付けないらしく、これでライブラリの力を制限しなければならない。~~パネルが追い付けないとかどういう黒魔術や…~~

`drop_privileges`
このライブラリをそのまま実行すると勝手にroot権限を放棄してあらゆるファイルやらディレクトリやらにアクセス拒否される。
**-1**を指定して権限放棄を無効化しておこう。

### オプション変数対照表
`struct RGBLedMatrixOptions`のものを細字、
`struct RGBLedRuntimeOptions`のものを太字で表記

**スペースの都合上説明が雑なので、細かい説明はReadmeを読みましょう。**
よくわからん設定はmemset()で0に設定して放置.


| オプション | 変数 | 設定内容 |デフォルト値|
| :-:| :-: | :-: | :-:|
| --led-cols|cols|パネル1枚あたりの幅|32ピクセル?|
|--led-rows|rows|パネル1枚あたりの高さ|32ピクセル|
|--led-chain|chain_length|パネルの連結数|1枚|
|--led-parallel|parallel|パネルの並列数|1系統|
|--led-multiplexing	|multiplexing(明記なし)|パネルのアドレス端子が少ないとき(高さ32列で3本など)に指定|0(無効)?|
|--led-row-addr-type	| row_address_type|アドレス端子がAB2本の時に使う?|0(無効)|
|--led-panel-type|panel_type|特殊なHUB75規格のパネルを使う時に指定?|0(無効)
|--led-gpio-mapping|発見できず|ピン配置を変更したい時に|ライブラリに記載のピン割り当て|
|--led-slowdown-gpio|**gpio_slowdown**|GPIO速度の調整|0(減速なし)|
|--led-pixel-mapper|pixel_mapper_config|特殊なパネルのつなぎ方をしたときに設定|?|
|--led-brightness|brightness|デフォルトのパネルの明るさ|100%?|
|--led-pwm-bits|pwm_bits|PWM解像度|11bit|
|--led-show-refresh|show_refresh_rate|リフレッシュレートをシェルに表示|0(無効)|
|--led-limit-refresh|limit_refresh_rate_hz|リフレッシュレートを固定|無制限|
|--led-scan-mode|scan_mode|スキャンモード変更。謎。|progressive|
|--led-pwm-lsb-nanoseconds|pwm_lsb_nanoseconds|PWMの調整?。謎。|?|
|--led-pwm-dither-bits|pwm_dither_bits|PWMの調整?。謎。|?|
|--led-no-hardware-pulse|発見できず|動かない原因がサウンドシステムかどうかを確認|-
|--led-no-drop-privs|**drop_privileges**(明記なし)|root権限にとどまる|0(無効)?
|--led-daemon     |**daemon**|デーモン云々|?
|--led-inverse   |inverse_colors|謎。|?|
|--led-rgb-sequence |led_rgb_sequence|謎。|?|
|発見できず|disable_hardware_pulsing|謎。Q&Aのちらつき改善の項目に関係あり?|?|
|発見できず|**do_gpio_init**|GPIOを使わない場合に指定?|0(無効)?
|発見できず|**drop_priv_user**|権限を削除するユーザー|deamon|
|発見できず|**drop_priv_group**|権限を削除するグループ|deamon|

## 初期設定
```C
    /*初期設定*/
    options.rows = 32;                // パネルの行(デフォルト32)
    options.cols = 64;                // パネルの列
    options.chain_length = 3;         // パネルの連結数
    options.pwm_bits = 4;             // PWMのビット(高くすると表現できる色の種類が、低くすると動作の安定度が上がる)
    options_run.drop_privileges = -1; // root権限で動作
    options_run.gpio_slowdown = 2;    // GPIO速度を設定

    matrix_options = led_matrix_create_from_options_and_rt_options(&options, &options_run)
```

設定用の構造体の中の変数に各種値を入れたうえで関数に渡す。
設定方法は上記の構造体2個に加え、コマンドライン引数から設定する方法もある。

| 関数 | 渡すもの |
|:-:|:-:|
|  led_matrix_create_from_options() |  RGBLedMatrixOptions 、コマンドライン引数 |
|  led_matrix_create_from_options_and_rt_options()  | RGBLedMatrixOptions、RGBLedRuntimeOptions |

2個の構造体を渡しつつコマンドライン引数も対応している~~欲張り~~関数はないっぽい。
どちらも内部で`led_matrix_create_from_options_optional_edit()`を呼んでいるので頑張ればできないことはなさそう。
特に指定のない場合はこのタイミングでroot権限を放棄するので注意。

**この関数が返却する`RGBLedMatrix`構造体を後々使うので忘れず受け取っておきましょう。**

今回は2つの構造体を使う方法で設定した。

## 描画用キャンバスの準備

```C
    /*キャンバスの準備*/
    offscreen_canvas = led_matrix_create_offscreen_canvas(matrix_options); // 描画用キャンバスを用意する
    led_canvas_get_size(offscreen_canvas, &led_width, &led_height);        // キャンバスの大きさを返す
```

`led_matrix_create_offscreen_canvas()`に先ほど受け取った構造体を渡す。
描画用キャンバスへのポインタを返してくるので忘れず受け取っておく。

`led_canvas_get_size()`はパネルのサイズをポインタで指定された変数に入れる。
移植性云々を考えると省略しない方が安定か。

## キャンバスに描画
```C
led_canvas_set_pixel(offscreen_canvas, x, y, 0, 0, 0); // キャンバスの座標x,yの色を(0,0,0)に設定
led_canvas_set_pixel(offscreen_canvas, x, y, pixel[n], pixel[n + 1], pixel[n + 2]); // 座標(x,y)のrgb値を設定
```
キャンバスの(x,y)座標の色を指定する。
この時点ではまだ表示されない。

## LEDパネルに表示

```C
offscreen_canvas = led_matrix_swap_on_vsync(matrix_options, offscreen_canvas); // キャンバスを渡してパネルに反映(返してくるキャンバスのポインタを保持すること)
```

上で設定した描画用キャンバスを、設定の項目で受け取った構造体とともに渡す。
この関数が実行される前に表示されていたキャンバスのポインタが返ってくるので受け取っておく。
**なお、返されるキャンバスには表示されていたデータがそのまま残っているので、かならず初期化すること。忘れたときにサイズの違う画像データが混在しているとひどいことになります。**

# #03(ソフト編2)

執筆中