xilinxFPGA-串口发送和接收模块实例详解(基于verilog)

xilinxFPGA-串口发送和接收模块实例详解(基于verilog)

串口原理

首先我们得直到串口是怎样进行通信的。
在这里插入图片描述
我们可以看到,串口通讯的数据格式是由一位起始位、七个数据位(其中最后一位数据位可以作为检验位来使用。)、一位停止位,在空闲时刻为高电平,当我们使用串口发送时,就可以按照这种时序进行发送,接收时也要按照这种时序进行接收。

波特率计算

什么是波特率?

每秒钟发送的数据个数。

概念很简单,我们需要怎样去计算呢?常见的波特率有 9600192003840057600115200

波特率计算:
9600:1_000_000_000 / 9600;
19200:1_000_000_000 / 19200;
38400:1_000_000_000 / 38400;
57600:1_000_000_000 / 57600;
115200:1_000_000_000 / 115200;

这儿的 1_000_000_000 是代表 1s 因为我的开发板是以 ns 为时间单位的,所以我的波特率计算也是以 ns 为时间单位。

串口发送

发送思路原理:我们可以通过定义一个计数器,来记录每次发送所需要的的时间,然后用一个状态机来记录发送的状态。
module uart_tx_bottom(
	input Clk,	//定义输入时钟信号。
	input Reset_n,	//定义复位信号。
	input [7:0]Data,	//定义需要发送的八位数据。
	input Send_en,	//定义发送使能信号,此信号为1时才可以发送数据。	
	output reg uart_tx,	//定义发送信号,reg型是因为需要在always块中给它赋值。
	output reg Tx_done	//定义发送完成标志信号,以便下次发送数据。
);
	parameter Band_Set = 433;	//这儿是利用时钟计数发送一位的时间,1_000_000_000 / 115200 / 20 - 1// 其中 / 20 是因为我的开发板时钟周期为20ns, - 1 是因为计数器从0开始计数,原本算出需要计数434次,只需要加到433时就是434次了。
	reg [3:0]bps_cnt;	//数据发送状态,因为发送有起始位,数据位,结束位,用这个来记录数据发送到哪个位置了。
	reg [17:0]div_cnt;	//一位数据位发送时间计数器,记录发送数据的时间,用于更新下一段数据。
	wire bps_clk;	//每一位发送数据前后的标志位,具体作用后面会讲到。
	
	always @(posedge Clk or negedge Reset_n)	begin	//定义一个计数器的always块。
		if (!Reset_n)
			div_cnt <= 0;	//若复位将计数器归0.
		else if (Send_en)	begin	//如果有使能才可以进行自加。
			if (Band_Set <= div_cnt)	//当中间<=写法错误时,这种写法会让编辑器自动报错,可以减少因为没注意而引发的功能错误,而且使用<=可以对未知的情况进行判断,增加程序的健壮性。
				div_cnt <= 0;	//当计数器记满时,计数器归0.
			else
				div_cnt <= div_cnt + 1'b1;	//计数器未记满,每个时钟上升沿自加1.
		end
		else
			div_cnt <= 0;	//没有使能信号,使计数器归0
	end
		
	assign bps_clk = (div_cnt == 1);	//当每次计数器记到 1 的时候就让数据开始发送,因为如果div_cnt == Band_Set,在每次发送时还需要等待一个数据位的时间数据才会发送,而这样定义后只需要等待1个时钟周期数据就会发送了。因为bps_clk是wire类型的,所以用assign语句赋值,而不是用always语句赋值。
	
	always @(posedge Clk or negedge Reset_n)	begin	//定义发送状态块,记录发送的状态。
		if (!Reset_n)
			bps_cnt <= 0;	//	若是复位,状态为0else if (Send_en)	begin	//发送使能,状态才会改变。
			if (bps_clk)	begin	//当有发送的标志时,状态才自增一次。
				if (bps_cnt == 11)
					bps_cnt <= 0;	//当状态计数到第11个状态,状态归0else
					bps_cnt <= bps_cnt + 1'b1;	//状态自增1。
			end
		end
		else
			bps_cnt <= 0;	//发送不使能,状态归0end
	
	always @(posedge Clk or negedge Reset_n)	begin	//串口发送块。
		if (!Reset_n)
			uart_tx <= 1;	//当复位时,数据线发送高电平,因为空闲状态时数据线为高电平。
		else begin
			case(bps_cnt)	//开始发送数据,以数据状态来决定发送的是哪个数据。
				1:uart_tx <= 0;	//发送起始位,为什么不是0状态时刻发送0,因为在复位或者空闲时状态为0,而此时需要·uart_tx为高电平,所以在0状态时刻不能为02:uart_tx <= Data[0];	//发送数据低位,数据从低位发送到高位,此下依次发送数据。
				3:uart_tx <= Data[1];
				4:uart_tx <= Data[2];
				5:uart_tx <= Data[3];
				6:uart_tx <= Data[4];
				7:uart_tx <= Data[5];
				8:uart_tx <= Data[6];
				9:uart_tx <= Data[7];	//一般来说,不用校验位,直接发送数据最后一位。
				default:uart_tx <= 1;	//数据位发送完成之后,需要发送停止位,我们可以发现,空闲和停止位都是高电平,所以用default来将这些状态综合。
				//细心的可以发现,我们状态定义了11个,这儿9个就结束了,为什么需要定义11个呢?因为在最后一个停止位发送为第10个状态,而停止位需要保留一个数据周期,所以在第10个状态发送停止位后,需要再延后一个状态,来保证停止位的时间,所以是11个状态。
			endcase
		end
	end
	
	always @(posedge Clk or negedge Reset_n)	begin	//定义发送完成标志块。
		if (!Reset_n)
			Tx_done <= 0;	//复位时,发送完成标志位为0.
		else if (10 <= bps_cnt && bps_clk)	//当状态为第10或者11个状态时且发送一个数据的标志位置位时才使总发送标志位置1。
			Tx_done <= 1;
		else
			Tx_done <= 0;	//其余时刻都将状态标志位置0end
endmodule

串口接收

接收思路原理:因为数据传输是不稳定的,会有波动,所以我们可以将每一个数据接收时间分为 16 份,检测其中中间的 7 份,7 份当中最多的状态就是此时的数据状态。
module uart_rx(	//定义串口接收模块。
	input Clk,	//定义系统时钟信号。
	input Reset_n,	//定义复位。
	input uart_RXD,	//定义串口接收信号。
	output reg [7:0]uart_Data,	//定义接收数据输出。
	output reg Rx_done	//定义接收完成使能信号。
);
	reg [1:0]state_RXD;	//定义一个两位寄存器来储存此时刻和上一时刻的接收信号。
	wire nedge_RXD;	//定义接收信号下降沿寄存器。
	reg uart_EN;	//定义接收使能。
	reg [8:0]cnt_Scan;	//定义9位寄存器来计数每一位的16分之1的时间。
	parameter Bps_DR = 1_000_000_000 / 115200 / 16 / 20 - 1;	//定义每16/1所需时钟周期。
	reg [7:0]cnt_state;	//定义8位寄存器来记录一共160个状态。
	localparam MAX = 159;	//定义总共多少状态。localparam代表不能在上层模块中修改。
	reg [2:0]state_Start;	//定义起始信号寄存器。
	reg [2:0]state_Data[7:0];	//定义数据信号寄存器,代表73位的寄存器。
	reg [2:0]state_Stop;	//定义结束信号。
	reg state_done;	//定义16/1位接收完成使能信号。
	
	always @(posedge Clk or negedge Reset_n)	begin	//定义数据信号此时刻和上一时刻接收模块。
		if (!Reset_n)	//默认状态为0.
			state_RXD <= 2'd0;
		else	//记录状态到寄存器里。
			state_RXD <= {state_RXD[0], uart_RXD};
	end
	assign nedge_RXD = (state_RXD == 2'b10);	//判断是否为下降沿信号。
	
	always @(posedge Clk or negedge Reset_n)	begin	//定义接收使能模块。
		if (!Reset_n)	//复位使能无效。
			uart_EN <= 1'b0;
		else if (nedge_RXD)	//只有当下降沿,也就是起始位被检测到使能接收。
			uart_EN <= 1'b1;
		else if (cnt_state == MAX || state_Start[2])	//160个状态计满或者起始信号为假时,后面会讲到,起始信号为真时,state_Start[2]为0。
			uart_EN <= 0;
		else	//其他状态使能信号不变。
			uart_EN <= uart_EN;
	end
	
	always @(posedge Clk or negedge Reset_n)	begin	//定义每16/1为计数器模块。
		if (!Reset_n)	//复位清零。
			cnt_Scan <= 9'd0;
		else if (uart_EN)	begin	//使能为有效时。
			if (Bps_DR <= cnt_Scan)	//计满归零。
				cnt_Scan <= 9'd0;
			else	//否则加一。
				cnt_Scan <= cnt_Scan + 1'b1;
		end
		else	//使能失效清零。
			cnt_Scan <= 9'd0;
	end
	
	always @(posedge Clk or negedge Reset_n)	begin	//160为状态计数器模块。
		if (!Reset_n)	//复位清零。
			cnt_state <= 8'd0;
		else if (uart_EN)	begin	//使能有效。
			if (state_done)	begin	//16/1为接收使能有效。
				if (MAX <= cnt_state)	//计满清零。
					cnt_state <= 8'd0;
				else	//否则加一。
					cnt_state <= cnt_state + 1'b1;
			end
			else	//其他状态不变。
				cnt_state <= cnt_state;
		end
		else	//使能失效清零。
			cnt_state <= 8'd0;
	end
	
	always @(posedge Clk or negedge Reset_n)	begin	//数据接收模块。
		if (!Reset_n)	begin	//复位清零。
			state_Start <= 3'd0;
			state_Data[0] <= 3'd0;	//定义成[x:0]a[y:0]这样的数据只能一个一个赋值,不能一起赋值。
			state_Data[1] <= 3'd0;
			state_Data[2] <= 3'd0;
			state_Data[3] <= 3'd0;
			state_Data[4] <= 3'd0;
			state_Data[5] <= 3'd0;
			state_Data[6] <= 3'd0;
			state_Data[7] <= 3'd0;
			state_Stop <= 3'd0;
		end
		else if (uart_EN)	begin	//接收使能有效。
			case (cnt_state)	//更据状态赋值。
				5,6,7,8,9,10,11:state_Start <= state_Start + uart_RXD;	//因为接收信号只有01,所以只需要每次累加,取中间七次,当数据大于4时,便为高电平,否则为低电平,下同。
				21,22,23,24,25,26,27:state_Data[0] <= state_Data[0] + uart_RXD;
				37,38,39,40,41,42,43:state_Data[1] <= state_Data[1] + uart_RXD;
				53,54,55,56,57,58,59:state_Data[2] <= state_Data[2] + uart_RXD;
				69,70,71,72,73,74,75:state_Data[3] <= state_Data[3] + uart_RXD;
				85,86,87,88,89,90,81:state_Data[4] <= state_Data[4] + uart_RXD;
				101,102,103,104,105,106,107:state_Data[5] <= state_Data[5] + uart_RXD;
				117,118,119,120,121,122,123:state_Data[6] <= state_Data[6] + uart_RXD;
				133,134,135,136,137,138,139:state_Data[7] <= state_Data[7] + uart_RXD;
				149,150,151,152,153,154,155:state_Stop <= state_Stop + uart_RXD;
			endcase
		end
		else	begin	//使能失效清零。
			state_Start <= 3'd0;
			state_Data[0] <= 3'd0;
			state_Data[1] <= 3'd0;
			state_Data[2] <= 3'd0;
			state_Data[3] <= 3'd0;
			state_Data[4] <= 3'd0;
			state_Data[5] <= 3'd0;
			state_Data[6] <= 3'd0;
			state_Data[7] <= 3'd0;
			state_Stop <= 3'd0;
		end
	end
	
	always @(posedge Clk or negedge Reset_n)	begin	//数据读取转换模块。
		if (!Reset_n)	//复位清零。
			uart_Data <= 8'd0;
		else if (cnt_state == MAX)	begin	//将每一位一位数据读取出来,当数据大于4时,数据第三位为1,当数据小于4时,第三位为0,所以我们可以直接用第三位的值来判断读取的值。
			uart_Data[0] <= state_Data[0][2];
			uart_Data[1] <= state_Data[1][2];
			uart_Data[2] <= state_Data[2][2];
			uart_Data[3] <= state_Data[3][2];
			uart_Data[4] <= state_Data[4][2];
			uart_Data[5] <= state_Data[5][2];
			uart_Data[6] <= state_Data[6][2];
			uart_Data[7] <= state_Data[7][2];
		end
		else	//其他状态数据不变。
			uart_Data <= uart_Data;
	end
	
	always @(posedge Clk or negedge Reset_n)	begin	//16/1位完成使能信号。
		if (!Reset_n)	//复位清零。
			state_done <= 1'b0;
		else if (cnt_Scan == Bps_DR)	//当每16/1计数器计满时使能信号。
			state_done <= 1'b1;
		else	//否则使能失效。
			state_done <= 1'b0;
	end
	
	always @(posedge Clk or negedge Reset_n)	begin	//接收完成使能信号模块。
		if (!Reset_n)	//复位清零。
			Rx_done <= 1'b0;
		else if (cnt_state == MAX)	//160个状态计满时使能接收完成信号。
			Rx_done <= 1'b1;
		else	//其他时刻使能信号失效。
			Rx_done <= 1'b0;
	end
endmodule

检测代码

以下代码提供为大家检测,它的作用是由电脑发送数据,再由开发板发送回来,博主已经亲测成功,如果测试出来数据失败,可以私信或者评论博主一起解决问题。

module uart(
	input Clk,
	input Reset_n,
	input uart_RXD,
	output uart_TXD
);
	reg [7:0]tx_Data;
	wire [7:0]rx_Data;
	reg uart_EN;
	wire Tx_done;
	wire Rx_done;
	uart_tx uart_tx(
		.Clk		(Clk),
		.Reset_n	(Reset_n),
		.uart_Data	(tx_Data),
		.uart_EN	(uart_EN),
		.uart_TXD	(uart_TXD),
		.Tx_done    (Tx_done)
	);
	uart_rx uart_rx(
		.Clk		(Clk),
		.Reset_n	(Reset_n),
		.uart_RXD	(uart_RXD),
		.uart_Data	(rx_Data),
		.Rx_done    (Rx_done)
	);
	always @(*)	begin
		tx_Data <= rx_Data;
	end
	always @(posedge Clk or negedge Reset_n)	begin
		if (!Reset_n)
			uart_EN <= 1'b0;
		else if (Rx_done)
			uart_EN <= 1'b1;
		else if (Tx_done)
			uart_EN <= 1'b0;
		else
			uart_EN <= uart_EN;
	end
endmodule
  • 17
    点赞
  • 86
    收藏
    觉得还不错? 一键收藏
  • 18
    评论
评论 18
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值