t-hom’s diary

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

PCケースの3.5インチベイに液晶ディスプレイを組込んでみた

サーバー機の3.5インチフロントベイが余っているので液晶ディスプレイを組み込んでみた。

横長のディスプレイを探したのだがちょうど良いサイズのものは製造されてなさそうなので以下を3つ並べている。

※基盤含めて縦幅が26ミリを超えると取り付けできないので部品の選定にかなり苦労した。

アイデアとしてはディスプレイの取り付け土台とモニタフレームを用意して濃黒の半透明アクリルパネルで基盤部分を覆い隠すことでディスプレイの光だけが見えるというもの。

ただ黒の半透明アクリルが一般的ではないのかAmazonで見つけられなかったので半透明アクリルに自動車のヘッドライト用減光シートで代用することにした。

綺麗に貼るためにヘラとフィルム貼りスプレー併せて調達

しかし初回はこのざまである。なかなか難しい。

何度か試してモニタフレームサイズの綺麗な箇所が作れたらバンドソーで切り出した。

2023年の1月に購入して1年間段ボールで眠っていたバンドソー。今更開梱して机にビスで固定しろと書かれてて絶望したけどちょうど良い木の板があったのでそれに固定して使った。※厚みのあるものを切るときは作業台とかに固定しないと危険だと思うので今後検討したい。

3Dプリントパーツの設計調整も試行錯誤の連続で苦労した。

最終的な設計はこんな感じ。

モニター土台のもう片方の固定パーツは挿し込んでからネジ止めするため、ヒートプレス機でインサートナットを埋め込んだ。

ハンダゴテでも出来るけど歪みなく作りたい場合は便利。

フレームとアクリルパネルは以下のボンドで接着した。PLAの接着にベストな接着剤については諸説あるけど力がかかる部品ではないのでヨドバシの文具コーナーで見つけたものを試した。PLAの表記はないけどアクリル◎とPP・PE・PET・ABSが◎となってたのが決め手。

Arduinoで実験中の様子。イメージ通り。

そして地獄の配線作業。ArduinoとディスプレイはSPIという方式で通信するのだが、通信の宛先を決めるChip Select線以外の電力線やデータ線は共通になっているので3台分をまとめることになる。パネルと本体の接続はシンプルにしたいのでパネル側であらかたまとめてしまうことにした。ケーブル2本を強引にかしめた部分がなかなかデュポン端子カバーに刺さらずに苦労した。

共通系が6本(GND・VCC・SCL・SDA・RES・DC)とCS線がそれぞれ1本で合計9本のラインで済むのでArduinoとの接続は10ピン用の端子カバーで綺麗にまとめられた。

また、今回はArduino Nano Everyを採用。USBは以下の製品を使ってマザーボードから直接接続する形とした。

組み込んでみたのがこちら。最初はArduino Unoで検討してたので専用の固定土台を設計しようかと思っていたけどNano Everyならマスキングテープで十分。見える場所ではないし。

ということで外装が完成。

ディスプレイ3台を更新するためのArduinoコードは次のとおり。

#include <Adafruit_GFX.h>    // Core graphics library
#include <Adafruit_ST7735.h> // Hardware-specific library for ST7735
#include <SPI.h>

#define TFT_CS1        10
#define TFT_CS2        7
#define TFT_CS3        6

#define TFT_RST        9
#define TFT_DC         8

Adafruit_ST7735 tft[] = {
  Adafruit_ST7735(TFT_CS1, TFT_DC, TFT_RST),
  Adafruit_ST7735(TFT_CS2, TFT_DC, -1),
  Adafruit_ST7735(TFT_CS3, TFT_DC, -1)
};

typedef struct { 
  uint16_t color;
  String str;
  byte size;
} message;

message data[3][10];

void reset(int n){
  for(int i=1;i<10;i++){
    data[n][i]={0, "", 0};
  }
}

void setup(void) {
  Serial.begin(9600);
  for(int i=0;i<3;i++){
    reset(i);
    tft[i].initR(INITR_MINI160x80);
    tft[i].setRotation(3);
    tft[i].fillScreen(ST77XX_BLACK);
  }
}

void loop() {
  while (Serial.available() == 0) {
  }
  String s = Serial.readString();
  
  if(s.length()<12){
    Serial.println("NACK");
    return;
  }
  for(int i=0;i<12;i++){
    if(!isDigit(s.charAt(i))){
      Serial.println("NACK");
      return;
    }
  }
  Serial.println("ACK");

  int n = s.substring(0,1).toInt();
  if(n<3){
    data[n][s.substring(1,2).toInt()] = {
      RGB888_to_BGR565(
        byte(s.substring(3,6).toInt()),
        byte(s.substring(6,9).toInt()),
        byte(s.substring(9,12).toInt())
      ),
      s.substring(12),
      byte(s.substring(2,3).toInt())
    };
  } else if (n<6){
    n-=3;
    update_screen(tft[n], data[n]);
  } else if (n<9){
    n-=6;
    reset(n);
  }
}

uint16_t RGB888_to_BGR565(byte red, byte green, byte blue) {
  if(red>255){red=255;}
  if(green>255){green=255;}
  if(blue>255){blue=255;}
  uint16_t bgr565 = 0;
  bgr565 = blue>>3;
  bgr565 = bgr565<<6;

  bgr565 += green>>2;
  bgr565 = bgr565<<5;

  bgr565 += red>>3;
  return bgr565;
}

void update_screen(Adafruit_ST7735 tft, message *m) {
  tft.setTextWrap(false);
  tft.setCursor(0, 0);
  tft.fillScreen(ST77XX_BLACK);

  for(int i=0;i<10;i++){
    tft.setTextColor(m[i].color);
    tft.setTextSize(m[i].size);
    tft.println(m[i].str);
  }
}

シリアル通信で受信した内容を12桁の固定長+可変長のフォーマットで解釈して指定のモニターの表示データを決める仕組みとなっている。

モニターは0~2番までの3台で、データを実際に反映する際はモニター番号に3を足した3~5を指定、データをクリアする場合はモニター番号に6を足した6~8を指定する。

データの解読が完了したらシリアル通信でACKという文字列、エラーならNACKを返す仕組み。
※ACK=Acknowledgement(了承)

実際に送るデータの例はこちら。

600000000000 Deletion
012255255255 CPU Temp
03412812812845 Deg
300000000000 Load

700000000000 Deletion
112255255255 HDD Usage
134255000000 5%
400000000000 Load

800000000000 Deletion
212255255255 Last Logon
232255128128 2024/01/18
500000000000 Load

Linux側からはAWS S3の使用量をあらかじめテキストファイルに保存する仕組みを作っているのでそのテキストから容量を所定のフォーマットにしてArduino側にシリアル通信で送る仕組みを作った。

#!/usr/bin/env python3
import time
import serial
f = open('/mnt/encrypted/Archive/s3usage.txt', 'r')
data = f.readlines()
usage = int(data[1].split(':')[1].strip())/1024**3
usage = round(usage, 5 - len(str(int(usage))))

ser = serial.Serial('/dev/ttyACM0', 9600)
time.sleep(1.5)

ser.write("600000000000 Deletion".encode())
print(ser.readline().strip().decode())
ser.write("012255255255S3 Usage(GB)".encode())
print(ser.readline().strip().decode())
ser.write(("034128128128" + str(usage)).encode())
print(ser.readline().strip().decode())
ser.write("300000000000 Load".encode())
print(ser.readline().strip().decode())

ser.close()

※接続したArduinoはttyACM0というデバイスとして認識された。これは環境によるので注意。また、実行ユーザーをdialoutグループに追加しておかないとttyACM0へのアクセスが拒否されるため注意。

さて、これをcronジョブで任意の時間に実行すれば自動的にアップデートされるようになる。

ということで再度冒頭の写真。

3つのディスプレイのうちあと2つは今のところダミーのHDD容量と実際に更新される現在時刻が入ってるけどこんなところに時計要らないのでこれは変えるつもり。
何を表示させるかはこれから検討しようと思う。

以上。

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