FPGA学习笔记:UART串口接收模块设计

一、UART串口接收原理

1.起始位检测

串口处于空闲状态时,tx为高电平,故接收模块rx端口接收到的也是高电平。起始位为低电平,所以检测起始位只需要检测rx的下降沿即可,检测电路如下,采用二级D触发器解决亚稳态问题
起始位检测

2.采样

在实验室条件下,串口发送的数据通常是稳定的,在接收时取数据中点作为接收数据。
工业环境下,干扰较多,信号中通常会产生冲激,用取数据中点的方法容易受到脉冲信号的影响,故用采样的方法保证接收数据的准确性
采样
上图为单bit数据接收的示意图,将每位数据等分成16份,两边标红的部分在数据跳变的边沿处,容易出现干扰,因此对中间绿色部分的稳定数据进行采样,采样第5-11份,共采样7次

二、UART串口接收模块设计

1.模块端口说明

端口名称方向说明
Clkinput系统时钟
Rst_ninput系统复位
rxinput串口接收端口
baud_set[2:0]input波特率设置
data[7:0]output8位并行接收数据
rx_doneoutput串口接收完成标志
uart_stateoutput串口接收状态标志

串口接收模块
模块检测到起始位,即rx下降沿时,开始接收数据,uart_state置1。baud_set是数值为0-7的3位二进制数,分别对应波特率2400、4800、9600、19200、38400、57600、115200、256000;当串口接收完成时,rx_done置1,uart_state置0。

2.波特率设置表格

波特率是指每秒通信数据的比特个数,则每1/bps秒发送1bit数据,则波特率分频计数时间为 1 ∗ 1 0 9 / b p s 1∗10^9/ bps 1109/bps ns,将数据等分为16份,对中间7份进行采样,系统时钟为50Mhz,得出分频计数值计算公式
分频计数值 = 1 ∗ 1 0 9 / b p s / 16 / 20 − 1 分频计数值=1*10^9/bps/16/20-1 分频计数值=1109/bps/16/201
根据公式计算得出如下表格:

baud_set波特率波特率分频计数值
024001301
14800650
29600324
319200161
43840080
55760053
611520026
738400011

3.代码设计

和串口发送模块的设计类似,接收模块可包括以下7个部分:起始位检测、串口接收状态标志uart_state赋值、波特率分频计数最大值cnt_bpsmax赋值、波特率时钟分频计数器、波特率时钟计数器、数据采样接收、串口接收完成标志rx_done赋值。
(1)起始位检测
s0_Rx为二级同步寄存器,用于解决亚稳态问题,s1_Rx为数据寄存器,存储前一时钟周期rx值,用于判断起始位。

	reg [1:0] s0_Rx;	//同步寄存器,用于解决亚稳态问题
	reg s1_Rx;			//数据寄存器,用于判断起始位下降沿
	wire nedge;			//判断起始位下降沿
	//二级同步寄存器,消除亚稳态
	always@(posedge Clk or negedge Rst_n)
	if(!Rst_n) 
		s0_Rx <= 2'b0;
	else
		s0_Rx <= {s0_Rx[0],rx};
	//数据寄存器,储存上一周期的rx
	always@(posedge Clk or negedge Rst_n)
	if(!Rst_n) 
		s1_Rx <= 1'b0;
	else
		s1_Rx <= s0_Rx[1];
		
	assign nedge = s1_Rx && !s0_Rx[1];

起始位接收错误标志
将每一位数据分为16份,取第5-11份进行采样,故波特率时钟计数器bps_cnt=12时,起始位已接收完成,此时判断起始位是否错误,若起始位错误,则START_BIT[2]=1

	wire START_ERR;		//起始位接收错误标志
	assign START_ERR = (bps_cnt == 8'd12) && START_BIT[2];

(2)串口接收状态标志uart_state赋值
当检测到起始位时,uart_state赋值为1;当停止位接收完毕时或起始位接收错误时,串口接收完成或停止,uart_state赋值为0。

	always@(posedge Clk or negedge Rst_n)
	if(!Rst_n)
		uart_state <= 1'b0;
	else if(nedge)
		uart_state <= 1'b1;
	else if(bps_cnt == 8'd159 || START_ERR)
		uart_state <= 1'b0;	

(3)波特率分频计数最大值cnt_bpsmax赋值
根据上面计算出的波特率设置表格对cnt_bpsmax进行赋值。复位时,cnt_bpsmax为324,表示默认波特率为9600。

	always@(posedge Clk or negedge Rst_n)//波特率选择
	if(!Rst_n)
		cnt_bpsmax <= 16'd324;//默认波特率9600
	else
		case(baud_set)
			0:cnt_bpsmax <= 16'd1301;	//波特率2400
			1:cnt_bpsmax <= 16'd650;	//波特率4800
			2:cnt_bpsmax <= 16'd324;	//波特率9600
			3:cnt_bpsmax <= 16'd161;	//波特率19200
			4:cnt_bpsmax <= 16'd80;		//波特率38400
			5:cnt_bpsmax <= 16'd53;		//波特率57600
			6:cnt_bpsmax <= 16'd26;		//波特率115200
			7:cnt_bpsmax <= 16'd11;		//波特率256000
			default:cnt_bpsmax <= 16'd324;
		endcase

(4)波特率时钟分频计数器
得到波特率分频计数最大值后,编写波特率时钟分频计数器。

	always@(posedge Clk or negedge Rst_n)//分频计数
	if(!Rst_n)
		cnt <= 16'd0;
	else  if(uart_state) begin
		if(cnt == cnt_bpsmax)
			cnt <= 16'd0;
		else
			cnt <= cnt + 1'b1;
	end
	else
		cnt <= 16'b0;

(5)波特率时钟计数器
共接收10位:起始位、8位data、停止位,每位分为16份,bps_cnt计数160次。起始位接收错误时,波特率时钟停止计数。

	always@(posedge Clk or negedge Rst_n)//波特率时钟计数
	if(!Rst_n)
		bps_cnt <= 8'd0;
	else if(uart_state) begin
		if(bps_cnt == 8'd159 || START_ERR)
			bps_cnt <= 8'd0;
		else if(cnt == 16'b1)
			bps_cnt <= bps_cnt + 1'b1;
		else
			bps_cnt <= bps_cnt;	
	end
	else
		bps_cnt <= 8'd0;

(6)数据采样接收
每位数据采样7次,对7次采样结果累加,若结果为3、4、5、6,则认为该数据为1,若结果为0、1、2,则认为该数据为0。将每位数据的采样结果存储在位宽为3的寄存器中:

	reg [2:0] rx_data [7:0];//8个3位寄存器
	reg [2:0] START_BIT;
	reg [2:0] STOP_BIT;

起始位在bps_cnt为5-11时采样,其它位数据在此基础上加16的倍数,每次采样将当前rx值s0_Rx[1]累加到起始位寄存器START_BIT中

	always@(posedge Clk or negedge Rst_n)//数据采样接收
	if(!Rst_n) begin
		START_BIT <= 3'b0;
		for(i=0;i<8;i=i+1)
			rx_data[i] <= 3'b0;
		STOP_BIT <= 3'b0;
	end
	else if(cnt == 16'b1) begin
		case(bps_cnt)
			0:begin
				START_BIT <= 3'b0;
				for(i=0;i<8;i=i+1)
					rx_data[i] <= 3'b0;
				STOP_BIT <= 3'b0;
			end
			  5,  6,  7,  8,  9, 10, 11: START_BIT  <= START_BIT  + s0_Rx[1];
			 21, 22, 23, 24, 25, 26, 27: rx_data[0] <= rx_data[0] + s0_Rx[1];
			 37, 38, 39, 40, 41, 42, 43: rx_data[1] <= rx_data[1] + s0_Rx[1];
			 53, 54, 55, 56, 57, 58, 59: rx_data[2] <= rx_data[2] + s0_Rx[1];
			 69, 70, 71, 72, 73, 74, 75: rx_data[3] <= rx_data[3] + s0_Rx[1];
			 85, 86, 87, 88, 89, 90, 91: rx_data[4] <= rx_data[4] + s0_Rx[1];
			101,102,103,104,105,106,107: rx_data[5] <= rx_data[5] + s0_Rx[1];
			117,118,119,120,121,122,123: rx_data[6] <= rx_data[6] + s0_Rx[1];
			133,134,135,136,137,138,139: rx_data[7] <= rx_data[7] + s0_Rx[1];
			149,150,151,152,153,154,155: STOP_BIT   <= STOP_BIT   + s0_Rx[1];
			default;
		endcase
	end
	else begin
		START_BIT <= START_BIT;
		for(i=0;i<8;i=i+1)
			rx_data[i] <= rx_data[i];
		STOP_BIT <= STOP_BIT;	
	end

数据全部采样完成时,对采样数据解码,计算每位数据的具体数值,当7次采样值大于等于3时,寄存器第2位为1,该位数据值也为1,因此rx_data[i][2]可作为每位数据的值,将其存储到data中。

	always@(posedge Clk or negedge Rst_n)//采样数据解码
	if(!Rst_n)	
		data <= 8'b0;
	else if(bps_cnt == 8'd159)
		for(i=0;i<8;i=i+1)
			data[i] <= rx_data[i][2];
	else
		data <= data;

(7)串口接收完成标志rx_done赋值
停止位接收完成时,串口接收结束,rx_done置1。

	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;

串口接收源代码

//本模块用于实现串口数据接收,系统频率为50Mhz,时钟周期为20ns
module uart_data_rx(
	input Clk,					//系统时钟
	input Rst_n,				//系统复位
	input rx,					//串口接收端口
	input [2:0] baud_set,	//波特率设置
	
	output reg [7:0] data,	//8位并行接收数据
	output reg rx_done,		//串口接收完成标志
	output reg uart_state	//串口接收状态标志
);
	
	reg [1:0] s0_Rx;	//同步寄存器,用于解决亚稳态问题
	reg s1_Rx;			//数据寄存器,用于判断起始位下降沿
	wire nedge;			//判断起始位下降沿
	wire START_ERR;	//起始位接收错误标志
	
	reg [15:0] cnt;			//波特率分频计数器
	reg [15:0] cnt_bpsmax;	//波特率分频计数最大值
	reg [7:0] bps_cnt;		//波特率时钟计数器
	
	reg [2:0] rx_data [7:0];//8个3位寄存器
	reg [2:0] START_BIT;
	reg [2:0] STOP_BIT;
	
	integer i;//for循环使用
	
	//二级同步寄存器,消除亚稳态
	always@(posedge Clk or negedge Rst_n)
	if(!Rst_n) 
		s0_Rx <= 2'b0;
	else
		s0_Rx <= {s0_Rx[0],rx};
		
	//数据寄存器,储存上一周期的rx
	always@(posedge Clk or negedge Rst_n)
	if(!Rst_n) 
		s1_Rx <= 1'b0;
	else
		s1_Rx <= s0_Rx[1];

	assign nedge = s1_Rx && !s0_Rx[1];
	assign START_ERR = (bps_cnt == 8'd12) && START_BIT[2];
	
	always@(posedge Clk or negedge Rst_n)//波特率选择
	if(!Rst_n)
		cnt_bpsmax <= 16'd324;//默认波特率9600
	else
		case(baud_set)
			0:cnt_bpsmax <= 16'd1301;	//波特率2400
			1:cnt_bpsmax <= 16'd650;	//波特率4800
			2:cnt_bpsmax <= 16'd324;	//波特率9600
			3:cnt_bpsmax <= 16'd161;	//波特率19200
			4:cnt_bpsmax <= 16'd80;		//波特率38400
			5:cnt_bpsmax <= 16'd53;		//波特率57600
			6:cnt_bpsmax <= 16'd26;		//波特率115200
			7:cnt_bpsmax <= 16'd11;		//波特率256000
			default:cnt_bpsmax <= 16'd324;
		endcase
		
	always@(posedge Clk or negedge Rst_n)//分频计数
	if(!Rst_n)
		cnt <= 16'd0;
	else  if(uart_state) begin
		if(cnt == cnt_bpsmax)
			cnt <= 16'd0;
		else
			cnt <= cnt + 1'b1;
	end
	else
		cnt <= 16'b0;
		
	always@(posedge Clk or negedge Rst_n)//波特率时钟计数
	if(!Rst_n)
		bps_cnt <= 8'd0;
	else if(uart_state) begin
		if(bps_cnt == 8'd159 || START_ERR)
			bps_cnt <= 8'd0;
		else if(cnt == 16'b1)
			bps_cnt <= bps_cnt + 1'b1;
		else
			bps_cnt <= bps_cnt;	
	end
	else
		bps_cnt <= 8'd0;

	always@(posedge Clk or negedge Rst_n)//数据采样接收
	if(!Rst_n) begin
		START_BIT <= 3'b0;
		for(i=0;i<8;i=i+1)
			rx_data[i] <= 3'b0;
		STOP_BIT <= 3'b0;
	end
	else if(cnt == 16'b1) begin
		case(bps_cnt)
			0:begin
				START_BIT <= 3'b0;
				for(i=0;i<8;i=i+1)
					rx_data[i] <= 3'b0;
				STOP_BIT <= 3'b0;
			end
			  5,  6,  7,  8,  9, 10, 11: START_BIT  <= START_BIT  + s0_Rx[1];
			 21, 22, 23, 24, 25, 26, 27: rx_data[0] <= rx_data[0] + s0_Rx[1];
			 37, 38, 39, 40, 41, 42, 43: rx_data[1] <= rx_data[1] + s0_Rx[1];
			 53, 54, 55, 56, 57, 58, 59: rx_data[2] <= rx_data[2] + s0_Rx[1];
			 69, 70, 71, 72, 73, 74, 75: rx_data[3] <= rx_data[3] + s0_Rx[1];
			 85, 86, 87, 88, 89, 90, 91: rx_data[4] <= rx_data[4] + s0_Rx[1];
			101,102,103,104,105,106,107: rx_data[5] <= rx_data[5] + s0_Rx[1];
			117,118,119,120,121,122,123: rx_data[6] <= rx_data[6] + s0_Rx[1];
			133,134,135,136,137,138,139: rx_data[7] <= rx_data[7] + s0_Rx[1];
			149,150,151,152,153,154,155: STOP_BIT   <= STOP_BIT   + s0_Rx[1];
			default;
		endcase
	end
	else begin
		START_BIT <= START_BIT;
		for(i=0;i<8;i=i+1)
			rx_data[i] <= rx_data[i];
		STOP_BIT <= STOP_BIT;	
	end
	
	always@(posedge Clk or negedge Rst_n)//采样数据解码
	if(!Rst_n)	
		data <= 8'b0;
	else if(bps_cnt == 8'd159)
		for(i=0;i<8;i=i+1)
			data[i] <= rx_data[i][2];
	else
		data <= data;

	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)
		uart_state <= 1'b0;
	else if(nedge)
		uart_state <= 1'b1;
	else if(bps_cnt == 8'd159 || START_ERR)
		uart_state <= 1'b0;	

endmodule

三、仿真

1.仿真文件

仿真时,需将之前写好的串口发送模块例化进来,模拟PC端串口发送数据8’hab和8’h23,由接收模块接收。

`timescale 1ns/1ns
module uart_data_rx_tb();

	//共用参数
	reg Clk,Rst_n;
	reg [2:0] baud_set;
	wire rx_tx;//将tx连接rx
	
	//uart发送模块参数
	reg send_en;
	reg [7:0] data_tx;
	wire uart_state_tx,tx_done;
	
	//uart接收模块参数
	wire rx_done;
	wire uart_state_rx;
	wire [7:0] data_rx;

	uart_data_rx uart_data_rx(
		.Clk(Clk),
		.Rst_n(Rst_n),
		.rx(rx_tx),
		.baud_set(baud_set),
		
		.data(data_rx),
		.rx_done(rx_done),
		.uart_state(uart_state_rx)
	);
	
	uart_data_tx PC_tx(   
		.Clk(Clk),
		.Rst_n(Rst_n),
		.send_en(send_en),
		.data(data_tx),
		.baud_set(baud_set),
		
		.tx(rx_tx),
		.tx_done(tx_done),
		.uart_state(uart_state_tx)
	);

	initial Clk = 1;
	always #10 Clk = ~Clk;
	
	initial begin
		Rst_n = 1'b0;
		send_en = 1'b0;
		data_tx = 8'h0;
		baud_set = 3'd2;
		#201;//20+1为了和其他时钟错开,便于观察结果
		
		Rst_n = 1'b1;
		#200;
		
		data_tx = 8'hab;
		send_en = 1'b1;
		#20;
		send_en = 1'b0;
		@(posedge tx_done);//等待发送完成
		#5000000;
		
		data_tx = 8'h23;
		send_en = 1'b1;
		#20;
		send_en = 1'b0;
		@(posedge tx_done);
		#5000000;
		$stop;
	end
endmodule

2.仿真结果

串口接收仿真

  • 0
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值