カラーグラフィックLCDを使用したLinux用小型端末


はじめに

以前,LinuxマシンにUSBでカラーグラフィックスLCDを接続して音楽の再生やPCのシャットダウンを操作できるようにしましたが,最近自宅サーバをリプレースしたので,もう少し小型でシンプルで見栄えのよいものを新たに作ってみました.
前回はテキストの表示と画像の表示のどちらもできるようにして,LinuxのFIFOに読み書きすることでLCDへの出力やスイッチからの入力を行いましたが,今回はより汎用的に擬似端末とterminfoを使い端末として扱えるようにしました.
作成したソフトウェアは2つあり,1つはUSBで接続したLCDとスイッチをLinuxから仮想端末としてアクセスできるようにするためのデーモンで,もう1つは(この仮想端末以外でも使える汎用の)音楽再生などをするためのファイルマネージャー(ファイラー)です.

ハードウェア

前回は操作用のキーとグラフィック液晶ディスプレイが別々のシステムに分かれていましたが,今回は1つのシステムにまとめました.
グラフィックLCDには昔買ったNOKIA3300-LCDという132x132ピクセルのものを使いました.これは前回使ったLQ020B8UB02と比較して,3.3Vとバックライトの7~9Vのみで動作し,また信号の数も少ないので扱いやすいです.しかしSTN端末で発色はそれほどよくありませんでした.前回作ったディスプレイは,結局ほとんど音楽再生のために使ったので,今回はグラフィック表示をすることは考えずに主にテキストの表示を目的として割り切って作りました.
回路図は下図のとおりです.

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

PCとはUSBで接続しますが,インターフェースにはFT245RLをBitBangモードで使いました.このICは3.3Vのレギュレータを内蔵しているので,LCDの電源はそこから供給します.
このグラフィックLCDのNOKIA3300-LCDは,コントローラーICにS1D15G10を使用し,インターフェースは9ビットシリアルモードに固定されています.そのため,前回LQ020B8UB02を使用した際は信号の数が多かった(8ビットのデータバス+制御信号等)ためフリップフロップICを入れて信号をラッチしましたが,今回は必要な信号が3本(SDATA, CS, SCK)と少ないためFT245RLに直結しています.4つのプッシュスイッチもFT245RLに直結しています.ハードウェアは非常にシンプルになりました.
ただし,シリアル通信のためデータの転送速度はあまり速くありません.BitBangモードでは転送速度が1,000,000bytes/secですが,このLCDは画面サイズが132x132ピクセルで,1ピクセルあたり1バイトの色情報(パレット番号)を送る必要があり,1バイトのデータを転送するにはコマンド・データ指定用の1ビットを付与した9ビットのデータを送る必要があり,1ビットのデータを送るにはクロック信号(SCK)のエッジを作るためにクロックをLとHに変化させて2回出力を行う必要があるため,結局1画面を書き換えるのに要する時間はおよそ132*132*9*2/1000000=0.314秒かかることになります(実測では10回の平均値で0.309秒でした).つまり1秒間に3回程度しか画面を更新できないので,テキストをスクロールして表示させたりするのには不向きですが,このLCDは画面の一部を部分的に更新することもできるので,カーソルの移動などは問題なく描画できます.FT245RLとLCDの間にAVRなどを入れて一度のデータ出力で0.5ビット以上のデータを送るようにすれば描画速度は上げられますが,今回はできるだけ回路をシンプルにすることを優先しました.

LCDのバックライトには7~9Vが必要ですが,スイッチトキャパシタ式の昇圧DC-DCコンバータを作り5Vから生成しました.
超小型AVRのATtiny10を使い100kHzの矩形波を出力しています.単純なアセンブラのファームウェアです.マイクロコントローラらしい使い方ではありませんが,小さなサイズに収めることができました.

DC-DCモジュールの配線図 DC-DCモジュールの配線図 DC-DCモジュール DC-DCモジュール

ケースはアクリル板を使い自作しました.正面と背面は1.5mmのアクリル板で,側面は4mmのアクリル角材を2つ重ねています.
透明のアクリル板でケースを作ると,アクリル用接着剤を使い簡単に接合することができて,LCDなどは窓を開けなくてもそのまま見ることができて,自由なサイズのケースが作れるのでとても便利です.

配線後の様子 配線後の様子 ケースに収めて完成 ケースに収めて完成

ソフトウェア(擬似端末デーモン)

このソフトウェアは,FTDIのドライバを使用してFT245RLに接続されたグラフィックLCDとスイッチを制御します.Linux上の他のプロセスからは,/dev/pts/x(xは実行時の環境により決まる値)のような擬似端末としてLCDやスイッチにアクセスすることができるようになります.例えば,"echo foo > /dev/pts/x"などとすると"foo"という文字列がLCD上に出力され,"cat /dev/pts/x"などとするとスイッチを押した場合に対応する文字が出力されます.

スイッチは,デフォルトでは4つあるキーが左からそれぞれ"h","j","k","l"を出力することになっていますが,特別なエスケープシーケンスを送ることで任意の文字にキーマッピングを変更できます.

この擬似端末は,カーソル移動や文字色の変更などもエスケープシーケンスで行うことができます.標準的なANSIのエスケープシーケンスなどを使うと状態遷移の扱いが面倒になるため,独自のシンプルなものを定義しました.terminfoのデータを定義したので,terminfoやcursesなどを使用する一般的なテキストベースのアプリケーション(viやemacsなど)はこの擬似端末上で動かすことができます.

この擬似端末は日本語はUTF-8で文字コードを送れば表示できます(このプログラム内部ではiconvライブラリでEUC-JPに変換して処理しています).
また,対応しているエスケープシーケンスは下記の表のとおりです.
色を指定する際のカラー番号は0=黒,1=赤,2=緑,3=黄,4=青,5=マゼンタ,6=シアン,7=白です.

エスケープシーケンス 機能
\x01 x カーソルのx座標を(x - 32)に設定.
\x02 y カーソルのy座標を(y - 32)に設定.
\x03 c 文字色を(c - 32)に設定.
\x04 c 背景色を(c - 32)に設定.
\x11 k 1番目のキーが押された時に送信する文字をkに設定(デフォルトは'h').
\x12 k 2番目のキーが押された時に送信する文字をkに設定(デフォルトは'j').
\x13 k 3番目のキーが押された時に送信する文字をkに設定(デフォルトは'k').
\x14 k 4番目のキーが押された時に送信する文字をkに設定(デフォルトは'l').
\x08 (^H) 直前に1文字を消す(バックスペース).
\x0a (^J) 1行下に移動する(ラインフィード).
\x0b (^K) カーソル位置から行末までを削除する.
\x0d (^M) 行頭へ移動する(キャリッジリターン).
\x0e カーソル位置から最終行の行末までを削除する.

引数を持つエスケープシーケンスでは32を加えた値を渡すようになっていますが,これはterminfoで\x00を出力すると\x80が出力されたり,\x0aを出力しようとすると\x0d+0x0aが出力されたりするので,それらを避けるために文字が割り当てられたコードを出力するようにするためです.
前回はビットマップを転送するエスケープシーケンスを定義しましたが,今回は使う予定がなかったので定義していません.今回はSTN液晶であり,また256色パレットを使用していて表現力が少ないので,写真などのグラフィック表示はあまり実用的ではないと思います.

このプログラムはキー入力とLCD出力の両方の処理を行わなくてはなりませんが,下記のようなロジックで動作しています.簡単に言えば,キー入力のセンスやキーリピートの処理は1秒間に30回行われて,仮想端末へ書き込まれた情報があるとVRAMがすぐに更新されます.VRAMの内容を実際にグラフィックLCDに転送するのは,仮想端末への入力を全て処理した後であり,1行毎に更新します(変更があった行しか更新しません).1行ごとに更新するのは,キーセンスの割り込みを妨害しないためと,また仮想端末への入力が行われた場合に更新作業よりも優先してVRAMの更新を行うためです.

interruption_30Hz {
  if (どれかキーが押されているかキーリピートが発生) {
    キーに対応した文字コードを送信
  }
}
main {
  forever {
    if (入力バッファが空ではない) {
      バッファから1文字取り出して,エスケープシーケンスの処理をしてVRAMを更新
      continue
    }
    仮想端末への入力があれば入力バッファに蓄積
    if (入力バッファが空ではない) {
      continue
    }
    if (VRAMの内容がLCDに反映されていない行がある) {
      VRAMの内容をLCDに送信(1行分のみ)
      continue
    }
    仮想端末への入力があれば入力バッファに蓄積しそれまでブロックする
  }
}

このプログラム(lcdtermd)は,"make"と"make install"をした後に,"update-rc.d lcdtermd defaults"とすればブート時に自動的に起動するようになります.それをやめたい場合は"update-rc.d lcdtermd remove"とします.
何も引数を付けずにこのプログラムを立ち上げると,LCDとキーがどの仮想端末に接続されたか(例えば/dev/pts/3)を表示します.引数を付けて起動した場合は,標準入力・標準出力・エラー出力をその仮想端末につないだプロセスを新たに生成し,指定された引数はコマンド名とそのコマンドのための引数であると解釈して,そのプロセスで実行します.
以下の画像は,"env TERMINFO=/usr/local/lib/lcdfm/terminfo TERM=lcdterm vi"などとしてviやemacsを起動した場合の例です.

viの例 viの例 emacsの例 emacsの例

ソフトウェア(ファイルマネージャ)

前回は音楽再生などに使用するビジュアルシェル(ファイルマネージャ)は自作しましたが,今回は面倒なのでvifmというファイルマネージャを使おうと思っていました.今回わざわざ仮想端末とterminfoを使ったのも,vifmを使うためでした.しかしながら,vifmは日本語のファイル名への対応が不十分で,またカスタマイズできる範囲も限られていたため,結局自作することにしました.

今回はcurses (ncursesw)を使用したので,この擬似端末専用のソフトということではなく,xtermやteratermなどの一般的な端末ならばどの端末でも動作します.
キー操作は,'k'と'j'でカーソルを上下に移動し,'h'で親ディレクトリに移動し,'l'でカーソルで指定されたディレクトリに移動するかファイルを実行します.
このプログラムは,

$ lcdfm <configuration file> <root directory>

というように2つの引数を渡して起動します.
<root directory>はこのファイルマネージャにとってルートとなるディレクトリを指定します(そこから親のディレクトリへは移動できません).
<configuration file>はどのような拡張子を持つファイルに対してどのプログラムを起動するかを指定する設定ファイルで,中身は次のようになっています:

txt	/usr/bin/view
m3u	/usr/local/lib/lcdfm/m3uplayer.sh
pls	/usr/local/lib/lcdfm/m3uplayer.sh
url	/usr/local/lib/lcdfm/urlplayer.sh
vlc	/usr/local/lib/lcdfm/vlcplayer.sh

拡張子と起動するプログラムがタブ区切りで入っています.プログラムは,選択されたファイルを引数にして起動されます. 

実行例  実行例


[戻る]
(2014-01 製作)
2014-02-22 ページ作成
2015-07-18 lcdfm.tgzを更新
T. Nakagawa