目前市场上的LCD1602液晶,其控制芯片主要有Samsung S6A0069X or KS0066U,Hitachi HD44780,SMOS SED1278。Digilent的Spartan-3E开发板上配置的LCD1602的控制芯片是Sitronix的ST7066U,而且开发板配套的开发说明书上写“为了减少使用I/O口,采用4位传输模式”,真心蛮坑的,选择4位或者8位传输模式应该让用户选择嘛,呵呵……废话不说了,开始正文了。
开发板的字符型LCD的接口情况如Figure5-1所示,其中,SF_D<11-8>为数据传输信号,LCD_E是LCD的读写使能信号。LCD_RS是LCD的片选寄存器控制信号,在进行写指令操作的时候,应该将其置0,而在进行读写数据的时候,应该置1。LCD_RW为读写控制位信号,为0时表示写数据(LCD接收来自FPGA的数据),为1时表示读数据(LCD显示寄存器中的数据)。关于LCD接口的详细说明如Table5-1所示。
液晶控制芯片内部有三组存储器,分别为显示数据存储器(DD RAM)、字符发生器存储器(CG ROM)和字符产生器存储器(CG RAM)。关于这三组存储器的功能以及相应的概念,网上有很多,这里不再赘述。百度空间里有这么一篇文章可以帮助对这三组存储器的理解:http://hi.baidu.com/%C4%E1%BF%A8njord/blog/item/f070efcbc55f6fe753664f2e.html。
Figure5-6的上半部分说明了LCD采用4位数据总线时输入命令和数据的时序情况,所有的命令和数据均以8位形式传送给字符显示屏控制芯片,采用4位数据总线传输时,每一次8位传送操作必须分两次4位传输操作才能完成,先传送高4位,再传送低4位,其间隔时间至少1μs。待完成一个8位的传输操作后,与下一次传输操作的时间间隔至少要超过40 μs。而在传输清屏指令之后,与下一次传输操作的时间间隔至少要超过1.64 ms。上电后,必须向液晶显示屏控制芯片传送初始化命令。由于接通电源后,液晶显示屏控制芯片默认为第一次写数据操作是8位数据传输,而实验开发板上的液晶显示屏控制芯片的DB3~DB0没有连接,仅连接了4位数据总线DB7~DB4,这就需要重复写入功能控制字0X3后再写入采用4位数据总线传输方式,传输功能设置控制字中的DL=0,以及写入功能控制字0X2。操作步骤如下:
(1) 电源接通后,等待15 ms以上,当时钟频率为50 MHz时,15 ms就等于750 000个时钟周期。
(2) 传送控制字SF_D<11:8>=0x3,数据稳定后,LCD_E变成高电平,并且保持高电平12个时钟周期。
(3) 等待4.1 ms或更长时间,当时钟为50 MHz时,即为205 000个时钟周期。
(4) 传送控制字SF_D<11:8>=0x3,LCD_E变成高电平,并且保持高电平12个时钟周期。
(5) 等待100 μs或更长时间,当时钟为50 MHz时,即为5000个时钟周期。
(6) 传送控制字SF_D<11:8>=0x2,LCD_E保持高电平12个时钟周期。
(7) 等待40 μs或更长时间,当时钟为50 MHz时,即为2000个时钟周期。
(8) 传送功能设置命令控制字0X28,DL=0时,采用4位数据总线DB7~DB4;N=1时,双行显示;F =0时,5×7点阵。
(9) 传送模式设置命令,0X06,设置显示屏自动增加地址指针。
(10) 传送显示打开命令,0X0c,打开显示屏,关闭光标显示。
(11) 等待至少1.64 ms(82 000个时钟周期)。
初始化完成后,再传输指定地址计数器地址和显示的数据。当地址计数器配置为自动增1和显示多个字符时,依次传输多个字符编码,每个字符自动存储并显示在下个位置。
以上就是在LCD上显示字符的介绍了,我第一次按照上面的步骤写下来一共整了54个状态,满屏的代码看着真心不舒服。后来采用分频的方法,分频到100us,这样就可以大大减少状态机的状态个数啦。在这个基础上,如果要想实现LCD上的字符滚动显示,我最初的想法就是,通过改变数据的DDRAM地址来实现,即完整的一个字符串写完后,清屏一下,然后将原来的DDRAM地址+1,再写一次,这样就相当于将整个字符串向右移动一位了,如果整个字符串移动到LCD最右端,让DDRAM地址回到最初的位置,如此反复。为了实现动态的效果,在写完一次字符串后,延时一段时间(我这里用的是1s),否则移动太快,液晶上什么也看不到。
下面给出源程序:
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.STD_LOGIC_ARITH.ALL;
use IEEE.STD_LOGIC_UNSIGNED.ALL;
---- Uncomment the following library declaration if instantiating
---- any Xilinx primitives in this code.
--library UNISIM;
--use UNISIM.VComponents.all;
entity LCD1602_Update is
Port ( Clk : in STD_LOGIC;
Reset : in STD_LOGIC;
LCD_DATA : out STD_LOGIC_VECTOR (7 downto 4);
LCD_EN : out STD_LOGIC;
LCD_RS : out STD_LOGIC;
LCD_RW : out STD_LOGIC);
end LCD1602_Update;
architecture Behavioral of LCD1602_Update is
Signal Clk_scan : STD_LOGIC := '0';
Signal Clk_100us : STD_LOGIC ;
--Signal Reset : STD_LOGIC := '0'; --Method 2
Type string1 is array(0 to 5) of STD_LOGIC_VECTOR(7 downto 0);
constant data1 : string1 := (x"48",x"45",x"4c",x"4c",x"4f",x"21"); --"HELLO!"
Type State_Display is (
st_disp1,st_disp2,st_disp3,st_disp4,st_disp5,
st_disp6,st_disp7,st_disp8,st_disp9,st_disp10,
st_disp11,st_disp12,st_disp13,st_disp14,st_disp15,
st_disp16,st_disp17,st_disp18,st_disp19,st_disp20,
st_disp21
);
Signal Current_Disp : State_Display := st_disp1;
begin
Proc_CLK_100us:process(Clk) --Frequency division to 10KHz,which is 100us
variable cnt_clk : integer range 0 to 5000 := 0;
begin
if(rising_edge(Clk)) then
if(cnt_clk < 2500) then
cnt_clk := cnt_clk + 1;
Clk_scan <= '0';
elsif(cnt_clk < 4999) then
cnt_clk := cnt_clk + 1;
Clk_scan <= '1';
else
cnt_clk := 0;
end if;
Clk_100us <= Clk_scan;
end if;
end process Proc_CLK_100us;
Proc_Display:process(Clk_100us,Reset)
variable cnt_disp : integer range 0 to 10000;
variable cnt_rw : integer range 0 to 6 := 0;
variable cnt_loop : integer range 0 to 16 := 0;
begin
-- Method 1 : Use a button(always High) to set the signal port "Reset"
if(Reset = '0') then
LCD_RS <= '0';
LCD_RW <= '0';
LCD_EN <= '0';
LCD_DATA <= "0000";
cnt_disp := 0;
cnt_rw := 0;
cnt_loop := 0;
Current_Disp <= st_disp1;
-- Method 2 : Use delay to set the signal "Reset" to 1
-- if(cnt_disp < 50000) then
-- cnt_disp := cnt_disp + 1;
-- else
-- cnt_disp := 0;
-- end if;
-- Reset <= '1';
elsif(rising_edge(CLK_100us)) then
case Current_Disp is
when st_disp1 =>
if(cnt_disp < 150) then --Wait for 15ms or longer
cnt_disp := cnt_disp + 1;
Current_Disp <= st_disp1;
else
cnt_disp := 0;
Current_Disp <= st_disp2;
end if;
when st_disp2 => --Write SF_D<11:8>=0x3, pulse LCD_E High for 240ns.
LCD_DATA <= x"3";
LCD_EN <= '1';
if(cnt_disp < 41) then --Wait for 4.1ms or longer
cnt_disp := cnt_disp + 1; --实验发现这样写没有问题!!!但最好还是再用一个状态
Current_Disp <= st_disp2;
else
cnt_disp := 0;
LCD_EN <= '0';
Current_Disp <= st_disp3;
end if;
when st_disp3 => --Write SF_D<11:8>=0x3, pulse LCD_E High for 240ns.
if(cnt_disp < 1) then --Wait 100 μs or longer
cnt_disp := cnt_disp + 1;
LCD_DATA <= x"3";
LCD_EN <= '1';
Current_Disp <= st_disp3;
else
cnt_disp := 0;
LCD_EN <= '0';
Current_Disp <= st_disp4;
end if;
when st_disp4 => --Write SF_D<11:8>=0x3, pulse LCD_E High for 240ns.
if(cnt_disp < 1) then --Wait 40 μs or longer
cnt_disp := cnt_disp + 1;
LCD_DATA <= x"3";
LCD_EN <= '1';
Current_Disp <= st_disp4;
else
cnt_disp := 0;
LCD_EN <= '0';
Current_Disp <= st_disp5;
end if;
when st_disp5 => --Write SF_D<11:8>=0x2, pulse LCD_E High for 240ns.
if(cnt_disp < 1) then --Wait 40 μs or longer
cnt_disp := cnt_disp + 1;
LCD_DATA <= x"2";
LCD_EN <= '1';
Current_Disp <= st_disp5;
else
cnt_disp := 0;
LCD_EN <= '0';
Current_Disp <= st_disp6;
end if;
when st_disp6 => -- Function Set 0x28
if(cnt_disp < 1) then --Wait 40 μs or longer
cnt_disp := cnt_disp + 1;
LCD_DATA <= x"2";
LCD_EN <= '1';
Current_Disp <= st_disp6;
else
cnt_disp := 0;
LCD_EN <= '0';
Current_Disp <= st_disp7;
end if;
when st_disp7 =>
if(cnt_disp < 1) then --Wait 40 μs or longer
cnt_disp := cnt_disp + 1;
LCD_DATA <= x"8";
LCD_EN <= '1';
Current_Disp <= st_disp7;
else
cnt_disp := 0;
LCD_EN <= '0';
Current_Disp <= st_disp8;
end if;
when st_disp8 => --Entry Mode Set 0x06
if(cnt_disp < 1) then --Wait 40 μs or longer
cnt_disp := cnt_disp + 1;
LCD_DATA <= x"0" ;
LCD_EN <= '1';
Current_Disp <= st_disp8;
else
cnt_disp := 0;
LCD_EN <= '0';
Current_Disp <= st_disp9;
end if;
when st_disp9 =>
if(cnt_disp < 1) then --Wait 40 μs or longer
cnt_disp := cnt_disp + 1;
LCD_DATA <= x"6";
LCD_EN <= '1';
Current_Disp <= st_disp9;
else
cnt_disp := 0;
LCD_EN <= '0';
Current_Disp <= st_disp10;
end if;
when st_disp10 => --Display On/Off 0x0C
if(cnt_disp < 1) then --Wait 40 μs or longer
cnt_disp := cnt_disp + 1;
LCD_DATA <= x"0";
LCD_EN <= '1';
Current_Disp <= st_disp10;
else
cnt_disp := 0;
LCD_EN <= '0';
Current_Disp <= st_disp11;
end if;
when st_disp11 =>
if(cnt_disp < 1) then --Wait 40 μs or longer
cnt_disp := cnt_disp + 1;
LCD_DATA <= x"C";
LCD_EN <= '1';
Current_Disp <= st_disp11;
else
cnt_disp := 0;
LCD_EN <= '0';
Current_Disp <= st_disp12;
end if;
when st_disp12 => --Clear Display 0x01
if(cnt_disp < 1) then --Wait 40 μs or longer
cnt_disp := cnt_disp + 1;
LCD_DATA <= x"0";
LCD_EN <= '1';
Current_Disp <= st_disp12;
else
cnt_disp := 0;
LCD_EN <= '0';
Current_Disp <= st_disp13;
end if;
when st_disp13 =>
if(cnt_disp < 1) then --Wait 40 μs or longer
cnt_disp := cnt_disp + 1;
LCD_DATA <= x"1";
LCD_EN <= '1';
Current_Disp <= st_disp13;
else
cnt_disp := 0;
LCD_EN <= '0';
Current_Disp <= st_disp14;
end if;
when st_disp14 =>
if(cnt_disp < 17) then --Wait 1.64ms or longer
cnt_disp := cnt_disp + 1;
Current_Disp <= st_disp14;
else
cnt_disp := 0;
Current_Disp <= st_disp15;
end if;
when st_disp15 => --Set DDRAM Address 0x00+0x80
if(cnt_disp < 1) then --Wait 40 μs or longer
cnt_disp := cnt_disp + 1;
LCD_DATA <= x"8";
LCD_EN <= '1';
Current_Disp <= st_disp15;
else
cnt_disp := 0;
LCD_EN <= '0';
Current_Disp <= st_disp16;
end if;
when st_disp16 => --Shift the DDRAM Address
if(cnt_disp < 1) then --Wait 40 μs or longer
cnt_disp := cnt_disp + 1;
LCD_DATA <= x"0" + conv_std_logic_vector(cnt_loop,4);
if(cnt_loop < 15) then
cnt_loop := cnt_loop + 1;
else
cnt_loop := 0;
end if;
LCD_EN <= '1';
Current_Disp <= st_disp16;
else
cnt_disp := 0;
LCD_EN <= '0';
Current_Disp <= st_disp17;
end if;
when st_disp17 => --Write Data to DDRAM(st_disp17-st_disp20)
if(cnt_rw < 6) then
LCD_DATA <= data1(cnt_rw)(7 downto 4); --Write the Upper Nibble
LCD_EN <= '1';
LCD_RS <= '1';
Current_Disp <= st_disp18;
else
cnt_rw := 0;
Current_Disp <= st_disp21;
end if;
when st_disp18 =>
LCD_EN <= '0';
Current_Disp <= st_disp19;
when st_disp19 =>
LCD_DATA <= data1(cnt_rw)(3 downto 0); --Write the Lower Nibble
LCD_EN <= '1';
LCD_RS <= '1';
Current_Disp <= st_disp20;
when st_disp20 =>
LCD_EN <= '0';
cnt_rw := cnt_rw + 1;
Current_Disp <= st_disp17;
when st_disp21 =>
if(cnt_disp < 10000) then --Delay 1 min to display the next string
cnt_disp := cnt_disp + 1;
Current_Disp <= st_disp21;
else
cnt_disp := 0;
LCD_RS <= '0';
Current_Disp <= st_disp12;
end if;
when others => null;
end case;
end if;
end process Proc_Display;
end Behavioral;