【原创】如何使用DE2的1602LCD --之一(quartus)(verilog)(digital logic)

1. 缘起

    会了点HDL和数字逻辑基础后,操作DE2上的开关,led,7断码数码管都没啥问题,但至此好像也只能玩玩n年前教科书上都有的lab,啥数字钟,汽车尾灯,交通灯之类。浪费了DE2的资源,未免对不起DE2的价钱。板子上最明显的东东莫过于那个1602的LCD了,遂想玩玩这个东东。伟大的教育培养的惯性思维:照书本来,狗来狗去,国产的似乎就只有一本关于DE2的书(上交的一个博士写的),拜读一下,大半是DE2附带的光盘lab的中译版,。。。总之,DE2的玩法,没在这本书里找到,估计又是应付国内职评的产物。没期望过,所以没啥失望,好在web上tw的教育网页可打开,那边的学生都推荐OO无双说过的那两本,遍历卓越等,大陆没引进,所以没得卖。再看看老美的,别个用乔治亚理工的helben写的那本,down个电子版(同样国内没引进,amazon上60多刀)。遗憾没得随书的DVD,里面的source code都在DVD上,只能看个大概。看来国内的高人都没得时间写书。要玩转这个LCD,还的靠自己。一切还是老老实实的从DE2附带的DEMO开始(感谢terasic,起码提供了demo)。

2. 涅槃

    手边最基本、最可靠的东东就是DE2光盘里的DEMO和datasheet了,俺喜欢从低级的玩起,所以代码就从default开始。先看datasheet,这个LCD的控制器是HD4470,说是较通用的型号,以前没玩过,不知道有多通用,简化版的datasheet有16页,主要说明这块LCD可以显示英文、数字、标点符号和日文。中文估计得自己加字库,(加到哪里,flash?啥,俺现在还比较糊,所以先不考虑显示中文。)

    总结一下这块LCD:共计11条指令,指令格式用RS,RW,DB7-0共10位的方式表示。

  1.     display_clear = 00_0000_0001

            功能:DDRAM设为20H、光标回到起始位置、AC置0.

   2. return_home = 00_0000_001x

       功能:光标回到起始位置、AC置0、DDRAM内容不变。

   3. mode_set = 00_0000_01 l/D S

       功能:设定每次输入一位数据后,光标的移动方向,并且设定每次写入的一个字符是否移动。

                l/D  0-左移;1-右移。

                S    0-屏不动;1-屏右移1。

    4. display_on = 00_0000_1 D C B

        功能:控制显示的开关,光标显示/关闭,以及光标是否闪烁。

                D 0-显示关;1-显示开。

                C 0-无光标;1-有光标。

                B 0-光标闪;1-不闪。

    5. cursor or display shift = 00_0001_S/C R/L xx

        功能:S/C   R/L

                 0       0     光标左移1格,且AC-1

                 0       1     光标右移1格,且AC+1

                 1       0     显示屏上的字符全左移1格,但光标不动

                 1       1     显示屏上的字符全右移1格,但光标不动

    6. function set = 00_001DL_NFxx

        功能:设定数据总线位数,显示的行数及字型。

                 DL 0-4 bit  1-8 bit

                 N   0-1行    1-2行

                 F   0-5*8dots  1-5*11dots

    7. set CGRAM address = 00_01_AC5-0

    8. set DDRAM address = 00_1_AC6-0

    9. read busy flag and address = 01_BF_AC6-0

       BF 1-忙, 0-可接受外部数据或指令。

    10. write data to RAM = 10_D7-0

          字符码写入DDRAM,显示字符;自定义图形存入CGRAM。

    11. read data from RAM = 11_D7-0

除了上述11条指令,比较难的就是时序,读、写的时序,每条指令的执行时间。对于LCD_EN,有2中有效状态,高电平和下降沿。其为高电平时读取信息;下降沿时执行指令。在将E置高电平之前,先设置好RS和RW,在E下降沿到来之前,准备好写入的命令或数据。只需在适当的地方加上延时,就可以满足要求。

DDRAM里的内容是要显示的字符的地址,DDRAM的地址是显示字符的位置。AC在显示字符串时用。

3. 代码

    目的是显示如图所示的字符。

Image002

代码:

  
    
1 module LCD_top(
2 CLOCK_50, // 50 MHz
3   LCD_ON, // LCD Power ON/OFF
4   LCD_BLON, // LCD Back Light ON/OFF
5   LCD_RW, // LCD Read/Write Select, 0 = Write, 1 = Read
6   LCD_EN, // LCD Enable
7   LCD_RS, // LCD Command/Data Select, 0 = Command, 1 = Data
8   LCD_DATA // LCD Data bus 8 bits
9 );
10
11 input CLOCK_50; // 50 MHz
12 inout [ 7 : 0 ] LCD_DATA; // LCD Data bus 8 bits
13 output LCD_ON; // LCD Power ON/OFF
14 output LCD_BLON; // LCD Back Light ON/OFF
15 output LCD_RW; // LCD Read/Write Select, 0 = Write, 1 = Read
16 output LCD_EN; // LCD Enable
17 output LCD_RS; // LCD Command/Data Select, 0 = Command, 1 = Data
18
19 // LCD ON
20 assign LCD_ON = 1 ' b1;
21 assign LCD_BLON = 1 ' b1;
22
23 wire DLY_RST;
24
25 Reset_Delay r0 ( .iCLK(CLOCK_50),.oRESET(DLY_RST) );
26
27 LCD_TEST u5 ( // Host Side
28 .iCLK(CLOCK_50),
29 .iRST_N(DLY_RST),
30 // LCD Side
31 .LCD_DATA(LCD_DATA),
32 .LCD_RW(LCD_RW),
33 .LCD_EN(LCD_EN),
34 .LCD_RS(LCD_RS) );
35
36 endmodule
37
  
    
1 module LCD_TEST ( // Host Side
2 iCLK,iRST_N,
3 // LCD Side
4 LCD_DATA,LCD_RW,LCD_EN,LCD_RS );
5 // Host Side
6 input iCLK,iRST_N;
7 // LCD Side
8 output [ 7 : 0 ] LCD_DATA;
9 output LCD_RW,LCD_EN,LCD_RS;
10 // Internal Wires/Registers
11 reg [ 5 : 0 ] LUT_INDEX;
12 reg [ 8 : 0 ] LUT_DATA;
13 reg [ 5 : 0 ] mLCD_ST;
14 reg [ 17 : 0 ] mDLY;
15 reg mLCD_Start;
16 reg [ 7 : 0 ] mLCD_DATA;
17 reg mLCD_RS;
18 wire mLCD_Done;
19
20 parameter LCD_INTIAL = 0 ;
21 parameter LCD_LINE1 = 5 ;
22 parameter LCD_CH_LINE = LCD_LINE1 + 16 ;
23 parameter LCD_LINE2 = LCD_LINE1 + 16 + 1 ;
24 parameter LUT_SIZE = LCD_LINE1 + 32 + 1 ;
25
26 always @( posedge iCLK or negedge iRST_N)
27 begin
28 if ( ! iRST_N)
29 begin
30 LUT_INDEX <= 0 ;
31 mLCD_ST <= 0 ;
32 mDLY <= 0 ;
33 mLCD_Start <= 0 ;
34 mLCD_DATA <= 0 ;
35 mLCD_RS <= 0 ;
36 end
37 else
38 begin
39 if (LUT_INDEX < LUT_SIZE)
40 begin
41 case (mLCD_ST)
42 0 : begin
43 mLCD_DATA <= LUT_DATA[ 7 : 0 ];
44 mLCD_RS <= LUT_DATA[ 8 ];
45 mLCD_Start <= 1 ;
46 mLCD_ST <= 1 ;
47 end
48 1 : begin
49 if (mLCD_Done)
50 begin
51 mLCD_Start <= 0 ;
52 mLCD_ST <= 2 ;
53 end
54 end
55 2 : begin
56 if (mDLY < 18 ' h3FFFE) // 5.2ms
57 mDLY <= mDLY + 1 ;
58 else
59 begin
60 mDLY <= 0 ;
61 mLCD_ST <= 3 ;
62 end
63 end
64 3 : begin
65 LUT_INDEX <= LUT_INDEX + 1 ;
66 mLCD_ST <= 0 ;
67 end
68 endcase
69 end
70 end
71 end
72
73 always
74 begin
75 case (LUT_INDEX)
76 // Initial
77 LCD_INTIAL + 0 : LUT_DATA <= 9 ' h038; //Fun set
78 LCD_INTIAL + 1 : LUT_DATA <= 9 ' h00C; //dis on
79 LCD_INTIAL + 2 : LUT_DATA <= 9 ' h001; //clr dis
80 LCD_INTIAL + 3 : LUT_DATA <= 9 ' h006; //Ent mode
81 LCD_INTIAL + 4 : LUT_DATA <= 9 ' h080; //set ddram address
82 // Line 1
83 LCD_LINE1 + 0 : LUT_DATA <= 9 ' h120; // http://halflife.cnblogs.com
84 LCD_LINE1 + 1 : LUT_DATA <= 9 ' h168; // h
85 LCD_LINE1 + 2 : LUT_DATA <= 9 ' h174; // t
86 LCD_LINE1 + 3 : LUT_DATA <= 9 ' h174; // t
87 LCD_LINE1 + 4 : LUT_DATA <= 9 ' h170; // p
88 LCD_LINE1 + 5 : LUT_DATA <= 9 ' h13A; // :
89 LCD_LINE1 + 6 : LUT_DATA <= 9 ' h12F; // /
90 LCD_LINE1 + 7 : LUT_DATA <= 9 ' h12F; // /
91 LCD_LINE1 + 8 : LUT_DATA <= 9 ' h168; // h
92 LCD_LINE1 + 9 : LUT_DATA <= 9 ' h161; // a
93 LCD_LINE1 + 10 : LUT_DATA <= 9 ' h16C; // l
94 LCD_LINE1 + 11 : LUT_DATA <= 9 ' h166; // f
95 LCD_LINE1 + 12 : LUT_DATA <= 9 ' h16C; // l
96 LCD_LINE1 + 13 : LUT_DATA <= 9 ' h169; // i
97 LCD_LINE1 + 14 : LUT_DATA <= 9 ' h166; // f
98 LCD_LINE1 + 15 : LUT_DATA <= 9 ' h165; // e
99 // Change Line
100 LCD_CH_LINE: LUT_DATA <= 9 ' h0C0;
101 // Line 2
102 LCD_LINE2 + 0 : LUT_DATA <= 9 ' h12E; // .
103 LCD_LINE2 + 1 : LUT_DATA <= 9 ' h163; // c
104 LCD_LINE2 + 2 : LUT_DATA <= 9 ' h16E; // n
105 LCD_LINE2 + 3 : LUT_DATA <= 9 ' h162; // b
106 LCD_LINE2 + 4 : LUT_DATA <= 9 ' h16C; // l
107 LCD_LINE2 + 5 : LUT_DATA <= 9 ' h16F; // o
108 LCD_LINE2 + 6 : LUT_DATA <= 9 ' h167; // g
109 LCD_LINE2 + 7 : LUT_DATA <= 9 ' h173; // s
110 LCD_LINE2 + 8 : LUT_DATA <= 9 ' h12E; // .
111 LCD_LINE2 + 9 : LUT_DATA <= 9 ' h163; // c
112 LCD_LINE2 + 10 : LUT_DATA <= 9 ' h16F; // o
113 LCD_LINE2 + 11 : LUT_DATA <= 9 ' h16D; // m
114 LCD_LINE2 + 12 : LUT_DATA <= 9 ' h120;
115 LCD_LINE2 + 13 : LUT_DATA <= 9 ' h120;
116 LCD_LINE2 + 14 : LUT_DATA <= 9 ' h120;
117 LCD_LINE2 + 15 : LUT_DATA <= 9 ' h120;
118 default : LUT_DATA <= 9 ' h000;
119 endcase
120 end
121
122 LCD_Controller u0 ( // Host Side
123 .iDATA(mLCD_DATA),
124 .iRS(mLCD_RS),
125 .iStart(mLCD_Start),
126 .oDone(mLCD_Done),
127 .iCLK(iCLK),
128 .iRST_N(iRST_N),
129 // LCD Interface
130 .LCD_DATA(LCD_DATA),
131 .LCD_RW(LCD_RW),
132 .LCD_EN(LCD_EN),
133 .LCD_RS(LCD_RS) );
134
135 endmodule
  
    
1 module LCD_Controller ( // Host Side
2 iDATA,iRS,
3 iStart,oDone,
4 iCLK,iRST_N,
5 // LCD Interface
6 LCD_DATA,
7 LCD_RW,
8 LCD_EN,
9 LCD_RS );
10 // CLK
11 parameter CLK_Divide = 16 ;
12
13 // Host Side
14 input [ 7 : 0 ] iDATA;
15 input iRS,iStart;
16 input iCLK,iRST_N;
17 output reg oDone;
18 // LCD Interface
19 output [ 7 : 0 ] LCD_DATA;
20 output reg LCD_EN;
21 output LCD_RW;
22 output LCD_RS;
23 // Internal Register
24 reg [ 4 : 0 ] Cont;
25 reg [ 1 : 0 ] ST;
26 reg preStart,mStart;
27
28 /////////////////////////////////////////////
29 // Only write to LCD, bypass iRS to LCD_RS
30 assign LCD_DATA = iDATA;
31 assign LCD_RW = 1 ' b0;
32 assign LCD_RS = iRS;
33 /////////////////////////////////////////////
34
35 always @( posedge iCLK or negedge iRST_N)
36 begin
37 if ( ! iRST_N)
38 begin
39 oDone <= 1 ' b0;
40 LCD_EN <= 1 ' b0;
41 preStart <= 1 ' b0;
42 mStart <= 1 ' b0;
43 Cont <= 0 ;
44 ST <= 0 ;
45 end
46 else
47 begin
48 ////// Input Start Detect ////// /
49 preStart <= iStart;
50 if ({preStart,iStart} == 2 ' b01) // latch ?
51 begin
52 mStart <= 1 ' b1;
53 oDone <= 1 ' b0;
54 end
55 ///////////////////////////////// /
56 if (mStart) // generate LCD_EN
57 begin
58 case (ST)
59 0 : ST <= 1 ; // Wait Setup, tAS >= 40ns
60 1 : begin
61 LCD_EN <= 1 ' b1;
62 ST <= 2 ;
63 end
64 2 : begin
65 if (Cont < CLK_Divide)
66 Cont <= Cont + 1 ;
67 else
68 ST <= 3 ;
69 end
70 3 : begin
71 LCD_EN <= 1 ' b0;
72 mStart <= 1 ' b0;
73 oDone <= 1 ' b1;
74 Cont <= 0 ;
75 ST <= 0 ;
76 end
77 endcase
78 end
79 end
80 end
81
82 endmodule
  
    
1 module Reset_Delay(iCLK,oRESET);
2 input iCLK;
3 output reg oRESET;
4 reg [ 19 : 0 ] Cont;
5
6 always @( posedge iCLK)
7 begin
8 if (Cont != 20 ' hFFFFF) //21ms
9 begin
10 Cont <= Cont + 1 ;
11 oRESET <= 1 ' b0;
12 end
13 else
14 oRESET <= 1 ' b1;
15 end
16
17 endmodule

仿真结果:

lcd_test

4. 小结

    上面的代码也是terasic的default文件的设计思想,利用2个FSM来实现写入数据,并显示。在LCD_Controller里的FSM是写操作的过程,如下图

write

关键是产生有效的E。LCD_TEST里的FSM则是初始化数据,调用LCD_Controller写入数据,设置合理延时(5.2ms),循环递进显示下一条数据。难点在于这2个FSM咋联系起来,一说子函数调用,简单,关键是这里从一个FSM的某个状态进入另一个FSM的某个状态,被调用的FSM的多个状态又包含在上层的FSM的一个状态中,其间的时序,关联就靠start和done来实现,这一点也是仿真了demo,才分析出来的。可能比较麻烦,所以才没人把每步的时序分解说明。看来,读代码的“黑屋修炼”是不可太期望别个会帮助的。

ps: 在仿真时,用qII花了不少时间,用modelsim,提示内存不足。

你可能感兴趣的:(Verilog)