4
5

More than 3 years have passed since last update.

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

Posted at

こんにちは。

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

結果

こんな事態にっ!
Dsc03705.jpg

準備

以下条件に当てはまる素材を集めて、ツボ錬金でもやらかせば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を使うため、公式ケースとかは厳しいです。
Dsc03691.jpg

次にLEDのインターフェース基板。
今回は余らせまくってた、Adafruitの古い基板を使うことにしました。
Dsc03690.jpg

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

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

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

配線は適当にポリイミドで留めてます。
Dsc03689.jpg

組み立て

まずはフロント側、こんな感じ。
Dsc03697.jpg

裏は、12cmファン用のステーに、M3ネジで固定しておりますよと。
Dsc03698.jpg

ハメ込むとこんな感じになります。
Dsc03684.jpg

これだけじゃ寂しいので、底面にもLEDを敷きました。
Dsc03685.jpg

こいつの固定はマグネット、4隅にマウント。
Dsc03686.jpg

このマグネットは、秋葉原の千石電商の隣のLED屋とかで扱っています、べんりべんり。
Dsc03687.jpg

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

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

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

Raspberry Pi周りの接続は、こんな感じで。
Dsc03695.jpg

あとはH510i Eliteの電源裏にある隙間にラズパイ押し込んで、完成です。
Dsc03696.jpg

組み込み完成

まあつまり、このPCは。
何気に2環境同居な、「デュアル構成PC」なのですが。
まあ外側からは普通のPCに見えます。
Dsc03682.jpg

後ろも普通のPCです。
Dsc03683.jpg

NZXT純正の制御機構「HUE2」ともバッティングしてません。
Dsc03688.jpg
*ただまあ最初からついていたファンは、全部外しちゃいましたが。

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

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で確認、ね。

動作確認

前面パネルは、こんな感じ!。
Dsc03709.jpg

底面パネルは、忙しくないとこんな感じで。
Dsc03700.jpg

忙しくなって温度上がってくると、わちゃわちゃします。
Dsc03710.jpg

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

やらかしてみて

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

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

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

まんぞく!。

4
5
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
4
5