FPGA接收串口数据并通过LCD1602显示

一、前言

在学习《FPGA设计与Verilog HDL实现》第九章内容Verilog驱动常用I/O外设时,书中有一个驱动LCD1602的例程,但其是通过状态机显示固定的几个字符。本着动手实践的原则,决定利用手头的硬件实现FPGA接收串口数据并在LCD1602上显示,下面记录项目开始的过程。因为刚接触FPGA不久,如有错误欢迎大家批评指正。

  • 硬件:EP4CE6E22C8NG
  • 开发工具:Quartus II 13.0 + Modelsim + Visio
  • 全局时钟:50MHz

二、设计框图

  • 了解LCD1602的接口和数据读写时序,这部分内容也比较多,参看panhongfeng111的博客:基于FPGA的LCD1602显示屏驱动。
  • 根据需要实现的功能划分模块,我们把模块划分为串口接收LCD显示控制两部分,如图所示:
    FPGA接收串口数据并通过LCD1602显示_第1张图片

按照上述划分的框图,分模块把波形图绘制出来(方便后面程序的编写),不一定需要全部绘制,方便自己编写代码时捋清时序就行。

  • 串口接收部分框图

-

串口接收部分时序图

FPGA接收串口数据并通过LCD1602显示_第2张图片

  • LCD控制部分框图

FPGA接收串口数据并通过LCD1602显示_第3张图片

LCD控制部分波形图(没画完,也不规范,自己画的时候能理解就行)
FPGA接收串口数据并通过LCD1602显示_第4张图片
三、模块代码的编写
(这部分不大好描述,稍微注释一下方便大家理解)

  • 串口接收模块代码
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

五、仿真结果和上板验证

  • 仿真结果

FPGA接收串口数据并通过LCD1602显示_第5张图片

  • 上板验证
    串口调试助手发送 123456789ABCDEFGHIJKLM
    FPGA接收串口数据并通过LCD1602显示_第6张图片
    LCD1602显示

你可能感兴趣的:(笔记,fpga开发,硬件工程,嵌入式硬件)