-- 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;