FPGA基础设计(八):串口收发模块设计

串口发送模块

模块设计

在这里插入图片描述
输入信号
1、Clk、Rst_n:时钟、复位信号;
2、data_byte:待发送的数据,由外部模块传送进来;
3、Send_En:发送控制信号,表明待发送的数据已经稳定,可以进行发送;
4、baud_Set:波特率设置端口,增加模块的通用性,将波特率的计数值做成参数形式。
输出信号
1、Rs232_Tx:发送出去的信号;
2、Tx_Done:一帧数据发送完毕的标志信号;
3、uart_state:串口模块处于发送数据状态or空闲态标志信号。

总体实现:按照约定的波特率,将一帧数据一位一位的发送出去。
如下图,BPS_CLK即为发送数据的波特率时钟,检测到一个脉冲,从起始位开始发送数据直至停止位。
在这里插入图片描述

所以有两个要实现的组件:波特率时钟产生组件、数据发送组件

1、发送波特率时钟产生;

若以9600波特率发送数据,每隔5208个系统时钟周期产生一次脉冲。
那么问题来了,具体从5208个时钟周期内哪一个周期开始发送第一位数据,然后接着又等待5208个周期发送第二位数据…实际上可以在这个区间上的任意值开始发送第一位数据【边界除外】,等到下一次计数到这个值,发送第二位数据,经历的间隔都是5208个系统时钟周期。

 // 增加模块的通用性,将波特率的计数值做成参数形式
always@(posedge Clk or negedge Rst_n)
        if(!Rst_n)
	       bps_DR <= 	16'd5207;
		  else begin
		    case(Baud_Set)
		      0:bps_DR <= 16'd5207;//9600bps
			  1:bps_DR <= 16'd2603;//19200bps
			  2:bps_DR <= 16'd1301;//38400bps
			  3:bps_DR <= 16'd867; //57600bps
			  4:bps_DR <= 16'd433; //115200bps
				  
			 default:bps_DR <= 16'd5207;//9600bps
		  	 endcase
		  end 
// 分频计数
always@(posedge Clk or negedge Rst_n)
	if(!Rst_n)
		Div_cnt <= 16'd0;
	else if(Div_cnt == bps_DR)
		Div_cnt <= 16'd0;
	else 
		Div_cnt <= Div_cnt + 1'b1;
// 产生波特率时钟,即BPS_CLK  
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;

2、数据发送组件

我们已经得到波特率时钟,每来一个脉冲就发送一位数据;由于一共发送一帧10位数据,如果知道当前脉冲是第几个,那么就发送第几位数据到Tx端,使用计数替代计时的思想:每来一个波特率时钟脉冲,进行计数一次

always@(posedge Clk or negedge Rst_n)
		if(!Rst_n)
			bps_cnt <= 1'b0;
		else if(bps_cnt == 4'd11)// 当计数到10,才开始发送停止位,所以计数到11才发送完整的一帧数据
			bps_cnt <= 1'b0;
		else if(bps_clk)
			bps_cnt <= bps_cnt + 1'b1;
		else
			bps_cnt <= bps_cnt;

根据计数值bps_cnt可确定传输哪一位数据
在这里插入图片描述

always@(posedge Clk or negedge Rst_n)
	  if(!Rst_n)
		 Rs232_Tx <= 1'b1;  // 空闲状态
	  else begin
		 case(bps_cnt)  // 传输10位数据:一帧
			  4'd0: Rs232_Tx <= 1'b1;//有它的作用,两帧数据之间留出的空闲位
			  4'd1: Rs232_Tx <= 1'b0;//起始位
			  4'd2: Rs232_Tx <= r_data_byte[0];//先传输低位
			  4'd3: Rs232_Tx <= r_data_byte[1];
			  4'd4: Rs232_Tx <= r_data_byte[2];
			  4'd5: Rs232_Tx <= r_data_byte[3];
			  4'd6: Rs232_Tx <= r_data_byte[4];
			  4'd7: Rs232_Tx <= r_data_byte[5];
			  4'd8: Rs232_Tx <= r_data_byte[6];
			  4'd9: Rs232_Tx <= r_data_byte[7];
			  4'd10: Rs232_Tx <= 1'b1;//停止位
			  default:Rs232_Tx <= 1'b1;
			  endcase
	  end  

这里与野火教程区别是分频计数器在一开始就计数,没有触发使能计数条件,不知道数据是否处于稳态就开始计数,一般串口发送的数据都来自串口接收模块,在接收模块中自己会判断确定数据稳定后才能被外部模块使用。

波形分析

1、
在这里插入图片描述
在一开始复位后Div_cnt就开始计数,计数到1;到第一个系统时钟上升沿,检测到Div_cnt为1,拉高bps_clk;
到第二个上升沿,检测到bps_clk为高,bps_cnt开始计数;
等到第三个上升沿,检测bps_cnt为1;等到第四个上升沿,Rs232_Tx为0生效,即发送了起始位。

2、
在这里插入图片描述
第一个上升沿检测Div_cnt计数到1;
非阻塞赋值,第二个上升沿bps_clk才为高电平,且开始bps_cnt <= bps_cnt + 1’b1,只进行加1操作;
第三个上升沿才进行bps_cnt <= bps_cnt + 1’b1的赋值操作,bps_cnt为2,同时Rs232_Tx <= r_data_byte[0],下一个时钟沿才会被赋值;
第四个上升沿,Rs232_Tx <= r_data_byte[0]赋值操作,r_data_byte[0]为1,Rs232_Tx变为高电平。

3、
在这里插入图片描述
计数到11,在第二个时钟沿才赋值Tx_Done <= 1’b1,且赋值bps_cnt <= 1’b0,bps_cnt重新赋为0,uart_state为0,处于空闲状态

串口接收模块

前言

串口接收的往往是外部环境中数据,在工业环境存在强电干扰,导致通信线上的电平发送变化,对于数据的接收进行处理保证通信稳定。

串口接收时序图
方法一
对接收的每位数据只在中间位置采样一次作为该数据的电平状态,如上图,产生波特率时钟BPS_CLK,计数到中间位置时,采样当前的电平。其中BPS_CLK产生原理与串口接收模块相同,只不过每次计数
方法二
方法一只采样一次很可能恰好采样到被干扰的信号,导致出错。那就多采样几次求概率的方式进行状态判定!
在这里插入图片描述
如上图,对接收的每位数据都分成16份,其中舍去两边的10份,只采样中间的6份,对每次采样结果进行累加,高电平数量大于3,则采用结果为高电平,否则低电平。
这里有一个巧妙的处理,将累加结果存储到3位宽的寄存器中,取最高位,若最高位为1,说明采样结果为高电平,最高位为0,则为低电平。

模块设计

在这里插入图片描述
输入信号
1、Clk、Rst_n
2、Baud_Set:波特率设置端口,增加模块的通用性,将波特率的计数值做成参数形式;
3、Rs232_Rx:外部输入信号,要进行接收的串口数据。

输出信号
1、Data_Byte:并行数据输出;
2、Rx_Done:接收数据完成标志信号。

关键是如何正确的接收串行数据并将其转换为并行数据输出

接收串行数据设计

与串口发送模块设计思想一样,计数替代计时,产生波特率时钟,每产生一个波特率时钟脉冲计数一次,然后采样一次当前的电平状态,唯一不同的是每位数据采样16次,取中间的16次采样结果,采样频率是发送模块的16倍。

设计流程
1、接收的是外部信号,避免亚稳态,先进行同步处理:使用两级缓存
2、起始信号是低电平,检测下降沿,判断是否是数据帧的起始信号
3、产生波特率时钟,每产生一个脉冲,采样一次输入信号
4、对波特率时钟脉冲进行计数,根据计数值可知当前是第几次采样
5、根据计数值,将每位对应的6次结果累加,

1、同步处理

 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;//第一级寄存器稳定输出概率70%~80%
          s1_Rs232_Rx <= s0_Rs232_Rx;//第二级寄存器稳定输出的概率99%				
		end

2、起始位检测

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; //只有s1_Rs232_Rx是稳定信号,所以数据稳定后再进行两级寄存得到边沿
          tmp1_Rs232_Rx <= tmp0_Rs232_Rx;//两级寄存器					
		end	
assign nedge = ~tmp0_Rs232_Rx & tmp1_Rs232_Rx;	//满足起始位检测   

3、产生波特率时钟脉冲bps_clk

 always@(posedge Clk or negedge Rst_n)
         if(!Rst_n)
			  bps_DR <= 16'd324;
			else begin
			  case(Baud_Set)
			  0: bps_DR <= 16'd324;//9600bps
			  1: bps_DR <= 16'd162;//19200bps
			  2: bps_DR <= 16'd80;//38400bps 
			  3: bps_DR <= 16'd53;//57600bps
			  4: bps_DR <= 16'd26;//115200bps
			  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;	
     
 always@(posedge Clk or negedge Rst_n)
         if(!Rst_n)					
			  bps_clk <= 1'b0;
			else if(Div_cnt == 16'd1)
           bps_clk <= 1'b1;//比如9600波特率来说,分频计数此时的1到下一次的1计数325次
		   else             //在波特率时钟计数以此来计数,bps_clk来临16次才传输一位数据
           bps_clk <= 1'b0;

4、波特率时钟脉冲计数

 //波特率时钟计数		
 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;

5、累加采样结果

			 
      always@(posedge Clk or negedge Rst_n)
		        if(!Rst_n)begin
				    START_BIT <= 3'd0;
				    r_data_byte[0] <= 3'd0;//复位所有数据都清空,共8个存储单元
					 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//6次值中,每个值不止出现一次,只有在出现bps_clk高电平时对应的值才有效
				    case(bps_cnt)    //bps_cnt的值并不持续一个时钟周期,6次值中,每个值不止出现一次
					     0:begin //bps_cnt计数到0时所以寄存器进入全0状态,方便下一次进行数据采集
						    START_BIT <= 3'd0;
							 r_data_byte[0] <= 3'd0;//复位所有数据都清空,共8个存储单元
							 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;//此时根据波特率计数s1_Rs232_Rx已经处于第1个数据位区间
					     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:; 
						  endcase
				  end 

串转并数据输出

将每位的采样电平转化为并行输出

 always@(posedge Clk or negedge Rst_n)
	        if(!Rst_n)
          	    Data_Byte <= 8'b0;	
			  else if(bps_cnt == 8'd159)begin //虽然计数160次,但是接收的数据寄存在r_data_byte保持稳定不变。计数160表明一帧数据接收完毕
			    Data_Byte[0] <= r_data_byte[0][2];//6次采样叠加大于3,说明高电平多,大概率是高电平,只要大于3第3位就为,小于3为0,只需判断第3位就行
	          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];//r_data_byte是有8个存储单元,每个单元3位的二维存储器,第8个存储单元的第二位 
			  end 
	        else
	          Data_Byte <= Data_Byte;	

参考

小梅哥教程:【新版学习教材】FPGA自学笔记——设计与验证公开版
野火教程:征途Pro《FPGA Verilog开发实战指南——基于Altera EP4CE10》2021.7.10(上)

  • 23
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值