UART串口接收设计

一、设计思路

1.端口设置

为实现UART串口接收,所设计的模块应具有如下端口:
时钟、复位、波特率设置、串行数据输入、并行数据输出、一次数据接收完成标志。如图:

module uart_byte_rx(
			Clk,        //模块时钟50M
			Rst_n,      //模块复位
			baud_set,   //波特率设置
			Rs232_Rx,   //RS232数据输入
			 
			data_byte,  //并行数据输出
			Rx_Done     //一次数据接收完成标志
		);

2.状态分析

空闲状态时,Tx必须为1.
开始发送数据时,发送的第一位数据为0,代表起始位
紧接着的是8位的数据位
数据位后面是1位的停止位,停止位的值为1。
一帧数据格式为“0 X X X X X X X X 1”。

3.其他注意事项

考虑到实际应用场景中可能会存在许多不正常的脉冲干扰,所以为了确保采集到的数据的准确性,我们将每位数据采集6次。
我们不应把每位数据等分为6份进行采集,这是因为数据产生时在跳变沿处可能会有极不稳定的状态。因此我们将数据分为16份,在中间的6份进行采集。

二、描述思路

1.代码设计思路

首先需要设置同步寄存器消除亚稳态

	reg s0_Rs232_Rx,s1_Rs232_Rx;//同步寄存器
	reg tmp0_Rs232_Rx,tmp1_Rs232_Rx;//数据寄存器
	
	//同步寄存器,消除亚稳态
	always@(posedge Clk or negedge Rst_n)
	if(!Rst_n)begin
		s0_Rs232_Rx <= 1'b0;
		s1_Rs232_Rx <= 1'b0;	
	end
	else begin
		s0_Rs232_Rx <= Rs232_Rx;
		s1_Rs232_Rx <= s0_Rs232_Rx;	
	end
	
	//数据寄存器
	always@(posedge Clk or negedge Rst_n)
	if(!Rst_n)begin
		tmp0_Rs232_Rx <= 1'b0;
		tmp1_Rs232_Rx <= 1'b0;	
	end
	else begin
		tmp0_Rs232_Rx <= s1_Rs232_Rx;
		tmp1_Rs232_Rx <= tmp0_Rs232_Rx;	
	end

然后沿用与UART发送相同的思路,由输入端口设置波特率

//设置波特率(即给定计数器模长)
	always@(posedge Clk or negedge Rst_n)
	if(!Rst_n)
		bps_DR <= 16'd324;
	else begin
		case(baud_set)
			0:bps_DR <= 16'd324;
			1:bps_DR <= 16'd162;
			2:bps_DR <= 16'd80;
			3:bps_DR <= 16'd53;
			4:bps_DR <= 16'd26;
			default:bps_DR <= 16'd324;			
		endcase
	end

计数器:


	//计数器
	always@(posedge Clk or negedge Rst_n)
	if(!Rst_n)
		div_cnt <= 16'd0;
	else if(uart_state)begin
		if(div_cnt == bps_DR)
			div_cnt <= 16'd0;
		else
			div_cnt <= div_cnt + 1'b1;
	end
	else
		div_cnt <= 16'd0;

利用此计数器,设置采集信号的高电平脉冲:

// bps_clk gen
	always@(posedge Clk or negedge Rst_n)
	if(!Rst_n)
		bps_clk <= 1'b0;
	else if(div_cnt == 16'd1)	//当计数器计到1时将波特率时钟拉高,其他时候拉低,这样就产生了相应频率的高脉冲
		bps_clk <= 1'b1;
	else
		bps_clk <= 1'b0;

然后是对产生的高脉冲进行计数

//对产生的高脉冲进行计数
	always@(posedge Clk or negedge Rst_n)
	if(!Rst_n)
		bps_cnt <= 8'd0;
	else if(bps_cnt == 8'd159 | (bps_cnt == 8'd12 && (START_BIT > 2)))
		bps_cnt <= 8'd0;
	else if(bps_clk)
		bps_cnt <= bps_cnt + 1'b1;
	else

根据高脉冲的计数值进行数据的采样:

	//内部寄存器r_data_byte负责将6次采集到的值加起来,当采集到的1个数超过3时,第二位数据变为1,否则为0
	always@(posedge Clk or negedge Rst_n)
	if(!Rst_n)begin
		START_BIT = 3'd0;
		r_data_byte[0] <= 3'd0;
		r_data_byte[1] <= 3'd0;
		r_data_byte[2] <= 3'd0;
		r_data_byte[3] <= 3'd0;
		r_data_byte[4] <= 3'd0;
		r_data_byte[5] <= 3'd0;
		r_data_byte[6] <= 3'd0;
		r_data_byte[7] <= 3'd0;
		STOP_BIT = 3'd0;
	end
	else if(bps_clk)begin
		case(bps_cnt)
			0:begin
					START_BIT = 3'd0;
					r_data_byte[0] <= 3'd0;
					r_data_byte[1] <= 3'd0;
					r_data_byte[2] <= 3'd0;
					r_data_byte[3] <= 3'd0;
					r_data_byte[4] <= 3'd0;
					r_data_byte[5] <= 3'd0;
					r_data_byte[6] <= 3'd0;
					r_data_byte[7] <= 3'd0;
					STOP_BIT = 3'd0;			
				end
			6,7,8,9,10,11:START_BIT <= START_BIT + s1_Rs232_Rx;
			22,23,24,25,26,27:r_data_byte[0] <= r_data_byte[0] + s1_Rs232_Rx;
			38,39,40,41,42,43:r_data_byte[1] <= r_data_byte[1] + s1_Rs232_Rx;
			54,55,56,57,58,59:r_data_byte[2] <= r_data_byte[2] + s1_Rs232_Rx;
			70,71,72,73,74,75:r_data_byte[3] <= r_data_byte[3] + s1_Rs232_Rx;
			86,87,88,89,90,91:r_data_byte[4] <= r_data_byte[4] + s1_Rs232_Rx;
			102,103,104,105,106,107:r_data_byte[5] <= r_data_byte[5] + s1_Rs232_Rx;
			118,119,120,121,122,123:r_data_byte[6] <= r_data_byte[6] + s1_Rs232_Rx;
			134,135,136,137,138,139:r_data_byte[7] <= r_data_byte[7] + s1_Rs232_Rx;
			150,151,152,153,154,155:STOP_BIT <= STOP_BIT + s1_Rs232_Rx;
			default:
				begin
					START_BIT = START_BIT;
					r_data_byte[0] <= r_data_byte[0];
					r_data_byte[1] <= r_data_byte[1];
					r_data_byte[2] <= r_data_byte[2];
					r_data_byte[3] <= r_data_byte[3];
					r_data_byte[4] <= r_data_byte[4];
					r_data_byte[5] <= r_data_byte[5];
					r_data_byte[6] <= r_data_byte[6];
					r_data_byte[7] <= r_data_byte[7];
					STOP_BIT = STOP_BIT;						
				end
		endcase
	end

这里值得注意的是,当6次采集到的值相加大于3时,结果的第2位会变成1,因此只需要观察r_data_byte的第2位值即可确定采到的是0还是1。

最后将采集到的数据送到并行输出端

//设置并行数据输出
	always@(posedge Clk or negedge Rst_n)
	if(!Rst_n)
		data_byte <= 8'd0;
	else if(bps_cnt == 8'd159)begin	//将采集结果的第2位赋值给并行输出端
		data_byte[0] <= r_data_byte[0][2];
		data_byte[1] <= r_data_byte[1][2];
		data_byte[2] <= r_data_byte[2][2];
		data_byte[3] <= r_data_byte[3][2];
		data_byte[4] <= r_data_byte[4][2];
		data_byte[5] <= r_data_byte[5][2];
		data_byte[6] <= r_data_byte[6][2];
		data_byte[7] <= r_data_byte[7][2];
	end	

2.源代码

module uart_byte_rx(
			Clk,        //模块时钟50M
			Rst_n,      //模块复位
			baud_set,   //波特率设置
			Rs232_Rx,   //RS232数据输入
			 
			data_byte,  //并行数据输出
			Rx_Done     //一次数据接收完成标志
		);

	input Clk;
	input Rst_n;
	input [2:0]baud_set;
	input Rs232_Rx;
	
	output reg [7:0]data_byte;
	output reg Rx_Done;
	
	reg s0_Rs232_Rx,s1_Rs232_Rx;//同步寄存器
	
	reg tmp0_Rs232_Rx,tmp1_Rs232_Rx;//数据寄存器
	
	reg [15:0]bps_DR;//分频计数器计数最大值
	reg [15:0]div_cnt;//分频计数器
	reg bps_clk;//
	reg [7:0]bps_cnt;
	
	reg uart_state;
	
	reg [2:0] r_data_byte [7:0];
	//reg [7:0] tmp_data_byte;
	reg [2:0] START_BIT,STOP_BIT;
	
	wire nedege;
	
	//同步寄存器,消除亚稳态
	always@(posedge Clk or negedge Rst_n)
	if(!Rst_n)begin
		s0_Rs232_Rx <= 1'b0;
		s1_Rs232_Rx <= 1'b0;	
	end
	else begin
		s0_Rs232_Rx <= Rs232_Rx;
		s1_Rs232_Rx <= s0_Rs232_Rx;	
	end
	
	//数据寄存器
	always@(posedge Clk or negedge Rst_n)
	if(!Rst_n)begin
		tmp0_Rs232_Rx <= 1'b0;
		tmp1_Rs232_Rx <= 1'b0;	
	end
	else begin
		tmp0_Rs232_Rx <= s1_Rs232_Rx;
		tmp1_Rs232_Rx <= tmp0_Rs232_Rx;	
	end
	
	assign nedege = !tmp0_Rs232_Rx & tmp1_Rs232_Rx;
	
	//设置波特率(即给定计数器模长)
	always@(posedge Clk or negedge Rst_n)
	if(!Rst_n)
		bps_DR <= 16'd324;
	else begin
		case(baud_set)
			0:bps_DR <= 16'd324;
			1:bps_DR <= 16'd162;
			2:bps_DR <= 16'd80;
			3:bps_DR <= 16'd53;
			4:bps_DR <= 16'd26;
			default:bps_DR <= 16'd324;			
		endcase
	end
	
	//计数器
	always@(posedge Clk or negedge Rst_n)
	if(!Rst_n)
		div_cnt <= 16'd0;
	else if(uart_state)begin
		if(div_cnt == bps_DR)
			div_cnt <= 16'd0;
		else
			div_cnt <= div_cnt + 1'b1;
	end
	else
		div_cnt <= 16'd0;
		
	// bps_clk gen
	always@(posedge Clk or negedge Rst_n)
	if(!Rst_n)
		bps_clk <= 1'b0;
	else if(div_cnt == 16'd1)	//当计数器计到1时将波特率时钟拉高,其他时候拉低,这样就产生了相应频率的高脉冲
		bps_clk <= 1'b1;
	else
		bps_clk <= 1'b0;
	
	//对产生的高脉冲进行计数
	always@(posedge Clk or negedge Rst_n)
	if(!Rst_n)
		bps_cnt <= 8'd0;
	else if(bps_cnt == 8'd159 | (bps_cnt == 8'd12 && (START_BIT > 2)))
		bps_cnt <= 8'd0;
	else if(bps_clk)
		bps_cnt <= bps_cnt + 1'b1;
	else
		bps_cnt <= bps_cnt;

	//设置标志接收完毕的信号
	always@(posedge Clk or negedge Rst_n)
	if(!Rst_n)
		Rx_Done <= 1'b0;
	else if(bps_cnt == 8'd159)
		Rx_Done <= 1'b1;
	else
		Rx_Done <= 1'b0;
		
	//设置并行数据输出
	always@(posedge Clk or negedge Rst_n)
	if(!Rst_n)
		data_byte <= 8'd0;
	else if(bps_cnt == 8'd159)begin	//将采集结果的第2位赋值给并行输出端
		data_byte[0] <= r_data_byte[0][2];
		data_byte[1] <= r_data_byte[1][2];
		data_byte[2] <= r_data_byte[2][2];
		data_byte[3] <= r_data_byte[3][2];
		data_byte[4] <= r_data_byte[4][2];
		data_byte[5] <= r_data_byte[5][2];
		data_byte[6] <= r_data_byte[6][2];
		data_byte[7] <= r_data_byte[7][2];
	end	
		
	//内部寄存器r_data_byte负责将6次采集到的值加起来,当采集到的1个数超过3时,第二位数据变为1,否则为0
	always@(posedge Clk or negedge Rst_n)
	if(!Rst_n)begin
		START_BIT = 3'd0;
		r_data_byte[0] <= 3'd0;
		r_data_byte[1] <= 3'd0;
		r_data_byte[2] <= 3'd0;
		r_data_byte[3] <= 3'd0;
		r_data_byte[4] <= 3'd0;
		r_data_byte[5] <= 3'd0;
		r_data_byte[6] <= 3'd0;
		r_data_byte[7] <= 3'd0;
		STOP_BIT = 3'd0;
	end
	else if(bps_clk)begin
		case(bps_cnt)
			0:begin
					START_BIT = 3'd0;
					r_data_byte[0] <= 3'd0;
					r_data_byte[1] <= 3'd0;
					r_data_byte[2] <= 3'd0;
					r_data_byte[3] <= 3'd0;
					r_data_byte[4] <= 3'd0;
					r_data_byte[5] <= 3'd0;
					r_data_byte[6] <= 3'd0;
					r_data_byte[7] <= 3'd0;
					STOP_BIT = 3'd0;			
				end
			6,7,8,9,10,11:START_BIT <= START_BIT + s1_Rs232_Rx;
			22,23,24,25,26,27:r_data_byte[0] <= r_data_byte[0] + s1_Rs232_Rx;
			38,39,40,41,42,43:r_data_byte[1] <= r_data_byte[1] + s1_Rs232_Rx;
			54,55,56,57,58,59:r_data_byte[2] <= r_data_byte[2] + s1_Rs232_Rx;
			70,71,72,73,74,75:r_data_byte[3] <= r_data_byte[3] + s1_Rs232_Rx;
			86,87,88,89,90,91:r_data_byte[4] <= r_data_byte[4] + s1_Rs232_Rx;
			102,103,104,105,106,107:r_data_byte[5] <= r_data_byte[5] + s1_Rs232_Rx;
			118,119,120,121,122,123:r_data_byte[6] <= r_data_byte[6] + s1_Rs232_Rx;
			134,135,136,137,138,139:r_data_byte[7] <= r_data_byte[7] + s1_Rs232_Rx;
			150,151,152,153,154,155:STOP_BIT <= STOP_BIT + s1_Rs232_Rx;
			default:
				begin
					START_BIT = START_BIT;
					r_data_byte[0] <= r_data_byte[0];
					r_data_byte[1] <= r_data_byte[1];
					r_data_byte[2] <= r_data_byte[2];
					r_data_byte[3] <= r_data_byte[3];
					r_data_byte[4] <= r_data_byte[4];
					r_data_byte[5] <= r_data_byte[5];
					r_data_byte[6] <= r_data_byte[6];
					r_data_byte[7] <= r_data_byte[7];
					STOP_BIT = STOP_BIT;						
				end
		endcase
	end
	
	//控制uart_state寄存器
	always@(posedge Clk or negedge Rst_n)
	if(!Rst_n)
		uart_state <= 1'b0;
	else if(nedege)
		uart_state <= 1'b1;
	else if(Rx_Done || (bps_cnt == 8'd12 && (START_BIT > 2)))
		uart_state <= 1'b0;
	else
		uart_state <= uart_state;		

endmodule

3.顶层模块

module uart_rx_top(
			Clk,     //模块时钟  
			Rst_n,   //模块复位
			Rs232_Rx //RS232数据输入
		);

	input Clk;
	input Rst_n;
	input Rs232_Rx;
	
	reg [7:0]data_rx_r;
	wire [7:0]data_rx;
	wire Rx_Done;
	
	uart_byte_rx uart_byte_rx(
		.Clk(Clk),
		.Rst_n(Rst_n),
		.baud_set(3'd0),
		.Rs232_Rx(Rs232_Rx),
		
		.data_byte(data_rx),
		.Rx_Done(Rx_Done)
	);
	
	//这是一个探针
	ISSP issp(
		.probe(data_rx_r),
		.source()
	);
	
	
	always@(posedge Clk or negedge Rst_n)
	if(!Rst_n)
		data_rx_r <= 8'd0;
	else if(Rx_Done)
		data_rx_r <= data_rx;
	else
		data_rx_r <= data_rx_r;
		
endmodule

4.testbench代码

`timescale 1ns/1ns
`define clk_period 20

module uart_byte_rx_tb;

	reg Clk;
	reg Rst_n;
	
	wire [7:0]data_byte_r;
	wire Rx_Done;	
	
	reg [7:0]data_byte_t;
	reg send_en;
	reg [2:0]baud_set;
	
	wire Rs232_Tx;
	wire Tx_Done;
	wire uart_state;
	
	uart_byte_rx uart_byte_rx(
		.Clk(Clk),
		.Rst_n(Rst_n),
		.baud_set(baud_set),
		.Rs232_Rx(Rs232_Tx),
		
		.data_byte(data_byte_r),
		.Rx_Done(Rx_Done)
	);
	
	uart_byte_tx uart_byte_tx(
		.Clk(Clk),
		.Rst_n(Rst_n),
		.data_byte(data_byte_t),
		.send_en(send_en),
		.baud_set(baud_set),
		
		.Rs232_Tx(Rs232_Tx),
		.Tx_Done(Tx_Done),
		.uart_state(uart_state)
	);
	
	initial Clk = 1;
	always#(`clk_period/2)Clk = ~Clk;
	
	initial begin
		Rst_n = 1'b0;
		data_byte_t = 8'd0;
		send_en = 1'd0;
		baud_set = 3'd4;
		#(`clk_period*20 + 1 );
		Rst_n = 1'b1;
		#(`clk_period*50);
		data_byte_t = 8'haa;
		send_en = 1'd1;
		#`clk_period;
		send_en = 1'd0;
		
		@(posedge Tx_Done)
		
		#(`clk_period*5000);
		data_byte_t = 8'h55;
		send_en = 1'd1;
		#`clk_period;
		send_en = 1'd0;
		@(posedge Tx_Done)
		#(`clk_period*5000);
		$stop;	
	end
	
endmodule

三、仿真

仿真结果如图
在这里插入图片描述

四、小结

这次的学习可以算是“UART发送设计”的后续,发送与接收的大致思路差不多 。通过发送和接收我们可以实现FPGA与计算机之间的通信。


版权声明:本文为m0_51261356原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。