目次
前
次
Charactor LCDC(LCD Controller)
CPLD/FPGAを利用して、Charactor LCD(Liquid Crytal Display)Controller
を作成してみます。
キャラクタを扱うLCDのデファクトスタンダードインタフェースは
HD47780であることが殆どです。
キャラクタLCDは、14ピンをもち、信号線は4種類になります。
- 電源ピン Vdd GND
- コントラスト調整 Vo
- 制御ピン E RS WR
- データ DB0 -> DB7
通常は、キャラクタLCDからデータを入力することは殆どない
のでWRピンを、0Vに固定し、書き込みだけできるようにします。
制御ピンがE RS の2本、データピンが8本の合計10ピンで表示
を制御できます。コントラストは、外付け可変抵抗で調整すると
して、ここでは考えません。
マイクロコンピュータから、シリアルでデータを転送し、トリガー
を与えて、データを出力するコントローラを作成します。
マイクロコンピュータから、次の3信号を出力します。
- シリアルデータ LDAT
- 転送クロック LCLK
- トリガー LTRG
入力と出力の信号が決まったので、ブロック図を作成します。
ブロック図
入出力信号を決めたので、シフトレジスタを用意し
データを格納します。
制御信号のうち、RS信号は0であるか1であるかは
マイクロコンピュータが理解しているので、データ
の前に付加して、内部シフトレジスタに転送します。
entity定義
ブロック図で外部とやりとりする信号が決まれば、entityを定義します。
entity lcdc is
Port (
-- system
nRESET : in std_logic;
CLOCK : in std_logic;
-- processor interface
LDAT : in std_logic; -- data
LCLK : in std_logic; -- latch trigger
LTRG : in std_logic; -- perform trigger
-- LCD interface
E : out std_logic;
RS : out std_logic;
POUT : out std_logic_vector(7 downto 0)-- ;
);
end lcdc;
トリガー用シフトレジスタ定義
2ビットのレジスタをカスケードに接続し
シフトレジスタを構成します。
rising_edgeを捉えて、シーケンサ用のトリガー
を生成します。
システムに与えるリセット、クロックを使います。
process( nRESET , CLOCK )
begin
if ( nRESET = '0' ) then
iSLTRG <= "00" ;
elsif rising_edge( CLOCK ) then
-- shift register
iSLTRG <= iSLTRG(0) & LTRG ;
end if;
end process;
iTRG <= '1' when ( iSLTRG = "01" ) else '0' ;
データ用シフトレジスタ定義
RSの指定とデータを含めた9ビットのレジスタを
カスケードに接続して、シフトレジスタを構成します。
クロックは、システムクロックとは別にします。
内部ブロック図から、次のように定義します。
process( nRESET , LCLK )
begin
if ( nRESET = '0' ) then
iREG <= (others => '0') ;
elsif rising_edge( LCLK ) then
iREG <= iREG(7 downto 0) & LDAT ;
end if;
end process;
シーケンサ定義
シーケンサは、トリガーが与えられたならば
一気呵成に動作するラウンドロビンとします。
ベースカウンタには、2ビットジョンソンカウンタを
使います。
ステートと実行する内容は、以下とします。
- トリガー待ち
- E信号をHとする
- ウエイト
- E信号をLとする
ウエイトは、データのセットアップ、ホールドタイム
を確保する目的で入れます。
クロックには、システムクロックを利用します。
内部ブロック図から、次のように定義します。
type stype is (S0,S1,S2,S3);
signal iCUR_STA : stype ;
process( nRESET , CLOCK )
begin
if ( nRESET = '0' ) then
iCUR_STA <= S0 ;
elsif rising_edge( CLOCK ) then
-- state machine
case iCUR_STA is
-- wait trigger
when S0 =>
if ( iTRG = '1' ) then
iCUR_STA <= S1 ;
end if ;
-- enable E(high)
when S1 =>
iCUR_STA <= S2 ;
-- wait
when S2 =>
iCUR_STA <= S3 ;
-- disable E(low)
when S3 =>
iCUR_STA <= S0 ;
-- default
when others =>
iCUR_STA <= S0 ;
end case ;
end if;
end process;
出力信号
シフトレジスタ、シーケンサを定義したので
LCDに与える信号を確定します。
E信号
E信号の状態は、シーケンサが管理しています。
シーケンサは、トリガーが入るとクロックにより
S0->S1->S2->S3と状態値を変化させます。
S0->S1->S2->S3の変化に合わせて、0->1->1->0と
出力値を変化させます。
E <= '1' when ( iCUR_STA = S1 or iCUR_STA = S2 ) else '0' ;
RS信号
ブロック図作成前の仕様検討時に、9ビットの
MSB(Most Significant Bit)を、RSに転用と
決めたので、MSBをそのまま出力します。
RS <= iREG(8) ;
データ
データ用シフトレジスタのLSBから8ビット分を
出力するだけで充分です。
POUT <= iREG(7 downto 0) ;
全ソースコード
すべてのブロックの定義ができたので
ソースコードにまとめます。
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.STD_LOGIC_ARITH.ALL;
use IEEE.STD_LOGIC_UNSIGNED.ALL;
entity lcdc is
Port (
-- system
nRESET : in std_logic;
CLOCK : in std_logic;
-- processor interface
LDAT : in std_logic; -- data
LCLK : in std_logic; -- latch trigger
LTRG : in std_logic; -- perform trigger
-- LCD interface
E : out std_logic;
RS : out std_logic;
POUT : out std_logic_vector(7 downto 0)-- ;
);
end lcdc;
architecture behavioral of lcdc is
signal iSLTRG : std_logic_vector(1 downto 0);
signal iTRG : std_logic;
signal iREG : std_logic_vector(8 downto 0);
type stype is (S0,S1,S2,S3);
signal iCUR_STA : stype ;
begin
-- system
process( nRESET , CLOCK )
begin
if ( nRESET = '0' ) then
iSLTRG <= "00" ;
elsif rising_edge( CLOCK ) then
-- shift register
iSLTRG <= iSLTRG(0) & LTRG ;
end if;
end process;
iTRG <= '1' when ( iSLTRG = "01" ) else '0' ;
-- get data from processor
process( nRESET , LCLK )
begin
if ( nRESET = '0' ) then
iREG <= (others => '0') ;
elsif rising_edge( LCLK ) then
-- shift register
iREG <= iREG(7 downto 0) & LDAT ;
end if;
end process;
-- sequencer
process( nRESET , CLOCK )
begin
if ( nRESET = '0' ) then
iCUR_STA <= S0 ;
elsif rising_edge( CLOCK ) then
-- state machine
case iCUR_STA is
-- wait trigger
when S0 =>
if ( iTRG = '1' ) then
iCUR_STA <= S1 ;
end if ;
-- enable E(high)
when S1 =>
iCUR_STA <= S2 ;
-- wait
when S2 =>
iCUR_STA <= S3 ;
-- disable E(low)
when S3 =>
iCUR_STA <= S0 ;
-- default
when others =>
iCUR_STA <= S0 ;
end case ;
end if;
end process;
E <= '1' when ( iCUR_STA = S1 or iCUR_STA = S2 ) else '0' ;
RS <= iREG(8) ;
POUT <= iREG(7 downto 0) ;
end behavioral;
ファームウエア例
CPLD/FPGAにデータを渡し、トリガーを与えるC言語の
関数を定義します。
CPLD/FPGAの内部には、9ビットのレジスタがあります。
最上位(MSB)の9ビット目には、ファンクションか
データかを1ビットで指定し、続く8ビットには
データを入れます。
データを渡したなら、転送指示トリガーを与えます。
関数名をsend_data_lcdとします。
入力パラメータは、9ビット以上なので16ビットとします。
typedef unsigned short UWORD ;
typedef unsigned char UBYTE ;
void send_data_lcd(UWORD x)
9ビットデータは、LDATのビット値を指定して
LCLKにパルスを与えます。
データを渡したなら、転送を指示するトリガーを与えます。
void send_data_lcd(UWORD x)
{
UBYTE i ;
for ( i = 0 ; i < 9 ; i++ ) {
/* impress bit value */
LDAT = OFF ;
if ( x & 0x0100 ) { LDAT = ON ; }
/* impress LCLK : H */
LCLK = ON ;
/* shift */
x <<= 1 ;
/* impress LCLK : L */
LCLK = OFF ;
}
/* impress LTRG : H */
LTRG = ON ;
/* impress LTRG : L */
LTRG = OFF ;
}
関数send_data_lcdを定義すると、LCDの初期化は
マイクロ秒単位の遅延処理関数ms_delayがあるとして
次のように定義できます。
void init_lcd(void)
{
/* initialize hardware */
ms_delay(20) ; /* 20ms */
send_data_lcd( 0x0100 | 0x30 );
ms_delay(5) ; /* 5ms */
send_data_lcd( 0x0100 | 0x30 );
ms_delay(1); /* 1ms */
send_data_lcd( 0x0100 | 0x30 );
ms_delay(1); /* 1ms */
send_data_lcd( 0x0100 | 0x20 );
ms_delay(1); /* 1ms */
/* set function */
send_data_lcd(0x28);
/*
Function
001 DL N F * *
DL(Data Length) = 0 (4bits)
N(Row) = 1 (2 row)
F(Font) = 0 (5x7)
001 0 1 0 * *
*/
send_data_lcd(0x08);
/*
Display off
0000 1 D C B
D(Display) = 0 (OFF)
C(Cursor) = 0 (OFF)
B(Blink) = 0 (OFF)
0000 1 0 0 0
*/
send_data_lcd(0x01);
/*
Clear
0000 0001
*/
ms_delay(2); /* 2ms */
send_data_lcd(0x06);
/*
Entry Mode
0000 01 I/D S
I/D(Increment/Decrement) = 1 (Increment)
S(Shift) = 0 (No shift)
0000 01 1 0
*/
ms_delay(2); /* 2ms */
send_data_lcd(0x0c);
/*
Display on
0000 1 D C B
D(Display) = 1 (ON)
C(Cursor) = 0 (OFF)
B(Blink) = 0 (OFF)
0000 1 1 0 0
*/
send_data_lcd(0x02);
/*
Cursor Home
0000 001*
*/
/* place 0 row 0 column */
send_data_lcd(0x80);
}
フレームバッファ方式文字列表示
マイコンの負担を減らすように、FPGAの中に
フレームバッファを用意し表示させてみます。
2行x16桁のキャラクタタイプLCDを利用します。
フレームバッファは、32バイトでよいので、VHDL
で配列を構成して実現します。
VHDLでは、既存のデータタイプを拡張して配列を
作ります。次のように配列を定義します。
type LFRAME is array(0 to 15) of std_logic_vector(7 downto 0);
この定義は、配列の雛形を指定しただけなので
回路中に入れるには、signalを利用します。
signal iDUPPWER : LFRAME ;
signal iDLOWER : LFRAME ;
signalの利用で、FPGA内部に配列を確保します。
初期化では、配列に格納した指定値をLCDに与えれば
よいので、ROMとして扱います。
CONSTANT LCDRST : LFRAME := LFRAME'(
X"30" , X"30" , X"30" , X"20" ,
X"28" , X"08" , X"01" , X"06" ,
X"0C" , X"02" , X"80" , X"00" ,
X"00" , X"00" , X"00" , X"00"
) ;
ROMの内容は、初期化に必要なシーケンスで
利用するデータにしてあります。
0 最短15ms待ち
1 RS=L状態で、0x30を転送
2 最短4.1ms待ち
3 RS=L状態で、0x30を転送
4 最短100us待ち
5 RS=L状態で、0x30を転送
6 RS=L状態で、0x20を転送
7 RS=L状態で、0x28を転送
8 RS=L状態で、0x08を転送
9 RS=L状態で、0x01を転送
10 最短2ms待ち
11 RS=L状態で、0x06を転送
12 最短2ms待ち
13 RS=L状態で、0x0cを転送
14 RS=L状態で、0x02を転送
15 RS=L状態で、0x80を転送
初期化には、上のシーケンスを使えばよいので
トリガーを与えて、一気呵成に動くシーケンサ
を使います。
process (nRESET,iCLK)
begin
if ( nRESET = '0' ) then
iRSTA <= 0 ; -- state
iCNT <= 0 ; -- handling counter
iPTR <= 0 ; -- pointer
iTCNT <= 0 ; -- delay counter
elsif rising_edge( iCLK ) then
case iRSTA is
-- wait trigger
when 0 => if ( iRTRG = '1' ) then
iRSTA <= 1 ;
else
iRSTA <= 0 ;
end if ;
-- set pointer and counter
when 1 => iCNT <= 0 ;
iPTR <= 0 ;
iRSTA <= 2 ;
-- ? complete
when 2 => if ( iCNT = 16 ) then
iRSTA <= 11 ;
else
iRSTA <= 3 ;
end if ;
-- branch time wait or send data
when 3 => if ( iCNT = 0 ) then
iRSTA <= 7 ;
iTCNT <= 15 ;
elsif ( iCNT = 2 ) then
iRSTA <= 7 ;
iTCNT <= 5 ;
elsif ( iCNT = 4 ) then
iRSTA <= 7 ;
iTCNT <= 1 ;
elsif ( iCNT = 10 or iCNT = 12 ) then
iRSTA <= 7 ;
iTCNT <= 2 ;
else
iRSTA <= 3 ;
end if ;
-- send data
when 4 => iRSTA <= 5 ;
iLDAT <= LCDRST(iPTR) ;
-- LCD_E : H
when 5 => iRSTA <= 6 ;
-- LCD_E : L and pointer increment
when 6 => iRSTA <= 7 ;
iPTR <= iPTR + 1 ;
-- update counter
when 7 => iRSTA <= 8 ;
-- delay
when 8 => if ( iTCNT = 0 ) then
iRSTA <= 9 ;
else
iRSTA <= 8 ;
end if ;
-- delay counter decrement
when 9 => iTCNT <= iTCNT - 1 ;
iRSTA <= 10 ;
-- handling counter increment
when 11 => iCNT <= iCNT + 1 ;
iRSTA <= 2 ;
-- return first state
when 11 => iRSTA <= 0 ;
-- default
when others =>
iRSTA <= 0 ;
end case ;
end if ;
end process ;
iLCD_E <= '1' when ( iRSTA = 5 ) else '0' ;
このシーケンサでは、時間待ちをしているので
データ転送と時間待ちに分けて動作を切替えます。
時間待ちの分解能を1msとし、1ms生成のために
最短0.5ms(2kHz)で1ステートを実行します。
iCLKを最速2kHzにします。
次は、フレームバッファの内容をLCDに転送する
シーケンスを考えます。
文字をどこから格納するのかを、指定する処理を
文字列を転送する前に入れておきます。
1行は16文字なので、1行は17回の転送処理に
なります。フレームバッファを上下で用意した
ので、文字格納位置を2回指定して対応します。
process (nRESET,CLOCK) -- CLOCK : 1MHz
begin
if ( nRESET = '0' ) then
iSTATE <= 0 ; -- state
iFCNT <= 0 ; -- handling counter
iFPTR <= 0 ; -- pointer
elsif rising_edge( CLOCK ) then
case iSTATE is
-- wait trigger
when 0 => if ( iSTRG = '1' ) then
iSTATE <= 1 ;
else
iSTATE <= 0 ;
end if ;
-- set pointer and counter
when 1 => iFCNT <= 0 ;
iFPTR <= 0 ;
iSTATE <= 2 ;
-- complete upper line
when 2 => if ( iFCNT = 17 ) then
iSTATE <= 6 ;
else
iSTATE <= 3 ;
end if ;
-- send data
when 3 => if ( iFCNT = 0 ) then
iLFDAT <= X"80" ;
else
iLFDAT <= iDUPPER(iFPTR) ;
end if ;
iSTATE <= 4 ;
-- LCD_E : H
when 4 => iSTATE <= 5 ;
-- LCD_E : L and pointer increment
when 5 => if ( iFCNT > 0 ) then
iFPTR <= iFPTR + 1 ;
end if ;
iFCNT <= iFCNT + 1 ;
iSTATE <= 2 ;
-- set pointer and counter
when 6 => iFCNT <= 0 ;
iFPTR <= 0 ;
iSTATE <= 7 ;
-- complete lower line
when 7 => if ( iFCNT = 17 ) then
iSTATE <= 11 ;
else
iSTATE <= 8 ;
end if ;
-- send data
when 8 => if ( iFCNT = 0 ) then
iLFDAT <= X"84" ;
else
iLFDAT <= iDLOWER(iFPTR) ;
end if ;
iSTATE <= 9 ;
-- LCD_E : H
when 9 => iSTATE <= 10 ;
-- LCD_E : L and pointer increment
when 10 => if ( iFCNT > 0 ) then
iFPTR <= iFPTR + 1 ;
end if ;
iFCNT <= iFCNT + 1 ;
iSTATE <= 7 ;
-- return first state
when 11 => iSTATE <= 0 ;
-- default
when others =>
iSTATE <= 0 ;
end case ;
end if ;
end process ;
iLCD_E <= '1' when ( iSTATE = 4 ) else
'1' when ( iSTATE = 9 ) else
'0' ;
iLCD_RS <= '1' when ( iFCNT > 0 ) else '0' ;
このシーケンスでは、RS信号を'L'、'H'のどちらかで
使うので、切分けにiFCNTを利用しています。
初期化とデータ転送では、LCDに必要な信号のうち
データ、RS、Eがバッティングすることがあるので
整理します。
データは、初期化か文字列表示のいずれとします。
iLCD_DAT <= iLFDAT when ( iSTATE > 0 ) else iLDAT ;
RS信号は、文字列表示のときだけ'H'に。
iLCD_RS <= '1' when ( iFCNT > 0 ) else '0' ;
E信号は、初期化と文字列表示のシーケンサの中で
特定の状態だけで'H'にします。
iLCD_E <= '1' when ( iSTATE = 4 ) else
'1' when ( iSTATE = 9 ) else
'1' when ( iRSTA = 4 ) else
'0' ;
文字列表示は、フレームバッファの内容を
定期的に出力すればよいので、100msごとに
トリガーを与えれば充分です。100msは100Hz
に相当するので、100Hzのジェネレータを定義
して使います。
process (nRESET,iCLK) -- iCLK : 2kHz
begin
if ( nRESET = '0' ) then
iSCNT <= 0 ; -- counter
elsif rising_edge( iCLK ) then
if ( iSCNT = 19 ) then
iSCNT <= 0 ; -- clear
else
iSCNT <= iSCNT + 1 ; -- increment
end if ;
end if ;
end process ;
iSTRG <= '1' when ( iSCNT = 1 ) else '0' ;
フレームバッファに文字列を設定できるようにします。
process (nRESET,CLOCK)
begin
if ( nRESET = '0' ) then
iTRG_SFT <= "000" ;
elsif rising_edge(CLOCK) then
iTRG_SFT <= iTRG_SFT(1 downto 0) & TRG ;
end if ;
end process ;
iTRG <= '1' when ( iTRG_SFT = "011" ) else '0' ;
process (nRESET,CLOCK)
begin
if ( nRESET = '0' ) then
iLSTA <= "00" ;
iLADR <= 0 ;
iLREG <= X"00" ;
elsif rising_edge(CLOCK) then
case conv_integer(iLSTA) is
-- wait trigger
when 0 => if ( iTRG = '1' ) then
iLSTA <= "01" ;
else
iLSTA <= "00" ;
end if ;
-- get address and data
when 1 => iLADR <= conv_integer( LADR ) ;
iLREG <= LREG ;
iLSTA <= "00" ;
-- store
when 3 => if ( iLADR > 15 ) then
iDLOWER( iLADR - 16 ) <= iLREG ;
else
iDUPPER( iLADR ) <= iLREG ;
end if ;
iLSTA <= "10" ;
-- return first state
when 3 => iLSTA <= "00" ;
-- default
when others =>
iLSTA <= "00" ;
end case ;
end if ;
end process ;
パワーオンで、LCDを初期化するために、次の回路を
利用します。
パワーオンリセットを、信号の立ち上がりとみなし
シーケンサを動かしLCD初期化信号を生成します。
process (nRESET,iCLK) -- iCLK : 2kHz
begin
if ( nRESET = '0' ) then
iRTRG_SFT <= "000" ;
elsif rising_edge(iCLK) then
iRTRG_SFT <= iRTRG_SFT(1 downto 0) & RTRG ;
end if ;
end process ;
iRTRG <= '1' when ( iRTRG_SFT = "011" ) else '0' ;
目次
前
次