FPGA-LCD1602显示

一、LCD1602显示原理

LCD1602是一种专门用来显示字母、数字、符号等的点阵型液晶模块。1602的意思是每行显示16个字符,显示两行。LCD1602只能显示ASCII码字符,如数字、大小写字母等。

1、引脚功能说明

LCD1602分为带背光和不带背光两种型号,采用标准的14脚(无背光)或16脚(带背光)接口,各引脚接口说明如下图。
在这里插入图片描述

2、LCD1602的RAM地址映射

LCD1602的控制器大部分为HD44780。它里面有三种存储器:CGROM、CGRAM、DDRAM。DDRAM是显示数据RAM,用来存放待显示的字符代码。DDRAM共80字节,其地址和屏幕的对应关系如下图所示。CGROM和CGRAM是LCD1602模块上的字模存储器。CGROM用来存储192个常用字符的字模,由于已固化在LCD1602模块中,只能读取。CGRAM用来存储用户自定义的字符,可以读也可以写。
在这里插入图片描述
需要在指定位置写入内容时,要先指定地址,如在第一行第一位写入,地址位是00H,再加上D7的1,即80H(0010000000),第二行第一位是40H,再加上D7的1,即C0H(0011000000),以此类推。

3、基本操作时序

读操作时序图如下。
在这里插入图片描述
写操作时序图如下。
在这里插入图片描述

二、源代码

module lcd_1602(clk,rst_n,lcd_en,lcd_rw,lcd_rs,lcd_data);
input clk;
input rst_n;
output lcd_en;      //使能端
output lcd_rw;      //读、写选择端
output lcd_rs;      //指令和数据寄存器选择端
output [7:0]lcd_data;  //数据端

wire clk;
wire rst_n;
wire lcd_en;
wire lcd_rw;
reg lcd_rs;
reg [7:0]lcd_data;

wire [127:0] row_1;  //第一行地址,16x8=128
wire [127:0] row_2;  //第二行地址

//LCD1602需要显示的字符
assign row_1 = "This is the FPGA";   //第一行显示的内容,可以显示16个字符
assign row_2 = "control LCD1602!";   //第二行显示的内容,空格也算字符

parameter TIME_20MS = 1000_000;  //等待20ms,系统上电稳定
parameter TIME_500HZ = 100_000;  //LCD1602的工作频率为500HZ

//模块工作采用状态机驱动
//因为此状态机一共有40个状态,这里用了格雷码,一次只有1位发生改变
parameter IDLE = 8'h00;
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)
      cnt_20ms <= 0;
    else if(cnt_20ms == TIME_20MS - 1)
      cnt_20ms <= cnt_20ms;
    else
      cnt_20ms <= cnt_20ms + 1;
  end
  
wire delay_done = (cnt_20ms == TIME_20MS-1)? 1'b1 : 1'b0;  //上电延时完成
reg [19:0] cnt_500hz;
always @(posedge clk or negedge rst_n)
  begin
    if(rst_n == 1'b0)
      cnt_500hz <= 0;
    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

//使能端,下降沿执行命令
assign lcd_en = (cnt_500hz > (TIME_500HZ-1)/2)? 1'b0 : 1'b1;
//write_flag置高一周期
wire write_flag = (cnt_500hz == TIME_500HZ - 1)? 1'b1 : 1'b0;

//状态机控制LCD1602的显示过程
reg [5:0] c_state;  //当前状态
reg [5:0] n_state;  //下一个状态

always @(posedge clk or negedge rst_n)
  begin
    if(rst_n == 1'b0)
      c_state <= IDLE;
    else if(write_flag == 1)  //每一个工作周期改变一次状态
      c_state <= n_state;
    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)
      lcd_rs <= 0;            //0表示命令,1表示数据
    else if(write_flag == 1)  //当前状态为7个指令中任意一个时,将RS置0
      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))
          lcd_rs <= 0;  //写指令
        else
          lcd_rs <= 1;  //写数据
      end
    else
      lcd_rs <= lcd_rs;
  end

//各状态数据
always @(posedge clk or negedge rst_n)
  begin
    if(rst_n == 1'b0)
      lcd_data <= 0;
    else if(write_flag)
      begin
        case(n_state)
        IDLE:lcd_data <= 8'hxx;
        SET_FUNCTION:lcd_data <= 8'h38;  //设置显示两行,5x7点阵
        DISP_OFF:lcd_data <= 8'h08;      //不显示
        DISP_CLEAR:lcd_data <= 8'h01;    //清屏
        ENTRY_MODE:lcd_data <= 8'h06;    //地址加1
        DISP_ON:lcd_data <= 8'h0C;       //开始显示,没有光标,不闪烁
        
        ROW1_ADDR:lcd_data <= 8'h80; //设置DDRAM地址,从第一行开始,将输入的row_1分配给对应的显示位
        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;  //设置DDRAM,从第二行开始
        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 <= row_2[63:56];
        ROW2_9:lcd_data <= row_2[55:48];
        ROW2_A:lcd_data <= row_2[47:40];
        ROW2_B:lcd_data <= row_2[39:32];
        ROW2_C:lcd_data <= row_2[31:24];
        ROW2_D:lcd_data <= row_2[23:16];
        ROW2_E:lcd_data <= row_2[15:8];
        ROW2_F:lcd_data <= row_2[7:0];
      endcase
    end
  else
    lcd_data <= lcd_data;
  end

endmodule

三、上板验证

在这里插入图片描述

  • 6
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值