串口接收模块源码分析

模块输入输出端口

module uart_byte_rx(
	clk,//50M时钟
	rst_n,//复位信号
	baud_set,//波特率设置信号
	rs232_rx,//串口数据接受线
	
	data_byte,//接收到的数据
	rx_done//接受完成信号
);

时钟默认时50M,可以修改,波特率设置是选择了几个常见的串口通信速率,通过选择器选择

代码逻辑

	//同步寄存器,消除亚稳态
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

串口的输入信号rs232_rx相对于 FPGA 内部信号来说是一个异步信号(rs232_rx的状态
不依赖于时钟 Clk),如不进行处理直接将其输入使用,容易出现时序违例导致亚稳态。因此
这里就需要先将信号同步到 FPGA 的时钟域内才可以供后续模块使用,常见的同步方法即使
用两级触发器,也就是使用触发器对信号打两拍的方式进行与系统时钟进行同步,参考电路
即下图 所示(参考按键输入的)。其中 key_in(想象成rs232_rx) 为按键输入, key_in_sb (想象成s1_rs232_rx)为同步后的信号。

	//数据寄存器
	//将同步后的数据在延时两拍,用来判断rx_232的下降沿(下降沿是uart通信的起始信号)
	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
	
	//生成rx_232的下降沿
	assign nedege = !tmp0_rs232_rx & tmp1_rs232_rx;
	

说一下为什么这个可以判断下降沿,边沿检测其原理就是利用寄存器在时钟信号的控制下,输入状态即为下一时刻输出状态这一特性进行比较判断,原理如下图

 其检测过程,可以假设 data_in 从 0 变 1,也就是上升沿:
1. 第一个时钟到来, 第一个寄存器 regA_data 的输出为 0;
2. 第二个时钟沿到来后第一个寄存器输出为 1,第二个寄存器输出此时为 0,这样对
两个寄存器输出进行相关组合逻辑运算则可检测出上升沿;
3. 同理 data_in 从 1 变为 0,也就是下降沿:
4. 第一个时钟到来, 第一个寄存器 regA_data 的输出为 1;
5. 第二个时钟沿到来后第一个寄存器输出为 0,第二个寄存器输出此时为 1。

assign neg_out = !data_in & regA_data;

assign pos_out = data_in & !regA_data;

	//波特率选择
	always@(posedge clk or negedge rst_n)
	if(!rst_n)
		bps_DR <= 16'd324;
	else begin
		case(baud_set)
			0:bps_DR <= 16'd324;//9600
			1:bps_DR <= 16'd162;//
			2:bps_DR <= 16'd80;
			3:bps_DR <= 16'd53;
			4:bps_DR <= 16'd26;//115200
			default:bps_DR <= 16'd324;			
		endcase
	end

波特率生成电路,即通过一个选择器,根据输入的选择确定相应的波特率

波特率计算取下:举例9600 bps

9600bps的意义是一秒钟传输9600bit,因此传输一个bit(码元速率)的时间为1S除以9600,考虑到fpga是数字电路,并且输入查询的时钟为50M(50000000),因此传输1bit需要计数50000000除以9600=5208,

然而上面选择器对应的输出并不是5208,而是324,下面说明为什么这样设计

在实际工业应用中,现场往往有非常强的电磁干扰,只采样一次就作为该数据的电平状态是不可靠的。 很有可能恰好采集到被干扰的信号而导致结果出错,因此这里提出以下改进型的单 bit 数据接收方式示意图,使用多次采样求概率的方式进行状态判定,如下图所示。

 将每一位数据再平均分成了 16 小段。对于 Bit_x 这一位数据,考虑到数据在刚刚发生变化和即将发生变化的这一时期,数据极有可能不稳定的(用深灰色标出的两段),在这两个时间段采集数据,很有可能得到错误的结果,因此判定这两段时间的电平无效,采集时直接忽略。而中间这一时间段(用浅灰色标出),数据本身是比较稳定的,一般都代表了正确的结果。也就是前面提到的中间测量方式,但是也不排除该段数据受强电磁干扰而出现错误的电平脉冲。因此对这一段电平,进行多次采样,并求高低电平发生的概率, 6 次采集结果中,取出现次数多的电平作为采样结果。例如,采样 6 次的结果分别为 1/1/1/1/0/1/,则取电平结果为 1,若为 0/0/1/0/0/0,,则取电平结果为 0,当 6 次采样结果中 1 和 0 各占一半(各 3 次),则可判断当前通信线路环境非常恶劣,数据不具有可靠性,不进行处理。

从上面知晓,我们需要对每一bit再进行细分,细分成16分,因此9600bps对应的16细分为5208除以16=324

	//波特率分频计数器(上面是分频系数)
	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;
		
	// 波特率时钟生成
	always@(posedge clk or negedge rst_n)
	if(!rst_n)
		bps_clk <= 1'b0;
	else if(div_cnt == 16'd1)
		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;

上面代码就是根据前面的原理生成的计数器和分频时钟,bps_cnt是以每一个码元的16分之1进行加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;

rx_done是串口接收完成信号,告知后面模块电路串口接收到数据了

	//生成最后接受的数据(1 byte)	
	always@(posedge clk or negedge rst_n)
	if(!rst_n)
		data_byte <= 8'd0;
	else if(bps_cnt == 8'd159)begin
		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	

生成串口数据,在这里是将一个数组的最高位赋值给接收的字节,

讲一下为什么有个数组,

前面我们说过,每一个码元被分成了16分,我们对中间的六分进行采样,如果一位码元仅仅使用一位的话,我们就不能累积六次的结果,从而降低电磁的干扰,前面说我们在六次中采中同一电平次数大于4次,即认为正确采样,因此需要一个计数器(计数器最大值大于6)来统计采样的次数,这显然不可能是1bit可以做到的,因此设置了3bit的计数器,因为有8个码元,因此创建了8个3bit数组(3bit最大值为7>6),

	//串口接受过程,
	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

上面是核心的串口接收部分,需要说一下的是第二段赋值的地方,这个时候由于每个bit采样了6六次,所以才会出来6,7,8,9,10,11;22,23,24,25,26,27成组的数据(这些都是每个码元中间六段bps_cnt的计数值),

r_data_byte[0] <= r_data_byte[0] + s1_rs232_rx;

r_data_byte[0]这是一个数组,在verilog中这个表示寄存器的第0位(r_data_byte定义成寄存器的情况下)或者是第一个数组(r_data_byte定义成数组的情况下),在六次计数中,分别加上rx数据线上的数据,即可统计六次中采样电平数

假设六次中有5次高电平,这个数组就会加成5,假设有六次,这个数组就会加成6,而不用判断rx电平高低,因为判断为1时,需要的是加1,判断为0时需要假的是0,

说一下为什么统计电平为什么不需要两个数组,因为在六次采样中,不是1就是0,因此两种电平的采样总数为6,因此只需要采样高电平的次数

	//串口接受状态,为高时表明串口正在进行接受工作,为低时表示空闲,可以告知其他模块在空闲时间发送数据
	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;	

上面用来表示串口工作状态,为1是表示串口正在工作,为0时空闲

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值