Reputation: 1
I am designing a driver of the classic LCD1602 module by VHDL. There are many good examples on the web. But when I tried to write it in my own way, I just can't make it work. Here is my code.
library ieee;
use ieee.std_logic_1164.all;
entity led_ssd_lcd_driver is
generic (
timePowerUp : positive := 5_000_000; --100 ms
timeFuncSet1 : positive := 250_000; --5 ms
timeFuncSet2 : positive := 10_000; --200 us
timeInstructionExecute : positive := 2000; --40 us
timeReturnHome : positive := 100_000; --2 ms
timeRisingGap : positive := 3; -- 60 ns
timeHoldE : positive := 12; --240 ns
timeMax : positive := 5_300_000
);
port (
clk, rst : in std_logic;
RS, RW: out std_logic;
E : out std_logic;
DB : out std_logic_vector(7 downto 0)
);
end entity led_ssd_lcd_driver;
architecture led_ssd_lcd_driver of led_ssd_lcd_driver is
type state is (
PowerUp, Ready, WriteData
);
signal pr_state, nx_state : state;
signal exeTime : integer range 0 to timeMax;
signal is_powerup : std_logic; --Initial process
signal RW_in, RS_in : std_logic;
signal busy : std_logic; --'1':Setting display content. '0':Idle.
signal DB_in : std_logic_vector(7 downto 0);
attribute keep : boolean;
attribute keep of busy, is_powerup: signal is true;
begin
-- LCD display
process(clk, rst)
variable count : integer range 0 to timeMax;
begin
if (rst='0') then
pr_state <= PowerUp;
count := 0;
elsif (clk'event and clk='1') then
count := count + 1;
if (is_powerup = '1') then
if (count < 5_000_000) then -- Power up time: 100 ms
elsif (count < 5_000_003) then -- +60 ns Function Set x3
RS <= '0'; RW <= '0';
DB <= "00110000";
E <= '0';
elsif (count < 5_000_015) then -- +240 ns
E <= '1';
elsif (count < 5_250_015) then -- +5 ms
E <= '0';
elsif (count < 5_250_027) then -- +240 ns
E <= '1';
elsif (count < 5_260_027) then -- +200 us
E <= '0';
elsif (count < 5_260_039) then -- +240 ns
E <= '1';
elsif (count < 5_270_039) then -- +200 us
E <= '0';
elsif (count < 5_270_042) then -- +60 ns Display format
RS <= '0'; RW <= '0';
DB <= "00110000";
E <= '0';
elsif (count < 5_270_054) then -- +240 ns
E <= '1';
elsif (count < 5_272_054) then -- +40 us
E <= '0';
elsif (count < 5_272_057) then -- +60 ns Display on
RS <= '0'; RW <= '0';
DB <= "00001111";
E <= '0';
elsif (count < 5_272_069) then -- +240 ns
E <= '1';
elsif (count < 5_274_069) then -- +40 us
E <= '0';
elsif (count < 5_274_072) then -- +60 ns Clear display
RS <= '0'; RW <= '0';
DB <= "00000001";
E <= '0';
elsif (count < 5_274_084) then -- +240 ns
E <= '1';
elsif (count < 5_276_084) then -- +40 us
E <= '0';
elsif (count < 5_276_087) then -- +60 ns Set entry mode
RS <= '0'; RW <= '0';
DB <= "00000110";
E <= '0';
elsif (count < 5_276_099) then -- +240 ns
E <= '1';
elsif (count < 5_278_099) then -- +40 us
E <= '0';
else
count := 0;
pr_state <= nx_state;
end if;
elsif (busy = '1') then
if (count < timeRisingGap) then
RS <= RS_in; RW <= RW_in;
DB <= DB_in;
E <= '0';
elsif (count < timeRisingGap + timeHoldE) then
E <= '1';
elsif (count < timeRisingGap + timeHoldE + exeTime) then
E <= '0';
else
count := 0;
pr_state <= nx_state;
end if;
else
RS <= RS_in; RW <= RW_in;
DB <= DB_in;
E <= '0';
pr_state <= nx_state;
count := 0;
end if;
end if;
end process;
process(pr_state)
begin
case pr_state is
when PowerUp =>
is_powerup <= '1';
busy <= '0';
RS_in <= '0'; RW_in <= '0';
DB_in <= "00000000";
exeTime <= 0;
nx_state <= WriteData;
when Ready =>
is_powerup <= '0';
busy <= '0';
RS_in <= '0'; RW_in <= '0';
DB_in <= "00000000";
exeTime <= timeInstructionExecute;
nx_state <= Ready;
when WriteData =>
is_powerup <= '0';
busy <= '1';
RS_in <= '1'; RW_in <= '0';
DB_in <= "00100001";
exeTime <= timeInstructionExecute;
nx_state <= Ready;
end case;
end process;
end architecture led_ssd_lcd_driver;
The test bench is as this.
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
entity lcd_driver_tb is
end entity lcd_driver_tb;
architecture behavior of lcd_driver_tb is
component led_ssd_lcd_driver
port (
clk, rst : in std_logic;
RS, RW: out std_logic;
E : out std_logic;
DB : out std_logic_vector(7 downto 0)
);
end component led_ssd_lcd_driver;
signal clk_in : std_logic:='0';
signal rst_in : std_logic;
signal RS_out, RW_out : std_logic;
signal DB_out : std_logic_vector(7 downto 0);
signal E_out : std_logic;
begin
clock_generate: process --T = 20e-9 s -> F = 1e9/20 = 5e7 Hz = 50 MHz
begin
clk_in <= '0';
wait for 10 ns;
clk_in <= '1';
wait for 10 ns;
end process clock_generate;
p1: led_ssd_lcd_driver
port map (clk => clk_in,
rst => rst_in,
RS => RS_out,
RW => RW_out,
E => E_out,
DB => DB_out);
end architecture behavior;
It is supposed to display an exclamation mark(!) and a blinking cursor on the screen. But it only displays the blinking cursor. And I used Signal Tap Logic Analyzer to see what happened to the output signals. But I found nothing abnormal.
There definitely are some improper trigger conditions set in the Signal Tap. Because I got the SAME wave form with my another version of the code. And that version of the code works perfectly!
About the code, the initialization process literally followed the instruction of HD44780 datasheet. I have another version of the driver which put every signal transition as a state. And there was a lot of states, but it worked. Then I tried to put the initial process in the sequential part of the FSM, which is, intuitively, sensible. However, it doesn't work. I guess the problem can be somewhere after the WriteData
state set but I can't tell.
This question maybe too specific and personal. As a FPGA beginner and a self learner, I'm aware the shortcomings of my knowledge about Finite State Machine. But I think I could learn a lot from this question.
Upvotes: -4
Views: 38
Reputation: 1
After a careful examination of the HD44780 datasheet(I have to complain its chaotic type setting.) and my example code, I figured out where the bug in my code is. It's the execution timing of Clear Display instruction.
According to document HD44780(LCD-II) ADE-207-272(Z) '99.9 Rev.0.0 page 26, the Clear Display instruction "writes space code 20H into all DDRAM addresses and then sets DDRAM address 0 into the address counter and returns the display to its original status.". In other words, it does two things. One is writing 32 blank space characters to DDRAM. It costs 37+4(us) per character, according to Table 6 in page 25. That is totally 41(us)*32=1312(us). The other thing is returning the cursor to the left-top position, which is equivalent to the Return Home instruction and it costs 1.52(ms) according to Table 6.
Therefore, to execute the Clear Display instruction, about 1.32+1.52=2.84(ms) is needed. But I left it only 40(us). Here is the corrected part of my code,
elsif (count < 5_274_072) then -- +60 ns Clear display
RS <= '0'; RW <= '0';
DB <= "00000001";
E <= '0';
elsif (count < 5_274_084) then -- +240 ns
E <= '1';
elsif (count < 5_416_084) then -- +2.84 ms
E <= '0';
elsif (count < 5_416_087) then -- +60 ns Set entry mode
RS <= '0'; RW <= '0';
DB <= "00000110";
E <= '0';
elsif (count < 5_416_099) then -- +240 ns
E <= '1';
elsif (count < 5_418_099) then -- +40 us
E <= '0';
else
count := 0;
pr_state <= nx_state;
end if;
I never thought the bug would happened in the LCD initial phase. Since the cursor was blinked as I set. But in fact my hypothesis was wrong.
Upvotes: -1