DelphiでLCDに8bitパラレルで描画する
0.はじめに
前回の記事(DelphiでFT232Hを使ってみる)でFT232Hを使えば8bitパラレルの制御ができることがわかりました。また、別の記事(DelphiでLCDに描画する)では、LCDに描画するのにI2C+4bitパラレルでは遅すぎる、ともわかりました。
そうなると、8bitパラレルで描画したらどれくらい早くなるのか、興味が出てきます。ちょうど、20桁、4行のLCDモジュールを持っていて、それについているPCF8574のモジュールは壊れています(と言うより、壊してしまいました)。意味のないPCF8574モジュールを取り外して、8bitパラレルでLCDモジュールに描画して、どの程度の速度で描けるのか、確認してみます。
1.システム構成
PCとFT232HをUSBで接続し、MPSSEで8bitパラレルで制御を行います。
LCDはネットで購入できる16ピンタイプのモジュールです。16桁2行を始めとして色々なタイプがあるようですが、今回は20桁4行を使用します。
端子説明は下記となります。
2.FT232Hの制御
FT232HはACBusとADBusの8bitx2のI/Oを持っていて、Bit Bang ModeとMPSSEの2通りの制御方法がありますが、今回必要なのは8+3(D0:7とE,RS,R/W)の制御信号です。最初はBit Bang Modeで考えていたのですが、ACBusの制御方法がわからず、ネット記事にも出来ないようなことが書かれていたので、MPSSEに変更しました。
まず、FTDI社から提供されているD2XXUnit.pasを同じディレクトリにコピーして、usesに"D2XXUnit"を追加してMPSSEを設定します。LatencyTimerとTimeoutsの設定は、おまじないのように思っています。
//Device Open
Open_USB_Device;
Reset_USB_Device;
//ModeとPortの設定
AD_Direction:=$FF; //出力に設定
AC_Direction:=$FF;
AD_Value:=$00; //0に暫定設定
AC_Value:=$00;
Set_USB_Device_LatencyTimer(16);
Set_USB_Device_Timeouts(1000,1000);
Set_USB_Device_BitMode($00,$00); //Reset
//MPSSEに設定
Res:=Set_USB_Device_BitMode(AD_Direction,$02); //$02=MPSSE mode
if Res=0 then
begin
MemoDevice.Lines.Add('MPSSEに設定');
MemoDevice.Lines.Add('AD Direction : $'+inttohex(AD_Direction));
MemoDevice.Lines.Add('AC Direction : $'+inttohex(AC_Direction));
end else
MemoDevice.Lines.Add('エラー');
ここではACBusを設定していないのですが、特に問題は無いようです・・・
3.LCDモジュールの制御
4bitによる制御と違い、8bit制御は非常に簡単です。
ADBusに書き込みたいデータやインストラクションを設定して、Eを"1"から"0"に切り替えるだけで実行されます。なお、RSはデータ時は"0"、インストラクション時は"1"に設定します。
まずイニシャライズですが、仕様書にはウエイト時間を入れながらFunction setを4回送るように記載されています。標準の8bitで使用しますので、1回だけでも問題はなさそうです。
この場合のイニシャライズにかかる時間は、TimeGetTime関数での計測で約40msecでした。
WaitTimer()を無効にするとイニシャライズ計測時間は0msecとなり、正しくイニシャライズはされません。設定時間は0でもいいので、何らかのウエイト時間は必要です。
//Function set
AD_Value:=$38;
//1回目
Write8I(AD_Value);
WaitTimer(5);
//2回目
//Write8I(AD_Value); //無効
//WaitTimer(1);
//3回目
//Write8I(AD_Value); //無効
//WaitTimer(0);
//4回目
//Write8I(AD_Value); //無効
//WaitTimer(0);
//5回目
AD_Value:=$08; //表示OFF
Write8I(AD_Value);
WaitTimer(0);
//6回目
AD_Value:=$01; //表示クリア
Write8I(AD_Value);
WaitTimer(0);
//7回目
AD_Value:=$06; //エントリーモード
Write8I(AD_Value);
WaitTimer(0);
//8回目
AD_Value:=$0F; //表示ON
Write8I(AD_Value);
別procedureのWrite8I()で、実際の書き込み動作を実行しています。
なお、書き込みたいデータは下記の3回で設定します。
AddToBuffer(設定したいBUS) → ACBus=$82、ADBus=$80
AddToBuffer(設定したいデータ)→ bit7..bit0で設定
AddToBuffer(BUSのI/O) → 初期設定から変更はないと思われる
まず、ACBusのEを"1"に設定して、次にADBusでインストラクションを設定して一旦送信します。ここで送信を実行しないと、キャラクター書き込みの際に表示漏れが起きていましたので、イニシャライズ時にも同様に送信しています。多分Eの設定時間(Min.140nsec)が短すぎるだけなのでしょうから、BF(ビジーフラグ)を読んで、という処理は行っていません。
その後Eを"0"に設定して、送信して終了です。
procedure TFrmLCD.Write8I(TX_AD:Byte);
begin
AC_Value:=$01; //E=1,RS=0
//Set High Byte
AddToBuffer($82); //ACBus
AddToBuffer(AC_Value); //E=1,RS=0
AddToBuffer(AC_Direction); //Output
//Set Low Byte
AddToBuffer($80); //ADBus
AddToBuffer(TX_AD); //
AddToBuffer(AD_Direction); //Output
//ここで一旦送信しないと、表示漏れが出ることがある
SendBytes(OutIndex); // send
OutIndex := 0;
//Set E
AddToBuffer($82); //ACBus
AddToBuffer(AC_Value-1); //E=0,RS=0
AddToBuffer(AC_Direction); //Output
//TX
SendBytes(OutIndex); // send
OutIndex := 0;
end;
イニシャライズが正常に終了すると、左上の桁がブリンキングします(分かりやすいようにブリンキングする設定としています)。
次に描画したいキャラクターのデータを送ります。
Write8I()で描画を開始したい位置を設定し、Write8C()で実際の書き込みを行っています。
//1行目
Write8I($80);
for i := 1 to length(edit1.text) do
begin
Buf:=edit1.text;
B:=ord(widechar(Buf[i]));
Write8C(B);
end;
//2行目
Write8I($C0);
for i := 1 to length(edit3.text) do
begin
Buf:=edit3.text;
B:=ord(widechar(Buf[i]));
Write8C(B);
end;
//3行目
Write8I($94);
for i := 1 to length(edit2.text) do
begin
Buf:=edit2.text;
B:=ord(widechar(Buf[i]));
Write8C(B);
end;
//4行目
Write8I($D4);
for i := 1 to length(edit4.text) do
begin
Buf:=edit4.text;
B:=ord(widechar(Buf[i]));
Write8C(B);
end;
別procedureのWrite8C()は下記となりますが、Write8I()と違うのはRS(AC1)の値だけです。
procedure TFrmLCD.Write8C(TX_AD:Byte);
begin
AC_Value:=$03; //E=1,RS=1
//Set High Byte
AddToBuffer($82); //ACBus
AddToBuffer(AC_Value); //E=1,RS=1
AddToBuffer(AC_Direction); //Output
//Set Low Byte
AddToBuffer($80); //ADBus
AddToBuffer(TX_AD); //
AddToBuffer(AD_Direction); //Output
//ここで一旦送信しないと、表示漏れが出ることがある
SendBytes(OutIndex); // send
OutIndex := 0;
//Set E
AddToBuffer($82); //ACBus
AddToBuffer(AC_Value-1); //E=0,RS=1
AddToBuffer(AC_Direction); //Output
//TX
SendBytes(OutIndex); // send
OutIndex := 0;
end;
参考までに、FTDI社を参考にしたSendBytes()とOutIndexのコードも記載しておきます。
procedure TFrmLCD.SendBytes(NumberOfBytes : integer);
var
i : integer;
begin
i := Write_USB_Device_Buffer( NumberOfBytes);
OutIndex := OutIndex - i;
end;
procedure TFrmLCD.AddToBuffer(I:integer);
begin
FT_Out_Buffer[OutIndex]:= I AND $FF;
inc(OutIndex);
end;
4.おわりに
20桁4行の80文字を描画させた時、TimeGetTime関数での計測結果はだいたい10msecでした。I2C+4bitパラレルで32文字を描画した時は420msec程度でしたので飛躍的に向上しています。
今更こういうキャラクタータイプのLCDを使う製品はないかもしれませんが、使うのであれば8bitパラレルで、となりますね。
(と言うより、PCF8574のI2C部分で時間がかかっているのでしょうね)
なお、今回使用したLCDモジュールは表示数が多いため、Dutyは1/16で使用することになります。表示させた写真を掲載していますが、これはあえて電源電圧を3.8Vくらいまで上げてコントラストを改善した写真であり、実際のコントラストは非常に悪いです。商品化にはLCDの改善や電源電圧の見直しなどが必要です。