11.EP4CE10F17使用LCD1602显示信息

前记:师夷长技以自强


1.基本概念

LCD1602:可以显示两行每行16列的字符,故称为1602。实验中使用的是3.3v的1602显示模块,可以在某宝中随表找到。

端口说明

编号名称功能
1VSS电源地
2VDD电源正极
3VL液晶显示偏压,接VDD时对比度最弱,接地时对比度最强
4RS数据/命令选择,1-数据寄存器,0-指令寄存器
5R/W读/写选择,1-读,0-写
6E使能信号
7-14D双向数据线
15BLA背光正极
16BLK背光负极

指令:lcd1602共有11条指令,下面逐一介绍

(1)清屏指令

RSR/WDB7DB6DB5DB4DB3DB2DB1DB0
0000000001

说明:清除屏幕显示内容,光标返回屏幕左上角,执行该指令时间较长。

(2)光标归为指令

RSR/WDB7DB6DB5DB4DB3DB2DB1DB0
000000001x

说明:光标返回屏幕左上角,不改变屏幕显示内容。

(3)输入模式设置指令

RSR/WDB7DB6DB5DB4DB3DB2DB1DB0
00000001I/DS

I/D=1:写入数据后光标右移

I/D=0:写入数据后光标左移

S=1:显示移动

S=0:显示不移动

说明:推荐值0x06,即写入数据后光标右移,显示不移动

(4)显示开关控制指令

RSR/WDB7DB6DB5DB4DB3DB2DB1DB0
0000001DCB

D=1:显示开,D=0:显示关。

C=1:光标显示,C=0:光标不显示

B=1:光标闪烁,B=0:光标不闪烁

说明:推荐值0x0c,即显示开,不显示光标,光标不闪烁。

(5)光标或显示移动指令

RSR/WDB7DB6DB5DB4DB3DB2DB1DB0
000001S/CR/Lxx

S/C=1:移动对象是显示,此时光标会跟着显示移动

S/C=0:移动对象是光标

R/L=1:移动方向是右

R/L=0:移动方向是左

说明:这个指令可以实现屏幕的滚动显示效果。

(6)工作方式设置指令

RSR/WDB7DB6DB5DB4DB3DB2DB1DB0
00001DLNFxx

DL=1:8位数据接口(D7-D0)

DL=0:4位数据接口(D7-D4)

N=1:两行显示

N=0:一行显示

F=1:5x10点阵字符

F=0:5x8点阵字符

说明:推荐值0x38,即8位数据接口、两行显示、5x8点阵。因为不能以两行5x10点阵方式进行显示,则0x38与0x3c的效果是一样的。

(7)设置CGRAM地址指令

RSR/WDB7DB6DB5DB4DB3DB2DB1DB0
0001aaaaaa

说明:用户自定义没有的字符时会用到。

(8)设置DDRAM地址指令

RSR/WDB7DB6DB5DB4DB3DB2DB1DB0
001aaaaaaa

说明:在对DDRAM进行读写前,首先要设置DDRAM地址,然后才能进行读写。

(9)读忙信号和地址计数器AC

RSR/WDB7DB6DB5DB4DB3DB2DB1DB0
01BFaaaaaaa

BF=1:lcd1602正忙,不能接受单片机指令。

BF=0:lcd1602空闲,可以接受单片机指令。

说明:据说这条指令执行不成功,有一个简化的方法是设置恰当的延时。

(10)写数据到CGRAM或DDRAM

RSR/WDB7DB6DB5DB4DB3DB2DB1DB0
10dddddddd

说明:指令执行时,要在DB7-DB0上设置好要写入的数据,然后执行写命令。

(11)从CGRAM或DDRAM读数据指令

RSR/WDB7DB6DB5DB4DB3DB2DB1DB0
11dddddddd

说明:指令执行时,要先设置好CGRAM或DDRAM的地址,然后执行读命令,数据最后被读入DB7-DB0.

 

一般的初始化过程

延时15ms,

写指令38H(不检测忙信号),

延时5ms,

以后每次写指令、读/写数据操作均需要检测忙信号

写指令38H(显示模式)

写指令08H(显示关闭)

写指令01H(显示清屏)

写指令06H(光标移动和显示模式)

写指令0CH(显示开启,无光标)

注意问题:

1.除了第一条写的指令外,以后写的指令(或者数据)都要检查模块是否忙。

2.写数据前先写地址,注意第二行地址的D7恒为1.

3.使用lcd1602的第一步应该是初始化模块,设置正确的显示和光标模式等。

2.LCD1602与FPGA的连接

lcd1602的管脚排列顺序如下

12345678910111213141516
VSSVDDVLRSR/WED0D1D2D3D4D5D6D7BLABLK

开发板上的P1插槽管脚信息如下

对比可知,lcd1602模块的1管脚连P1的29管脚,lcd1602模块的16管脚连P1的1管脚,最后多出的一个管脚通过杜邦线连接开发板的GND即可。

3.驱动设计

1.lcd驱动模块的输入输出管脚大致如下

信号名功能
Clk系统时钟
Rst_n系统复位
Pos[4:0]光标位置
Data[7:0]需要显示字符的ASCII码
Set_Cursor设置光标指令
Set_Data设置要显示的数据
Clr_Screen清屏指令
RSlcd1602数据/命令选择
R/Wlcd1602读写选择
Elcd1602使能
Dout[7:0]lcd1602数据接口
Init_Donelcd1602初始化完成

 

2.底层读写时序模块

由于读写lcd模块设计到几个信号的操作,里面有延时要求,所以应该设计一个底层模块,专门负责命令和数据的写。对于lcd忙否的判断,可以通过延时5ms的方法确定lcd足够可以完成命令和数据的接收执行和显示更新等,这样避免了发送指令等待接收再判断的复杂过程。

(1)写时序图和对应的时序参数

(2)模块和状态的设计

设计模块的输入输出管脚如下

可以划分为下面几个状态

IDLE        : wait to work
SET_A        : set RS RW DB to a appropriate value
DLY1        :wait tsp1
SET_E        :set E to 1
DLY2        :wait tpw
RSET_E        :set E to 0
DLY3        :wait 200ns

根据状态的转移关系得到如下图,其中省去了自环的转移

(3)编码

模块的veryLog文件如下

module lcd1602_driver(
    Clk,
    Rst_n,
    Set_Data,
    Set_Cmd,
    Data_Drv,
    RS,
    RW,
    DB,
    E,
    Write_Done
);
    input Clk;
    input Rst_n;
    input Set_Data;
    input Set_Cmd;
    input [7:0]Data_Drv;
    
    output reg RS;
    output reg RW;
    output reg [7:0]DB;
    output reg E;
    output reg Write_Done;
    
    localparam IDLE   = 7'b0000001,
                  SET_A  = 7'b0000010,
                  DLY1   = 7'b0000100,
                  SET_E  = 7'b0001000,
                  DLY2     = 7'b0010000,
                  RSET_E    = 7'b0100000,
                  DLY3   = 7'b1000000;
    reg [6:0]state;
    reg [3:0]Cnt;
    always @ (posedge Clk,negedge Rst_n)
    if(!Rst_n)
        state <= IDLE;
    else
        case(state)
            IDLE:
                if(Set_Data | Set_Cmd)
                    state <= SET_A;
                else
                    state <= IDLE;
            SET_A:
                state <= DLY1;
            DLY1:
                if(Cnt >= 1)
                    state <= SET_E;
                else
                    state <= DLY1;
            SET_E: 
                state <= DLY2;
            DLY2:
                if(Cnt >= 7)
                    state <= RSET_E;
                else
                    state <= DLY2;                    
            RSET_E:
                state <= DLY3;
            DLY3:
                if(Cnt >= 9)
                    state <= IDLE;
                else
                    state <= DLY3;                    
        endcase
    
    always @ (posedge Clk,negedge Rst_n)
    if(!Rst_n)
        Cnt <= 4'd0;
    else
        case(state)
            IDLE:
                Cnt <= 4'd0;
            SET_A:
                Cnt <= 4'd0;
            DLY1:
                Cnt <= Cnt + 4'd1;
            SET_E: 
                Cnt <= 4'd0;
            DLY2:
                Cnt <= Cnt + 4'd1;                
            RSET_E:
                Cnt <= 4'd0;
            DLY3:
                Cnt <= Cnt + 4'd1;        
        endcase    

    always @ (posedge Clk)
    case(state)
        IDLE:
            begin
                E <= 0;
                Write_Done <= 0;
                if(Set_Data)
                    RS <= 1;
                else if(Set_Cmd)
                    RS <= 0;
                else
                    RS <= RS;
            end
        SET_A:
            begin
                RW <= 0;
                DB <= Data_Drv;                    
            end
        DLY1:
            ;
        SET_E: 
            E <= 1;
        DLY2:
            ;                
        RSET_E:
            E <= 0;
        DLY3:
            if(Cnt >= 9)
                    Write_Done <= 1;
    endcase    
endmodule
 

编写对应的testbench文件如下

`timescale 1ns/1ns
module lcd1602_driver_tb();
    reg Clk;
    reg Rst_n;
    reg Set_Data;
    reg Set_Cmd;
    reg [7:0]Data_Drv;
    
    wire RS;
    wire RW;
    wire [7:0]DB;
    wire E;
    wire Write_Done;
    lcd1602_driver lcd1602_driver(
        Clk,
        Rst_n,
        Set_Data,
        Set_Cmd,
        Data_Drv,
        RS,
        RW,
        DB,
        E,
        Write_Done
    );
    
    initial Clk = 0;
    always #10 Clk = ~Clk;
    
    initial begin
        Rst_n = 0;
        Set_Data = 0;
        Set_Cmd = 0;
        Data_Drv = 8'h38;
        #20;
        Rst_n = 1;
        Set_Data = 1;
        #20;
        Set_Data = 0;
        wait(Write_Done);
        #20;
        Data_Drv = 8'h88;
        Set_Data = 1;
        #20;
        Set_Data = 0;
        wait(Write_Done);
        #20;
        Data_Drv = 8'h56;
        Set_Cmd = 1;
        #20;
        Set_Cmd = 0;
        #20000;
        $stop;
    end

endmodule
 

可以得到预期的仿真结果

3.控制模块的编写

(1)状态划分

大的状态可以分为两部分,第一部分是完成lcd1602的初始化,第二部分是执行用户的命令。

第一部分:

IDLE:空闲状态,也就是系统复位后进入的状态。
DELAY15MS:延时等待15ms的状态。
CMD38H1:第一次发送38指令。
DELAY5MS :延时等待5ms的状态。
CMD38H2:第二次发送38指令。
CMD08H :发送08指令。
CMD01H :发送01指令。
CMD06H :发送06指令。
CMD0CH :发送0C指令。

注:对于初始化所发送命令的来源可以参考lcd1602的使用手册。上面的CMD08H,CMD01H,CMD06H,CMD0CH执行命令之前要延时5ms,以备命令因为模块未执行完而丢失。

第二部分:
USER_CMD :监听用户输入的命令。
WAIT_DONE:等待命令执行完成。

(2)辅助信号

信号名说明
[19:0]cnt计时变量
time_out5ms计时到变量
init_done初始化完成后一直为高,否则为低
  
  
  
  
  
  

(3)状态转移图

 

(4)编码

module lcd1602(
    Clk,
    Rst_n,
    Pos,
    Data,
    Set_Cursor,
    Set_Data,
    Clr_Screen,
    RS,
    RW,
    E,
    Dout,
    VL,
    Init_Done
);

    input Clk;
    input Rst_n;
    input [4:0]Pos;
    input [7:0]Data;
    input Set_Cursor;
    input Set_Data;
    input Clr_Screen;
    output RS;
    output RW;
    output E;
    output [7:0]Dout;
    output VL;
    output reg Init_Done;

    localparam IDLE =         16'b0000_0000_0000_0001,
                  DELAY15MS =     16'b0000_0000_0000_0010,
                  CMD38H1 =     16'b0000_0000_0000_0100,
                  DELAY5MS =     16'b0000_0000_0000_1000,
                  CMD38H2 =     16'b0000_0000_0001_0000,
                  CMD08H =         16'b0000_0000_0010_0000,
                  CMD01H =         16'b0000_0000_0100_0000,
                  CMD06H =         16'b0000_0000_1000_0000,
                  CMD0CH =         16'b0000_0001_0000_0000,
                  USER_CMD =     16'b0000_0010_0000_0000,
                  WAIT_DONE =     16'b0000_0100_0000_0000;
                  
    reg [19:0]cnt;
    reg [15:0]state;
    wire time_out;
    
    assign time_out = (cnt >= 20'd249999);
    
    always @ (posedge Clk,negedge Rst_n)
    if(!Rst_n)
        state <= IDLE;
    else
        case(state)
            IDLE:
                state <= DELAY15MS;
            DELAY15MS:
                if(cnt >= 20'd749999)
                    state <= CMD38H1;
                else
                    state <= state;
            CMD38H1:
                state <= DELAY5MS;
            DELAY5MS:
                if(time_out)
                    state <= CMD38H2;
                else 
                    state <= state;
            CMD38H2:
                if(time_out)
                    state <= CMD08H;
                else 
                    state <= state;
            CMD08H:
                if(time_out)
                    state <= CMD01H;
                else 
                    state <= state;
            CMD01H:
                if(time_out)
                    state <= CMD06H;
                else 
                    state <= state;
            CMD06H:
                if(time_out)
                    state <= CMD0CH;
                else 
                    state <= state;
            CMD0CH:
                if(time_out)
                    state <= USER_CMD;
                else 
                    state <= state;
            USER_CMD:
                if(Set_Cursor|Set_Data|Clr_Screen)
                    state <= WAIT_DONE;
                else 
                    state <= state;
            WAIT_DONE:
                if(time_out)
                    state <= USER_CMD;
                else 
                    state <= state;
        endcase
        
    always @ (posedge Clk,negedge Rst_n)
    if(!Rst_n)
        cnt <= 20'b0;
    else
        case(state)
            IDLE,CMD38H1,USER_CMD:
                cnt <= 20'b0;
            DELAY15MS:
                if(cnt >= 20'd749999)
                    cnt <= 20'b0;
                else
                    cnt <= cnt + 20'b1;
            DELAY5MS,CMD38H2,CMD08H,CMD01H,CMD06H,CMD0CH,WAIT_DONE:
                if(time_out)
                    cnt <= 20'b0;
                else 
                    cnt <= cnt + 20'b1;
        endcase
    
    reg wr_cmd;
    reg wr_data;
    reg [7:0]data_dr;
    always @ (posedge Clk,negedge Rst_n)
    if(!Rst_n)begin
        wr_cmd <= 0;
        wr_data <= 0;
        data_dr <= 8'd0;
        Init_Done <= 0;
    end
    else
        case(state)
            IDLE:
                ;
            DELAY15MS:
                ;
            CMD38H1,CMD38H2:
                if(time_out)
                    begin
                        wr_cmd <= 1;
                        data_dr <= 8'h38;
                    end
                else
                    wr_cmd <= 0;
            DELAY5MS:
                wr_cmd <= 0;
            CMD08H:
                if(time_out)
                begin
                    wr_cmd <= 1;
                    data_dr <= 8'h08;
                end
                else
                    wr_cmd <= 0;
            CMD01H:
                if(time_out)
                begin
                    wr_cmd <= 1;
                    data_dr <= 8'h01;
                end
                else
                    wr_cmd <= 0;
            CMD06H:
                if(time_out)
                begin
                    wr_cmd <= 1;
                    data_dr <= 8'h06;
                end
                else
                    wr_cmd <= 0;
            CMD0CH:
                if(time_out)
                begin
                    wr_cmd <= 1;
                    data_dr <= 8'h0C;
                    Init_Done <= 1;
                end
                else
                    wr_cmd <= 0;
            USER_CMD:
                if(Set_Cursor)
                    begin
                        wr_cmd <= 1;
                        data_dr <= {1'b1,Pos[4],2'b0,Pos[3:0]};
                    end
                else if(Set_Data)
                    begin
                        wr_data <= 1;
                        data_dr <= Data;
                    end
                else if(Clr_Screen)
                    begin
                        wr_cmd <= 1;
                        data_dr <= 8'h01;
                    end
                else 
                    begin
                        wr_cmd <= 0;
                        wr_data <= 0;
                    end
            WAIT_DONE:
                begin
                    wr_cmd <= 0;
                    wr_data <= 0;
                end
        endcase
        
    lcd1602_driver lcd1602_driver(
        .Clk(Clk),
        .Rst_n(Rst_n),
        .Set_Data(wr_data),
        .Set_Cmd(wr_cmd),
        .Data_Drv(data_dr),
        .RS(RS),
        .RW(RW),
        .DB(Dout),
        .E(E),
        .Write_Done()
    );    

    reg [3:0]vl_cnt;
    always @(posedge Clk,negedge Rst_n)
    if(!Rst_n)
        vl_cnt <= 4'd0;
    else
        vl_cnt <= vl_cnt + 4'd1;
        
    assign VL = (vl_cnt > 4'd9);
    
endmodule
 

为了测试lcd1602的显示功能,这里写一个小的测试文件,用到矩阵键盘和lcd1602,矩阵键盘可以设置输入数据的位置,清屏,输入0-9的数据等。

/*
用矩阵键盘输入数据,将数据显示在lcd1602上
键盘:
0    1    2    3
4    5    6    7
8    9    10    10
12    13    14    15
12:在第一行开始输入
13:在第二行开始输入
14:清屏
*/

module lcd1602_test(
    Clk,
    Rst_n,
    Key_Row,
    Key_Col,
    RS,
    RW,
    E,
    VL,
    DB
);
    input Clk;
    input Rst_n;
    input [3:0]Key_Row;
    
    output [3:0]Key_Col;
    output RS;
    output RW;
    output E;
    output VL;
    output [7:0]DB;

    wire Key_Flag;
    wire [3:0]Key_Value;
    MAT_KEY MAT_KEY(
        .Clk(Clk),
        .Rst_n(Rst_n),
        .Row_Data(Key_Row),
        .Col_Data(Key_Col),
        .Key_Flag(Key_Flag),     //按键值是否有效
        .Key_Value(Key_Value)    //按键值
    );
    /*Key_Board Key_Board(
        .Clk(Clk),
        .Rst_n(Rst_n),
        .Key_Board_Row_i(Key_Row),
        .Key_Board_Col_o(Key_Col),
        .Key_Flag(Key_Flag),
        .Key_Value(Key_Value)
    );*/
    reg [4:0]Pos;
    reg [7:0]Data;
    reg Set_Cursor;
    reg Set_Data;
    reg Clr_Screen;
    wire Init_Done;
    
    lcd1602 lcd1602(
        .Clk(Clk),
        .Rst_n(Rst_n),
        .Pos(Pos),
        .Data(Data),
        .Set_Cursor(Set_Cursor),
        .Set_Data(Set_Data),
        .Clr_Screen(Clr_Screen),
        .RS(RS),
        .RW(RW),
        .E(E),
        .VL(VL),
        .Dout(DB),
        .Init_Done(Init_Done)
    );
    
    always @ (posedge Clk,negedge Rst_n)
    if(!Rst_n)begin
        Set_Cursor <= 0;
        Set_Data <= 0;
        Clr_Screen <= 0;
        Pos <= 5'd0;
        Data <= 8'd0;
    end        
    else if(Init_Done & Key_Flag)
        case(Key_Value)
            4'd0:begin Data <= "0";Set_Data <= 1;end
            4'd1:begin Data <= "1";Set_Data <= 1;end
            4'd2:begin Data <= "2";Set_Data <= 1;end
            4'd3:begin Data <= "3";Set_Data <= 1;end
            4'd4:begin Data <= "4";Set_Data <= 1;end
            4'd5:begin Data <= "5";Set_Data <= 1;end
            4'd6:begin Data <= "6";Set_Data <= 1;end
            4'd7:begin Data <= "7";Set_Data <= 1;end
            4'd8:begin Data <= "8";Set_Data <= 1;end
            4'd9:begin Data <= "9";Set_Data <= 1;end
            4'd12:begin Pos <= 5'b00000;Set_Cursor <= 1;end
            4'd13:begin Pos <= 5'b10000;Set_Cursor <= 1;end
            4'd14:begin Clr_Screen <= 1;end
        endcase
    else
        begin
            Set_Cursor <= 0;
            Set_Data <= 0;
            Clr_Screen <= 0;
        end
endmodule
 

关于矩阵键盘的实现可以参考我之前的文章。将分析综合后的文件下到板子后试验,大体上可以使用,就是第0,1,2列的按键按下后会连按,还有多次输入后不能再输入共两个bug。能力有限,欢迎读者纠正。

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值