FPGA基于串口RS232的数据收发及数据回环实验


前言

在之前的文章中,我已经分别做过一次RS232的数据接收和数据发送了,本次用更容易理解的方式重新编写两种代码以及进行数据回环实验。以及学习使用In-System Sources & Probes Editor工具和串口调试工具进行调试。


一、串口接收模块


注:此图片摘自《FPGA Verilog开发指南》
依照波形图写代码会使设计事半功倍。

1.波形分析

  • rx_reg1、rx_reg2:消除亚稳态,同步时钟。
  • rx_reg3:与rx_reg2配合,产生下降沿信号判断起始位。
  • start_nedge:依照波形图可以看出起始位下降沿的判断位置。
  • work_en:使能信号
  • baud_cnt:波特率计数器,在使能信号有效时开始计数。
  • bit_flag:接收1bit数据标志信号,在波特率计数达到中点时刻时产生一个脉冲信号,达到控制数据接收的目的。
  • bit_cnt:接收数据计数器,一帧数据有八个数据位,计时从0-8。
  • rx_data:将接收的数据经过移位由串转并。
  • rx_flag:移位完成标志。

2.RTL代码

module uart_rx
#(
	parameter BAUD_CNT_MAX=13'd5207 //9600bps
)
(
	input wire clk,
	input wire 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_nedge;
	reg work_en;
	reg [12:0]baud_cnt;
	reg bit_flag;
	reg [3:0]bit_cnt;
	reg [7:0]rx_data;
	reg rx_flag;
	
	//输入同步,消除亚稳态,并为产生下降沿信号做延迟
	always @(posedge clk or negedge rst_n)
	if(!rst_n)
		rx_reg1<=1'b0;
	else 
		rx_reg1<=rx;
		
	always @(posedge clk or negedge rst_n)
	if(!rst_n)
		rx_reg2<=1'b0;
	else 
		rx_reg2<=rx_reg1;
		
	always @(posedge clk or negedge rst_n)
	if(!rst_n)
		rx_reg3<=1'b0;
	else 
		rx_reg3<=rx_reg2;
		
	//检测到起始位低电平信号
	always @(posedge clk or negedge rst_n)
	if(!rst_n)
		start_nedge<=1'b0;
	else if(~rx_reg2 && rx_reg3)
		start_nedge<=1'b1;
	else
		start_nedge<=1'b0;
		
	//接收使能信号,检测到起始位后开始置1,检测到接收八位信号后结束,置0
	always @(posedge clk or negedge rst_n)
	if(!rst_n)
		work_en<=1'b0;
	else if(start_nedge)
		work_en<=1'b1;
	else if((bit_cnt==4'd8) && bit_flag)
		work_en<=1'b0;
		
	//波特率计数器
	always @(posedge clk or negedge rst_n)
	if(!rst_n)
		baud_cnt<=1'b0;
	else if((baud_cnt==BAUD_CNT_MAX-1) || (!work_en))
		baud_cnt<=1'b0;
	else if(work_en)
		baud_cnt<=baud_cnt+1'b1;
	
	//接收标志信号
	always @(posedge clk or negedge rst_n)
	if(!rst_n)
		bit_flag<=1'b0;
	else if(baud_cnt==BAUD_CNT_MAX/2-1)
		bit_flag<=1'b1;
	else
		bit_flag<=1'b0;

	//接收数据计数器
	always @(posedge clk or negedge rst_n)
	if(!rst_n)
		bit_cnt<=1'b0;
	else if((bit_cnt==4'd8) && bit_flag)
		bit_cnt<=1'b0;
	else if(bit_flag)
		bit_cnt<=bit_cnt+1'b1;
		
	//串行数据转并行数据,移位操作
	always @(posedge clk or negedge rst_n)
	if(!rst_n)
		rx_data<=1'b0;
	else if(bit_flag && (bit_cnt>=4'd1) && (bit_cnt<=4'd8))
		rx_data<={rx_reg3,rx_data[7:1]};
		
	//移位完成标志
	always @(posedge clk or negedge rst_n)
	if(!rst_n)
		rx_flag<=1'b0;
	else if((bit_cnt==4'd8) && bit_flag)
		rx_flag<=1'b1;
	else 
		rx_flag<=1'b0;
	
	//rx_data不能直接输出,通过寄存器之后由po_data输出
	always @(posedge clk or negedge rst_n)
	if(!rst_n)
		po_data<=1'b0;
	else if(rx_flag)
		po_data<=rx_data;
		
	//rx_flag不能直接输出,通过寄存器之后由po_flag输出
	always @(posedge clk or negedge rst_n)
	if(!rst_n)
		po_flag<=1'b0;
	else 
		po_flag<=rx_flag;
endmodule 

3.仿真测试模块

`timescale 1ns/1ns
`define clk_period 20

module uart_rx_tb;

	reg clk;
	reg rst_n;
	reg rx;
	
	wire [7:0]po_data;
	wire po_flag;
	
	uart_rx
	#(
		.BAUD_CNT_MAX(13'd100)
	)
	uart_rx0(
		.clk(clk),
		.rst_n(rst_n),
		.rx(rx),
		
		.po_data(po_data),
		.po_flag(po_flag)
	);
	
	initial clk=1'b1;
	always #(`clk_period/2) clk=~clk;
	
	initial begin
		rst_n=1'b0;
		rx=1'b0;
		#(`clk_period*2)
		rst_n=1'b1;
	end
	
	initial begin
		#200
		rx_bit(8'd0);
		rx_bit(8'd1);
		rx_bit(8'd2);
		rx_bit(8'd3);
		rx_bit(8'd4);
		rx_bit(8'd5);
		rx_bit(8'd6);
		rx_bit(8'd7);
	end
	
	task rx_bit(
		input [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
			#(101*20);
		end
	endtask
endmodule 

仿真波形图:
在这里插入图片描述
总体波形图^
在这里插入图片描述
单bit数据接收^

3.使用In-System Sources & Probes IP核进行上板验证

在IP核中找到In-System Sources & Probes,在配置页面只需改变如下两格即可。
在这里插入图片描述

引入IP核代码:

module uart_rx_top(clk,rst_n,rx);

	input clk;
	input rst_n;
	input rx;
	
	wire [7:0]po_data;
	wire po_flag;
	
	uart_rx uart_rx(

		.clk(clk),
		.rst_n(rst_n),
		.rx(rx),
		
		.po_data(po_data),
		.po_flag(po_flag)

	);

	issp issp(
			.probe(po_data) 
		);

endmodule 

自行配置引脚,进行全编译(注意设置顶层文件),下载到板子上,打开串口调试工具和In-System Sources & Probes Editor
在这里插入图片描述
打开In-System Sources & Probes Editor后,调整配置,进入接收功能
在这里插入图片描述
在串口调式工具中发送十六进制数据,可以看到In-System Sources & Probes Editor中接收到了数据。
在这里插入图片描述

二、串口发送模块

在这里插入图片描述
注:此图片摘自《FPGA Verilog开发指南》
依照波形图写代码会使设计事半功倍。

1.波形分析

  • pi_data是输入端口输入的并行数据,串口发送模块需要把并行数据变为串行数据发送出去
  • bit_flag:发送数据标志信号,在波特率计数器的任意时刻都可以,但不能设置成0和终止位置,这样会在数据跳变时产生不稳定信号
  • bit_cnt:需要发送的数据共十位,故计数从0-9
  • tx:用case语句进行发送,在第一位和第十位分别发送0和1

2.RTL代码:

module uart_tx
#(
	parameter BAUD_CNT_MAX=13'd5207 //9600bps
)
(
	input wire clk,
	input wire rst_n,
	input wire [7:0]pi_data,
	input wire pi_flag,
	
	output reg tx
);

	reg [12:0]baud_cnt;
	reg work_en;
	reg bit_flag;
	reg [3:0]bit_cnt;
	
	//工作使能信号,数据可用信号pi_flag为有效时,使能有效,当发送计数器记满9并且发送标志信号有效时清零
	always @(posedge clk or negedge rst_n)
	if(!rst_n)
		work_en<=1'b0;
	else if(pi_flag)
		work_en<=1'b1;
	else if((bit_cnt==4'd9) && bit_flag)
		work_en<=1'b0;
	
	//波特率计数器,记满或使能信号无效时清零
	always @(posedge clk or negedge rst_n)
	if(!rst_n)
		baud_cnt<=1'b0;
	else if(baud_cnt==BAUD_CNT_MAX-1 || (!work_en))
		baud_cnt<=1'b0;
	else if(work_en)
		baud_cnt<=baud_cnt+1'b1;
		
	//发送标志信号,当波特率计数器开始计数时,就可以开始发送1bit数据,但不要使用0和最后一个计数时刻
	always @(posedge clk or negedge rst_n)
	if(!rst_n)
		bit_flag<=1'b0;
	else if(baud_cnt==13'd1)
		bit_flag<=1'b1;
	else 
		bit_flag<=1'b0;
		
	//发送数据计时器,
	always @(posedge clk or negedge rst_n)
	if(!rst_n)
		bit_cnt<=1'b0;
	else if(bit_cnt==4'd9 && bit_flag)
		bit_cnt<=1'b0;
	else if(bit_flag && work_en)
		bit_cnt<=bit_cnt+1'b1;
		
	always @(posedge clk or negedge rst_n)
	if(!rst_n)
		tx<=1'b0;
	else if(bit_flag)
		case(bit_cnt)
			0:tx<=1'b0;
			1:tx<=pi_data[0];
			2:tx<=pi_data[1];
			3:tx<=pi_data[2];
			4:tx<=pi_data[3];
			5:tx<=pi_data[4];
			6:tx<=pi_data[5];
			7:tx<=pi_data[6];
			8:tx<=pi_data[7];
			9:tx<=1'b1;
			default:tx<=1'b1;
		endcase
endmodule 

3.仿真测试模块

`timescale 1ns/1ns
`define clk_period 20

module uart_tx_tb;

	reg clk;
	reg rst_n;
	reg [7:0]pi_data;
	reg pi_flag;
	
	wire tx;

	uart_tx
	#(
		.BAUD_CNT_MAX(13'd5207) //9600bps
	)
	uart_tx0(
		.clk(clk),
		.rst_n(rst_n),
		.pi_data(pi_data),
		.pi_flag(pi_flag),
		
		.tx(tx)
	);
	
	initial clk=1'b1;
	always #(`clk_period/2) clk=~clk;
	
	initial begin
		rst_n=1'b0;
		pi_flag=1'b0;
		#(`clk_period*2+1);
		rst_n=1'b1;
		#20
		
		pi_flag=1'b1;
		pi_data=8'haa;
		#20;
		pi_flag=1'b0;
		#(`clk_period*5208*10);
		
		
		pi_flag=1'b1;
		pi_data=8'hbb;
		#20;
		pi_flag=1'b0;
		#(`clk_period*5208*10);
		
		
		pi_flag=1'b1;
		pi_data=8'hef;
		#20;
		pi_flag=1'b0;
		#(`clk_period*5208*10);

		pi_flag=1'b1;
		pi_data=8'h55;
		#20;
		pi_flag=1'b0;
		#(`clk_period*5208*10);
	end
endmodule 

仿真波形图:
在这里插入图片描述
总体仿真波形图^

在这里插入图片描述
单bit数据仿真波形图^

3.使用In-System Sources & Probes IP核进行上板验证

在IP核中找到In-System Sources & Probes,在配置页面只需改变如下两格即可。
在这里插入图片描述
引入IP核代码:

module uart_tx_top(clk,rst_n,tx);

	input clk;
	input rst_n;
	
	output tx;
	
	wire [7:0]pi_data;
	
	uart_tx uart_tx0(
		.clk(clk),
		.rst_n(rst_n),
		.pi_data(pi_data),
		.pi_flag(1'b1),
		
		.tx(tx)
	);

	issp_tx issp_tx0(
		.source(pi_data)  // sources.source
	);

endmodule 

自行配置引脚,进行全编译(注意设置顶层文件),下载到板子上,打开串口调试工具和In-System Sources & Probes Editor
在这里插入图片描述
打开In-System Sources & Probes Editor后,将进制调整为十六进制,在DATA数据栏输入1bit数据(两个十六进制数),就可以在串口调试工具中观察到发送到的数据
在这里插入图片描述
可以看到如果输入一个数据,就会一直不断发送,这在我们实际运用中是不希望见到的,那么为何会出现这种情况呢?
是因为我们在引入IP核的代码中,将发送数据模块中的pi_flag置为了1,方便我们观察,同时一直为1会使数据一直发送,在实际应用中,合理使用pi_flag可以避免这种情况。

三、数据回环测试

1.RTL代码

module rs232_top(
	input wire clk,
	input wire rst_n,
	input wire rx,
	
	output wire tx
);
	wire [7:0]po_data;
	wire po_flag;

	uart_rx uart_rx0(
		.clk(clk),
		.rst_n(rst_n),
		.rx(rx),
		
		.po_data(po_data),
		.po_flag(po_flag)
	);
	
	uart_tx uart_tx0(
		.clk(clk),
		.rst_n(rst_n),
		.pi_data(po_data),
		.pi_flag(po_flag),
		
		.tx(tx)
	);
endmodule 

2.RTL视图

在这里插入图片描述

3.仿真测试模块

`timescale 1ns/1ns
`define clk_period 20

module rs232_top_tb;

	reg clk;
	reg rst_n;
	reg rx;
	
	wire tx;

	rs232_top rs232_top0(
		.clk(clk),
		.rst_n(rst_n),
		.rx(rx),
		
		.tx(tx)
	);
	
	initial clk=1'b1;
	always #(`clk_period/2) clk=~clk;
	
	initial begin
		rst_n=1'b0;
		rx=1'b0;
		#(`clk_period*2)
		rst_n=1'b1;
	end
	
	initial begin
		#200;
		rx_byte();
	end
	
	task rx_byte();
		integer j;
		for(j=0;j<8;j=j+1)
			rx_bit(j);
	endtask
	
	task rx_bit(
		input [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

endmodule 

仿真波形图:
在这里插入图片描述

4.上板测试

打开串口调试助手,选择循环多条发送,可以看到由串口发送模块发送的数据都被串口接收模块接收到了,实验成功。
在这里插入图片描述

总结

本篇文章使用的设计方式相比于之前的设计方式更加清晰易懂,串行数据改并行数据的方法更简单。同时也是第一次使用In-System Sources & Probes工具,是一个很使用且方便的调试工具。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值