一、前言
在学习《FPGA设计与Verilog HDL实现》第九章内容Verilog驱动常用I/O外设时,书中有一个驱动LCD1602的例程,但其是通过状态机显示固定的几个字符。本着动手实践的原则,决定利用手头的硬件实现FPGA接收串口数据并在LCD1602上显示,下面记录项目开始的过程。因为刚接触FPGA不久,如有错误欢迎大家批评指正。
- 硬件:EP4CE6E22C8NG
- 开发工具:Quartus II 13.0 + Modelsim + Visio
- 全局时钟:50MHz
二、设计框图
- 了解LCD1602的接口和数据读写时序,这部分内容也比较多,参看panhongfeng111的博客:基于FPGA的LCD1602显示屏驱动。
- 根据需要实现的功能划分模块,我们把模块划分为串口接收和LCD显示控制两部分,如图所示:
按照上述划分的框图,分模块把波形图绘制出来(方便后面程序的编写),不一定需要全部绘制,方便自己编写代码时捋清时序就行。
- 串口接收部分框图
串口接收部分时序图
- LCD控制部分框图
LCD控制部分波形图(没画完,也不规范,自己画的时候能理解就行)
三、模块代码的编写
(这部分不大好描述,稍微注释一下方便大家理解)
- 串口接收模块代码
module uart_rx //模块名
#(
parameter UART_BPS = 14'd9600 , //设置波特率
parameter CLK_FREQ = 26'd50_000_000 //全局时钟
)
(
input wire sys_clk , //时钟
input wire sys_rst_n, //复位
input wire rx , //串口接收端
output reg [7:0] po_data , //输出数据
output reg po_flag //输出标志位
);
reg rx_reg1;
reg rx_reg2;
reg rx_reg3;
reg start_flag;
reg work_en;
reg [15:0] baud_cnt;
reg bit_flag;
reg [3:0] bit_cnt;
reg [7:0] rx_data;
reg rx_flag;
parameter BAUD_CNT_MAX = CLK_FREQ / UART_BPS; //由波特率和全局时钟计算出计数最大值
always@(posedge sys_clk or negedge sys_rst_n) //打拍,与系统时钟同步
if(sys_rst_n == 1'b0)
rx_reg1 <= 1'b1;
else
rx_reg1 <= rx;
always@(posedge sys_clk or negedge sys_rst_n) //打两拍,同步
if(sys_rst_n == 1'b0)
rx_reg2 <= 1'b1;
else
rx_reg2 <= rx_reg1;
always@(posedge sys_clk or negedge sys_rst_n) //打三拍,方便产生标志信号
if(sys_rst_n == 1'b0)
rx_reg3 <= 1'b1;
else
rx_reg3 <= rx_reg2;
always@(posedge sys_clk or negedge sys_rst_n) //串口通信开始标志信号
if(sys_rst_n == 1'b0)
start_flag <= 1'b0;
else if((rx_reg2 == 1'b0) && (rx_reg3 == 1'b1) && (work_en == 1'b0))
start_flag <= 1'b1;
else
start_flag <= 1'b0;
always@(posedge sys_clk or negedge sys_rst_n) //串口通信标志信号
if(sys_rst_n == 1'b0)
work_en <= 1'b0;
else if(start_flag == 1'b1)
work_en <= 1'b1;
else if((bit_cnt == 4'd8) && (bit_flag == 1'b1))
work_en <= 1'b0;
else
work_en <= work_en;
always@(posedge sys_clk or negedge sys_rst_n) //根据波特率计数
if(sys_rst_n == 1'b0)
baud_cnt <= 16'd0;
else if((baud_cnt == BAUD_CNT_MAX - 1'b1) || (work_en == 1'b0))
baud_cnt <= 16'd0;
else
baud_cnt <= baud_cnt + 1'b1;
always@(posedge sys_clk or negedge sys_rst_n) //产生读取数据标志位
if(sys_rst_n == 1'b0)
bit_flag <= 1'b0;
else if(baud_cnt == BAUD_CNT_MAX / 2 - 1)
bit_flag <= 1'b1;
else
bit_flag <= 1'b0;
always@(posedge sys_clk or negedge sys_rst_n) //对接收位数计数
if(sys_rst_n == 1'b0)
bit_cnt <= 4'd0;
else if((bit_cnt == 4'd8) && (bit_flag == 1'b1))
bit_cnt <= 4'd0;
else if(bit_flag ==1'b1)
bit_cnt <= bit_cnt + 1'b1;
else
bit_cnt <= bit_cnt;
always@(posedge sys_clk or negedge sys_rst_n) //移位接收字节数据
if(sys_rst_n == 1'b0)
rx_data <= 8'd0;
else if((bit_cnt >= 4'd1) && (bit_cnt <= 4'd8) && (bit_flag == 1'b1))
rx_data <= {rx_reg3,rx_data[7:1]};
always@(posedge sys_clk or negedge sys_rst_n) //完成接收一字节标志位
if(sys_rst_n == 1'b0)
rx_flag <= 1'b0;
else if((bit_cnt == 4'd8) && (bit_flag == 1'b1))
rx_flag <= 1'b1;
else
rx_flag <= 1'b0;
always@(posedge sys_clk or negedge sys_rst_n) //接收数据
if(sys_rst_n == 1'b0)
po_data <= 8'd0;
else if(rx_flag == 1'b1)
po_data <= rx_data;
always@(posedge sys_clk or negedge sys_rst_n) //同步接收数据标志信号
if(sys_rst_n == 1'b0)
po_flag <= 1'b0;
else
po_flag <= rx_flag;
endmodule
- LCD控制模块代码
module lcd_ctrl
(
input wire sys_clk ,
input wire sys_rst_n ,
input wire [7:0] po_data ,
input wire po_flag ,
output reg lcd_rs ,
output reg lcd_rw ,
output reg lcd_en ,
output reg [7:0] lcd_data
);
//---------------------接收字节计数--------------------------
reg [7:0] byte_reg [0:31];
reg [7:0] byte_cnt ;
integer i;
always@(posedge sys_clk , negedge sys_rst_n)
if(sys_rst_n == 1'b0)
byte_cnt <= 8'd0;
else if((byte_cnt == 8'd255) && (po_flag == 1'b1))
byte_cnt <= 8'd0;
else if(po_flag == 1'b1)
byte_cnt <= byte_cnt + 1'b1;
else
byte_cnt <= byte_cnt;
always@(posedge sys_clk , negedge sys_rst_n)
if(sys_rst_n == 1'b0)
begin
for(i=0;i<32;i=i+1)
byte_reg[i] = 8'd0;
end
else if(po_flag == 1'b1)
byte_reg[byte_cnt % 32] <= po_data;
//----------------产生lcd1602使能驱动clk_en--------------------
reg [31:0] clk_cnt ;
reg clk_en ;
always@(posedge sys_clk , negedge sys_rst_n)
if(sys_rst_n == 1'b0)
clk_cnt <= 32'd0;
else if(clk_cnt == 32'd49999)
clk_cnt <= 32'd0;
else
clk_cnt <= clk_cnt + 1'b1;
always@(posedge sys_clk , negedge sys_rst_n)
if(sys_rst_n == 1'b0)
clk_en <= 1'b0;
else if(clk_cnt == 32'd49999)
clk_en <= 1'b1;
else
clk_en <= 1'b0;
//----------------LCD1602显示状态机----------------------------
localparam IDLE = 4'd0 ,
MODE_SET = 4'd1 ,
MODE_SET1 = 4'd2 ,
CURSOR_SET = 4'd3 ,
CURSOR_SET1 = 4'd4 ,
CADDR_SET = 4'd5 ,
CADDR_SET1 = 4'd6 ,
CLEAR_SET = 4'd7 ,
CLEAR_SET1 = 4'd8 ,
ADDR_SET = 4'd9 ,
ADDR_SET1 = 4'd10 ,
DATA_SET = 4'd11 ,
DATA_SET1 = 4'd12 ;
parameter MODE_COM = 8'h38 ,
CURSOR_COM = 8'h0c ,
CADDR_COM = 8'h06 ,
CLEAR_COM = 8'h01 ,
ADDRESS1 = 8'h80 ,
ADDRESS2 = 8'hc0 ;
reg [3:0] state ;
reg [7:0] send_cnt ;
always@(posedge sys_clk , negedge sys_rst_n)
if(sys_rst_n == 1'b0)
begin
state <= IDLE;
lcd_rs <= 1'b0;
lcd_en <= 1'b0;
lcd_rw <= 1'b0;
lcd_data <= 8'd0;
send_cnt <= 8'd0;
end
else if(clk_en == 1'b1)
begin
case(state)
IDLE :
begin
if(byte_cnt >= 1'b1)
state <= MODE_SET;
else
state <= IDLE;
lcd_rs <= 1'b0;
lcd_en <= 1'b0;
lcd_rw <= 1'b0;
lcd_data <= 8'd0;
end
MODE_SET :
begin
state <= MODE_SET1;
lcd_rs <= 1'b0;
lcd_en <= 1'b1;
lcd_rw <= 1'b0;
lcd_data <= MODE_COM;
end
MODE_SET1 :
begin
state <= CURSOR_SET;
lcd_en <= 1'b0;
end
CURSOR_SET :
begin
state <= CURSOR_SET1;
lcd_rs <= 1'b0;
lcd_en <= 1'b1;
lcd_rw <= 1'b0;
lcd_data <= CURSOR_COM;
end
CURSOR_SET1 :
begin
state <= CADDR_SET;
lcd_en <= 1'b0;
end
CADDR_SET :
begin
state <= CADDR_SET1;
lcd_rs <= 1'b0;
lcd_en <= 1'b1;
lcd_rw <= 1'b0;
lcd_data <= CADDR_COM;
end
CADDR_SET1 :
begin
state <= CLEAR_SET;
lcd_en <= 1'b0;
end
CLEAR_SET :
begin
state <= CLEAR_SET1;
lcd_rs <= 1'b0;
lcd_en <= 1'b1;
lcd_rw <= 1'b0;
lcd_data <= CLEAR_COM;
end
CLEAR_SET1 :
begin
state <= ADDR_SET;
lcd_en <= 1'b0;
end
ADDR_SET :
begin
state <= ADDR_SET1;
lcd_rs <= 1'b0;
lcd_en <= 1'b1;
lcd_rw <= 1'b0;
if(send_cnt % 32 == 5'd15)
lcd_data <= ADDRESS2;
else if(send_cnt % 32 == 5'd0)
lcd_data <= ADDRESS1;
else
lcd_data <= lcd_data;
end
ADDR_SET1 :
begin
state <= DATA_SET;
lcd_en <= 1'b0;
end
DATA_SET :
begin
state <= DATA_SET1;
lcd_rs <= 1'b1;
lcd_en <= 1'b1;
lcd_rw <= 1'b0;
lcd_data <= byte_reg[send_cnt];
if(send_cnt < 8'd255)
send_cnt <= send_cnt + 1'b1;
else if(send_cnt == 8'd255)
send_cnt <= 8'd0;
end
DATA_SET1 :
begin
if(byte_cnt > send_cnt)
begin
if(send_cnt == 5'd15)
state <= ADDR_SET;
else
state <= DATA_SET;
end
else
state <= DATA_SET1;
lcd_en <= 1'b0;
end
default :
begin
state <= IDLE;
lcd_rs <= 1'b0;
lcd_en <= 1'b0;
lcd_rw <= 1'b0;
lcd_data <= 8'd0;
send_cnt <= 5'd0;
end
endcase
end
else
begin
lcd_rs <= lcd_rs;
lcd_en <= lcd_en;
lcd_rw <= lcd_rw;
lcd_data <= lcd_data;
send_cnt <= send_cnt;
end
endmodule
四、顶层模块和Verilog Test Bench代码
- 顶层模块代码
module lcd1602_test
(
input wire sys_clk ,
input wire sys_rst_n ,
input wire rx ,
output wire lcd_rs ,
output wire lcd_rw ,
output wire lcd_en ,
output wire [7:0] lcd_data
);
wire [7:0] po_data ;
wire po_flag ;
uart_rx
#(
.UART_BPS (14'd9600 ),
.CLK_FREQ (26'd50_000_000 )
)
uart_rx_inst
(
.sys_clk (sys_clk ),
.sys_rst_n (sys_rst_n ),
.rx (rx ),
.po_data (po_data ),
.po_flag (po_flag )
);
lcd_ctrl lcd_ctrl_inst
(
.sys_clk (sys_clk ),
.sys_rst_n (sys_rst_n ),
.po_data (po_data ),
.po_flag (po_flag ),
.lcd_rs (lcd_rs ),
.lcd_rw (lcd_rw ),
.lcd_en (lcd_en ),
.lcd_data (lcd_data )
);
endmodule
- Test Bench仿真代码
`timescale 1ns/1ns
module tb_lcd1602_test();
reg sys_clk;
reg sys_rst_n;
reg rx;
wire lcd_rs ;
wire lcd_rw ;
wire lcd_en ;
wire [7:0] lcd_data ;
initial
begin
sys_clk <= 1'b1;
sys_rst_n <= 1'b0;
rx <= 1'b1;
#20
sys_rst_n <= 1'b1;
end
always #10 sys_clk <= ~sys_clk;
task rx_byte();
integer j;
for(j = 0; j < 8; j = j + 1)
rx_bit(j);
endtask
initial
begin
#200
rx_byte();
rx_byte();
rx_byte();
rx_byte();
rx_byte();
rx_byte();
end
task rx_bit
(
input reg [7:0] data
);
integer i;
for(i = 0;i < 10;i = i + 1)
begin
case(i)
0: rx <= 1'b0;
1: rx <= data[0];
2: rx <= data[1];
3: rx <= data[2];
4: rx <= data[3];
5: rx <= data[4];
6: rx <= data[5];
7: rx <= data[6];
8: rx <= data[7];
9: rx <= 1'b1;
endcase
#(5208*20);
end
endtask
lcd1602_test lcd1602_test_inst
(
.sys_clk (sys_clk ),
.sys_rst_n (sys_rst_n ),
.rx (rx ),
.lcd_rs (lcd_rs ),
.lcd_rw (lcd_rw ),
.lcd_en (lcd_en ),
.lcd_data (lcd_data )
);
endmodule
五、仿真结果和上板验证文章来源:https://www.toymoban.com/news/detail-462173.html
- 仿真结果
文章来源地址https://www.toymoban.com/news/detail-462173.html
- 上板验证
串口调试助手发送 123456789ABCDEFGHIJKLM
LCD1602显示
到了这里,关于FPGA接收串口数据并通过LCD1602显示的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!