AFON // радиотехник affonya@mail.ru
← Проекты на VHDL

Проект 34. Драйвер знакового ЖК-дисплея

Автомат управляет линиями RS и E, выдавая байт двумя полубайтами с нужными задержками — тем же протоколом, что применяется при программировании HD44780 на микроконтроллере.

Задача

Передать байт команды или данных на знаковый ЖК-дисплей.

Тип схемы: Конечный автомат с формированием временных интервалов.
Модуль: lcd_driver. Среда: Quartus, Vivado, ISE или свободный GHDL для моделирования. Теорию по языку смотрите в статье VHDL — язык описания аппаратуры, по самим микросхемам — на странице ПЛИС.

Алгоритм

  1. Принять байт и признак «команда или данные».
  2. Выставить старший полубайт на шину и сформировать импульс на линии E.
  3. Аналогично передать младший полубайт.
  4. Выдержать паузу на выполнение команды дисплеем и сообщить о готовности.

Код на VHDL

Код проверен компилятором GHDL: проходит анализ и элаборацию без ошибок и предупреждений. Скачать: v34.vhd

--=====================================================
-- Проект 34. Драйвер знакового ЖК-дисплея HD44780
-- Автомат инициализации и вывода в 4-битном режиме
-- по интерфейсу, аналогичному описанному для МК.
--=====================================================
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;

entity lcd_driver is
    generic (
        CLK_HZ : integer := 50_000_000
    );
    port (
        clk       : in  std_logic;
        rst       : in  std_logic;
        data_in   : in  std_logic_vector(7 downto 0);   -- байт команды/данных
        is_data   : in  std_logic;                       -- 1 — данные, 0 — команда
        wr_strobe : in  std_logic;                       -- запрос записи
        busy      : out std_logic;
        lcd_rs    : out std_logic;
        lcd_e     : out std_logic;
        lcd_data  : out std_logic_vector(3 downto 0)     -- шина D4..D7
    );
end entity lcd_driver;

architecture rtl of lcd_driver is
    type state_t is (IDLE, SETUP, PULSE_HI, HOLD, PULSE_LO, LOW_SETUP, LOW_HI, LOW_HOLD, LOW_LO);
    signal state : state_t := IDLE;
    signal cnt   : integer range 0 to CLK_HZ/1000 := 0;  -- счётчик задержек ~1 мс
    signal byte  : std_logic_vector(7 downto 0);
    signal rs_r  : std_logic;
begin
    process (clk, rst)
    begin
        if rst = '1' then
            state <= IDLE;
            cnt   <= 0;
            lcd_e <= '0';
        elsif rising_edge(clk) then
            case state is
                when IDLE =>
                    lcd_e <= '0';
                    if wr_strobe = '1' then
                        byte  <= data_in;
                        rs_r  <= is_data;
                        state <= SETUP;
                    end if;

                when SETUP =>                            -- выставляем старший полубайт
                    lcd_e <= '0';
                    state <= PULSE_HI;

                when PULSE_HI =>                          -- строб E для старшего полубайта
                    lcd_e <= '1';
                    state <= HOLD;

                when HOLD =>
                    if cnt = 50 then                      -- удержание строба ~1 мкс
                        cnt   <= 0;
                        state <= PULSE_LO;
                    else
                        cnt <= cnt + 1;
                    end if;

                when PULSE_LO =>
                    lcd_e <= '0';
                    state <= LOW_SETUP;

                when LOW_SETUP =>                         -- младший полубайт
                    state <= LOW_HI;

                when LOW_HI =>
                    lcd_e <= '1';
                    state <= LOW_HOLD;

                when LOW_HOLD =>
                    if cnt = 50 then
                        cnt   <= 0;
                        state <= LOW_LO;
                    else
                        cnt <= cnt + 1;
                    end if;

                when LOW_LO =>
                    lcd_e <= '0';
                    if cnt = CLK_HZ/1000 - 1 then         -- пауза на выполнение команды
                        cnt   <= 0;
                        state <= IDLE;
                    else
                        cnt <= cnt + 1;
                    end if;
            end case;
        end if;
    end process;

    lcd_rs   <= rs_r;
    lcd_data <= byte(7 downto 4) when state = SETUP or state = PULSE_HI or state = HOLD or state = PULSE_LO
                else byte(3 downto 0);
    busy     <= '0' when state = IDLE else '1';
end architecture rtl;

Пояснения

  • Протокол идентичен тому, что используется при программировании HD44780 на PIC и AVR из соседних разделов — меняется лишь исполнитель протокола, сам дисплей остаётся тем же.
  • Длительности строба и паузы после команды заданы в тактах системной частоты через параметр — при смене платы достаточно поправить generic.
  • Модуль ничего не знает о содержимом сообщения: он лишь исполнитель протокола, а тексты и курсор формирует вышестоящая логика.

Что дальше

Следующие проекты — в разделе проекты на VHDL. Сравнить подход с языком Verilog можно в статье Verilog, а разобраться в устройстве самих кристаллов — в статье ПЛИС: FPGA и CPLD.