t-hom’s diary

主にVBAネタを扱っているブログ…とも言えなくなってきたこの頃。

Raspberry Piでネットワーク対応の電光掲示板(16×96ドット)

今回はRaspberry Pi Zero WHで電光掲示板を動かしてみた。
ただ動かすだけではなく、無線LANに接続して他の端末から受信したメッセージを表示させる。

私がこれを作成した目的は、前回紹介した空気モニターの内容を掲示板に常時表示させるためである。
※前回記事
thom.hateblo.jp

作成するもの

動作イメージはこんな感じ。

※mp4からGIFアニメ化したときにかなり高速になってしまったけど、実物はだいたいこれの3分の1くらいのスピードでスクロールする。

色々と調べた結果をベースとしてプログラムで空気品質の基準値を定めていて、基準内の項目は緑の文字で、閾値付近はオレンジで、基準オーバーは赤の文字で表示させるようにした。

材料

Raspberry Pi Zero WH

最初はArduinoで動かすことを考えたが、掲示板のドット数からしてメモリが不足するという情報があったのでラズパイにした。
最初はRaspberry Pi 3B+で実験していたが、以前購入したまま眠っているZero WHがあるので私はそちらを使用した。
無線LANに対応したラズパイがあれば何でも動くと思う。

LEDマトリクスパネル(16x32を3枚)

ラズパイZero用ユニバーサル基盤

ビニール被膜の単線ワイヤー

電気ワイヤーは単線タイプと撚り線タイプがある。
針金のように一本だけ入ったのが単線タイプで、細い線が複数ねじりあわされたのが撚り線タイプ。
撚り線のメリットは柔らかいことであるが、今回は配線が細かくてカチっと位置決めしないとハンダ付けしにくいので単線タイプを使用する。

熱収縮チューブ

これはLEDマトリクスに電源を供給するためのコードを改造する為に使用した。

LEDマトリクスに付属の電源コードは1本がパネル2枚分に枝分かれしていて、それがLEDパネル1枚に1セット入っている。
1本で2パネルなので2本使えば良いのだが、そうすると1パネル分あまって邪魔だし、大本の電源は1本化したいので、1本を3パネルに分岐させるようなコードを作成した。

アルミチャネル材

これはホームセンターとかで売ってるコの字型の長いアルミ棒。
2本買って、3つのパネルを連結するのに使用した。なんでも良いと思うけど私が使ってるのは以下の商品のようだ。(JANコードで検索した)
tcss.vivahome.com

工具

アルミ用ヤスリ

切断面を危なくない程度に仕上げるために使用。
私の場合はパネルの上用と下用で切断する長さが2ミリほどずれてしまったのでヤスリで削って合わせた。
たった2ミリだけどヤスリで削るのはとても時間がかかった。。

ボール盤

今回これのためにボール盤を購入。評価は賛否分かれてたけど、普通に使用できて満足。
代用としてはセンターポンチと手持ちタイプの電動ドリルで穴開けする方法もあるが私は何度やってもズレる。これでアルミ材を1つダメにした。
これはネジで止める固定用の穴なので、ズレると3枚のLEDにスキマができたり上下ズレたりで綺麗に1枚にならない。
1万ちょっとでこの手の工具が購入できるということでテンションが上がったこともあり、そのままポチってしまった。

ただ付属のバイスでは縦方向の材料の位置合わせが難しく、他のアルミ材を一緒に挟んで位置を調整したりと、位置合わせにとても苦労した。
細かい位置合わせ用のテーブルが別途あるようなのでそういうのを買うと良いのかもしれない。
ちなみにこの会社、ちゃんと日本で検品してから出荷してくれてるらしく、一度開封されているので未開封品ではない。
また、本体の袋が機械油まみれで届くので新聞紙を敷いたりキッチンペーパー等で余計なところに付いた油を取ったり色々汚れる覚悟は必要。

使用上の注意を守らないとかなり危ないので、一般的なボール盤の注意点や、この製品の注意点についてよく調べてから使うこと。
特に油まみれになるので軍手とかしたくなるが、巻き込み事故の原因になるので絶対に素手で扱うこと。
金属片が飛んでくる恐れがあるのでセーフティーゴーグルをすること。

集合住宅なので音が心配だったけど、空ける穴が小さいためかかなり静かだった。
卓上ボール盤は普通にマンション住まいでも使えると思う。

鉄工用の丸軸ドリルビット

3ミリを使用した。

アルミ用ってのがあればそれで良いけどホームセンターで見つけられなかったので鉄工用を購入。

ハンドドリル用だと六角軸のものが主流で、そのまま使えなくはないがおススメはしない。
ボール盤のドリルチャックは3つの歯でドリルをくわえるような形になっていて、無理な力がかかった時に丸軸ドリルがスリップして本体の破損を防ぐらしい。六角だと滑れないので本体がダメージを受ける。

作成手順

配線計画

細かい配線の前にまずは全体図。

ラズパイに接続されたユニバーサル基盤に、データ信号用のボックスソケットと電源用のネジ端子が接続されていて、ネジ端子から各LEDパネルへ電源が供給されている。データ信号はLEDパネルのInputから入り、Outputから出た信号が次のパネルのInputへ接続される。それぞれのパネルをフラットケーブルで接続する際に、Output→InputとつなげばOK。

次にボックスコネクターとラズパイのピンの対応について説明する。
ボックスコネクタをアップで確認するとそれぞれのピンに記号が書かれているが、残念ながら基盤が隠れて右半分しか確認できない。

ラズパイとの対応も併せて、残りはこちらのサイトで確認することになる。
github.com

最初に出てくる画像は忘れて構わない。2つ目のこれ↓がLEDパネルのInputピンアサインである。

つまり切り欠きを左としたときに、次のようなピンアサインになっている。

次に、以下の図を確認する。LEDパネルはラズパイから最大3レーン接続でき、それぞれのレーンに最大12枚ずつチェーンできる。

今回は1レーンしか使わないので、ニコチャンマークに注目する。

次にラズパイ側のピンとの対応図が出てくる。

そのままExcelに貼り付けて、余計な記号等を消し込む。

10番ピンの(for 64 row matrix, 1:32)と、22番ピンの(for 32 row matrix, 1:16)はどちらも消してOK。
rowってのが行数を表すが、今回買ったLEDパネルは16行x32列なので関係ない。

ボックスコネクタ側であるが、GNDはおそらく3か所中1か所繋いでおけばOKなのと、Dは今回使用しないので実際には次のようになる。

あとはユニバーサル基盤にラズパイと重ね合わせるためのピンヘッダと16ピンソケットを取り付けて、配線の対応どおりに繋いでいくだけ。


繋ぐ前からひどいことになるのは見えているわけだが。。根気よく頑張る。

実際の基盤上の配線

ユニバーサル基盤上の配線が完成するとこんな感じになる。

裏面

※実はユニバーサル基盤の裏と表を間違えたっぽい。。せっかく5Vとかのラズパイのピンアサインが書かれているのに上下左右逆になってしまい余計配線で混乱することになった。

表面

対応表を見ながらハンダで繋いでいくのだが、ハンダをつけるのに何度も裏返すので、途中で頭がこんがらがってくる。
これだけで3時間かかった。めちゃくちゃ大変。

※実は自分で作らなくても専用ハットが売ってるらしいんだけど、もともと参考にしたサイトがそういうのを使わずにジャンパーピンで全部つないじゃったということだったので、真似してうまくいった成功体験からハットは使わずに自分で作った次第。皆さん真似される方はぜひハットを購入して、それをまた記事にしていただけたらと。。

ちなみにパネル用の電源はラズパイの5Vピンと、余っているGNDピンからネジ端子まで引っ張ってきている。

ネジ端子の基盤に挿すピンは太くてそのままでは挿せないので、ドリルで少し穴を広げてそのまま配線用のハンダで固定した。

電源コードの配線は配線図通りで良いので写真での解説は割愛する。赤線は赤線同士、黒線は黒線同士くっついていればそれで動くので適当に切断してしかるべき場所の被膜を剥いて熱収縮チューブを通してから半田づけで線同士を結合し、熱収縮チューブをハンダごてで撫でて収縮させて金属部分を覆えば完成。

ラズパイのセットアップ

ラズパイゼロには、Raspberry Pi OS Liteをインストールしておく。
これはGUIを持たないコマンドだけのOSで、余計なリソース消費が無いのでIoTにはおススメ。

Micro USBにOSを書き込んだ後、所定のフォルダにWifi設定を記述したテキストファイルを入れておくとモニターもキーボードも繋ぐことなくインストールできる。「ラズパイZero ヘッドレスインストール」などと検索すると方法が見つかるかと思う。

セットアップして起動まで終わったらSSHで接続して、sudo apt-get update、sudo apt-get upgradeを済ませておく。
メモリが少ないせいか、割と時間がかかる。

次にライブラリ等の入手とインストールを実施。

2022/3/29 Raspberry Pi OS BullsEyeへアップグレードした際に少し変わったので更新
gitがインストールされてないので、そのインストールコマンドも書いておく。gccは入ってたかどうか忘れたけど一応。。

# 以下コメントアウトしたのはRasbperry Pi OS(Buster)での古いやり方
#cd /home/pi/
#sudo apt-get install git
#sudo apt-get install gcc
#git clone https://github.com/hzeller/rpi-rgb-led-matrix/
#cd rpi-rgb-led-matrix
#make -C examples-api-use
#cd /home/pi/
#sudo pip3 install Pillow
#sudo pip3 install feedparser
#sudo apt-get install ttf-takao-mincho

# 以下がRaspberry Pi OS(BullsEye)でのやり方
cd /home/pi/
sudo apt install git
git clone https://github.com/hzeller/rpi-rgb-led-matrix/
cd rpi-rgb-led-matrix
make -C examples-api-use
cd /home/pi/
sudo apt install python3-pip
sudo pip install Pillow
sudo pip install feedparser
sudo apt install fonts-takao-mincho
sudo apt-get install libopenjp2-7

そしてサンプルを実行

sudo  /home/pi/rpi-rgb-led-matrix/examples-api-use/demo --led-no-hardware-pulse --led-rows=16 --led-cols=96 -D 1 -m 20  /home/pi/rpi-rgb-led-matrix/examples-api-use/runtext16.ppm

実行するとこんな感じで文字が流れていく。

電光掲示板側のプログラムコード

いろんなところからお借りしたコードを混ぜてるので余計なインポートが残ってると思うけど気にせず公開。。
このコードはソケット通信でデータを待ち受けて、データが来たらppmファイルを生成し、先ほどのテキストスクロールデモを開始して作成したppmファイルを再生させている。
データはUnicode文字列・RGBコードで構成されたリストをjson形式で受け取る形式である。ちょうどコード中に「電光掲示板テスト」と書かれているあたりがデータ形式である。

注意点として、コード中のIPアドレスの部分は自分のラズパイのアドレスを入れること。
50007は待ち受けポートなのでそのままでも良い。

プログラムファイル名はserver.pyとしておく。

import socket
import json
from subprocess import Popen
from time import sleep
from PIL import Image, ImageFont, ImageDraw

def createPPM(text):
    font = ImageFont.truetype("/usr/share/fonts/truetype/takao-mincho/TakaoMincho.ttf", 16)
    all_text = ""
    for text_color_pair in text:
        t = text_color_pair[0]
        all_text = all_text + t
    
    width, ignore = font.getsize(all_text)
    im = Image.new("RGB", (width + 30, 16), "black")
    draw = ImageDraw.Draw(im)
    
    x = 0;
    for text_color_pair in text:
        t = text_color_pair[0]
        c = tuple(text_color_pair[1])
        draw.text((x, 0), t, c, font=font)
        x = x + font.getsize(t)[0]
    
    im.save("/home/pi/message.ppm")
    return Popen(["exec /home/pi/rpi-rgb-led-matrix/examples-api-use/demo --led-no-hardware-pulse --led-rows=16 --led-cols=96 -D 1 -m 20 /home/pi/message.ppm"], shell=True)


proc = createPPM([
    [u"    " + "電光掲示板テスト",[255,255,0]],
    [u"    " + "これは初期メッセージです。",[0,255,255]],
    [u"    " + "ネットワーク経由でメッセージを送付してください。",[255,0,255]]])

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind(('192.168.1.5', 50007))
    s.listen(1)
    while True:
        try:
            conn, addr = s.accept()
            with conn:
                data = conn.recv(1024)
                if not data:
                    break
                proc.terminate()
                proc = createPPM(json.loads(data.decode('utf-8')))
                conn.sendall(b'Received: ' + data)
        except KeyboardInterrupt:
            proc.terminate()
            s.close()

クライアント側のコード

これは同一ネットワーク上に存在している別のPCなり別のラズパイから実行させるコードである。
ファイル名はclient.pyとしておく。

import socket
import json

msg = ((
	(u"    " + "オレンジ色のメッセージテスト",(255,165,0)),
	(u"    " + "ライムグリーン色のメッセージテスト",(50,205,50)),
	(u"    " + "ターコイズ色のメッセージテスト",(64,224,208))))

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
	s.connect(('192.168.1.5', 50007))
	s.sendall(bytes(json.dumps(msg), 'utf-8'))
	data = s.recv(1024)
	print(repr(data.decode('utf-8')))

実行

まずラズパイゼロでサーバープログラムを実行する

sudo python3 server.py

※一般ユーザーでも実行できるけどなぜかLEDがちらつく。おそらくsudoで安定するのはリソースを優先的に使用できるのではないかと思われる。

これでサーバー側はデータ受信待ちになる。

次にクライアント端末からクライアントプログラムを実行する。

python3 client.py

これでクライアントプログラムで指定した文字が、指定した色で電光掲示板に表示されたら成功。
サーバーは起動しっぱなしでOK。

クライアントのメッセージをいじって再度実行すれば、表示メッセージも変わる。

12/10追記

sshで実行してる間、ターミナル開きっぱなしにして運用していたがメインPCの電源を落としたりネットワークが瞬断した際にSSHも切断されてしまい、server.pyが落ちて掲示板が消えてしまう。
対策として、nohupコマンドと「&」によってバックグラウンド実行させることにした。

sudo nohup python3 server.py &

ただ、初回のメッセージ受信時に既存のdemoプロセスとは別にdemoプロセスが起動してしまい、2つのメッセージが被って表示がおかしくなる事象が発生した。これは後述のトラブル対応で片方のdemoプロセスをKillすることで以降は発生していない。

実行時のトラブル対応

サーバープログラムは記述ミスやなんらかの原因で終了したりすると、demoというプロセスが残り続け、pythonコードが終了しても掲示板メッセージが消えない。この状態で再実行すると、表示が重なったような感じで実行されてしまう。
こうなったらdemoプロセスを終了させればよいので、「ps -All | grep demo」でPIDを特定し、「sudo kill PID」で終了させると改善する。

GUIが無いので基本的にコマンドでなんとかするしかない。Linuxの基本的なコマンド(特にリブートやシャットダウンなど)はここでは解説しないので検索するなどしてトラブルに対処してもらえると良いかと思う。

組立と設置

これも割と苦労したポイントではあるが、どちらかというと材料と加工ツールの選定に時間を取られただけなので、ここでの説明は簡単に。

まずアルミチャネル材をLEDパネル3枚分の長さに切断する。これを2本用意する。
私の場合はあとで壁にネジで取り付ける想定で、LEDパネルよりも4センチほど長めにした。
結局壁の有孔ボードのフックにちょうど引っかかったので、今後ネジ止めするかどうかは不明。

次にヤスリで切断面を均す。

穴を空ける位置に油性マジックで印をつけ、ボール盤で穴あけ。

8ミリ長のM3ネジでアルミチャネルとLEDパネルを止めていく。
LEDパネル側がネジ穴になっているので特にナットとかは使わなくてOK。

任意の方法で壁に取り付けて完成。

主な参考サイト

qiita.com
digirakuda.org

以下再掲
github.com

以下はそもそもこれをやりたくなったキッカケ
dailyportalz.jp
Arduinoでビットパターン書いてるっぽいんだけど、コードの掲載はないのでどうやってるのか不明。

後書き

モノが完成してから記事にするまでずいぶんと時間がかかってしまった。記事を書き終わるまで大体4時間。なかなか大変な作業であるが、人に説明するとなってようやく理解できることもある。
たとえばLEDとラズパイのピン接続について、最初私が作るときは英語サイトで書いてる内容はあんまりちゃんと読んでなくて、ほぼQiitaの丸コピで作ったんだけど、私が意味を分かってないままここで再掲しても単なる劣化版記事になってしまう。わざわざ書くからには、先人が書いた記事を参考にしつつもそこに何等かの付加価値をつけたいと思い、少し余分に解説してみた。

これを見て自分でやりたくなった誰かがまた別の切り口で記事を書き、解説レベルが上がることで真似する方もハードルが下がって応用例が増え、それをまた誰かが真似て発展させるという好循環が生まれると良いなと思う。

以上

当ブログは、amazon.co.jpを宣伝しリンクすることによってサイトが紹介料を獲得できる手段を提供することを目的に設定されたアフィリエイト宣伝プログラムである、 Amazonアソシエイト・プログラムの参加者です。