zipeilu
zipeilu

Reputation: 1

Using Finite State Machine model to design LCD1602 driver in VHDL

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.

Waveform of signals output

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

Answers (1)

zipeilu
zipeilu

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

Related Questions