---------------------------------------------------------- 
--             LCD 2 lines controller                  
--
--               8 bits interface                       --
---------------------------------------------------------- 
-- ESIEE
-- Creation : A. Exertier, mars 2008
---------------------------------------------------------- 

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

---------------------------------------------------------- 
--                    INPUTS
---------------------------------------------------------- 
-- clk        : system clock
-- resetn     : aynchronous active low reset
-- en_250kHz  : enable at 250 kHz (lasts 1 clock cycle)
-- char       : input ASCII character
-- write_char : write character command
-- ready      : set to 1 when device is ready for a writing
---------------------------------------------------------- 
--                    OUTPUTS
---------------------------------------------------------- 
-- LCD_DATA  : ASCII data to LCD
-- LCD_RS    : RS to LCD
-- LCD_RW    : Read/write to LCD
-- LCD_EN    : Enable to LCD
------------------------------------------------------------ 

Entity LCD_controller is
  
     port( 
        clk           : in std_logic;
        resetn        : in std_logic;
        en_250kHz     : in std_logic;                     

        mode          : in  std_logic_vector(1 downto 0);
        char          : in  std_logic_vector (7 downto 0);  
        address       : in  std_logic_vector(6 downto 0);
        write_char    : in  std_logic;   
        write_address : in  std_logic;
        ready         : out std_logic;  
        D             : in  std_logic;
        C             : in  std_logic;
        B             : in  std_logic;
        
        -- LCD signals
        
        LCD_data      : inout std_logic_vector(7 downto 0);
        LCD_RS        : out   std_logic;
        LCD_RW        : out   std_logic;
        LCD_EN        : out   std_logic 
        );

end LCD_Controller;


 
Architecture RTL of LCD_Controller is
  
  type State_type is (Boot, FunctionSet, DisplayControl, EntryModeSet, Clear,
                      Address_set, Waiting, Verify, Putchar, Homecursor,
                      WriteAddress, WriteData);
  signal present : State_type;
  
  signal future  : State_type;
  
  
-- Timing Constants :
  
  constant T_30ms       : integer := 30_000/4; -- 7500;  -- 30 ms
  
  constant T_2ms        : integer := 2_000/4;  --500;   -- 2 ms
  
  constant T_40us       : integer := 12;    -- > 39 us after E down
  
  constant En_delay     : integer := 1;  -- 8 us 
  
  constant LastPosition : integer := 16;
  

-- CLEAR DisplayControl
  constant CLR   : std_logic_vector(7 downto 0) := "00000001";
-- RETURN HOME
  constant RET_HOME : std_logic_vector(7 downto 0) := "00000010"; -- 0000001x
  -- DDRAM address to 0 (beginning of first line)

-- ENTRY MODE SET  
  constant ENTRY_MODE   : std_logic_vector(7 downto 0) := "00000110";  -- 0000 01 I/D S
  -- I/D : 1 => Increment, 0 => Decrement
  -- S   : 1 => DisplayControl shift

-- FUNCTION SET
  constant FUNC_SET   : std_logic_vector(7 downto 0) := "00111100";  -- 001 DL N F xx
  -- DL : 1 => 8 bits,    0 => 4 bits
  -- N  : 1 => 2 lines,   0 => 1 line
  -- F  : 1 => 5x10 dots, 0 => 5x8 dots

-- DisplayControl ON/OFF CONTROL
  constant DON   : std_logic_vector(7 downto 0) := "00001110"; -- 0000 1 D C B
  -- D : 1 => Display on
  -- C : 1 => Cursor on
  -- B : 1 => Cursor blink on  
  
  constant RET_LINE2 : std_logic_vector(7 downto 0) := "11000000"; 
  -- set DDRAM  address to 32 (beginning of second line)  
  constant RET_LINE1 : std_logic_vector(7 downto 0) := "10000000";
  -- set DDRAM  address to 0 (beginning of first line)
  
  

 signal Position : natural range 0 to 2*LastPosition;
  
  signal Count    : natural range 0 to T_30ms;
  signal inc_C    : std_logic;
  
  signal cmd_P    : std_logic_vector(1 downto 0);
  --signal reset_P  : std_logic;
  
  signal test_EN  : std_logic;
  signal test_T_40us      : std_logic;


  
  begin


  LCD_RW <= '0';                       
  
  
process (Clk, resetn)

     begin
  
     if resetn = '0' then 
   
     	present  <= Boot;
    	position <= 0;
    	count    <= 0;
  
     elsif rising_edge(clk) then
    
     	if en_250kHz = '1' then 
      
     		present <= future;
      
     		if inc_C = '1' then count <= count+1;
      
     		else count <= 0;
      
     		end if;
      		case cmd_P is
       			when "00"     => position <= 0;
				when "01"     => if position >= 31 then position <= 0;
                         		 else                   position <= position+1;
                         		 end if;
        		when "10"     => if    address(6 downto 4) = "000" then position <= to_integer(unsigned(address(3 downto 0)));
                         		 elsif address(6 downto 4) = "100" then position <= to_integer(unsigned(address(3 downto 0)))+16;
                         		 else                                   position <= 0;
                         		 end if;
        		when others   => null;
      		end case;      
   
        end if;
  
      end if;  
  end process;


test_EN          <= '1' when Count = En_delay else '0';
test_T_40us      <= '1' when count >= T_40us  else '0';

 process(present, Write_char, mode, write_address, count, Position,char,
        address,test_EN, test_T_40us     , D, C, b) is

 begin
  
 	future   <=  present;
  
 	inc_C    <= '1';
  
 	cmd_P    <= "11";
  
 	ready    <= '0';
  
 	LCD_RS   <= '0';
  
 	LCD_EN   <= '0';
  
 	LCD_data <= X"00";


 	
 	case present is


		when Boot 			=>  -- Wait for 30 ms               
               
			if Count = T_30ms then 
 				future <= FunctionSet;
 				inc_C <= '0';
 			end if;
            cmd_P <= "00";


            
		when FunctionSet 	=> 	--  Function Set
			LCD_data  <= FUNC_SET;            
					 
			LCD_EN    <= test_EN;
					 
			if test_T_40us      = '1' then
				future <= EntryModeSet;
				inc_C <= '0';
			end if;
			cmd_P <= "00";



			
		when EntryModeSet 	=> --  Entry Mode Set
						
			LCD_data  <= ENTRY_MODE;
						
			LCD_EN    <= test_EN;
						
			if test_T_40us      = '1' then
				future <= DisplayControl;
				inc_C <= '0';
			end if;
			cmd_P <= "00";
			
		when DisplayControl =>
			-- Display ON/OFF control
						
			LCD_data  <= DON(7 downto 3)&D&C&B;
			LCD_EN    <= test_EN;

			if test_T_40us      = '1' then
				future <= Clear;
				inc_C <= '0';
			end if;
			cmd_P <= "00";
			
		when Clear =>
			-- Clear Display
			LCD_data <= CLR;
			LCD_EN    <= test_EN;
			if Count >= T_2ms then
				inc_C <= '0';
				future <= Address_set;
			end if;						
			cmd_P <= "00";
			
		when Address_set =>         
			-- DDRAM address set
			LCD_data  <= RET_LINE1;

			LCD_EN    <= test_EN;

			if test_T_40us      = '1' then
						  
				future <= Waiting;
						  
				inc_C <= '0';				
			end if;			
			cmd_P <= "00";
			
		when Waiting =>               
			-- Waits for input
			if mode (1) = '1' then 
					  
				if write_address = '1' then future <= WriteAddress;
				elsif write_char = '1' then future <= WriteData;
				end if;
			elsif write_char = '1' then              
					   
				if  char = x"0C" then future <= Clear; 
            	else                  future <= Putchar;
				end if;
			end if;
					
			ready     <= '1';
					
			inc_C     <= '0';

			
		when WriteAddress =>
			-- Set CGRAM/DDRAM Address
			if mode(0) = '0' then LCD_data <= '1'&address;
			else                  LCD_data <= "01"&address(5 downto 0);
			end if;
			LCD_EN    <= test_EN;
			if test_T_40us      = '1' then
				future <= Waiting;
				inc_C <= '0';             
			end if;
			cmd_P <= "10";

		when WriteData =>  
			-- Write data to CGRAM or DDRAM
			LCD_RS    <= '1';
			LCD_EN    <= test_EN;
			LCD_DATA  <= char;
			if test_T_40us      = '1' then
				future <= Waiting;
				inc_C <= '0';
				cmd_P <= "01";									 
			end if;
			

		when Verify =>
              
			if char = x"0C" then  -- FormFeed => Clear Screen

				 future <= Clear;
			else future     <= Putchar;
             
			end if;
			inc_C      <= '0';
			LCD_data   <= char;


			
		when Putchar =>
			-- Display character on the LCD
			LCD_RS <= '1';
			LCD_EN    <= test_EN;
			if test_T_40us      = '1' then
				if Position = 15 or Position = 31 then 
					 future <= HomeCursor;
				else future <= Waiting;
					 cmd_P     <= "01";
				end if;
				inc_C     <= '0';
			end if;
			LCD_data   <= char;


		
		when HomeCursor =>
			LCD_EN    <= test_EN;
			if Position = 31 then LCD_data <= RET_LINE1; --RET_HOME;
			else                  LCD_data <= RET_LINE2;
			end if;
			if test_T_40us      = '1' then
					--if Count >= T_2ms     then
				if Position = 31 then cmd_P <= "00";
				else                  cmd_P <= "01";
				end if;
				future  <= Waiting;
				inc_C   <= '0';            
					
			end if;
  
		end case;

 end process;


end RTL;