• We recently switched our forum platform. If you experience any issues please email support@crystalfontz.com

CFAH2002A-RMC-JP: VHDL + Spartan3e board problem

im looking right at the manual and the last command is: 00000111. bit<1> is I/D and bit<0> is SH.
Right, but why would you want to shift the entire display each time a character is entered? More likely you want to I/D (increment/decrement) the cursor position, so you'd want 000001x0.
Looking for additional LCD resources? Check out our LCD blog for the latest developments in LCD technology.
 

defenseman

New member
ok ok. wait a minute. it seems like im getting something. back the blocks are very dark. and i can barely see character.

i see a "<-" and then a blinking cursor. but again, its very hard to see because the character block is already dark.


EDIT. ok ok!!! adjusted my POT and now im VERY HAPPY. woohoo.

thanks alot cosmicvoid. id buy you a beer but your prob not in MA are you. haha.
 
I think you need to change your final mode command from 0x3C (00111100) to 0x38 (00111000), else you are selecting a nonexistant font.
 

jshamlet

New member
Having just recently written a VHDL module for the Altera DE2 board, I can give you a couple of pointers.

The first is, don't bother with reads. Yes, they can speed things up, but not by enough to make a difference. If you only write, you can get away with just holding E high for 250ns, and then holding it low for at least another 250ns. The total cycle time must be at least 500ns. Normally, this isn't a problem - as you have to wait for the previous command to finish.

The second tip is to use a FIFO. I wrote a simple FIFO for this model, but the coregen models will work fine.

Here is my working LCD controller code. Note, I haven't included the FIFO wrapper or FIFO models, but those should be fairly trivial. I can send you the rest of the model if you need it, though.

Code:
-- VHDL Units :  Char_LCD
-- Description:  Automates access to a character LCD
-- Notes      :  Delay counter is less than 16-bits as long as
--            :  Sys_Freq < 65.535 MHz
--            :
-- Registers  :  Addr  |   Bits  | Description
--            :--------+---------+----------------------------------------------
--            :  0x0 - |aaaa aaaa| Write DATA to FIFO      (no read access)
--            :  0x1 - |aaaa aaaa| Write COMMANDS to FIFO  (no read access)
--            :  0x2 - |---- dcba| Flushes FIFO on writes, reads FIFO flags
--            :        |         |  a = FIFO Empty flag
--            :        |         |  b = FIFO Almost Empty flag
--            :        |         |  c = FIFO Almost Full flag
--            :        |         |  d = FIFO Full flag
--            :  0x3 - |---- --ba| LCD power & backlight control
--            :        |         |  a = LCD Power / Reset (write to 1 to reset)
--            :        |         |  b = LCD Backlight control (1 = on)
--            :
-- LCD Notes  : Function Set : "0 0 1 DL | N F x x"
--            : DL = Interface width. 1:8-bit, 0:4-bit   (default is 8-bit)
--            : N  = Display lines    1:2-line, 0:1-line (default is 1-line)
--            : F  = Font type        1:5x11, 0:5x8      (default is 5x8)
--            : Display Control : "0 0 0 0 | 1 D C B"
--            : D  = Display on/off   1:on, 0:off        (default is off)
--            : C  = Cursor on/off    1:on, 0:off        (default is off)
--            : B  = Blinking on/off  1:on, 0:off        (default is off)
--            : Entry Mode : "0 0 0 0 | 0 1 I/D S"
--            : I/D= Increment Addr   1:inc by 1         (default is inc by 1)
--            : S  = Display shift    1:on, 0:off        (default is off)

-- Revision History
-- Author          Date     Change
------------------ -------- ---------------------------------------------------
-- Seth Henry      04/13/06 Design Start

library ieee;
use ieee.std_logic_1164.all;
use ieee.std_logic_unsigned.all;
use ieee.std_logic_arith.all;

entity Char_LCD is
  generic(
    Sys_Freq                 : real := 50000000.0;
    Init_Wait_Time           : std_logic_vector(3 downto 0) := x"8";--(x*4.1ms)
    IF_Width_4_8n            : integer range 0 to 1 := 0; -- default is 8-bit
    Lines_2_1n               : std_logic := '1'; -- Default is 2 lines
    Font_Sel                 : std_logic := '0'; -- Default is 5x8
    Flush_FIFO_on_LCD_Reset  : std_logic := '1'; -- Default is enabled
    Vendor_Select            : integer := 2 ); -- Default is "Simple_FIFO"
  port(
    Clock                    : in  std_logic;
    Reset_n                  : in  std_logic;
    -- Local I/O
    Addr                     : in  std_logic_vector(1 downto 0);
    Wr_Data                  : in  std_logic_vector(7 downto 0);
    Wr_En                    : in  std_logic;
    Wr_Pending               : out std_logic;
    Rd_Data                  : out std_logic_vector(7 downto 0);
    Rd_En                    : in  std_logic;
    -- LCD I/O
    LCD_DQ                   : out std_logic_vector(7 downto 4*IF_Width_4_8n);
    LCD_RW                   : out std_logic;
    LCD_EN                   : out std_logic;
    LCD_RS                   : out std_logic;
    LCD_ON                   : out std_logic;
    LCD_BL                   : out std_logic );
end entity;

architecture rtl of Char_LCD is
  constant LOW               : std_logic := '0';
  constant HIGH              : std_logic := '1';

  -- The ceil_log2 function returns the minimum register width required to
  --  hold the supplied integer.
  function ceil_log2 (x : in natural) return natural is
    variable retval          : natural;
  begin
    retval                   := 1;
    while 2**retval < x loop
      retval                 := retval + 1;
    end loop;
    return retval;
  end ceil_log2;

  -- Module control registers
  signal LCD_Pwr_D, LCD_Pwr  : std_logic;
  signal LCD_Blite_D, LCD_Blite: std_logic;
  signal LCD_Init_D, LCD_Init: std_logic;

  component Char_LCD_FIFO_wrapper is
  generic(
    Vendor_Select            : integer := Vendor_Select );
  port(
    Clock                    : in  std_logic;
    Reset_n                  : in  std_logic;
    Sync_Clr                 : in  std_logic;
    -- Data I/F
    Wr_Data                  : in  std_logic_vector(8 downto 0);
    Wr_En                    : in  std_logic;
    Rd_Data                  : out std_logic_vector(8 downto 0);
    Rd_En                    : in  std_logic;
    -- Flags
    Empty                    : out std_logic;
    Almost_Empty             : out std_logic;
    Almost_Full              : out std_logic;
    Full                     : out std_logic );
  end component;

  signal FIFO_Wr_Data        : std_logic_vector(8 downto 0);
  signal FIFO_Rd_Data        : std_logic_vector(8 downto 0);
  signal FIFO_Wr_En          : std_logic;
  signal FIFO_Rd_En          : std_logic;
  signal FIFO_Flush          : std_logic;

  signal FIFO_Empty          : std_logic;
  signal FIFO_Almost_Empty   : std_logic;
  signal FIFO_Almost_Full    : std_logic;
  signal FIFO_Full           : std_logic;

  signal FIFO_Sync_Clr       : std_logic;

  -- Delay timers
  signal Tick2               : std_logic; -- divide by two to shrink counter

  constant TICK_4_1MS        : integer := integer(Sys_Freq * 0.00205);
  constant WIDTH             : integer := ceil_log2(TICK_4_1MS);
  constant DLY_4_1MS         : std_logic_vector(WIDTH-1 downto 0) :=
                               conv_std_logic_vector(TICK_4_1MS, WIDTH);

  constant TICK_1_6MS        : integer := integer(Sys_Freq * 0.0008);
  constant DLY_1_6MS         : std_logic_vector(WIDTH-1 downto 0) :=
                               conv_std_logic_vector(TICK_1_6MS, WIDTH);

  constant TICK_100US        : integer := integer(Sys_Freq * 0.00005);
  constant DLY_100US         : std_logic_vector(WIDTH-1 downto 0) :=
                               conv_std_logic_vector(TICK_100US, WIDTH);

  constant TICK_50US         : integer := integer(Sys_Freq * 0.000025);
  constant DLY_50US          : std_logic_vector(WIDTH-1 downto 0) :=
                               conv_std_logic_vector(TICK_50US, WIDTH);

  -- This value is so small that it can get hit by rounding errors fast
  -- make sure to double check LCD_EN timing if the sys_freq changes by much
  constant TICK_230NS        : integer := integer(Sys_Freq * 0.000000125);
  constant DLY_230NS         : std_logic_vector(WIDTH-1 downto 0) :=
                               conv_std_logic_vector(TICK_230NS, WIDTH);

  -- We need at least 20ns after setting RS and RW before issuing E for writes
  -- Since our clock can vary up to 200MHz, this allows us to compensate by
  --  adding additional idle states in the state machine
  constant IDLE_STATES       : integer := integer(Sys_Freq * 0.00000002);
  constant IDLE_BITS         : integer := ceil_log2(IDLE_STATES);
  constant IDLE_DELAY        : std_logic_vector(IDLE_BITS - 1 downto 0) :=
                               conv_std_logic_vector(IDLE_STATES, IDLE_BITS);

  signal Idle_Ctr_D          : std_logic_vector(IDLE_BITS - 1 downto 0);
  signal Idle_Ctr            : std_logic_vector(IDLE_BITS - 1 downto 0);

  signal Dly_Trig            : std_logic;
  signal Dly_Trg_Q,Dly_Trg_QQ: std_logic;
  signal Dly_Start           : std_logic;
  signal Dly_Time            : std_logic_vector(WIDTH-1 downto 0);
  signal Timer_D, Timer      : std_logic_vector(WIDTH-1 downto 0);
  signal Timer_Int_D         : std_logic;
  signal Timer_Int           : std_logic;

  -- State Logic
  type LCD_STATES is (
    RESET, WAIT_POWER_GOOD, WAIT_4_1MS, WAIT_100US, INIT_FUNC_4BIT, INIT_FUNC,
    INIT_DISPLAY, IDLE, FIFO_READ, LCD_WRITE, LCD_SETUP, LCD_ENABLE, LCD_LOWER,
    LCD_WAIT );

  signal State_D, State      : LCD_STATES;
  signal NextState_D         : LCD_STATES;
  signal NextState           : LCD_STATES;

  signal LCD_IF_Type         : std_logic;
  signal Force_8bit_D        : std_logic;
  signal Force_8bit          : std_logic;

  signal Gen_Ctr_D, Gen_Ctr  : std_logic_vector(3 downto 0);

  -- Data path
  signal LCD_Data_D, LCD_Data: std_logic_vector(7 downto 0);
  signal LCD_Cmd_D, LCD_Cmd  : std_logic;

  -- 4-bit only signals
  signal LCD_Byte_Sel_D      : std_logic;
  signal LCD_Byte_Sel        : std_logic;

begin

  -- Control I/O
  LCD_BL                     <= LCD_Blite;
  LCD_ON                     <= LCD_Pwr;
  LCD_RW                     <= LOW; -- Permanently set to "write"
  LCD_RS                     <= LCD_Cmd;

Gen_8bit_fn: if( IF_Width_4_8n = 0 )generate
  LCD_DQ                     <= LCD_Data;
  LCD_IF_Type                <= HIGH;
end generate Gen_8bit_fn;

Gen_4bit_fn: if( IF_Width_4_8n = 1 )generate
  LCD_Byte_Mux: process(LCD_Byte_Sel, LCD_Data)
  begin
    LCD_DQ                   <= LCD_Data(7 downto 4);
    if( LCD_Byte_Sel = HIGH )then
      LCD_DQ                 <= LCD_Data(3 downto 0);
    end if;
  end process;
  LCD_IF_Type                <= LOW;
end generate Gen_4bit_fn;

  -- Misc I/O
  Wr_Pending                 <= FIFO_Full;

  Write_proc: process( Addr, Wr_Data, Wr_En, LCD_Blite,
                       LCD_Pwr, FIFO_Sync_Clr)
  begin
    LCD_Pwr_D                <= LCD_Pwr;
    LCD_Init_D               <= LOW;
    LCD_Blite_D              <= LCD_Blite;
    FIFO_Wr_En               <= LOW;
    FIFO_Flush               <= FIFO_Sync_Clr;
    -- Flip the LSB of the address when writing to the FIFO. This allows the
    --  address 0 to be data, and 1 to be commands. 
    FIFO_Wr_Data             <= (not Addr(0)) & Wr_Data;

    if( Wr_En = HIGH )then
      case Addr is
        when "00" | "01" =>
          FIFO_Wr_En         <= HIGH;
        when "10" =>
          FIFO_Flush         <= HIGH;
        when others =>
          LCD_Pwr_D          <= Wr_Data(0);
          LCD_Init_D         <= Wr_Data(0); -- Trigger LCD initialization
          LCD_Blite_D        <= Wr_Data(1);
      end case;
    end if;
  end process;

  Read_proc: process( Addr, Rd_En, FIFO_Empty, FIFO_Almost_Empty,
                      FIFO_Almost_Full, FIFO_Full, LCD_Blite, LCD_Pwr )
  begin
    Rd_Data                  <= (others => LOW);
    -- Only the upper two registers are readable
    if( Rd_En = HIGH and Addr(1) = HIGH )then
      Rd_Data                <= "0000" & FIFO_Full & FIFO_Almost_Full &
                                FIFO_Almost_Empty & FIFO_Empty;
      if( Addr(0) = HIGH )then
        Rd_Data              <= "000000" & LCD_Pwr & LCD_Blite;
      end if;
    end if;
  end process;

  U_FIFO : Char_LCD_FIFO_wrapper
  port map(
    Clock                    => Clock,
    Reset_n                  => Reset_n,
    Sync_Clr                 => FIFO_Flush,
    Wr_Data                  => FIFO_Wr_Data,
    Wr_En                    => FIFO_Wr_En,
    Rd_Data                  => FIFO_Rd_Data,
    Rd_En                    => FIFO_Rd_En,
    Empty                    => FIFO_Empty,
    Almost_Empty             => FIFO_Almost_Empty,
    Almost_Full              => FIFO_Almost_Full,
    Full                     => FIFO_Full );

  Delay_Proc: process( Dly_Time, Dly_Start, Timer, Tick2 )
  begin
    Timer_D                  <= Timer;
    if( Dly_Start = HIGH )then
      Timer_D                <= Dly_Time;
    elsif( Timer > 0 and Tick2 = HIGH )then
      Timer_D                <= Timer - 1;
    end if;    

    Timer_Int_D              <= LOW;
    if( Timer = 1 and Tick2 = HIGH )then
      Timer_Int_D            <= HIGH;
    end if;
  end process;

  State_Logic: process( State, NextState, LCD_Init, LCD_Data, LCD_Cmd,
                        Timer_Int, LCD_IF_Type, Force_8bit, Idle_Ctr,
                        FIFO_Empty, FIFO_Rd_Data, LCD_Byte_Sel, LCD_Pwr,
                        Gen_Ctr )
  begin
    State_D                  <= State;
    NextState_D              <= NextState;
    Force_8bit_D             <= Force_8bit;
    Gen_Ctr_D                <= Gen_Ctr;

    Dly_Trig                 <= LOW;
    Dly_Time                 <= DLY_4_1MS;
    Idle_Ctr_D               <= Idle_Ctr;

    FIFO_Rd_En               <= LOW;
    FIFO_Sync_Clr            <= LOW;

    LCD_Data_D               <= LCD_Data;
    LCD_Cmd_D                <= LCD_Cmd;
    LCD_Byte_Sel_D           <= LCD_Byte_Sel;

    LCD_EN                   <= LOW;

    case State is
      when RESET =>
        Force_8bit_D         <= HIGH; -- both 4 & 8-bit modes start out as 8-bit
        FIFO_Sync_Clr        <= Flush_FIFO_on_LCD_Reset;
        Gen_Ctr_D            <= Init_Wait_Time;
        LCD_Cmd_D            <= LOW; -- Indicate a command
        LCD_Data_D           <= "001" & LCD_IF_Type &
                                Lines_2_1n & Font_Sel & "00";
        State_D              <= WAIT_POWER_GOOD;

      when WAIT_POWER_GOOD =>
        NextState_D          <= WAIT_4_1MS;
        Dly_Trig             <= HIGH; -- Start the delay timer
        Dly_Time             <= DLY_4_1MS;
        if( Timer_Int = HIGH )then
          Dly_Trig           <= LOW; -- Give the trigger a rest for a clock
          Gen_Ctr_D          <= Gen_Ctr - 1;
          if( Gen_Ctr = 0 )then
            State_D          <= LCD_WRITE;
          end if;
        end if;

      when WAIT_4_1MS =>
        NextState_D          <= WAIT_100US;
        Dly_Trig             <= HIGH; -- Start the delay timer
        Dly_Time             <= DLY_4_1MS; -- with a 4.1ms
        if( Timer_Int = HIGH )then
          Dly_Trig           <= LOW; -- Give the trigger a rest for a clock
          State_D            <= LCD_WRITE;
        end if;

      when WAIT_100US =>
        NextState_D          <= INIT_FUNC;
        if( LCD_IF_Type = LOW )then
          NextState_D        <= INIT_FUNC_4BIT;
        end if;
        Dly_Trig             <= HIGH; -- Start the delay timer
        Dly_Time             <= DLY_100US; -- with a 100us
        if( Timer_Int = HIGH )then
          Dly_Trig           <= LOW; -- Give the trigger a rest for a clock
          State_D            <= LCD_WRITE;
        end if;

      when INIT_FUNC_4BIT =>
        NextState_D          <= INIT_FUNC;
        Dly_Trig             <= HIGH; -- Start the delay timer
        Dly_Time             <= DLY_100US; -- with a 100us 
        if( Timer_Int = HIGH )then
          Dly_Trig           <= LOW; -- Give the trigger a rest for a clock
          State_D            <= LCD_WRITE;
        end if;

      when INIT_FUNC =>
        Force_8bit_D         <= LOW; -- Begin normal 4/8-bit operations now
        NextState_D          <= INIT_DISPLAY;
        Gen_Ctr_D            <= x"4"; -- There are four init bytes to send
        Dly_Trig             <= HIGH; -- Start the delay timer
        Dly_Time             <= DLY_50US; -- with a 50us 
        if( Timer_Int = HIGH )then
          Dly_Trig           <= LOW; -- Give the trigger a rest for a clock
          State_D            <= LCD_WRITE;
        end if;

      when INIT_DISPLAY =>
        case Gen_Ctr is
          when x"4" =>
            LCD_Data_D       <= x"08";
          when x"3" =>
            LCD_Data_D       <= x"01";
          when x"2" =>
            LCD_Data_D       <= x"06";
          when x"1" =>
            LCD_Data_D       <= x"0C";
          when x"0" =>
            LCD_Data_D       <= x"00";
          when others =>
            null;
        end case;
        Gen_Ctr_D            <= Gen_Ctr - 1;
        if( Gen_Ctr = 0 )then
          NextState_D        <= IDLE;
        end if;
        State_D              <= LCD_WRITE;

      when IDLE =>
        if( FIFO_Empty = LOW )then
          FIFO_Rd_En         <= HIGH;
          State_D            <= FIFO_READ;
        end if;

      when FIFO_READ =>
        LCD_Cmd_D            <= FIFO_Rd_Data(8);
        LCD_Data_D           <= FIFO_Rd_Data(7 downto 0);
        NextState_D          <= IDLE;
        State_D              <= LCD_WRITE;

      when LCD_WRITE =>
        -- Most of the time, the LCD is driving the bus - so we need to give it
        --  a clock to stop driving.
        LCD_Byte_Sel_D       <= LOW;
        State_D              <= LCD_ENABLE;
        if( IDLE_STATES /= 0 )then
          Idle_Ctr_D         <= IDLE_DELAY - 1;
          State_D            <= LCD_SETUP;
        end if;

      -- For clock frequencies > 50MHz, additional setup time is required
      when LCD_SETUP =>
        Idle_Ctr_D           <= Idle_Ctr - 1;
        if( Idle_Ctr = 0 )then
          State_D            <= LCD_ENABLE;
        end if;

      when LCD_ENABLE =>
        LCD_EN               <= HIGH; -- Drive the EN line to latch the data
        Dly_Trig             <= HIGH; -- Start the delay timer
        Dly_Time             <= DLY_230NS; -- with a 100ms
        if( Timer_Int = HIGH or DLY_230NS = 0 )then
          Dly_Trig           <= LOW; -- Give the trigger a rest for a clock
          State_D            <= LCD_LOWER;
          if( (LCD_IF_Type or Force_8bit) = HIGH )then
            State_D          <= LCD_WAIT;
          end if;
        end if;

      when LCD_LOWER =>
        if( LCD_Byte_Sel = HIGH )then
          State_D            <= LCD_WAIT;
        else
          LCD_Byte_Sel_D     <= HIGH;
          Dly_Trig           <= HIGH; -- Start the delay timer
          Dly_Time           <= DLY_50US; -- with a 50us
          if( Timer_Int = HIGH )then
            Dly_Trig         <= LOW; -- Give the trigger a rest for a clock
            State_D          <= LCD_WRITE;
          end if;
        end if;

      when LCD_WAIT =>
        Dly_Trig           <= not Force_8bit;
        Dly_Time           <= DLY_50US;
        if( LCD_Cmd = LOW and LCD_Data < 4 )then
          Dly_Time         <= DLY_1_6MS;
        end if;

        if( Timer_Int = HIGH or Force_8bit = HIGH )then
          State_D          <= NextState;
        end if;


      when others =>
        null;
    end case;
    -- If the LCD isn't powered, there is little reason to run this SM
    if( LCD_Pwr = LOW )then
      State_D                <= IDLE;
    -- Force a reset when the LCD power bit is written high
    elsif( LCD_Init = HIGH )then
      State_D                <= RESET;
    end if;  
  end process;

  S_Regs: process( Clock, Reset_n )
  begin
    if( Reset_n = LOW )then
      LCD_Pwr                <= LOW; -- Default power off
      LCD_Init               <= LOW;
      LCD_Blite              <= LOW; -- Default backlight off
      -- Delay timer
      Tick2                  <= LOW;
      Timer                  <= (others => LOW);
      Dly_Trg_Q              <= LOW;
      Dly_Trg_QQ             <= LOW;
      Dly_Start              <= LOW;
      Idle_Ctr               <= IDLE_DELAY;
      Timer_Int              <= LOW;
      -- State logic
      State                  <= IDLE;
      NextState              <= IDLE;
      Force_8bit             <= LOW;
      Gen_Ctr                <= (others => LOW);
      -- Data path
      LCD_Data               <= (others => LOW);
      LCD_Cmd                <= LOW;
      LCD_Byte_Sel           <= LOW;
    elsif( rising_edge(Clock) )then
      LCD_Pwr                <= LCD_Pwr_D;
      LCD_Init               <= LCD_Init_D;
      LCD_Blite              <= LCD_Blite_D;
      -- Delay timer
      Tick2                  <= not Tick2;
      Timer                  <= Timer_D;
      Dly_Trg_Q              <= Dly_Trig;
      Dly_Trg_QQ             <= Dly_Trg_Q;
      Dly_Start              <= Dly_Trg_Q and (not Dly_Trg_QQ);
      Idle_Ctr               <= Idle_Ctr_D;
      Timer_Int              <= Timer_Int_D;
      -- State logic
      State                  <= State_D;
      NextState              <= NextState_D;
      Force_8bit             <= Force_8bit_D;
      Gen_Ctr                <= Gen_Ctr_D;
      -- Data path
      LCD_Data               <= LCD_Data_D;
      LCD_Cmd                <= LCD_Cmd_D;
      LCD_Byte_Sel           <= LCD_Byte_Sel_D;
    end if;
  end process;
end rtl;
One caveat - the DE2 board has an 8-bit interface. I haven't had a chance to test this code in 4-bit mode yet, though it should work by spec.
 

defenseman

New member
thx jshamlet.

thats a lot of code. is most of that really neccessary? you said you used a FIFO and im interested because I want to make a function for write one character...and once that works, a function to write a word.

im interested in new designs, but all i got right now is a big state machine with delays. one state to hold the data, and the next state to throw E low and keep the data there. where both states have the same amout of time. that goes on and on for whatever im outputting. ... nonetheless, i dont really like it.

can you explain how the FIFO (first in, first out) helps control the LCD?
 

jshamlet

New member
The FIFO just takes the burden off of the host processor. Most micro's (or cores) can run a lot faster than 4MHz. The FIFO lets you spool up a whole display worth of data at high speed, then let the state logic dribble it out to the display.

Otherwise, you have to add wait states, or otherwise waste time in your program.

I'm actually doing a small 8-bit micro in a Spartan 3e 100 where I don't have room for the FIFO, so I'm implementing a wait-state system. Essentially, the display controller can "halt" the processor for a few cycles to extend the write. It's wasteful, but in this case, necessary - since the 3s100e only has 8kB of SRAM.
 
Top