USB接続カラーグラフィックLCDによる自宅サーバの活用


はじめに

自宅で24時間動かし続けているサーバーがあります.
サーバーマシンなので普段モニターはつないでいませんが,常にPCの状態を表示したいと思い,しばらく前にキャラクタLCDを取り付けました.
しかしながらせっかく常時動かしているマシンなので,グラフィックLCDを取り付けてもっと多くの情報を表示できるようにすれば,mp3やネットラジオのプレーヤーとして利用したり,RSSを受信して表示させたり,画像を表示させたりなど色々とおもしろいことができそうなので,カラーグラフィックLCDをつなげて,制御用のソフトウェアを作成しました.制御用のソフトを作るにあたっては,できるだけフレキシブルでかつシンプルなものを目指して作りました.

このシステムは,グラフィックLCDと4つのキー入力および状態表示用のキャラクタLCDを備えており,指定したディレクトリ以下に存在する次のような種類のファイルを扱うことができます:

その他,特定の拡張子を持つファイルを実行するためのプラグインを容易に作成することができます.

実行例

キャラクタLCDとスイッチとグラフィックLCD
システム全体
ファイルやディレクトリの選択
(アイコンは,上からフォルダ,テキスト,音楽,RSS,実行ファイル,スクリーンセーバーを表す)
ファイルやディレクトリの選択
テキストの表示
テキストの表示
画像の表示
画像の表示
音楽の再生
音楽の再生
RSSの表示
RSSの表示
プログラムの実行(プロセス表示の例)
プログラムの実行(プロセス表示の例)

構成と特徴

システムの構成図は次のようになります:
システム構成図
キャラクタLCDとグラフィックLCDがつながっていますが,キャラクタLCDの方にはCPUの使用率や空きメモリ量,CPUやHDDの温度等を常時表示させて,グラフィックLCDの方に画像やテキストを表示させて利用します.操作するためのキーはキャラクタLCDのモジュールの方に付けていて,4つのキー(Next, Previous, Run, Back)があります.
どちらのモジュールもUSBでPCに接続します.

PC上のLinuxでは,下記の4つのバックグランドプロセス(daemon)が動いています.

clcddは,キャラクタLCDにデータを送ったり,また押されたキーのデータを受け取ったりします.
このdaemonは起動時に/dev/clcds/dev/clcdkという特殊なファイルを作成します.前者にデータを書き込むと(例えば,'echo "Hello, world" > /dev/clcds'),その内容がキャラクタLCDに表示されます.また後者を読み出すと(例えば'cat /dev/clcdk'),押されたキーに対応する文字を取得できます.
これらはキャラクタデバイスのように見えますが,実際はFIFO(名前付きパイプ)です.

glcddは,グラフィックLCDにデータを送ります.
このdaemonは起動時に/dev/glcdsという特殊なファイルを作成します.このファイルにASCIIやEUC-JPの文字列を書き込むと,その文字列がグラフィックLCDに出力されます.さらにいくつかのエスケープシーケンスが定義されており,文字色や背景色を変えたり,イメージデータを送ってグラフィックを出力させたりすることができます. 

monitordは,PCの状態をキャラクタLCDに出力します.
CPUの使用率,空きメモリ量,CPUやHDDの温度を一定の間隔で取得して出力します.

lcdshellは,ファイラー(Windowsでいうエクスプローラー)のようなもので,任意のファイルを選んで「実行」することができます.ファイルが実行された場合,そのファイルの拡張子にあらかじめ対応付けられていたプラグインが実行されます.例えば,音楽再生用プラグイン,画像表示用プラグイン,RSS表示用プラグイン,テキスト表示用プラグイン,等々が用意されています.
これらのプラグインは後から自由に追加できます.

「はじめに」で述べたように,このシステムはできるだけフレキシブルでシンプルなものを目指しており,そのためUnix的なデザインが多く存在します.例えばmonitordは標準出力に現在のPCの状態を出力するだけのプログラムで,その出力を/dev/clcdsにリダイレクトしています.同様にlcdshellは標準入力からキー入力を受け取り,標準出力に画面に表示すべきデータを出力するだけのプログラムで,実際に操作するデバイスは意識しておらず,プログラムの実行時に標準入出力をキーやグラフィックLCDのデバイスにリダイレクトして使います.またlcdshellからファイル実行時に呼ばれる各種プラグインも標準入力と標準出力を通じてデータをやり取りするだけのプログラムで,起動される時にパイプでキー入力とグラフィックLCDのデバイスに接続されます.このような作りにすることで,既存の標準入力からキー操作を入力し標準出力へ情報を出力するような「行儀のよい」Unixプログラムは,キーマッピングを変更するだけで容易にプラグインとして利用することができます.

ハードウェア

キャラクタLCD&プッシュスイッチモジュール

回路図と配線図は下記の通りです:

回路図 回路図  配線図 配線図 

このモジュールは基本的にLCD2USBと同じです.ただしプッシュスイッチの数を2個から4個に増やしてあります.追加したプッシュスイッチを認識させるため,ファームウェアをオリジナルのものから若干変更しています.

4つのスイッチは,左から順にBack,Next,Previous,Runと名付けています.ファイル一覧の画面では,NextとPreviousでカーソルを動かして目的のファイルやディレクトリを選択し,Runボタンでファイルを実行したりサブディレクトリへ移動し,Backで親ディレクトリへ戻ります.

グラフィックLCDモジュール

回路図と完成したモジュールの写真は下記の通りです:

回路図 回路図  完成したグラフィックLCDモジュール 完成したグラフィックLCDモジュール 

カラーグラフィックLCDには,LQ020B8UB02という2インチ132x162ドットTFT-LCDを利用しました.
このLCDは3種類の電源(1.8V,2.78V,12V)が必要な点が面倒ですが,フレームバッファを内蔵していて扱いやすいLCDです.また実際に使ってみたところ,視野角はやや狭く感じましたが発色は思っていたよりも良いものでした.

USBとの接続にはFT245RLを使用してBitBangモードでLCDをコントロールしました.
ただしBitBangモードで操作できるのは8bitの信号で,LCDを操作するには足りないので(8bitのデータバス+1bitのアドレスバス+WriteEnable+Resetの11bitが必要),Dフリップフロップの74VHC574を入れて信号をラッチさせました.
LCDにデータを1つ送るには,USBからBitBangで4サイクル分データを送る必要があります.つまり,最初のサイクルでD-FFにセットするデータを送り,2番目のサイクルでD-FFのクロックを立ち上げてデータをラッチさせ,3番目のサイクルで残りのデータをセットし,4番目のサイクルでLCDのWriteEnable端子をアクティブにします.
FT245RLはBitBangモードの場合1,000,000bytes/secの転送速度がありますが,これが1/4になるので,132x162のサイズの1ピクセルあたり2バイト(RGB565)のイメージデータを転送すると,約5.8frame/secの転送速度になります.
今回はできるだけハードウェアをシンプルにしたかったのでD-FFで済ませましたが,代わりにAVRなどを使ってデータ転送の無駄を減らせばもっと高速化できると思います.

LCDの電源に関して,1.8Vは定電圧レギュレータを用意しましたが,2.78Vについては最大定格が3.6VだったのでFT245RLから得られる3.3Vの出力を利用しました.またバックライト用の12V(15mA,最大20mA)については,コイル等は使わずにできるだけハードウェアをシンプルにしたかったので,外付け部品の少ない電圧反転チャージポンプICのTC7660を使用して-5Vを生成し,+5Vとの電位差で10Vを供給するようにしました(LCDのバックライトの電源は,他の端子と独立しています).
なお74VHC574の最低定格電圧は2Vですが1.8Vで動作させています.

LCDの配線はコネクタにUEWを直接ハンダ付けしました.

ソフトウェア: キャラクタLCD表示&キー入力(clcdd)

このプログラムはバックグラウンドで常時稼働し,ボタンが押された場合はそれに対応した文字を名前付きパイプ/dev/clcdkを通じて他のプロセスに渡し,また名前付きパイプ/dev/clcdsに他のプロセスから文字列が入力された場合はそれをキャラクタLCDに表示します.
libusbを使ってUSBに接続したキャラクタLCD&プッシュスイッチモジュールをコントロールしています.

プログラムの流れとしては,メインルーチンでは/dev/clcdsにデータが他のプロセスから書き込まれるまでブロック(sleep)し,データが書き込まれたらそれを内部に確保したバッファ(T-VRAM)に即座に書き込み,再びデータを待つ,というループを繰り返しています.
またインターバルタイマ(setitimer)を使用して1/30秒毎に起動されるようにした関数の中では,4つのボタンのどれかが押されていないかチェックして,もし押されていればそれに該当する文字を/dev/clcdkに書き込みます.またT-VRAMの内容に変化があった場合は,T-VRAMの内容をキャラクタLCDに表示します.
つまりどれだけ頻繁に/dev/clcdsにデータを送っても,1/30秒に1回しかLCDの画面は更新されません.
ボタンが押され続けている場合は,キーリピート処理も行っています.
ボタンが押された場合に出力される文字は,Backボタンは'\x1b(ESC)',Nextボタンは' '(SPC),Previousボタンは'\b(BS)',Runボタンは'\n(LF)'となっています.

ソフトウェア: グラフィックLCD表示(glcdd)

このプログラムはバックグラウンドで常時稼働し,名前付きパイプ/dev/glcdsに他のプロセスから文字列や画像データが入力された場合にそれをグラフィックLCDに表示します.
libftdiを使ってUSBに接続したグラフィックLCDをコントロールしています.

プログラムの流れとしては,メインルーチンでは/dev/glcdsにデータが他のプロセスから書き込まれるまでブロックし,データが書き込まれたらそれを内部に確保したバッファ(G-VRAM)に即座に書き込み,再びデータを待つ,というループを繰り返しています.
またインターバルタイマを使用して1/30秒毎に起動されるようにした関数の中では,G-VRAMの内容に変化があった場合にG-VRAMの内容をグラフィックLCDに表示します.
/dev/glcdsに表示すべきデータが書き込まれるといいましたが,ASCIIやEUC-JPの文字列を書き込んで出力することもできますし,また132x162ドットの画像を書き込んで表示することもできます.テキスト表示モードとグラフィック表示モードの2つのモードを持っており,特別なエスケープシーケンスでその表示モードを切り替えることができます.さらに,カーソルの移動や文字色や背景色の変更といったことも,特別なエスケープシーケンスを使って行うことができます.

このモジュールに入力できるエスケープシーケンスは下の表の通りです.これらのシーケンスを/dev/glcdsに書き込むことで,単に文字列を出力するだけではなくグラフィックLCDの様々な制御を行えるようにしてあります.例えば,'# echo -e "\0005\0377\0000\0000\0000\0377\0000Hello, world" > /dev/glcds'とすると,赤い文字で緑の背景に"Hello, world"と表示できます.
なお,ASCII文字や日本語EUC文字を書き込んだ場合に表示領域の右端に達した場合は自動的に改行し,最後の行で改行が起こった場合には一行スクロールされます.

エスケープシーケンス 機能
\x0d(\r) カーソルを行頭へ移動.
\x0a(\n) カーソルを次の行へ移動.
\x0c(\f) 画面を消去してカーソルをホーム位置(0, 0)へ移動.
\x06 x y カーソルをy(0-19)行x(0-31)列へ移動.
\x1b [ y' ; x' H カーソルを(y'-1)行(x'-1)列へ移動.
\x05 Rf Gf Bf Rb Gb Bb テキストの文字色・背景色の指定.(Rf, Gf, Bf)と(Rb, Gb, Bb)でそれぞれ文字色・背景色のR/G/B階調(0-255)を指定する.
\x0f テキスト表示モードに設定.
\x0e グラフィック表示モードに設定(\x01のエスケープシーケンスで設定したビットマップを表示する).
\x01 d0 d1 ... d42767 ビットマップの画像データの書き込み.d0 d1 ... d42767は132x162の赤5bit緑6bit青5bit(RGB565)のビットマップデータ.
\x04 c0 c1 f0 ... f7 外字フォントの登録.フォントサイズは8x8で各行について0/1でドットを表現した計8byteをf0 ... f7とし,文字コードがc0 c1の日本語EUC文字のフォントを置き換える.

上記のエスケープシーケンスは,Linuxのterminfoで定義されているdumb端末の上位互換となっています.
つまり原理的には,vim等のterminfoに基づいて端末制御を行うプログラムは/dev/glcdsを画面として動作することができます.

テキストの表示に使うフォントは,美咲フォントを使わせていただきました.
これは8x8という小さなサイズのフォントで,LCDの画面を有効に活用することができます.
png形式で提供されていたフォントデータをxbm形式に変換して,C言語から直接利用しています.xbm形式は昔から知ってはいましたが,使ったのはこれが初めてです.

ソフトウェア: PC状態監視(monitord)

このプログラムはバックグラウンドで常時稼働し,定期的にPCの状態をキャラクタLCDに表示します.
100行程度の簡単なプログラムで,CPUの使用率,空きメモリ量,CPUの温度,HDDの温度,の4つの情報を3秒毎に出力します.
CPUの使用率は/proc/loadavgから読み取っています.
空きメモリ量は/proc/meminfoから読み取っています.
CPUの温度は/sys/devices/platform/vt8231.24576/temp1_inputというデバイスから読み取った値を元に計算して求めています.このパスはマザーボードに依存し,またlmsensorstをインストールしないとアクセスできないかもしれません.lmsensorsと同じ計算式を使用して計算しています.
HDDの温度は/proc/ide/hda/smart_valuesから読み取っています.ただし,このデバイスにアクセスすると実際のHDDへのアクセスが発生するので,頻繁にアクセスが起こるのを避けるため一度読み取ったデータは60秒間キャッシュするようにしています.

ソフトウェア: ファイラー(lcdshell)

このプログラムはバックグラウンドで常時稼働し,グラフィックLCDとキー入力を利用して様々なファイルに対する操作を行います.
プログラムの第1引数にディレクトリを指定して起動すると,まず指定されたディレクトリのファイル一覧を取得して表示します.
Next,Previousボタンでカーソルを前後に動かし,Runボタンでファイルを実行し,Backボタンでプログラムを終了します.

Runボタンが押されると,あらかじめ関連付けられたプログラム(プラグイン)が,現在カーソルで選択されているファイルを第1引数にして起動されます.
どのような拡張子を持つファイルに対してどのプログラムが呼ばれるかはソース中で定義されています.また,アイコン(xbm形式の8バイト)も定義されています(実行例の「 ファイルやディレクトリの選択」を参照).
おもしろい点として,ディレクトリも通常のファイルと同じように扱われており,サブディレクトリを選択して実行するとこのlcdshell自身を再び呼び出すようになっています(ディレクトリは特別な"."という拡張子を持つファイルとして扱っています).
つまりディレクトリを深く降りていくと,それだけlcdshellのプロセスが生成されます.シンプルな仕組みですが,リソースが少し無駄でプロセスをkillするのが面倒という問題はあります.
lcdshellを最初に起動する場合は,"# lcdshell dir < /dev/clcdk > /dev/glcds"のようにして,プッシュスイッチを標準入力に,グラフィックLCDを標準出力に割り当てます.
(ディレクトリの場合も含めて)ファイルを実行する場合は新しくプロセスを生成してその標準入力と標準出力が受け継がれるので,ファイル実行中はプッシュスイッチの入力が標準入力から読み込めて,標準出力に書いた結果はグラフィックLCDに出力されます.
ところで,Backボタンを押すとプログラムを終了するため,サブディレクトリに対してlcdshellを実行している場合はそれが終了して親ディレクトリに戻ることになりますが,最初に実行したlcdshellでBackボタンを押すとプログラムが終了してしまい困ります.そこで最初に起動するlcdshellは,Unixのログインシェルと同じように先頭にハイフンを付けた"-lcdshell"という名前が0番目の引数に渡されるようにしておいて,Backボタンが押されてもプロセス名の先頭にハイフンがあれば終了しないようにしています.

現在のところ,(ディレクトリを除いて)下記の6種類のプラグインが用意されて,それぞれ拡張子が関連付けられています:

内容 拡張子
テキスト txt
画像 jpg, jpeg, png, bmp
音楽・ネットラジオ wav, mp3, m3u, pls
RSS rss
実行ファイル exe
スクリーンセーバー scr

これらのプラグインは,非常に簡単に用意できます.ディレクトリに対するプラグイン(=lcdshell)以外は全てシェルスクリプトとなっているので,以下では各プラグインの実際のスクリプトを説明します.

テキストファイル(txt)

#!/bin/sh
vim "+nmap <Space> <C-F>" "+nmap <BS> <C-B>" "+nmap <ESC> :q<CR>" "+nmap <CR> <C-F>" \
  "+set lines=20" "+set columns=32" -T dumb -R $*

テキストファイルは,vimをそのまま使って表示しています.
ソフトウェア: グラフィックLCD表示で説明したとおり,terminfoを利用するvimではこのグラフィックLCDをdumb端末として扱えるので,そのようにしています.
また,NextとRunボタンを前スクロール,Previousボタンを後スクロール,Backボタンをプログラムの終了になるように,キーマッピングを変更しています.

音楽・ネットラジオ(mp3, m3u, ply)

#!/bin/sh
export MPLAYER_VERBOSE=-5
ext=`echo $1 | sed -e 's/^.*\.//'`
if [ $ext = wav -o $ext = mp3 ]; then
  mplayer -msglevel demux=5:network=4 -input conf=`dirname $0`/mplayer.conf $1 |  nkf -e -u
elif [ $ext = m3u -o $ext = pls ]; then
  mplayer -msglevel demux=5:network=4 -input conf=`dirname $0`/mplayer.conf -playlist "$1" | nkf -e -u | \
    sed -u -e 's/^Clip info:.*/\f/g' -e 's/^ Title: \(.*\)/\x05\xff\x00\x00\xff\xff\xff\1/g' \
    -e 's/^ Artist: \(.*\)/\x05\xff\x00\xff\xff\xff\xff\1/g' \
    -e 's/^ Album: \(.*\)/\x05\x00\x00\xff\xff\xff\xff\1\x05\x00\x00\x00\xff\xff\xff/g'
fi

音楽の再生には,mplayerというソフトを使っています. 少しごちゃごちゃとしたスクリプトですが,wav/mp3ファイルとm3u/plsファイルでは扱いが異なるので分けて処理しています.
mplayerにいくつかオプションを渡していますが,出力されるメッセージを指定したりキーマッピングを変更しています.
キーマッピングは,Runキーが再生,Previousキーが前の曲,Nextキーが次の曲,Backキーが終了,となっています.
sedを使って,表示されるメッセージの色が項目ごとに異なるようにしています.

mplayer.confはキーマッピング設定用のファイルで,下記のような内容になっています:

ESC quit
SPACE pt_step 1
BS pt_step -1
ENTER pause

画像

#!/bin/sh
convert $1 -resize 132x162\> -size 132x162 xc:black +swap -gravity center -composite \
  -depth 8 rgb:- | `dirname $0`/rgbviewer
read -n 1

画像の表示は,まずImageMagickのconvertコマンドによりサイズが132x162で8ビットカラー深度のRGB形式に変換して, それをrgbviwerというコマンドに流しています.
rgbviewerは,標準入力から読み込んだRGB形式の画像をグラフィックLCD用のRGB565ビットマップに変換して標準出力に出力します.
最後の"read -n 1"はbashの内部コマンドで,任意のキーが1回押されるまでブロックします.

RSS

#!/bin/sh
`dirname $0`/keycheck rsstail -u `cat $1` | nkf -e -u | sed -u \
  -e 's/^Title: /\x05\xff\x00\x00\xff\xff\xff●\x05\x00\x00\x00\xff\xff\xff/g'

RSSの表示には,rsstailというソフトを使用しました.
sedを使って出力が見やすくなるようにしています.
またkeycheckというのは,その引数で指定されたプログラムを実行するとともに標準入力を監視し続け,もし何かキーが押されたら実行中のプログラムを停止するコマンドです.

実行ファイル

#!/bin/sh
`dirname $0`/keycheck $1

このプラグインは非常に単純で,単に指定されたプログラムを前述のkeycheckを経由して起動するだけです.
keycheckを使って動かしているので,プログラム実行中に任意のキーを押すことで動作を停止できます.
とりあえず,次のような2つの実行ファイル(シェルスクリプト)を用意してみました.

shutdown.exe

#!/bin/sh
echo "The system will be shutdown in"
for i in 10 9 8 7 6 5 4 3 2 1 0; do
  echo -e -n "$i seconds...     \r"
  sleep 1
done
/sbin/shutdown -h now

このスクリプトは,1秒毎に10からカウントダウンしていき,最後にマシンをシャットダウンします.
途中で何かキーを押せばキャンセルできます.

ps.exe

#!/bin/sh
while true; do
  echo -n -e "\0006\0000\0000" # move cursor to home position
  echo -n -e "\0005\0377\0000\0000\0377\0377\0377" # font color=red
  echo "%CPU %MEM USER     COMMAND"
  echo -n -e "\0005\0000\0000\0000\0377\0377\0377" # font color=black
  ps -e u | \
    awk '{if($3>99.9)$3=99.9;if($4>99.9)$4=99.9;gsub(/.*\//,"",$11);printf "%4.1f %4.1f %-8.8s %-13.13s\n", $3, $4, $1, $11;}' | \
    sort -r | head -n 18
  sleep 5
done

このスクリプトは,現在サーバー上で動いているプロセスをCPU使用率の高い順に表示します.
5秒毎に表示を更新します.

スクリーンセーバー

#!/bin/sh
`dirname $0`/keycheck `dirname $0`/ssaver "$1"

スクリーンセーバーは,前述のkeycheckを経由してssaverというコマンドを使い表示しています.
ssaverは,複数の画像を指定された時間間隔(デフォルトでは5秒)で次々に表示するプログラムです.
スクリーンセーバーのデータは,個々の表示するイメージデータを連結したデータになっています(サンプル).
具体的には,サイズが132x(162*n)の,RGB565のBMPフォーマット(GIMPで作成可能)の画像です.
keycheckを使って動かしているので,任意のキーが押されるとプログラムを終了して戻ります.

サーバー本体

サーバー本体

最後におまけで,今回ディスプレイを接続した自宅サーバーについて簡単に説明しておきます.
このサーバーはEPIA-5000というマザーボードを使ったマシンで,数ヶ月前にHDDの調子が悪くなって交換したことを除けば,これまでトラブルは一度も無く,9年ほど前に作ってから現在に至るまで停電と引越しの時を除きずっと動き続けています.
ファンレスのマシンで,電源やケース(というかただのアルミ板)は自作です.
CPUは非常に貧弱でメモリも128MBしか載せていませんが,一番の特徴は消費電力が少ないことで, 負荷をかけても20W弱と省電力なので安心して24時間動かし続けることができます.
このような常時稼働しているマシンが一台あると,firewall,named, ntpd,dhcpd,httpd,nfsd等を動かしたり,またディスクレスマシンのディスクサーバにしたり,決まった時間にラジオを録音したりWeb ページをダウンロードしたり,と色々な用途に役立ちます.

終わりに

ディスプレイのついていないサーバーにカラーグラフィックLCDをつけると色々な可能性が広がり,とても楽しいです.
今回はできるだけ柔軟で拡張性が高くなるように作ったので,もっとプラグインを追加していきたいと思います.例えば,テキスト表示のWebブラウザなどをグラフィックLCD上で動かせたらおもしろいと思っています.

今回は4つのキーを取り付けて操作できるようにしましたが,赤外線リモコンで操作できるようにしてもよかったかなと思います.
グラフィックLCDはパラレルポート経由で接続すれば,回路が簡単で転送速度も速く,またキャラクタ型デバイスドライバを実装できて良かったかもしれないと後で思いました.電源が必要だったり,パラレルポートが占有されてしまうという問題はありますが.
グラフィック液晶のバックライトはつけっぱなしにしているので寿命が少し気になります.LEDバックライトで消費電流は15mA(10V)程度と気にならないので,しばらく様子をみたいと思います.

プログラム


[戻る]
2011-05-14 ページ作成
(2011-04 製作)
T. Nakagawa