Switch to Normal Style Sheet
Site Icon The Lab Book Pages Andrew Greensted (Modified: 17 June 2008)
Electronics > LCDs

LCDs

VHDL Driver for a HD44780 driven Liquid Crystal Display.

Divider Bar Go to page top

• VHDL Driver Module

VHDL icon LCDDriver.vhdl
• File: LCDDriver.vhdl
-- LCD Driver Module for driving HD44780 Controller
-- A. Greensted, June 2007

-- Generic tickNum must be set such that:
-- tickNum = 10us / Period clk
-- This provides an internal tick every 10us
-- Clk: 100 MHz, tickNum: 1000
-- Clk: 32 MHz, tickNum: 320
-- Clk: 10 MHz, tickNum: 100

library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;

entity LCDDriver is
   generic ( tickNum    : positive := 320);
   port (   clk         : in    std_logic;
            reset       : in    std_logic;

            dIn         : in    std_logic_vector(7 downto 0);
            charNum     : in    std_logic_vector(5 downto 0);
            wEn         : in    std_logic;

            -- LCD Interface
            lcdData     : out   std_logic_vector(7 downto 0);
            lcdRS       : out   std_logic;
            lcdRW       : out   std_logic;
            lcdE        : out   std_logic);

end LCDDriver;

architecture Structural of LCDDriver is

   -- LCD interface constants
   constant LCD_READ      : std_logic := '1';
   constant LCD_WRITE     : std_logic := '0';
   constant DATA_CODE     : std_logic := '1';
   constant INSN_CODE     : std_logic := '0';

   -- Tick Generation
   subtype TICK_COUNTER_TYPE is integer range 0 to tickNum;
   signal tick            : std_logic;

   constant WARMUP_DELAY  : integer := 2000;  -- 2000: 20ms
   constant INIT_DELAY    : integer := 500;   -- 500:  5ms
   constant CHAR_DELAY    : integer := 10;    -- 10:   100us

   subtype DELAY_TYPE is integer range 0 to WARMUP_DELAY;
   signal timer           : DELAY_TYPE;

   type INIT_ROM_TYPE is array (0 to 6) of std_logic_vector(7 downto 0);
   constant initROM       : INIT_ROM_TYPE := (  b"0011_0000",  -- Init
                                                b"0011_0000",  -- Init
                                                b"0011_0000",  -- Init
                                                b"0011_1000",  -- Function Set: 8 bit, 2 lines, 5x7 characters
                                                b"0000_1100",  -- Display On/Off Control: Display on, Cursor off, Blink off
                                                b"0000_0001",  -- Clear Display: Move cursor to home
                                                b"0000_0110"); -- Entry Mode Set: Auto increment cursor, don't shift display

   type CHAR_RAM_TYPE is array(0 to 39) of std_logic_vector(7 downto 0);
   signal charRAM         : CHAR_RAM_TYPE := (   0=>x"41", 1=>x"6E", 2=>x"64", 3=>x"79", 4=>x"FE", 5=>x"47",
                                                20=>x"31", 21=>x"34", 22=>x"2F", 23=>x"30", 24=>x"36", 25=>x"2F", 26=>x"30", 27=>x"37",
                                                others=>x"A0");

   signal setLine         : std_logic;
   signal lineNum         : integer range 0 to 1;
   signal initialising    : std_logic;

   signal initROMPointer  : integer range 0 to INIT_ROM_TYPE'high;
   signal charRAMPointer  : integer range 0 to CHAR_RAM_TYPE'high;

   type STATE_TYPE is (WARMUP, STAGE1, STAGE2, STAGE3, DELAY);
   signal state           : STATE_TYPE;

begin

lcdRW <= LCD_WRITE;

TickGen : process(clk)
   variable tickCounter : TICK_COUNTER_TYPE;
begin
   if (clk'event and clk='1') then
      if (tickCounter = 0) then
         tickCounter := TICK_COUNTER_TYPE'high-1;
         tick <= '1';
      else
         tickCounter := tickCounter - 1;
         tick <= '0';
      end if;
   end if;
end process;

CharRAMWrite : process(clk)
   variable add : integer range 0 to 39;
begin
   if (clk'event and clk='1') then
      if (wEn='1') then
         add := to_integer(unsigned(charNum));
         charRAM(add) <= dIn;
      end if;
   end if;
end process;

Controller : process (clk)
begin
   if (clk'event and clk='1') then

      if (reset='1') then
         timer          <= WARMUP_DELAY;
         initROMPointer <= 0;
         charRAMPointer <= 0;

         lcdRS          <= INSN_CODE;
         lcdE           <= '0';
         lcdData        <= (others => '0');

         initialising   <= '1';
         setLine        <= '0';
         lineNum        <= 0;
         state          <= WARMUP;

      elsif (tick='1') then

         case state is

            -- Perform initial long warmup delay
            when WARMUP =>
               if (timer=0) then
                  state <= STAGE1;
               else
                  timer <= timer - 1;
               end if;

            -- Set the LCD data
            -- Set the LCD RS
            -- Initialise the timer with the required delay
            when STAGE1 =>
               if (initialising='1') then
                  timer    <= INIT_DELAY;
                  lcdRS    <= INSN_CODE;
                  lcdData  <= initROM(initROMPointer);

               elsif (setLine='1') then
                  timer    <= CHAR_DELAY;
                  lcdRS    <= INSN_CODE;
                  case lineNum is
                     when 0 => lcdData   <= b"1000_0000"; -- x00
                     when 1 => lcdData   <= b"1100_0000"; -- x40
                  end case;

               else
                  timer    <= CHAR_DELAY;
                  lcdRS    <= DATA_CODE;
                  lcdData  <= charRAM(charRAMPointer);

               end if;

               state <= STAGE2;

            -- Set lcdE (latching RS and RW)
            when STAGE2 =>
               if (initialising='1') then
                  if (initROMPointer=INIT_ROM_TYPE'high) then
                     initialising <= '0';
                  else
                     initROMPointer <= initROMPointer + 1;
                  end if;

               elsif (setLine='1') then
                  setLine <= '0';

               else

                  if (charRAMPointer=19) then
                     setLine <= '1';
                     lineNum <= 1;

                  elsif (charRAMPointer=39) then
                     setLine <= '1';
                     lineNum <= 0;
                  end if;

                  if (charRAMPointer=CHAR_RAM_TYPE'high) then
                     charRAMPointer <= 0;
                  else
                     charRAMPointer <= charRAMPointer + 1;
                  end if;

               end if;

               lcdE  <= '1';
               state <= STAGE3;

            -- Clear lcdE (latching data)
            when STAGE3 =>
               lcdE  <= '0';
               state <= DELAY;

            -- Provide delay to allow instruciton to execute
            when DELAY =>
               if (timer=0) then
                  state <= STAGE1;
               else
                  timer <= timer - 1;
               end if;

         end case;
      end if;
   end if;
end process;

end Structural;
Divider Bar Go to page top

• Modelsim TestBench

VHDL icon LCDDriver_TB.vhdl
Do icon LCDDriver_TB.do
LCD TestBench Waveform

• File: LCDDriver_TB.vhdl
library ieee;
use ieee.std_logic_1164.all;
use ieee.std_logic_arith.ALL;
use ieee.std_logic_unsigned.ALL;

entity LCDDriver_TB is
end LCDDriver_TB;

architecture TestBench of LCDDriver_TB is

   component LCDDriver
      generic ( tickNum    : positive := 320);
      port (   clk         : in    std_logic;
               reset       : in    std_logic;
               dIn         : in    std_logic_vector(7 downto 0);
               charNum     : in    std_logic_vector(5 downto 0);
               wEn         : in    std_logic;
               lcdData     : out   std_logic_vector(7 downto 0);
               lcdRS       : out   std_logic;
               lcdRW       : out   std_logic;
               lcdE        : out   std_logic);
   end component;

   signal clk      : std_logic;
   signal reset    : std_logic;
   signal dIn      : std_logic_vector(7 downto 0);
   signal charNum  : std_logic_vector(5 downto 0);
   signal wEn      : std_logic;
   signal lcdData  : std_logic_vector(7 downto 0);
   signal lcdRS    : std_logic;
   signal lcdRW    : std_logic;
   signal lcdE     : std_logic;

begin

MainClk : process
begin
   clk <= '1';
   wait for 15.625 ns;    -- 32 MHz
   clk <= '0';
   wait for 15.625 ns;
end process;

Driver : LCDDriver
generic map(tickNum  => 320)
port map(   clk      => clk,
            reset    => reset,
            dIn      => dIn,
            charNum  => charNum,
            wEn      => wEn,
            lcdData  => lcdData,
            lcdRS    => lcdRS,
            lcdRW    => lcdRW,
            lcdE     => lcdE);

TB : process
begin

   reset    <= '1';
   dIn      <= (others => '0');
   charNum  <= (others => '0');
   wEn      <= '0';

   wait until (clk'event and clk='1');
   wait until (clk'event and clk='1');

   reset <= '0';

   wait;

end process;

end TestBench;
• File: LCDDriver_TB.do
vlib work

vcom -93 -pedanticerrors -lint +acc=v LCDDriver.vhdl

vcom -93 LCDDriver_TB.vhdl
vsim -t 1ps -lib work LCDDriver_TB

configure wave -gridperiod 5000
configure wave -griddelta 10
configure wave -signalnamewidth 1

view wave -undock

add wave -height 20 -color orange                                    clk
add wave -height 20 -color gold                                      reset

add wave -divider "Tick"
add wave -height 20 -color yellow                                    Driver/tick
add wave -height 20 -color yellow -unsigned                          Driver/TickGen/tickCounter

add wave -divider "Init ROM"
add wave -height 20 -color coral                                     Driver/initROMPointer

add wave -divider "Char RAM"
add wave -height 20 -color coral                                     Driver/charRAMPointer

add wave -divider "Delay Timer"
add wave -height 20 -color coral                                     Driver/timer

add wave -divider "State Machine"
add wave -height 20 -color steelBlue                                 Driver/state
add wave -height 20 -color steelBlue                                 Driver/initialising
add wave -height 20 -color pink                                      Driver/setLine
add wave -height 20 -color salmon -unsigned                          Driver/lineNum

add wave -divider "LCD Interface"
add wave -height 20 -color cornflowerBlue                            lcdRW
add wave -height 20 -color cornflowerBlue                            lcdRS
add wave -height 20 -color cornflowerBlue                            lcdE
add wave -height 20 -color cornflowerBlue -hex                       lcdData

run 100 ms
Divider Bar Go to page top

• LCD Information

Divider Bar Go to page top