目次

Charactor LCDC(LCD Controller)

 CPLD/FPGAを利用して、Charactor LCD(Liquid Crytal Display)Controller  を作成してみます。  キャラクタを扱うLCDのデファクトスタンダードインタフェースは  HD47780であることが殆どです。  キャラクタLCDは、14ピンをもち、信号線は4種類になります。
  1. 電源ピン Vdd GND
  2. コントラスト調整 Vo
  3. 制御ピン E RS WR
  4. データ DB0 -> DB7
 通常は、キャラクタLCDからデータを入力することは殆どない  のでWRピンを、0Vに固定し、書き込みだけできるようにします。  制御ピンがE RS の2本、データピンが8本の合計10ピンで表示  を制御できます。コントラストは、外付け可変抵抗で調整すると  して、ここでは考えません。  マイクロコンピュータから、シリアルでデータを転送し、トリガー  を与えて、データを出力するコントローラを作成します。  マイクロコンピュータから、次の3信号を出力します。  入力と出力の信号が決まったので、ブロック図を作成します。

ブロック図

 入出力信号を決めたので、シフトレジスタを用意し  データを格納します。  制御信号のうち、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ビットジョンソンカウンタを  使います。  ステートと実行する内容は、以下とします。
  1. トリガー待ち
  2. E信号をHとする
  3. ウエイト
  4. 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' ;


目次 inserted by FC2 system