eucalyのアイコン画像

自作PCにLEDを仕込んでみる(やらかし気味の)

eucaly 2020年12月31日に作成  (2021年01月04日に更新)

自作PCにLEDを仕込んでみる(やらかし気味の)
  • この記事は、Qiitaからの転載です。
  • まあ、LED満載なゲーミングPCを作りたくて、やらかしました。
  • パソコンの中でライフゲームが温度由来で賑やかしてくれる、意味の分からない物体が出来上がってしまいました。
  • 記事内で使用している「Adafruit RGB Matrix HAT」は、マルツさん等で扱っております。
  • マルツオンライン: https://www.marutsu.co.jp/pc/i/574341/
  • 元URL: https://qiita.com/eucalyhome/items/9f27e6386833933fcb8a

こんにちは。

最近、本職方面でもLED屋さんしている、ゆうかりです。
まあ、昨今の流行に則り。
オイラも、「PCをLEDで光らせたく」なったので。
やらかしてみましたよ。

#結果
こんな事態にっ!

ここに動画が表示されます

*実物はチラついてません

準備

以下条件に当てはまる素材を集めて、ツボ錬金でもやらかせばOKです。

  • 中にLED仕込めそうで、外からLEDがちゃんと見えそうなケース
  • 適当なマトリクスLED
  • 多分窒息するので、それなりな放熱手段

まあ、「前面がガラスかアクリル」なケースで探してたのですが。
NZXTのH510i Elite ってケースにしてみました。
んでまあ、CPU排熱を外に出せるように、「kraken M22」という簡易水冷も導入、と。

#LEDの選定
昨今流行りの、「HUB75な表面実装フルカラーマトリクスLED」は、ツブの大きさで複数の種類があります。
型番の「P」の後ろの数字が、ツブの大きさです。
今回選定のケースは、前面に12cm ~ 14cmのファンを2つカマすことができるっぽいので。
それっぽい大きさを選定します。

例えば、P5の32x64であれば、大きさは「16cm x 32cm」、ちょっとデカいです。
まあ、代表的な解像度と大きさを表にすると、こんな感じ。

ピッチ サイズ 大きさ
P2 64x128 12.8cm x 25.6cm
P2.5 32x64 8.0cm x 16.0cm
P3 32x64 9.6cm x 19.2cm
P4 32x64 12.8cm x 25.6cm
P5 32x64 16.0cm x 32.0cm
P6.67 32x64 21.3cm x 42.6cm

ま、今回は、一番上「P2 64x128」を使うことにしてみました。
解像度あるほうが楽しいしね、多分。
なお、短辺64ドット以上の場合、アドレス線の「E」が必要になる可能性が高いです。
後述の基板選定時に、ご留意を。

ラズパイとインターフェースの準備

まあ光らすのにはRaspberryPiを使います。

今回は「RaspberryPi4 4GB」をチョイス。
ケースは適当に、但しGPIOを使うため、公式ケースとかは厳しいです。
キャプションを入力できます

次にLEDのインターフェース基板。
今回は余らせまくってた、Adafruitの古い基板を使うことにしました。
キャプションを入力できます

但し!、以下2つの改造が必要です。

  • ハードウェアPWM駆動化
  • アドレス線「E」の追加

まあ、この記載を参考に、線を2本追加と、パターンカット1箇所って感じです。
なお、アドレスE線は、2パターンあるので、まずお使いのパネルが「どっち」か、テスターなどで確認してくださいな。
使われないほうのアドレスE線は、GNDに落ちてます。
 *なので双方接続はNGですよっと。
キャプションを入力できます

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

配線は適当にポリイミドで留めてます。
キャプションを入力できます

組み立て

まずはフロント側、こんな感じ。
キャプションを入力できます

裏は、12cmファン用のステーに、M3ネジで固定しておりますよと。
キャプションを入力できます

ハメ込むとこんな感じになります。
キャプションを入力できます

これだけじゃ寂しいので、底面にもLEDを敷きました。
キャプションを入力できます

こいつの固定はマグネット、4隅にマウント。
キャプションを入力できます

このマグネットは、秋葉原の千石電商の隣のLED屋とかで扱っています、べんりべんり。
キャプションを入力できます

https://www.akiba-led.jp/product/1197

LEDは底側のLEDをラズパイに繋げ、そこからチェーンで前面LEDに接続しています。
これは純粋に配線都合です。

電源はSATAから取ることに。
まあ昔からのお約束ですが、パソコンの電源は一般的に、「黄色:12V」「赤:5V」「オレンジ:3.3V」「黒:GND」です。
RaspberryPi4とLEDで、まあ5A以上使いそうですが、パソコン電源なので大丈夫でしょう(適当)。
キャプションを入力できます

Raspberry PiとPCのインターフェースは、LANを使用します。
PC側がUSBLANです。
USBは、マザーボードのヘッダから、ケース用の変換コネクタを使って。
キャプションを入力できます

Raspberry Pi周りの接続は、こんな感じで。
キャプションを入力できます

あとはH510i Eliteの電源裏にある隙間にラズパイ押し込んで、完成です。
キャプションを入力できます

#組み込み完成
まあつまり、このPCは。
何気に2環境同居な、「デュアル構成PC」なのですが。
まあ外側からは普通のPCに見えます。
キャプションを入力できます

後ろも普通のPCです。
キャプションを入力できます

NZXT純正の制御機構「HUE2」ともバッティングしてません。
キャプションを入力できます
*ただまあ最初からついていたファンは、全部外しちゃいましたが。

ここまでやらかしておきながら、「ケース無改造」なのです、何げにヒドい・・・。

PC側の準備

PC側は、モニタリング用のソフトが必要です。
今回は、「OpenHardwareMonitor」を使いました。
このソフト、Webサーバ機能があり、外から状況を把握できるのですが。
レンダリングに使っているJSONを、ラズパイ側でもらっちゃおう作戦です。
https://forest.watch.impress.co.jp/docs/review/383668.html

但し上記ソフト、最近のアーキティクチャに対応していないので。
実際は、以下URLから、野良ビルドの「LibreHardwareMonitor」を使っています。
http://takkun87.blog40.fc2.com/blog-category-6.html

Webサーバ有効にして、F/Wを適当に設定、外部から以下JSONをゲットできれば成功です。
http://[IPアドレス]:8085/data.json

内容はこんな感じです(一部抜粋):

data.json

{ "id": 0, "Text": "Sensor", "Min": "Min", "Value": "Value", "Max": "Max", "ImageURL": "", "Children": [ { "id": 1, "Text": "DESKTOP-HOGEHOGE", "Min": "", "Value": "", "Max": "", "ImageURL": "images_icon/computer.png", "Children": [ { "id": 2, "Text": "ASRock Z390 Phantom Gaming 6", "Min": "", "Value": "", "Max": "", "ImageURL": "images_icon/mainboard.png", "Children": [ { "id": 3, "Text": "Nuvoton NCT6791D", "Min": "", "Value": "", "Max": "", "ImageURL": "images_icon/chip.png", "Children": [ { "id": 4, "Text": "Voltages", "Min": "", "Value": "", "Max": "", "ImageURL": "images_icon/voltage.png", "Children": [ { "id": 5, "Text": "Vcore", "Min": "1.392 V", "Value": "1.392 V", "Max": "1.408 V", "SensorId": "/lpc/nct6791d/voltage/0", "Type": "Voltage", "ImageURL": "images/transparent.png", "Children": [] }, { "id": 6, "Text": "Voltage #2", "Min": "1.680 V", "Value": "1.688 V", "Max": "1.688 V", "SensorId": "/lpc/nct6791d/voltage/1", "Type": "Voltage", "ImageURL": "images/transparent.png", "Children": [] }, { "id": 7, "Text": "AVCC", "Min": "3.488 V", "Value": "3.488 V", "Max": "3.488 V", "SensorId": "/lpc/nct6791d/voltage/2", "Type": "Voltage", "ImageURL": "images/transparent.png", "Children": [] }, { "id": 8, "Text": "3VCC", "Min": "3.488 V", "Value": "3.488 V", "Max": "3.488 V", "SensorId": "/lpc/nct6791d/voltage/3", "Type": "Voltage", "ImageURL": "images/transparent.png", "Children": [] }, { "id": 9, "Text": "Voltage #5", "Min": "1.016 V", "Value": "1.032 V", "Max": "1.032 V", "SensorId": "/lpc/nct6791d/voltage/4", "Type": "Voltage", "ImageURL": "images/transparent.png", "Children": [] }, { "id": 10, "Text": "Voltage #6", "Min": "1.088 V", "Value": "1.088 V",

あとは、PCのUSBLANとラズパイに、適当なローカルIPを振り、通信を担保しておきましょう。

ラズパイ側準備

やることはこんな感じ

  • LEDパネル用ライブラリの追加
  • プログラム
  • RAMDISKだのSYSTEMDだの定義

LEDパネル用ライブラリは、以下URLを参照のこと。
https://qiita.com/eucalyhome/items/e871e297bfd527ccaf2c

プログラム

「ステータス表示」と、「ライフゲーム(温度によって忙しさが変わる)」を実装してみました。
LEDはチェーン接続してあるので、最初の128ドットはライフゲーム、後の128ドットが縦書きのステータス表示、な感じで。

ledstat.py

#!/usr/bin/env python # -*- coding: utf-8 -*- from PIL import Image, ImageDraw, ImageOps, ImageFont, ImageFilter, ImageChops, ImageColor from rgbmatrix import RGBMatrix, RGBMatrixOptions import time, re, copy, os, codecs, random, datetime, json import numpy as np from scipy import signal class ledworker(object): def ledinit(self): self.options = RGBMatrixOptions() self.options.hardware_mapping = "adafruit-hat-pwm" self.options.led_rgb_sequence = "RGB" self.options.rows = 64 self.options.chain_length = 8 self.options.parallel = 1 self.options.pwm_bits = 11 self.options.brightness = 50 self.options.pwm_lsb_nanoseconds = 130 self.options.gpio_slowdown = 1 self.options.panel_type = "FM6126A" self.matrix = RGBMatrix(options = self.options) def ledoutput(self,handle): self.matrix.SetImage(handle) def imagenew(self): imagecolorspace = 'RGB' imagecolorfill = (0, 0, 80) imagesizex = 256 imagesizey = 64 image = Image.new(imagecolorspace, (imagesizex, imagesizey), imagecolorfill) return (image) class lifeworker(object): def initstate(self, width, height, init_alive_prob=0.5): N = width*height v = np.array(np.random.rand(N) + init_alive_prob, dtype=int) return v.reshape(height, width) def addstate(self, F, width, height, init_alive_prob=0.5): N = width*height v = np.array(np.random.rand(N) + init_alive_prob, dtype=int).reshape(height, width) v = F + v return v def countneighbor(self, F): mask = np.ones((3, 3), dtype=int) return signal.correlate2d(F, mask, mode="same", boundary="wrap") def nextgeneration(self, F): N = self.countneighbor(F) G = (N == 3) + F * (N == 4) return G def toimage(self, F): nparray = np.array(F, dtype=np.uint8)*255 image = Image.fromarray(np.uint8(nparray)).convert("1") return image class monitorworker(object): def getdata(self): datafile_hwdata = '/ramdisk/hwdata.json' outdata = {} outdata['cpuload'] = 0 outdata['cputemp'] = 0 outdata['gpuload'] = 0 outdata['gputemp'] = 0 outdata['cpuclock'] = 0 if not os.path.exists(datafile_hwdata): return (outdata) try: with open(datafile_hwdata) as f: hwdata = json.load(f) except: return (outdata) for primarykey in hwdata['Children'][0]['Children']: if 'cpu' in primarykey['ImageURL']: for secondarykey in primarykey['Children']: if 'Clocks' in secondarykey['Text']: for thirdkey in secondarykey['Children']: if 'CPU Core #1' in thirdkey['Text']: outdata['cpuclock'] =re.sub("[^0-9\.]","",thirdkey['Value']) if 'Load' in secondarykey['Text']: for thirdkey in secondarykey['Children']: if 'CPU Total' in thirdkey['Text']: outdata['cpuload'] =re.sub("[^0-9\.]","",thirdkey['Value']) if 'nvidia' in primarykey['ImageURL']: for secondarykey in primarykey['Children']: if 'Temperatures' in secondarykey['Text']: outdata['gputemp'] = re.sub("[^0-9\.]","",secondarykey['Children'][0]['Value']) if 'Load' in secondarykey['Text']: outdata['gpuload'] = re.sub("[^0-9\.]","",secondarykey['Children'][0]['Value']) if 'mainboard' in primarykey['ImageURL']: for secondarykey in primarykey['Children'][0]['Children']: if 'Temperatures' in secondarykey['Text']: outdata['cputemp'] = re.sub("[^0-9\.]","",secondarykey['Children'][0]['Value']) outdata['cpuload'] = "{0:5.1f}".format(float(outdata['cpuload'])) outdata['gpuload'] = "{0:5.1f}".format(float(outdata['gpuload'])) outdata['cpuclock'] = "{0:4d}".format(int(outdata['cpuclock'])) return (outdata) def writetext(draw,hpos,message,color,font): w, h = draw.textsize(str(message), font) w = 48 - w draw.text((w,hpos), str(message), color,font=font) def main(): led = ledworker() led.ledinit() image = led.imagenew() fontdata = "./GDhwGoJA-OTF112b2.otf" life = lifeworker() lifedatablue = life.initstate(128, 64, init_alive_prob=0.08) lifedatagreen = life.initstate(128, 64, init_alive_prob=0.08) monitor = monitorworker() outdata = monitor.getdata() imagebasel = Image.open('/data/ledstat/nzx_log_w.png').convert("RGB") imagebasel = imagebasel.transpose(Image.ROTATE_180) imagebaseh = copy.copy(imagebasel) imagebaseh = imagebaseh.transpose(Image.ROTATE_90) imageh = copy.copy(imagebasel) image = Image.new('RGB', (256, 64), (0, 0, 0)) image.paste(imageh,(128, 0)) imagehtemp = copy.copy(imagebaseh) draw = ImageDraw.Draw(imagehtemp) font = ImageFont.truetype(fontdata, 16) fontsmall = ImageFont.truetype(fontdata, 7) addcount = 0 while True: imageblue = life.toimage(lifedatablue) imagegreen = life.toimage(lifedatagreen) imagel = copy.copy(imagebasel) imagel.paste(Image.new("RGB", imagel.size, (0,255,255)), mask=imageblue) imagel.paste(Image.new("RGB", imagel.size, (0,0,255)), mask=imagegreen) image.paste(imagel,(128, 0)) led.ledoutput(image) lifedatablue = life.nextgeneration(lifedatablue) lifedatagreen = life.nextgeneration(lifedatagreen) time.sleep(0.05) addcount = addcount + 1 if addcount > 20: addcount = 0 outdatatemp = monitor.getdata() if outdatatemp['cputemp'] != 0: outdata = outdatatemp blueseed = (float(outdata['cputemp'])-40) / 200 greenseed = (float(outdata['gputemp'])-40) / 200 if blueseed < 0: blueseed = 0 if greenseed < 0: greenseed = 0 lifedatablue = life.addstate(lifedatablue,128, 64, init_alive_prob=blueseed) lifedatagreen = life.addstate(lifedatagreen,128, 64, init_alive_prob=greenseed) lifedatablue = life.nextgeneration(lifedatablue) lifedatagreen = life.nextgeneration(lifedatagreen) imagehtemp.paste(imagebaseh,(0, 0)) draw.text((0,0), "CPU", (0, 255, 255),font=font) writetext(draw,16,outdata['cpuload'],(255, 255, 255),font) draw.text((48,24), "%", (0, 255, 255),font=fontsmall) writetext(draw,32,outdata['cputemp'],(255, 255, 255),font) draw.text((48,40), "'C", (0, 255, 255),font=fontsmall) writetext(draw,48,outdata['cpuclock'],(255, 255, 255),font) draw.text((48,56), "MHz", (0, 255, 255),font=fontsmall) draw.text((0,74), "GPU", (0, 0, 255),font=font) writetext(draw,90,outdata['gpuload'],(255, 255, 255),font) draw.text((48,98), "%", (0, 0, 255),font=fontsmall) writetext(draw,106,outdata['gputemp'],(255, 255, 255),font) draw.text((48,116), "'C", (0, 0, 255),font=fontsmall) imageh = copy.copy(imagehtemp) imageh = imageh.transpose(Image.ROTATE_90) image.paste(imageh,(0, 0)) if __name__ == '__main__': main()

ライフゲームの実装は、以下URLを参考にしました。
https://qiita.com/sage-git/items/c6c175887faa4cf737fb

前提となるファイルは、以下の通り
128x64 背景ロゴ:/data/ledstat/nzx_log_w.png
PCステータス用data.json:/ramdisk/data.json
フォント:./GDhwGoJA-OTF112b2.otf

フォントは高速道路の奴使ってます、まあお好みで。
https://forest.watch.impress.co.jp/library/software/gdhighway/

次に、PCからJSONをゲットする適当なプログラムを

getdata.py

import urllib.request import json import time url = 'http://[ip address]:8085/data.json' datafile = '/ramdisk/hwdata.json' req = urllib.request.Request(url) while True: try: with urllib.request.urlopen(req) as res: body = json.load(res) fw = open(datafile,'w') json.dump(body,fw,indent=4) fw.close() except: time.sleep(30) time.sleep(2)

こいつはPython3で動きます注意、まあつまり無節操。

RAMDISK定義は簡単。
まず「/ramdisk/」をrootで777なディレクトリを作り。
FSTABに「tmpfs /ramdisk tmpfs defaults,size=16m 0 0」を書き足し。
「mount -a」かリブートすればOK。
dfして「tmpfs 16384 84 16300 1% /ramdisk」があればOKな感じです。

systemdへの登録は、以下な感じ。

/etc/system/getdata.service

[Unit] Description=getdata [Service] ExecStart=/usr/bin/python3 /data/ledstat/getdata.py Restart=always Type=simple User=pi [Install] WantedBy=multi-user.target

/etc/systemd/system/ledstat.service

[Unit] Description=ledstat [Service] WorkingDirectory=/data/ledstat ExecStart=/usr/bin/python /data/ledstat/ledstat.py Restart=always Type=simple [Install] WantedBy=multi-user.target

んで、「systemctl daemon-reload」からの

systemctl start getdata
systemctl enable getdata
systemctl start ledstat
systemctl enable ledstat

な感じで。
エラーが起きたらstatusで確認、ね。

#動作確認
前面パネルは、こんな感じ!。
キャプションを入力できます

底面パネルは、忙しくないとこんな感じで。
キャプションを入力できます

忙しくなって温度上がってくると、わちゃわちゃします。
キャプションを入力できます

なかなかに、たのしいw。

やらかしてみて

いやあ、なんというか。
オイラっぽいのが、できたかなと。

ちなみに、ゲームしてる最中は別のパソコンなんか見ないし。
LEDパネルで色々塞いでるので、温度上がり気味だし。
というかそもそもLEDで発熱してるし。

真面目に考えるといいことなんもないですが。
楽しいからいいのです!。
キレイだし!。

まんぞく!。

1
eucalyのアイコン画像
いつも、てきとうです
ログインしてコメントを投稿する