Verilog实现LCD1602显示驱动

在做计算器时,需要将结果显示出来,所以就使用了LCD1602模块,这里附上驱动代码和验证代码以及调试心得。

驱动代码如下:

//*************************************
//module name :lcd_drive
//engineer    :Jiahui Wen
//time          :2018-11-30
//function      :lcd1602 drive
//*************************************
module lcd_drive(     
    //input pin
    Clk,
    Rst_n,
    line_rom1,
    line_rom2,
    
    //output pin
    RS, //date(H) or command(L)
    RW, //read(H) or write(L)
    EN, //enable pin
    DB  //date bus
);

//input pin define
input Clk;
input Rst_n;
input [127 : 0]line_rom1; //write date for line 1;
input [127 : 0]line_rom2; //write date for line 2;
//output pin define
output reg RS;
output RW;
output EN;
output reg [7 : 0]DB;
//temporary wire type
wire done; //delay_20ms successed
wire write_flg;
//temporary register type

/****************************************
*function begin
****************************************/

//delay 20ms
reg [19 : 0]count;
localparam delay_20ms = 20'd1000_000;
//localparam delay_20ms = 20'd1000; //just for test;
always@(posedge Clk or negedge Rst_n)
if(!Rst_n)
    count <= 20'd0;
else if(count < delay_20ms - 1'b1)
    count <= count + 1'b1;
else 
    count <= count;

//delay_20ms successed?    
assign done = (count == delay_20ms - 1'b1);

//coduct 500HZ clock for EN pin;
reg [16 : 0]count2; //500HZ
localparam delay_2ms = 17'd100_000;
//localparam delay_2ms = 100; //just for test;
always@(posedge Clk or negedge Rst_n)
if(!Rst_n)  
    count2 <= 17'd0;
else if(done) begin
    if(count2 < delay_2ms - 1'b1)
        count2 <= count2 + 1'b1;
    else 
        count2 <= 17'd0;
end 
else 
    count2 <= 17'd0;
    
assign RW = 1'b0; // only write,never read;
assign EN = (count2 >= delay_2ms / 2) ? 1'b0 : 1'b1; 
assign write_flg = (count2 == delay_2ms - 1'b1) ? 1'b1 : 1'b0;

// 40 states
localparam IDLE = 8'h00;
//LCD1602 init
localparam DISP_SET = 8'h01; //display mode
localparam DISP_OFF = 8'h03; //display off
localparam CLR_SCR = 8'h02; //clear the LCD1602
localparam CURSOR_SET1 = 8'h06; //set cursor
localparam CURSOR_SET2 = 8'h07; //display only
//display 1th line 
localparam ROW1_ADDR = 8'h05;
localparam ROW1_0 = 8'h04;
localparam ROW1_1 = 8'h0C;
localparam ROW1_2 = 8'h0D;
localparam ROW1_3 = 8'h0F;
localparam ROW1_4 = 8'h0E;
localparam ROW1_5 = 8'h0A;
localparam ROW1_6 = 8'h0B;
localparam ROW1_7 = 8'h09;
localparam ROW1_8 = 8'h08;
localparam ROW1_9 = 8'h18;
localparam ROW1_A = 8'h19;
localparam ROW1_B = 8'h1B;
localparam ROW1_C = 8'h1A;
localparam ROW1_D = 8'h1E;
localparam ROW1_E = 8'h1F;
localparam ROW1_F = 8'h1D;
//display 2th line
localparam ROW2_ADDR = 8'h1C;
localparam ROW2_0 = 8'h14;
localparam ROW2_1 = 8'h15;
localparam ROW2_2 = 8'h17;
localparam ROW2_3 = 8'h16;
localparam ROW2_4 = 8'h12;
localparam ROW2_5 = 8'h13;
localparam ROW2_6 = 8'h11;
localparam ROW2_7 = 8'h10;
localparam ROW2_8 = 8'h30;
localparam ROW2_9 = 8'h31;
localparam ROW2_A = 8'h33;
localparam ROW2_B = 8'h32;
localparam ROW2_C = 8'h36;
localparam ROW2_D = 8'h37;
localparam ROW2_E = 8'h35;
localparam ROW2_F = 8'h34;

//FSM : alway1
reg [7 : 0]current_state, next_state;
always@(posedge Clk or negedge Rst_n)
if(!Rst_n)
    current_state <= IDLE;
else if(write_flg)
    current_state <= next_state;
else 
    current_state <= current_state;
    
//FSM : alway2
always@(*) begin 
case(current_state)
    //LCD1602 init
    IDLE : next_state = DISP_SET;
    DISP_SET : next_state = DISP_OFF;
    DISP_OFF : next_state = CLR_SCR;
    CLR_SCR : next_state = CURSOR_SET1;
    CURSOR_SET1 : next_state = CURSOR_SET2;
    CURSOR_SET2 : next_state = ROW1_ADDR;
    //display 1th line 
    ROW1_ADDR : next_state = ROW1_0;
    ROW1_0 : next_state = ROW1_1;
    ROW1_1 : next_state = ROW1_2;
    ROW1_2 : next_state = ROW1_3;
    ROW1_3 : next_state = ROW1_4;
    ROW1_4 : next_state = ROW1_5;
    ROW1_5 : next_state = ROW1_6;
    ROW1_6 : next_state = ROW1_7;
    ROW1_7 : next_state = ROW1_8;
    ROW1_8 : next_state = ROW1_9;
    ROW1_9 : next_state = ROW1_A;
    ROW1_A : next_state = ROW1_B;
    ROW1_B : next_state = ROW1_C;
    ROW1_C : next_state = ROW1_D;
    ROW1_D : next_state = ROW1_E;
    ROW1_E : next_state = ROW1_F;
    ROW1_F : next_state = ROW2_ADDR;
    //display 2th line
    ROW2_ADDR : next_state = ROW2_0;
    ROW2_0 : next_state = ROW2_1;
    ROW2_1 : next_state = ROW2_2;
    ROW2_2 : next_state = ROW2_3;
    ROW2_3 : next_state = ROW2_4;
    ROW2_4 : next_state = ROW2_5;
    ROW2_5 : next_state = ROW2_6;
    ROW2_6 : next_state = ROW2_7;
    ROW2_7 : next_state = ROW2_8;
    ROW2_8 : next_state = ROW2_9;
    ROW2_9 : next_state = ROW2_A;
    ROW2_A : next_state = ROW2_B;
    ROW2_B : next_state = ROW2_C;
    ROW2_C : next_state = ROW2_D;
    ROW2_D : next_state = ROW2_E;
    ROW2_E : next_state = ROW2_F;
    ROW2_F : next_state = ROW1_ADDR;
    default : next_state = IDLE;
endcase 

end 

//FSM : always3-1
always@(posedge Clk or negedge Rst_n)
if(!Rst_n)
    RS <= 1'b0;
else if(write_flg)begin
    if(next_state == IDLE || next_state == DISP_SET ||
       next_state ==DISP_OFF || next_state == CLR_SCR ||
       next_state ==CURSOR_SET1 || next_state == CURSOR_SET2 ||
       next_state == ROW1_ADDR ||next_state == ROW2_ADDR)
        RS <= 1'b0;//L : instruction
    else 
        RS <= 1'b1;//H : data
end 
else 
    RS <= RS;
    
//FSM : always3-2
always@(posedge Clk or negedge Rst_n)
if(!Rst_n)
    DB <= 8'h00;
else if(write_flg)
    case(next_state)
    IDLE : DB <= 8'hxx;
    //LCD1602 init
    DISP_SET : DB <= 8'h38; //display mode : set 16*2, 5*8, 8 bits data
    DISP_OFF : DB <= 8'h08; //display off
    CLR_SCR : DB <= 8'h01; //clear LCD1602
    CURSOR_SET1 : DB <= 8'h06; //set cussor
    CURSOR_SET2 : DB <= 8'h0c; //display on 
    //display 1th line 
    ROW1_ADDR : DB <= 8'h80;
    ROW1_0 : DB <= line_rom1[127 : 120];
    ROW1_1 : DB <= line_rom1[119 : 112];
    ROW1_2 : DB <= line_rom1[111 : 104];
    ROW1_3 : DB <= line_rom1[103 : 96];
    ROW1_4 : DB <= line_rom1[95 : 88];
    ROW1_5 : DB <= line_rom1[87 : 80];
    ROW1_6 : DB <= line_rom1[79 : 72];
    ROW1_7 : DB <= line_rom1[71 : 64];
    ROW1_8 : DB <= line_rom1[63 : 56];
    ROW1_9 : DB <= line_rom1[55 : 48];
    ROW1_A : DB <= line_rom1[47 : 40];
    ROW1_B : DB <= line_rom1[39 : 32];
    ROW1_C : DB <= line_rom1[31 : 24];
    ROW1_D : DB <= line_rom1[23 : 16];
    ROW1_E : DB <= line_rom1[15 : 8];
    ROW1_F : DB <= line_rom1[7 : 0];
    //display 2th line 
    ROW2_ADDR : DB <= 8'hC0;
    ROW2_0 : DB <= line_rom2[127 : 120];
    ROW2_1 : DB <= line_rom2[119 : 112];
    ROW2_2 : DB <= line_rom2[111 : 104];
    ROW2_3 : DB <= line_rom2[103 : 96];
    ROW2_4 : DB <= line_rom2[95 : 88];
    ROW2_5 : DB <= line_rom2[87 : 80];
    ROW2_6 : DB <= line_rom2[79 : 72];
    ROW2_7 : DB <= line_rom2[71 : 64];
    ROW2_8 : DB <= line_rom2[63 : 56];
    ROW2_9 : DB <= line_rom2[55 : 48];
    ROW2_A : DB <= line_rom2[47 : 40];
    ROW2_B : DB <= line_rom2[39 : 32];
    ROW2_C : DB <= line_rom2[31 : 24];
    ROW2_D : DB <= line_rom2[23 : 16];
    ROW2_E : DB <= line_rom2[15 : 8];
    ROW2_F : DB <= line_rom2[7 : 0];
    endcase 
else 
    DB <= DB;

endmodule 

顶层测试模块如下:

//*************************************
//module name :lcd1602_top
//engineer    :Jiahui Wen
//time          :2018-12-02
//function      :lcd1602 drive test top
//*************************************
module lcd1602_top(
    //input pin
    Clk,
    Rst_n,
    
    //output pin
    RS, //date(H) or command(L)
    RW, //read(H) or write(L)
    EN, //enable pin
    DB  //date bus
);
//input pin define
input Clk;
input Rst_n;
//output pin define
output RS;
output RW;
output EN;
output [7 : 0]DB;
//temporary register type
localparam [127 : 0]line_rom1 = "hello FPGA!     "; //write date for line 1;
localparam [127 : 0]line_rom2 = "Jiahui Wen      "; //write date for line 2;

lcd_drive u1(     
    //input pin
    .Clk(Clk),
    .Rst_n(Rst_n),
    .line_rom1(line_rom1),
    .line_rom2(line_rom2),
    
    //output pin
    .RS(RS), //date(H) or command(L)
    .RW(RW), //read(H) or write(L)
    .EN(EN), //enable pin
    .DB(DB)  //date bus
);

endmodule 

将顶层测试模块编译后,分配引脚下载到FPGA开发板上即可看到效果,这里我没有用仿真代码进行测试,唉,偷个懒。

注意事项:

先介绍一下LCD1602的代码驱动原理,这里实现的功能是,先对LCD进行初始指令的配置,然后,一直在LCD里面写入显示数据,因此LCD的底层驱动代码一旦被执行,LCD就会按照固定的频率进行刷新数据。

在代码进行验证的时候发现一个问题就是,[127 : 0]line_rom1 = "hello FPGA!     ";和[127 : 0]line_rom1 = "hello FPGA!";的显示效果是有区别的,前者显示效果是正确的,后者,没有明确进行赋值的地方都是一段乱码。所以在向LCD里面写入显示数据时,一定要注意,不显示的地方一定要写上空格。

你可能感兴趣的:(FPGA技术)