0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

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

Last updated at Posted at 2023-08-14

まえおき

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

準備するもの

rpi-rgb-led-matrix

主役の黒魔術ライブラリ。パネルの制御を担当。

wiringPi

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

stb

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

Remote-SSH(VScode拡張機能)

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

image.png

フォルダに入ってる画像を垂れ流すサンプル

コピペしても動かないので注意
パネルの設定、画像を入れるディレクトリを環境に合わせて設定すること。

#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-matrixstbはヘッダファイルの場所を教えてあげる必要がある。
rpi-rgb-led-matrixwiringPiはリンクが必要。
これらをコマンドに羅列する。

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

コマンド例
最適化はかけない方が無難か。(-O3かけたら動かなかった)
-g付けとくとgdbが使えるのでデバッグが楽。

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

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

起動

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

ライブラリの解説

#define

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

#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

初期設定

    /*初期設定*/
    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つの構造体を使う方法で設定した。

描画用キャンバスの準備

    /*キャンバスの準備*/
    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()はパネルのサイズをポインタで指定された変数に入れる。
移植性云々を考えると省略しない方が安定か。

キャンバスに描画

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パネルに表示

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

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

#03(ソフト編2)

執筆中

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?