基于FPGA的任意字节数的串口接收(含源码工程)_fpga串口接收多字节数据

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6

1、概述

        在这篇文章基于FPGA的任意字节数的串口发送含源码工程中实现了基于FPGA的任意字节数的串口发送那么对应的这一篇文章将分享给大家如何实现任意字节的FPGA接收方法。

        在这篇文章串口UART的FPGA实现含源码工程实现了基于FPGA的串口接收驱动。利用接收驱动可以实现 起始位1bit+数据位8bit+停止位1bit 共10bit的单字节接收。

        但是在实际应用过程中有时候需要一次性接收多个字节的数据。比如一次性通过UART接收5个字节的数据再将其组合成一个位宽为【39:0】的数据。诚然可以直接更改此文中的串口接收驱动使其变成 起始位1bit+数据位40bit+停止位1bit 共42bit的多字节传输。这种方法理论上是可行的因为UART协议并没有规定你一次要发送、接收多个少bit的数据既然能接收8个bit那同样能接收40个bit。

        但是很不幸实际上基本行不通因为通用的绝大部分上位机软件都不支持一次解析40bit的数据位如果你自己写上位机就当我没说。

        所以只能想点其他办法比如写个逻辑多次调用8bit即单字节的串口接收驱动那么40bit的数据就调用5次也就是5个 起始位1bit+数据位8bit+停止位1bit共10bit的单字节 分别接收再组合成一个40bit的数据就可以了。


2、串口接收驱动

        请参考串口UART的FPGA实现含源码工程在此文详细介绍了串口的接收驱动。

        以下代码可以实现 1bit+数据位8bit+停止位1bit 共10bit的单字节接收无奇偶校验。


// *******************************************************************************************************
// ** 作者  孤独的单刀                                                   			
// ** 邮箱  zachary_wu93@163.com
// ** 博客  https://blog.csdn.net/wuzhikaidetb 
// ** 日期  2022/08/05	
// ** 功能  1、基于FPGA的串口接收驱动模块
//			  2、可重新设置波特率BPS、主时钟CLK_FRE
//			  3、起始位1bit数据位8bit停止位1bit无奇偶校验。                                           									                                                                          			
// *******************************************************************************************************			

module uart_rx
#(
	parameter	integer	BPS		= 9_600		,		//发送波特率
	parameter 	integer	CLK_FRE	= 50_000_000		//输入时钟频率
)	
(	
//系统接口
	input 				sys_clk			,			//50M系统时钟
	input 				sys_rst_n		,			//系统复位
//UART接收线	
	input 				uart_rxd		,			//接收数据线
//用户接口	
	output reg 			uart_rx_done	,			//数据接收完成标志当其为高电平时代表接收数据有效
	output reg [7:0]	uart_rx_data				//接收到的数据在uart_rx_done为高电平时有效
);

//param define
localparam	integer	BPS_CNT = CLK_FRE / BPS;		//根据波特率计算传输每个bit需要多个系统时钟	
//reg define
reg 			uart_rx_d1		;					//寄存1拍
reg 			uart_rx_d2		;					//寄存2拍
reg 			uart_rx_d3		;					//寄存3拍
reg [31:0]		clk_cnt			;					//计数器用于计数发送一个bit数据所需要的时钟数
reg [3:0]  		bit_cnt			;					//bit计数器标志当前发送了多少个bit
reg 			rx_en			;					//接收标志信号拉高代表接收过程正在进行
reg [7:0]		uart_rx_data_reg;					//接收数据寄存
//wire define				
wire 			neg_uart_rxd	;					//接收数据线的下降沿

assign	neg_uart_rxd = uart_rx_d3 & (~uart_rx_d2);	//捕获数据线的下降沿用来标志数据传输开始
 
//将数据线打3拍作用1同步不同时钟域信号防止亚稳态作用2捕获下降沿
always@(posedge sys_clk or negedge sys_rst_n)begin
	if(!sys_rst_n)begin
		uart_rx_d1 <= 1'b0;
		uart_rx_d2 <= 1'b0;
		uart_rx_d3 <= 1'b0;
	end
	else begin
		uart_rx_d1 <= uart_rxd;
		uart_rx_d2 <= uart_rx_d1;
		uart_rx_d3 <= uart_rx_d2;
	end		
end

//捕获到数据下降沿起始位0后拉高传输开始标志位并在第9个数据终止位的传输过程正中数据比较稳定再将传输开始标志位拉低标志传输结束
always@(posedge sys_clk or negedge sys_rst_n)begin
	if(!sys_rst_n)
		rx_en <= 1'b0;
	else begin 
		if(neg_uart_rxd )								
			rx_en <= 1'b1;
		//接收完第9个数据终止位将传输开始标志位拉低标志传输结束判断高电平
		else if((bit_cnt == 4'd9) && (clk_cnt == BPS_CNT >> 1'b1) && (uart_rx_d3 == 1'b1) )
			rx_en <= 1'b0;
		else 
			rx_en <= rx_en;			
	end
end

//当数据传输到终止位时拉高传输完成标志位并将数据输出
always@(posedge sys_clk or negedge sys_rst_n)begin
	if(!sys_rst_n)begin
		uart_rx_done <= 1'b0;
		uart_rx_data <= 8'd0;
	end	
	//结束接收后将接收到的数据输出
	else if((bit_cnt == 4'd9) && (clk_cnt == BPS_CNT >> 1'd1) && (uart_rx_d3 == 1'b1))begin		
		uart_rx_done <= 1'b1;									//仅仅拉高一个时钟周期
		uart_rx_data <= uart_rx_data_reg;	
	end							
	else begin					
		uart_rx_done <= 1'b0;									//仅仅拉高一个时钟周期
		uart_rx_data <= uart_rx_data;
	end
end

//时钟每计数一个BPS_CNT传输一位数据所需要的时钟个数即将数据计数器加1并清零时钟计数器
always@(posedge sys_clk or negedge sys_rst_n)begin
	if(!sys_rst_n)begin
		bit_cnt <= 4'd0;
		clk_cnt <= 32'd0;
	end
	else if(rx_en)begin					            			//在接收状态
		if(clk_cnt < BPS_CNT - 1'b1)begin           			//一个bit数据没有接收完
			clk_cnt <= clk_cnt + 1'b1;              			//时钟计数器+1
			bit_cnt <= bit_cnt;                     			//bit计数器不变
		end                                         			
		else begin                                  			//一个bit数据接收完了	
			clk_cnt <= 32'd0;                       			//清空时钟计数器重新开始计时
			bit_cnt <= bit_cnt + 1'b1;              			//bit计数器+1表示接收完了一个bit的数据
		end                                         			
	end                                             			
		else begin                                  			//不在接收状态
			bit_cnt <= 4'd0;                        			//清零
			clk_cnt <= 32'd0;                       			//清零
		end		
end

//在每个数据的传输过程正中数据比较稳定将数据线上的数据赋值给数据寄存器
always@(posedge sys_clk or negedge sys_rst_n)begin
	if(!sys_rst_n)
		uart_rx_data_reg <= 8'd0;                            	//复位无接收数据
	else if(rx_en)                                           	//处于接收状态
		if(clk_cnt == BPS_CNT >> 1'b1) begin                 	//传输过程正中数据比较稳定
			case(bit_cnt)			                         	//根据位数决定接收的内容是什么
				4'd1:uart_rx_data_reg[0] <= uart_rx_d3;        	//LSB最低位
				4'd2:uart_rx_data_reg[1] <= uart_rx_d3;        	//
				4'd3:uart_rx_data_reg[2] <= uart_rx_d3;        	//
				4'd4:uart_rx_data_reg[3] <= uart_rx_d3;        	//
				4'd5:uart_rx_data_reg[4] <= uart_rx_d3;        	//
				4'd6:uart_rx_data_reg[5] <= uart_rx_d3;        	//
				4'd7:uart_rx_data_reg[6] <= uart_rx_d3;        	//
				4'd8:uart_rx_data_reg[7] <= uart_rx_d3;        	//MSB最高位
				default:;                                    	//1和9分别是起始位和终止位不需要接收
			endcase                                          	
		end                                                  	
		else                                                 	//数据不一定稳定就不接收
			uart_rx_data_reg <= uart_rx_data_reg;            
	else
		uart_rx_data_reg <= 8'd0;								//不处于接收状态
end	

endmodule 

3、任意字节接收的实现方法

        串口接收驱动模块的对外端口如下

 

        其中uart_rx_done信号是关键。当一个字节的数据被按约定的串口协议接收成功后该信号就会拉高一个时钟周期表示一次接收结束。

        可以每次在接收到一个字节后将接收到的数据通过移位寄存起来直到所有字节的数据都被成功接收。如下

 

        所以多字节数据的接收逻辑

  • 1、用户通过UART数据多次发送单字节数据比如5个
  • 2、根据uart_rx_done信号判断是否成功接收到一个信号并用计数器记录此时接收的是第几个字节的数据同时将其移位寄存起来
  • 3、当所有单字节数据均被接收完毕后拉高uart_bytes_vld表示一次多字节接收结束且此时的数据uart_bytes_data是有效数据

         完整代码如下


// *****************************************************************************************************************************
// ** 作者  孤独的单刀                                                   			
// ** 邮箱  zachary_wu93@163.com
// ** 博客  https://blog.csdn.net/wuzhikaidetb 
// ** 日期  2022/08/05	
// ** 功能  1、基于FPGA的串口多字节接收模块
//			  2、可设置一次接收的字节数、波特率BPS、主时钟CLK_FRE
//			  3、UART协议设置为起始位1bit数据位8bit停止位1bit无奇偶校验不可在端口更改只能更改发送驱动源码                                           									                                                                          			
//			  4、每接收到1次多字节后拉高指示信号一个周期指示一次多字节接收结束
//			  5、数据接收顺序先接收低字节、再接收高字节。如第1次接收到8’h34第2次接收到8’h12则最终接收到的数据为16'h12_34。                                          									                                                                          			
// *****************************************************************************************************************************	

module uart_bytes_rx
#(
	parameter	integer	BYTES 	 = 4			,				//一次接收字节数单字节8bit
	parameter	integer	BPS		 = 9600			,				//发送波特率
	parameter 	integer	CLK_FRE	 = 50_000_000					//输入时钟频率
)
(
//系统接口
	input 							sys_clk			,			//系统时钟
	input 							sys_rst_n		,			//系统复位低电平有效
//用户接口	
	output	[(BYTES * 8 - 1):0] 	uart_bytes_data	,			//接收到的多字节数据在uart_bytes_vld为高电平时有效
	output							uart_bytes_vld	,			//成功发送所有字节数据后拉高1个时钟周期代表此时接收的数据有效	
//UART接收	
	input 							uart_rxd					//UART发送数据线rx
);

//reg define
reg	[(BYTES*8-1):0]		uart_bytes_data_reg;					//寄存接收到的多字节数据先接收低字节后接收高字节
reg						uart_bytes_vld_reg;						//高电平表示此时接收到的数据有效
reg	[9:0]				byte_cnt;								//发送的字节个数计数(因为懒直接用10bit计数最大可以表示1024BYTE大概率不会溢出)			

//wire define
wire	[7:0]			uart_sing_data;							//接收的单个字节数据
wire					uart_sing_done;							//单个字节数据接收完毕信号
	
//对端口赋值
assign uart_bytes_data = uart_bytes_data_reg;
assign uart_bytes_vld  = uart_bytes_vld_reg;

//分别接收各个字节的数据
always @(posedge sys_clk or negedge sys_rst_n)begin		
	if(!sys_rst_n)		
		uart_bytes_data_reg <= 0;												
	else if(uart_sing_done)begin									//接收到一个单字节则将数据右移8bit实现最先接收的数据在低字节
		if(BYTES == 1)												//单字节就直接接收
			uart_bytes_data_reg <= uart_sing_data;											
		else														//多字节就移位接收
			uart_bytes_data_reg <= {uart_sing_data,uart_bytes_data_reg[(BYTES*8-1)-:(BYTES-1)*8]};														
	end	
	else		
		uart_bytes_data_reg <= uart_bytes_data_reg;				
end

//对接收的字节个数进行计数		
always @(posedge sys_clk or negedge sys_rst_n)begin		
	if(!sys_rst_n)		
		byte_cnt <= 0;		
	else if(uart_sing_done && byte_cnt == BYTES - 1)			//计数到了最大值则清零
		byte_cnt <= 0;										
	else if(uart_sing_done)										//发送完一个单字节则计数器+1
		byte_cnt <= byte_cnt + 1'b1;						
	else		
		byte_cnt <= byte_cnt;			
end

//所有数据接收完毕,拉高接收多字节数据有效信号
always @(posedge sys_clk or negedge sys_rst_n)begin
	if(!sys_rst_n)
		uart_bytes_vld_reg <= 1'b0;
	else if(uart_sing_done && byte_cnt == BYTES - 1)			//所有单字节数据接收完毕
		uart_bytes_vld_reg <= 1'b1;
	else 
		uart_bytes_vld_reg <= 1'b0;
end


//例化串口接收驱动模块
uart_rx #(
	.BPS			(BPS			),		
	.CLK_FRE		(CLK_FRE		)		
)	
uart_rx_inst
(	
	.sys_clk		(sys_clk		),			
	.sys_rst_n		(sys_rst_n		),	
	.uart_rx_done	(uart_sing_done	),			
	.uart_rx_data	(uart_sing_data	),			
	.uart_rxd		(uart_rxd		)
);

endmodule 

4、仿真

        使用该模块接收数据3次观测接收结果是否正常。分别使用单字节8位、双字节16位、5字节40位进行测试。


4.1、单字节仿真

        模拟上位机按UART协议三次发送随机的单字节数据过来观察能否正确接收。TB如下

`timescale 1ns/1ns	//定义时间刻度

module tb_uart_bytes_rx();

localparam	integer	BYTES    = 1						;	//一次接收的字节个数
localparam	integer	BPS 	 = 230400					;	//波特率
localparam	integer	CLK_FRE  = 50_000_000				;	//系统频率50M
localparam	integer	CNT      = 1000_000_000 / BPS		;	//计算出传输每个bit所需要的时间单位ns


reg 							sys_clk			;		//系统时钟
reg 							sys_rst_n		;		//系统复位低电平有效	
reg 							uart_rxd		;		//UART接收数据线

wire	[(BYTES * 8 - 1):0] 	uart_bytes_data	;		//接收到的多字节数据在uart_bytes_vld为高电平时有效
wire							uart_bytes_vld	;		//当其为高电平时代表此时接收到的多字节数据有效

initial begin	
	sys_clk <=1'b0;	
	sys_rst_n <=1'b0;
	uart_rxd <=1'b1;
	
	#20 //系统开始工作
	sys_rst_n <=1'b1;
	
	#3000
	repeat(3) begin						//重复生成8位随机数
		rx_byte({$random} % 256);		//
	end
	#60	$finish();
end

always #10 sys_clk=~sys_clk;	//设置主时钟20ns,50M

//定义任务每次发送的数据10 位(起始位1+数据位8+停止位1)
task rx_byte(
	input [7:0] data
);
	integer i; //定义一个常量
	//用 for 循环产生一帧数据for 括号中最后执行的内容只能写 i=i+1
	for(i=0; i<10; i=i+1) begin
		case(i)
		0: uart_rxd <= 1'b0;		//起始位
		1: uart_rxd <= data[0];		//LSB
		2: uart_rxd <= data[1];
		3: uart_rxd <= data[2];
		4: uart_rxd <= data[3];
		5: uart_rxd <= data[4];
		6: uart_rxd <= data[5];
		7: uart_rxd <= data[6];
		8: uart_rxd <= data[7];		//MSB
		9: uart_rxd <= 1'b1;		//停止位
		endcase
		#(CNT+10); 					//每发送1 位数据延时加10是为了减小误差
	end		
endtask 							//任务结束

//例化多字节接收模块
uart_bytes_rx #(
	.BYTES				(BYTES				),
	.BPS				(BPS				),		
	.CLK_FRE			(CLK_FRE			)		
)			
uart_bytes_rx_inst(			
	.sys_clk			(sys_clk			),			
	.sys_rst_n			(sys_rst_n			),
		
	.uart_bytes_data	(uart_bytes_data	),			
	.uart_bytes_vld		(uart_bytes_vld		),
	
	.uart_rxd			(uart_rxd			)	
);

endmodule 

       

        仿真结果如下

  

 

  • 模拟上位机调用了3次分别发送数据8'h248'h818'h09
  • 串口接收模块接收到了同样的3个数据8'h248'h818'h09

4.2、双字节仿真

        TB基本不用修改把BYTES这个参数改成2并把repeat的次数改成6就行。仿真结果如下

  

  • 上位机分别发送了6个数据8'h248'h818'h098'h638'h0d8'h8d
  • 接收到了3个数据16'h812416'h630916'h8d0d
  • 根据先接收低字节后高字节的原则将6个单字节组合成了3个双字节接收

4.3、5字节仿真

        TB基本不用修改把BYTES这个参数改成5并把repeat的次数改成10就行。仿真结果如下

  

        您照着上面的逻辑看看就行我就不啰嗦了。


5、实测

        下板实测可以通过上位机发送数据到FPGA同时使用在线逻辑仪signaltap II来观察接收的数据是否与发送一致。


5.1、单字节实测

        上位机发送数据8'h55  

        FPGA同样接收到数据8'h55

        测试结果与预期一致。 


5.2、双字节实测

        上位机发送数据8'h55、8'haa

       

        FPGA接收到数据16'haa55先接收低字节后接收高字节

        测试结果与预期一致。 


5.3、5字节实测

        上位机发送数据8'h9a、8'h78、8'h56、8'h34、8'h12

        

        FPGA接收到数据40'h123456789a先接收低字节后接收高字节 

        测试结果与预期一致。

        源码工程点击这里下载提取码oc60


  • 📣博客主页wuzhikai.blog.csdn.net
  • 📣本文由 孤独的单刀 原创首发于CSDN平台🐵
  • 📣您有任何问题都可以在评论区和我交流📞
  • 📣创作不易您的支持是我持续更新的最大动力如果本文对您有帮助还请多多点赞👍、评论💬和收藏⭐

 

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6