本科準備電賽的時候,學習單片機,由于自己沒有LCD,沒有碰。剛開始學FPGA時候,剛準備學LCD,自己又出了些事,后來一直忙到現在。如今再來看看LCD,感觸頗多,廢話不多說,代碼加文檔走起。
lcd1602應該算是一個難點,不管是對于單片機的學習還是FPGA的學習。因為里面涉及到時序分析,地址度寫,數據讀寫,指令讀寫,建立時間和保持時間,還有操作流程會變的復雜,這些都給剛學習的人一些困惑或是難以理解。在我的博文中,一直貫徹的理念是,希望能夠起到拋磚引玉的作用,盡量將思考的過程描述清楚,而不是簡單的寫出相關知識和代碼。
1.文檔分析:
關于lcd1602的文檔有很多,自己搜索一下網。市面上絕大部分都是基于HD44780,所以這里的控制顯示等都是一樣的。HD44780內置了DDRAM、CGROM、CGRAM.其中要讓lcd顯示就需要將DDRAM的相應地址寫入數據。


上圖可知DDRAM一共有40個地址,但是對應于1602顯示,只能有32個地址有效。這是因為1602可以顯示上下兩行,每一行顯示16符號,一共顯示32個符號,每個顯示對應于DDRAM一個地址。例如,我需要在1602的第一行最左邊顯示一個字母A。首先找到第一行最左邊對應DDRAM的地址是什么,查看上圖可知是:00H,然后大寫字母A對應于ASCII中為41H,此時我們只需要給DDRAM的00H地址寫個數據41H即可顯示了。
問題2:為何寫個41H,就可以顯示為"A"呢?
對于這個問題,就需要理解CGROM和CGRAM的作用。在芯片HD44780中內置了192個常用字符的字模,存于CGROM(character generate ROM)中,還有8個允許用戶自定義字符(也就是可以顯示八個中文字)的RAM,也就是CGRAM。具體描述為下圖:

可以從上圖分析A在字模中代碼:高4位為0100,低4位為0001.所以組成8位就變成了41H,這就說明了為何寫入41H就可以顯示“A”。
上圖紅框里面表示為CGRAM,字模代碼為:00H-0FH;ASCII的字模代碼為:20H-7FH;日文和希臘字符的字模代碼為:A0H-FFH;10H-1FH和80H-9FH沒有使用。
問題3:我要任意顯示一個字母,數字怎么辦?
這個問題是接著上面一個問題而言,具體就是:在1602中我要在某一行某個位置顯示我想要的數字或是字母,我應該對應DDRAM地址寫個什么樣的八位數據?例如,我想顯示“1”,那不是就寫個01H呢?此時就需要一個思維轉換,我們要顯示的“1”不再是一個數據,而是需要轉換為一個圖案,可以看到上圖有1的圖案,該圖案對應了31H,所以需要顯示一個“1”,我們就需要給1602的數據總線(DB7--DB0)輸入31H。以此類推,例如我們需要輸入kb129 is a good man,于是就需要給1602順序輸入:6BH,62H,31H,32H,39H ,20H(空格),69H,73H,20H,61H,20H,67H,6FH,6FH,64H,20H,6DH,61H,6EH。
問題4:那顯示漢字怎么辦呢?
問題2中解決了顯示任意一個字母和數字,但是漢字在圖中找不到漢字,怎么辦?這時候需要使用CGRAM了,先用字模軟件,將對應漢字的變為二進制數。

例如我想要顯示一個“電”字,由于1602中顯示的圖案為5*7或是5*10,所以在8*8中左邊三列不能使用。得到8列八位數據:04, 1F, 15,1F, 15,1F, 04,07.
然后就需要將這8個8bit數據寫入CGRAM中,寫CGRAM需要使用指令:

可以設置地址指針自加一模式,所以如果我們想把“電”這個字方在第1個CGRAM中,也就是對應DDRAM中的00H,就需要將地址寫為DB7--DB0:0100_0000.然后將數據04, 1F, 15,1F, 15,1F, 04,07依次寫進CGRAM中。這樣在CGROM字符的字模中00H就代表了“電”。
最后就是顯示,也就是如果需要將“電”顯示在1602中,就講地址指針指向DDRAM,然后寫數據為00H。

2.關注時序
上面已經了解到要讓1602顯示就需要將特地的地址輸入特地的值,如果你剛接觸1602,此時一定特別想了解怎么將上面的思路轉換為verilog代碼。但是一定需要注意時序問題,1602的讀寫時序為:


這里特別注意了setup和 hold time,但是實際使用中由于1602顯示不需要很高的時序,所以我們只需降低1602工作的時鐘就可以很容易滿足1602的時序要求。
在上篇博文中已經說了1602顯示原理,在這篇主要是講講怎么將那些原理轉換為verilog代碼。1602的顯示有很多種選擇,一共有11條指令,可以根據不同設置,達到不同的顯示,下面給了verilog顯示代碼,可以以此為基礎,修改為具體應用。由于明天是五一勞動節,所以祝大家五一快樂。
代碼顯示結果:

verilog代碼:
1.代碼頂層
//data: 2014-04-28
//addr: kb129
//info: this is the top of the LCD
module lcd_1602(
clk_50M,
rst,
en,
RS,
RW,
data
);
input
clk_50M;
input
rst;
output
en;
output
RS;
output RW;
output [7:0]
data;
wire
clk_500;
clk50M_500 u_clk50M_500
(
.clk_50M(clk_50M),
.rst(rst),
.clk_500(clk_500)
);
lcd_show u_lcd_show
(
.clk_LCD(clk_500),
.rst(rst),
.en(en),
.RS(RS),
.RW(RW),
.data(data)
);
endmodule
2.時鐘模塊
//data: 2014-04-28
//addr: kb129
//info: change the clk 50Mhz to 500Hz
module clk50M_500(
clk_50M,
rst,
clk_500
);
input
clk_50M;
input
rst;
output
clk_500;
reg
[8:0] cnt_1;
reg [7:0]
cnt_2;
reg
clk_500hz;
always@(posedge clk_50M)
begin
if(!rst)
begin
clk_500hz <= 0;
cnt_1
<= 0;
cnt_2 <= 0;
end
else if(cnt_2==8'd199)
begin
cnt_2 <= 0;
if(cnt_1==9'd499)
begin
cnt_1 <= 0;
clk_500hz <= ~clk_500hz;