Xilinx ZYNQ学习笔记(3)——FPGA纯PL驱动显示LCD1602

天天用ila显示内部寄存器之类的实在是一件非常麻烦的事情,而且及时性太差。
为了让FPGA能及时显示一些东西,比如某个变量型寄存器,我想我需要一种东西来帮助我显示。当然,这么高级的ZYNQ是肯定没有数码管之类的那种东西的,我也不知道开发板设计的时候为什么不整这个玩意上去,可能是认为太low了吧。

于是就找找手边,最简单的东西肯定就是LCD1602了,这个以前用单片机玩烂了的东西,不仅时序简单,还内置字库,可以直接丢ascii码给它而不用管字库的事情,感觉挺方便的。

在这里插入图片描述
看一下这副时序图,简单明了。我们需要搞清楚每个信号的作用。

RS:指令、数据选择端。对LCD1602发送指令时设为0,发送数据时设为1
R/W:读取、写入选择端。发送数据时设为0,读取数据时设为1
DB0—DB7:数据传输端,要发送的具体指令或数据都是在这8个端口上表示的。
E:使能端,让这个引脚设为1,就会让其他引脚的配置生效,之后应该置0

LCD1602这种东西,就没有必要从LCD回读什么信息了,之所以加了这个功能,我想可能是有些模组上带有按钮之类的东西,故需要加入这样一个功能。也就是说,RW这根线永远为0,等会儿我们在程序中直接一直置0就可了。
使能就不用说了。因为这东西毕竟还是串行时序的,所以我们还是需要RS这个东西,来让屏幕知道我们到底发了个什么玩意儿。
DB0-7的并行数据方式,无疑对FPGA来说比较友好,你可以直接一排一排的推数据过去,不用模拟慢吞吞的串行时序。

好了。我们已经知道了每个数据线是搞毛的,现在只要弄清楚要发什么东西,就可以把这东西驱动起来了。

查询资料,在上电后首先需要初始化。过程如下:
上电后首先等一下下,就是上电延迟。等他反应过来
写指令38H(不检测忙信号)
延迟5ms
写指令38H(不检测忙信号)
写指令38H:显示模式设置
写指令08H:显示关闭
写指令01H:显示清屏
写指令06H:显示光标移动设置
写指令0CH:显示开启以及光标设置

有意思的是,LCD1602画字符是一个一个画的,所以它支持显示光标这种东西。如果是别的显示器件,恐怕还得用单片机自己画个光标罢。

module lcd_1602_driver(
                clk    ,
                rst_n  ,
                lcd_en ,
                lcd_rw ,  //因为只执行写操作,所以永远为0.
                lcd_rs ,
                lcd_data,
                key1,
                key2,
              );
              
input        clk    ;
input        rst_n  ;
input        key1   ;
input        key2   ;

output       lcd_en ;
output       lcd_rw ;
output       lcd_rs ;
output [7:0] lcd_data;

wire         clk ;
wire         key1 ;
wire         key2 ;
wire         rst_n  ;
wire         lcd_en ;
wire         lcd_rw;
reg  [7:0]   lcd_data;
reg          lcd_rs  ;
reg [5:0]    c_state ;
reg [5:0]    n_state ;
wire  [127:0]  row_1;
wire  [127:0]  row_2;
wire        write_flag;
reg [7:0]   adder;
reg [32:0]   add_counter ;
reg [15:0]   plus;
wire        key_out;
assign row_1 ="TerayTech 2020  " ;  //第一行显示的内容
assign row_2 ="PUSH TO         ";  //第二行显示的内容
//----------------------------------------------------------------------
//initialize
//first step is waitng more than 20 ms. 数据手册要求的,目的是等待系统上电稳定。
parameter TIME_100MS = 200_000 ; //20000000/20=1000_000
parameter TIME_20MS = 1000_000 ; //20000000/20=1000_000
//parameter TIME_15MS = 9'h100 ; //just for test
parameter TIME_500HZ= 100_000  ; //
//parameter TIME_500HZ= 4'hf;  //just for test
//use gray code   
parameter         IDLE=    8'h00  ;  //因为此状态机一共有40个状态,所以这里用了格雷码,一次只有1位发生改变。00 01 03 02                      
parameter SET_FUNCTION=    8'h01  ;       
parameter     DISP_OFF=    8'h03  ;
parameter   DISP_CLEAR=    8'h02  ;
parameter   ENTRY_MODE=    8'h06  ;
parameter   DISP_ON   =    8'h07  ;
parameter    ROW1_ADDR=    8'h05  ;       
parameter       ROW1_0=    8'h04  ;
parameter       ROW1_1=    8'h0C  ;
parameter       ROW1_2=    8'h0D  ;
parameter       ROW1_3=    8'h0F  ;
parameter       ROW1_4=    8'h0E  ;
parameter       ROW1_5=    8'h0A  ;
parameter       ROW1_6=    8'h0B  ;
parameter       ROW1_7=    8'h09  ;
parameter       ROW1_8=    8'h08  ;
parameter       ROW1_9=    8'h18  ;
parameter       ROW1_A=    8'h19  ;
parameter       ROW1_B=    8'h1B  ;
parameter       ROW1_C=    8'h1A  ;
parameter       ROW1_D=    8'h1E  ;
parameter       ROW1_E=    8'h1F  ;
parameter       ROW1_F=    8'h1D  ;

parameter    ROW2_ADDR=    8'h1C  ;
parameter       ROW2_0=    8'h14  ;
parameter       ROW2_1=    8'h15  ;
parameter       ROW2_2=    8'h17  ;
parameter       ROW2_3=    8'h16  ;
parameter       ROW2_4=    8'h12  ;
parameter       ROW2_5=    8'h13  ;
parameter       ROW2_6=    8'h11  ;
parameter       ROW2_7=    8'h10  ;
parameter       ROW2_8=    8'h30  ;
parameter       ROW2_9=    8'h31  ;
parameter       ROW2_A=    8'h33  ;
parameter       ROW2_B=    8'h32  ;
parameter       ROW2_C=    8'h36  ;
parameter       ROW2_D=    8'h37  ;
parameter       ROW2_E=    8'h35  ;
parameter       ROW2_F=    8'h34  ;


//20ms的计数器,即初始化第一步
reg [19:0] cnt_20ms ;
always  @(posedge clk or negedge rst_n)begin
    if(rst_n==1'b0)begin
        cnt_20ms<=0;
    end
    else if(cnt_20ms == TIME_20MS -1)begin
        cnt_20ms<=cnt_20ms;
    end
    else
        cnt_20ms<=cnt_20ms + 1 ;
end
wire delay_done = (cnt_20ms==TIME_20MS-1)? 1'b1 : 1'b0 ;
//----------------------------------------------------------------------
//500ns  这里是分频,因为LCD1602的工作频率是500HZ,而FPGA是50Mhz,所以要分频
reg [19:0] cnt_500hz;
always  @(posedge clk or negedge rst_n)begin
    if(rst_n==1'b0)begin
        cnt_500hz <= 0;
    end
    else if(delay_done==1)begin
        if(cnt_500hz== TIME_500HZ - 1)
            cnt_500hz<=0;
        else
            cnt_500hz<=cnt_500hz + 1 ;
            
    end
    else
        cnt_500hz<=0;
end

//always  @(posedge clk or negedge rst_n)begin
//    if(rst_n==1'b0)begin
//        adder <= 0;
//        add_counter <= 0;
//    end
//    else if(add_counter==32'd49999999)begin
//        adder <= adder + 1;
//    end
//    else
//        add_counter <= add_counter + 1;
//end


// cycle counter:from 0 to 4 sec
always@(posedge clk or negedge rst_n)
begin
    if (rst_n == 1'b0)begin
        add_counter <= 32'd0;                     //when the reset signal valid,time counter clearing
        plus <= 16'd00;
        end
    else if (add_counter == 32'd999_999)      //4 seconds count(50M*4-1=199999999)
    begin
        add_counter <= 32'd0;                     //count done,clearing the time counter
        plus <= plus + 1'b1;
        end
    else if (plus == 16'd9999)
        plus <= 16'b0;
    else
        add_counter <= add_counter + 32'd1;             //timer counter = timer counter + 1
end




// LED control
always@(posedge clk or negedge rst_n)
begin
    if (rst_n == 1'b0)begin
        adder <= 8'd33;                     //when the reset signal active
        
        end
    else if (key_out == 1'b0)begin       //time counter count to 1st sec,LED1 lighten
    while(key_out == 1'b1)begin
    end
    adder <= adder + 1'd1;
    end
    else if (adder == 8'd127)
    adder <= 8'd33;
end


assign lcd_en = (cnt_500hz>(TIME_500HZ-1)/2)? 1'b0 : 1'b1;  //下降沿
assign write_flag = (cnt_500hz==TIME_500HZ - 1) ? 1'b1 : 1'b0 ;

//----------------------------------------------------------------------状态机
always  @(posedge clk or negedge rst_n)begin
    if(rst_n==1'b0)begin
        c_state <= IDLE    ;
    end
    else if(write_flag==1) begin
        c_state<= n_state  ;
    end
    else
        c_state<=c_state   ;
end

always  @(*)begin
    case (c_state)
        IDLE: n_state = SET_FUNCTION ;
SET_FUNCTION: n_state = DISP_OFF     ;
    DISP_OFF: n_state = DISP_CLEAR   ;
  DISP_CLEAR: n_state = ENTRY_MODE   ;
  ENTRY_MODE: n_state = DISP_ON      ;
  DISP_ON   : n_state = ROW1_ADDR    ;
   ROW1_ADDR: n_state = ROW1_0       ;
      ROW1_0: n_state = ROW1_1       ;
      ROW1_1: n_state = ROW1_2       ;
      ROW1_2: n_state = ROW1_3       ;
      ROW1_3: n_state = ROW1_4       ;
      ROW1_4: n_state = ROW1_5       ;
      ROW1_5: n_state = ROW1_6       ;
      ROW1_6: n_state = ROW1_7       ;
      ROW1_7: n_state = ROW1_8       ;
      ROW1_8: n_state = ROW1_9       ;
      ROW1_9: n_state = ROW1_A       ;
      ROW1_A: n_state = ROW1_B       ;
      ROW1_B: n_state = ROW1_C       ;
      ROW1_C: n_state = ROW1_D       ;
      ROW1_D: n_state = ROW1_E       ;
      ROW1_E: n_state = ROW1_F       ;
      ROW1_F: n_state = ROW2_ADDR    ;

   ROW2_ADDR: n_state = ROW2_0       ;
      ROW2_0: n_state = ROW2_1       ;
      ROW2_1: n_state = ROW2_2       ;
      ROW2_2: n_state = ROW2_3       ;
      ROW2_3: n_state = ROW2_4       ;
      ROW2_4: n_state = ROW2_5       ;
      ROW2_5: n_state = ROW2_6       ;
      ROW2_6: n_state = ROW2_7       ;
      ROW2_7: n_state = ROW2_8       ;
      ROW2_8: n_state = ROW2_9       ;
      ROW2_9: n_state = ROW2_A       ;
      ROW2_A: n_state = ROW2_B       ;
      ROW2_B: n_state = ROW2_C       ;
      ROW2_C: n_state = ROW2_D       ;
      ROW2_D: n_state = ROW2_E       ;
      ROW2_E: n_state = ROW2_F       ;
      ROW2_F: n_state = ROW1_ADDR    ;
     default: n_state = n_state      ;
   endcase 
   end   

   assign lcd_rw = 0;
   always  @(posedge clk or negedge rst_n)begin
       if(rst_n==1'b0)begin
           lcd_rs <= 0 ;   //order or data  0: order 1:data
       end
       else if(write_flag == 1)begin
           if((n_state==SET_FUNCTION)||(n_state==DISP_OFF)||
              (n_state==DISP_CLEAR)||(n_state==ENTRY_MODE)||
              (n_state==DISP_ON ) ||(n_state==ROW1_ADDR)||
              (n_state==ROW2_ADDR))begin
           lcd_rs<=0 ;
           end 
           else  begin
           lcd_rs<= 1;
           end
       end
       else begin
           lcd_rs<=lcd_rs;
       end     
   end                   

   always  @(posedge clk or negedge rst_n)begin
       if(rst_n==1'b0)begin
           lcd_data<=0 ;
       end
       else  if(write_flag)begin
           case(n_state)

                 IDLE: lcd_data <= 8'hxx;
         SET_FUNCTION: lcd_data <= 8'h38; //2*16 5*8 8位数据
             DISP_OFF: lcd_data <= 8'h08;
           DISP_CLEAR: lcd_data <= 8'h01;
           ENTRY_MODE: lcd_data <= 8'h06;
           DISP_ON   : lcd_data <= 8'h0c;  //显示功能开,没有光标,且不闪烁,
            ROW1_ADDR: lcd_data <= 8'h80; //00+80
               ROW1_0: lcd_data <= row_1 [127:120];
               ROW1_1: lcd_data <= row_1 [119:112];
               ROW1_2: lcd_data <= row_1 [111:104];
               ROW1_3: lcd_data <= row_1 [103: 96];
               ROW1_4: lcd_data <= row_1 [ 95: 88];
               ROW1_5: lcd_data <= row_1 [ 87: 80];
               ROW1_6: lcd_data <= row_1 [ 79: 72];
               ROW1_7: lcd_data <= row_1 [ 71: 64];
               ROW1_8: lcd_data <= row_1 [ 63: 56];
               ROW1_9: lcd_data <= row_1 [ 55: 48];
               ROW1_A: lcd_data <= row_1 [ 47: 40];
               ROW1_B: lcd_data <= row_1 [ 39: 32];
               ROW1_C: lcd_data <= row_1 [ 31: 24];
               ROW1_D: lcd_data <= row_1 [ 23: 16];
               ROW1_E: lcd_data <= row_1 [ 15:  8];
               ROW1_F: lcd_data <= row_1 [  7:  0];

            ROW2_ADDR: lcd_data <= 8'hc0;      //40+80
               ROW2_0: lcd_data <= row_2 [127:120];
               ROW2_1: lcd_data <= row_2 [119:112];
               ROW2_2: lcd_data <= row_2 [111:104];
               ROW2_3: lcd_data <= row_2 [103: 96];
               ROW2_4: lcd_data <= row_2 [ 95: 88];
               ROW2_5: lcd_data <= row_2 [ 87: 80];
               ROW2_6: lcd_data <= row_2 [ 79: 72];
               ROW2_7: lcd_data <= row_2 [ 71: 64];
               ROW2_8: lcd_data <= ((plus%32'd1000000)/32'd100000)+8'b00110000;//ROW2_8: lcd_data <= row_2 [ 63: 56];
               ROW2_9: lcd_data <= ((plus%32'd100000)/16'd10000)+8'b00110000;//ROW2_9: lcd_data <= row_2 [ 55: 48];
               ROW2_A: lcd_data <= ((plus%16'd10000)/16'd1000)+8'b00110000;//ROW2_A: lcd_data <= row_2 [ 47: 40];
               ROW2_B: lcd_data <= ((plus%16'd1000)/16'd100)+8'b00110000;//ROW2_B: lcd_data <= row_2 [ 39: 32];
               ROW2_C: lcd_data <= ((plus%16'd100)/8'd10)+8'b00110000;//ROW2_C: lcd_data <= row_2 [ 31: 24];
               ROW2_D: lcd_data <= (plus%8'd10)+8'b00110000;//row_2 [ 23: 16];
               ROW2_E: lcd_data <= row_2 [ 15:  8];
               ROW2_F: lcd_data <= adder;
           endcase                     
       end
       else
              lcd_data<=lcd_data ;
   end

debounce U_debounce(
    .clk(clk),
    .rst_n(rst_n),
    .key_in(key2),
    .key_out(key_out)
    );

endmodule
  • 3
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值