專注電子技術(shù)學(xué)習(xí)與研究
當(dāng)前位置:單片機(jī)教程網(wǎng) >> MCU設(shè)計(jì)實(shí)例 >> 瀏覽文章

LCD1602顯示之FPGA

作者:kb129   來源:kb129   點(diǎn)擊數(shù):  更新時(shí)間:2014年06月08日   【字體:

    本科準(zhǔn)備電賽的時(shí)候,學(xué)習(xí)單片機(jī),由于自己沒有LCD,沒有碰。剛開始學(xué)FPGA時(shí)候,剛準(zhǔn)備學(xué)LCD,自己又出了些事,后來一直忙到現(xiàn)在。如今再來看看LCD,感觸頗多,廢話不多說,代碼加文檔走起。

 

      lcd1602應(yīng)該算是一個(gè)難點(diǎn),不管是對于單片機(jī)的學(xué)習(xí)還是FPGA的學(xué)習(xí)。因?yàn)槔锩嫔婕暗綍r(shí)序分析,地址度寫,數(shù)據(jù)讀寫,指令讀寫,建立時(shí)間和保持時(shí)間,還有操作流程會變的復(fù)雜,這些都給剛學(xué)習(xí)的人一些困惑或是難以理解。在我的博文中,一直貫徹的理念是,希望能夠起到拋磚引玉的作用,盡量將思考的過程描述清楚,而不是簡單的寫出相關(guān)知識和代碼。

1.文檔分析:

     關(guān)于lcd1602的文檔有很多,自己搜索一下網(wǎng)。市面上絕大部分都是基于HD44780,所以這里的控制顯示等都是一樣的。HD44780內(nèi)置了DDRAM、CGROM、CGRAM.其中要讓lcd顯示就需要將DDRAM的相應(yīng)地址寫入數(shù)據(jù)。




上圖可知DDRAM一共有40個(gè)地址,但是對應(yīng)于1602顯示,只能有32個(gè)地址有效。這是因?yàn)?602可以顯示上下兩行,每一行顯示16符號,一共顯示32個(gè)符號,每個(gè)顯示對應(yīng)于DDRAM一個(gè)地址。例如,我需要在1602的第一行最左邊顯示一個(gè)字母A。首先找到第一行最左邊對應(yīng)DDRAM的地址是什么,查看上圖可知是:00H,然后大寫字母A對應(yīng)于ASCII中為41H,此時(shí)我們只需要給DDRAM的00H地址寫個(gè)數(shù)據(jù)41H即可顯示了。

問題2:為何寫個(gè)41H,就可以顯示為"A"呢?

       對于這個(gè)問題,就需要理解CGROM和CGRAM的作用。在芯片HD44780中內(nèi)置了192個(gè)常用字符的字模,存于CGROM(character generate ROM)中,還有8個(gè)允許用戶自定義字符(也就是可以顯示八個(gè)中文字)的RAM,也就是CGRAM。具體描述為下圖:


可以從上圖分析A在字模中代碼:高4位為0100,低4位為0001.所以組成8位就變成了41H,這就說明了為何寫入41H就可以顯示“A”。

上圖紅框里面表示為CGRAM,字模代碼為:00H-0FH;ASCII的字模代碼為:20H-7FH;日文和希臘字符的字模代碼為:A0H-FFH;10H-1FH和80H-9FH沒有使用。

問題3:我要任意顯示一個(gè)字母,數(shù)字怎么辦?

      這個(gè)問題是接著上面一個(gè)問題而言,具體就是:在1602中我要在某一行某個(gè)位置顯示我想要的數(shù)字或是字母,我應(yīng)該對應(yīng)DDRAM地址寫個(gè)什么樣的八位數(shù)據(jù)?例如,我想顯示“1”,那不是就寫個(gè)01H呢?此時(shí)就需要一個(gè)思維轉(zhuǎn)換,我們要顯示的“1”不再是一個(gè)數(shù)據(jù),而是需要轉(zhuǎn)換為一個(gè)圖案,可以看到上圖有1的圖案,該圖案對應(yīng)了31H,所以需要顯示一個(gè)“1”,我們就需要給1602的數(shù)據(jù)總線(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中解決了顯示任意一個(gè)字母和數(shù)字,但是漢字在圖中找不到漢字,怎么辦?這時(shí)候需要使用CGRAM了,先用字模軟件,將對應(yīng)漢字的變?yōu)槎M(jìn)制數(shù)。


例如我想要顯示一個(gè)“電”字,由于1602中顯示的圖案為5*7或是5*10,所以在8*8中左邊三列不能使用。得到8列八位數(shù)據(jù):04, 1F, 15,1F, 15,1F, 04,07.

然后就需要將這8個(gè)8bit數(shù)據(jù)寫入CGRAM中,寫CGRAM需要使用指令:


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

最后就是顯示,也就是如果需要將“電”顯示在1602中,就講地址指針指向DDRAM,然后寫數(shù)據(jù)為00H。

2.關(guān)注時(shí)序

      上面已經(jīng)了解到要讓1602顯示就需要將特地的地址輸入特地的值,如果你剛接觸1602,此時(shí)一定特別想了解怎么將上面的思路轉(zhuǎn)換為verilog代碼。但是一定需要注意時(shí)序問題,1602的讀寫時(shí)序?yàn)椋?/font>




這里特別注意了setup和 hold time,但是實(shí)際使用中由于1602顯示不需要很高的時(shí)序,所以我們只需降低1602工作的時(shí)鐘就可以很容易滿足1602的時(shí)序要求。

    在上篇博文中已經(jīng)說了1602顯示原理,在這篇主要是講講怎么將那些原理轉(zhuǎn)換為verilog代碼。1602的顯示有很多種選擇,一共有11條指令,可以根據(jù)不同設(shè)置,達(dá)到不同的顯示,下面給了verilog顯示代碼,可以以此為基礎(chǔ),修改為具體應(yīng)用。由于明天是五一勞動節(jié),所以祝大家五一快樂。

代碼顯示結(jié)果:


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.時(shí)鐘模塊

//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;
                           end
                  else
                         cnt_1   <= cnt_1+1;
             end
        else    cnt_2   <= cnt_2+1;
end 
assign clk_500 = clk_500hz;
endmodule

3.顯示模塊

//module: lcd_show.v
//data:2014-04-30
//addr: kb129         
//info: this is all the lcd module ,can show 8 zhongwen .
module lcd_show(
        clk_LCD,
        rst,
        en,
        RS,
        RW,
        data
);
input        clk_LCD;  // 500Hz
input        rst;      
output     en,RS,RW;
output   reg  [7:0]  data;
reg                 RS,en_sel;
reg      [4:0]    disp_count;
reg      [4:0]   wrtie_count;
reg      [2:0]   num;
reg      [3:0]   state;
parameter   clear_lcd           = 4'b0000,                    //清屏并光標(biāo)復(fù)位
                  set_disp_mode   = 4'b0001,                    //設(shè)置顯示模式:8位2行5x7點(diǎn)陣   
                  disp_on             = 4'b0010,                   //顯示器開、光標(biāo)不顯示、光標(biāo)不允許閃爍
                      shift_down    = 4'b0011,                    //文字不動,光標(biāo)自動右移
                    write_cgram    = 4'b0100,                    //寫中文進(jìn)入CGRAM,以顯示中文  
                  write_data_first  = 4'b0101,                    //寫入第一行顯示的數(shù)據(jù)
             write_data_second  = 4'b0110,                    //寫入第二行顯示的數(shù)據(jù)
                                 idel     = 4'b0111;                    //空閑狀態(tài)   
assign  RW = 1'b0;                            //RW=0時(shí)對LCD模塊執(zhí)行寫操作
assign  en = en_sel ? clk_LCD : 1'b0;
   
reg [7:0] data_character  [7:0];    //this is 五   00H
reg [7:0] data_character2 [7:0];    //節(jié)           01H
reg [7:0] data_character3 [7:0];   //日           02H
always @(posedge clk_LCD )
begin
    data_character[0] <= 8'h00;
    data_character[1] <= 8'h1e;
    data_character[2] <= 8'h08;
    data_character[3] <= 8'h1e;
    data_character[4] <= 8'h0a;
    data_character[5] <= 8'h0a;
    data_character[6] <= 8'h1F;
    data_character[7] <= 8'h00;
    data_character2[0] <= 8'h0A;
    data_character2[1] <= 8'h1f;
    data_character2[2] <= 8'h0A;
    data_character2[3] <= 8'h1f;
    data_character2[4] <= 8'h05;
    data_character2[5] <= 8'h05;
    data_character2[6] <= 8'h05;
    data_character2[7] <= 8'h04;
    data_character3[0] <= 8'h00;
    data_character3[1] <= 8'h1F;
    data_character3[2] <= 8'h11;
    data_character3[3] <= 8'h11;
    data_character3[4] <= 8'h1f;
    data_character3[5] <= 8'h11;
    data_character3[6] <= 8'h11;
    data_character3[7] <= 8'h1f;
end

reg [7:0]     data_first_line      [15:0];  //first line show data
reg [7:0]     data_second_line [15:0];  //second line show data
always @(posedge clk_LCD )
begin
   data_first_line[0] <= 8'h54;
   data_first_line[1] <= 8'h6F;
   data_first_line[2] <= 8'h20;
   data_first_line[3] <= 8'h6d;
   data_first_line[4] <= 8'h79;
   data_first_line[5] <= 8'h20;
   data_first_line[6] <= 8'h66;
   data_first_line[7] <= 8'h72;
   data_first_line[8] <= 8'h69;
   data_first_line[9] <= 8'h65;
   data_first_line[10] <= 8'h6e;
   data_first_line[11] <= 8'h64;
   data_first_line[12] <= 8'h73;
   data_first_line[13] <= 8'h8a;
 data_second_line[1] <= 8'h00;
 data_second_line[2] <= 8'h2d;
 data_second_line[3] <= 8'h01;
 data_second_line[4] <= 8'h02;
 data_second_line[5] <= 8'h68;
 data_second_line[6] <= 8'h61;
 data_second_line[7] <= 8'h70;
 data_second_line[8] <= 8'h70;
 data_second_line[9] <= 8'h79;
end

always @(posedge clk_LCD or negedge rst)
begin
   if(!rst)
      begin
          state         <= clear_lcd;             //復(fù)位:清屏并光標(biāo)復(fù)位  
          RS             <= 1'b0;                  //復(fù)位:RS=0時(shí)為寫指令;                      
          data          <= 8'b0;                  //復(fù)位:使DB8總線輸出全0
          en_sel        <= 1'b1;                  //復(fù)位:開啟夜晶使能信號
          disp_count <= 5'b0;
                 num   <= 3'b0;
        wrtie_count <= 5'b0;
      end
   else
      case(state)
      clear_lcd:                               //初始化LCD模塊
             begin          //清屏并光標(biāo)復(fù)位
                state  <= set_disp_mode;
                data  <= 8'h01;               
             end
      set_disp_mode:        //設(shè)置顯示模式:8位2行5x8點(diǎn)陣 
             begin
                state  <= disp_on;
                data  <= 8'h38;                              
             end
      disp_on:            //顯示器開、光標(biāo)不顯示、光標(biāo)不允許閃爍
             begin
                state  <= shift_down;
                data  <= 8'h0c;                           
             end
      shift_down:        //文字不動,光標(biāo)自動右移 
            begin
                state  <= write_cgram;
                data  <= 8'h06;                         
            end
      write_cgram:       //寫CGRAM
            begin
    case(num)
    0:begin
             data  <= 8'h40;        //the first character addr
             num   <= num+1;
             state <= write_cgram;
      end
    1:begin
             if(wrtie_count==8)
                  begin
                        data <= 8'h48;  //the second character addr
                        RS   <= 1'b0;
                        num  <= num+1;
                        state<= write_cgram;
                        wrtie_count <= 0;
                 end
           else
                 begin
                        data <= data_character[wrtie_count];
                        RS   <= 1'b1;
                        wrtie_count <= wrtie_count + 1'b1;
                        state     <= write_cgram;
                   end          
      end
    2:begin
            if(wrtie_count==8)
                  begin
                          data <= 8'h50;  //the second character addr
                          RS   <= 1'b0;
                          num  <= num+1;
                          state<= write_cgram;
                          wrtie_count <= 0;
                   end
             else
                   begin
                        data <= data_character2[wrtie_count];
                        RS   <= 1'b1;
                        wrtie_count <= wrtie_count + 1'b1;
                        state     <= write_cgram;
                 end      
      end
    3:begin
            if(wrtie_count==8)
                   begin
                           data <= 8'h80;  //the DDROM first line start addr
                           RS   <= 1'b0;

                           state<= write_data_first;
                          wrtie_count <= 0;
                   end
           else
                   begin
                         data <= data_character3[wrtie_count];
                         RS   <= 1'b1;
                         wrtie_count <= wrtie_count + 1'b1;
                        state     <= write_cgram;
                   end      
      end
       endcase
   end
      write_data_first:              //顯示第一行                         
            begin
                if(disp_count == 14)                      
                    begin
                        data    <= 8'hc2;               
                        RS     <= 1'b0;
                        disp_count   <= 4'b0;
                        state    <= write_data_second;        
                    end
                else
                    begin
                        data    <= data_first_line[disp_count];
                        RS     <= 1'b1;                  
                        disp_count   <= disp_count + 1'b1;
                        state    <= write_data_first;
                    end
            end
      write_data_second:                      //顯示第二行
            begin
                if(disp_count == 9)
                    begin
                        en_sel   <= 1'b0;
                        RS    <= 1'b0;
                        disp_count  <= 4'b0;
                        state   <= idel;                     
                    end
                else
                    begin
                        data    <= data_second_line[disp_count+1];
                        RS     <= 1'b1;
                        disp_count   <= disp_count + 1'b1;
                        state    <= write_data_second;
                    end             
            end
      idel:            //寫完進(jìn)入空閑狀態(tài)
            begin
                state <=  idel;             //在Idel狀態(tài)循環(huán) 
            end
      default:  state <= clear_lcd;         //若state為其他值,則將state置為Clear_Lcd
      endcase
end
endmodule

4.11條指令詳細(xì)說明

NO1:


NO.2

NO.3

NO.4

NO.5

NO.6

NO.7

NO.8 設(shè)定DDRAM地址

NO.9

NO.10

NO.11 從CGRAM和DDRAM中讀取數(shù)據(jù)





相關(guān)文章