2.3 基于FPGA的UART协议实现(三)简单UART传输FPGA实现 - Go语言中文社区

2.3 基于FPGA的UART协议实现(三)简单UART传输FPGA实现


2.3.3 简单UART传输FPGA实现

在这里插入图片描述

              图2 34 FPGA发送一帧串口数据(考虑波特率)
  如果图2 34考虑 115200 的波特率,结果如图2 34所示,每一位数据都保持 434 个时钟,为此 Verilog 可以这样表示,如代码2 11所示:
                      代码2 11

1.	reg [10:0]D1;  
2.	reg [8:0]C1;  
3.	always @( posedge CLOCK)  
4.	      case( i)  
5.	      0,1,2,3,4,5,6,7,8,9,10:  
6.	      if( C1 == 9’ d434 -1 ) begin C1 <= 9’ d0; i <= i + 1’ b1; endelse begin TXD <= D1[i]; C1 <= C1 + 1'b1; end  
7.	......  
8.	endcase  

  如代码2 11所示,步骤 1~8 不再保持一个时钟,换之每个步骤都保持 434 个时钟,因此每位 TXD 的发送数据也保持 8.68us。
  除此此外,串口传输协议不仅可以自定义波特率,串口传输协议也可以自定义一帧数据的位宽,自定义内容如表2 8所示:
                    表2 8 自定义一帧数据

在这里插入图片描述
  如表2 8所示,可以自定义的数据其中便包含数据位,默认下为 1 字节,自定义内容则是 5~9 位,校验位也可以设置为有或者无( 默认下是有),停止位也可以增至 2 位(默认下是 1 位)。
在这里插入图片描述
                图2 35 TX 功能模块的建模图
  如图2 35所示,该模块的左方有问答信号,还有 8 位的 iData,至于右方则是 TXD 顶层信号。此外,一帧数据的波特率为 115200 bps。
                  代码 2 12 TX 功能模块代码

1.	//****************************************************************************//  
2.	//# @Author: 碎碎思  
3.	//# @Date:   2019-04-21 21:14:51  
4.	//# @Last Modified by:   zlk  
5.	//# @WeChat Official Account: OpenFPGA  
6.	//# @Last Modified time: 2019-08-04 02:48:17  
7.	//# Description:   
8.	//# @Modification History: 2014-05-10 13:42:27  
9.	//# Date                By             Version             Change Description:   
10.	//# ========================================================================= #  
11.	//# 2014-05-10 13:42:27  
12.	//# ========================================================================= #  
13.	//# |                                                                       | #  
14.	//# |                                OpenFPGA                               | #  
15.	//****************************************************************************//  
16.	module tx_funcmod  
17.	(  
18.	    input CLOCK, RESET,  
19.	     output TXD,  
20.	     input iCall,  
21.	     output oDone,  
22.	     input [7:0]iData  
23.	);  
24.	     parameter B115K2 = 9'd434; // formula : ( 1/115200 )/( 1/50E+6 )      
25.	  
26.	     reg [3:0]i;  
27.	     reg [8:0]C1;  
28.	     reg [10:0]D1;  
29.	     reg rTXD;  
30.	     reg isDone;  
31.	       
32.	     always @( posedge CLOCK or negedge RESET )  
33.	         if( !RESET )  
34.	              begin  
35.	                  i <= 4'd0;  
36.	                  C1 <= 9'd0;  
37.	                   D1 <= 11'd0;  
38.	                     rTXD <= 1'b1;   
39.	                     isDone <= 1'b0;  
40.	              end  
41.	          else if( iCall )  
42.	              case( i )  
43.	                  
44.	                    0:  
45.	                     begin D1 <= { 2'b11 , iData , 1'b0 }; i <= i + 1'b1; end  
46.	                       
47.	                     1,2,3,4,5,6,7,8,9,10,11:       
48.	                     if( C1 == B115K2 -1 ) begin C1 <= 8'd0; i <= i + 1'b1; end  
49.	                     else begin rTXD <= D1[i - 1]; C1 <= C1 + 1'b1; end  
50.	  
51.	                    12:  
52.	                     begin isDone <= 1'b1; i <= i + 1'b1; end  
53.	                       
54.	                     13:  
55.	                     begin isDone <= 1'b0; i <= 4'd0; end  
56.	                  
57.	                endcase  
58.	      
59.	    assign TXD = rTXD;  
60.	    assign oDone = isDone;  
61.	      
62.	endmodule 

  第16~24行为相关的出入端声明,第 9 行则是波特率为 115200 的常量声明。
  第26~40行为相关的寄存器声明以及复位操作。
  第41~62行为部分核心操作。第 41 行的 if( iCall ) 表示该模块不使能就不工作。步骤 0 用来准备发送数据,其中 2’b11 是停止位与校验位(随便填),1’b0 则是起始位。步骤 1~11用来发送一帧数据。步骤 12~13 用来反馈完成信号并返回步骤。
  这样发送模块就完成了,理想时序如下图所示:
在这里插入图片描述
              图2 36 串口发送模块功能逻辑示意图
  由上图可知,串口发送模块是“定时发送”的过程。波特率模块产生的时间间隔是通过计数器实现的,由图2 37可知,每隔一定时间波特率模块就会产生一个高脉冲给TX_Pin_Out引脚。
  串口发送的框图如图2 37所示:
在这里插入图片描述
                  图2 37 串口模块RTL框图
  对于FPGA实现UART的RX模块功能主要就是电平采集。那么它到底是如何实现采集的呢?
在这里插入图片描述
                  图 2 38 RX模块电平采集示意图
  说到底,在发送模块中一位数据发送的时间间隔是通过波特率进行控制的,同理,在接受模块中采集一位数据的间隔同样也要通过波特率进行控制,具体如上图所示。
  上图中,数据采集都是在“每位数据的中间”进行着。RX_Pin_In 输入一帧数据,当检测到低电平(起始位), 在第 0 位数据,采取忽略的态度,然后接下来的 8 位数据位都被采集,最后校验位和停止位,却是采取了忽略的操作。有一点必须好好注意,串口传输数据“从最低位开始,到最高位结束”。
  因为 Verilog 无法描述理想以外的时序, 对此所有时序活动都必须看成理想时序。
在这里插入图片描述
              图2 39 FPGA接收一帧波特率为115200的数据
  当FPGA接收一帧数据为波特率115200之际,情况差不多如图2 41所示。 50Mhz是FPGA的时钟源,也是一帧数据的采集时钟, RXD 则是一帧数据的输入端。波特率为 115200的一位数据经过 50Mhz 的时钟量化以后,每一位数据大约保持 8.68us,即 434 个时钟。串口传输没有自己的时钟信号,所以我们必须利用 FPGA 的时钟源“跟踪”每一位数据。对此, FPGA 只能借用计数器“同步跟踪”而已,至于 Verilog 则可以这样描述,结果如代码2 13所示:
                      代码2 13

1.	0,1,2,3,4,5,6,7,8,9,10: //同步跟踪中 ...  
2.	      if( C1 == 434 -1 ) begin C1 <= 8'd0; i <= i + 1'b1; end  
3.	      else C1 <= C1 + 1'b1;  

  如代码2 13所示,所谓同步跟踪,就是利用计数器估计每一位数据 … 期间,步骤 0~10表示每一位数据,至于 C1 计数 434 个时钟则是同步跟踪中。其中-1考虑了步骤之间的跳转所耗掉的时钟。
在这里插入图片描述
                  图2 40 读取起始位
  知道串口的一帧数据都是从拉低的起始位开始,然而为了完美尾行,亦即实现精密控时,起始位的读取往往都是关键。如图2 40所示,当我们在第一个时钟读取(采集)起始位的时候,由于 Verilog 的读取只能经过读取过去值而已,余下起始位还有 433 个时钟需要我们跟踪,为此 Verilog 可以这样描述,结果如代码2 14所示:
                    代码2 14

1.	0:  
2.	    if( RXD == 1'b0 ) begin i <= i + 1'b1; C1 <= C1 + 4'd1; end  
3.	1: // stalk start bit  
4.	    if( C1 == BPS115K2 -1 ) begin C1 <= 8'd0; i <= i + 1'b1; end  
5.	    else C1 <= C1 + 1'b1;

  如代码2 14所示,步骤 0 用来检测起始位,如果 RXD 的电平为拉低状态, C1 立即递增以示同步跟踪已经用掉一个时钟,同样也可以看成 i 进入下一个步骤用掉一个时钟。然而步骤 1 是用来跟踪余下的 433 个时钟,但是计数器 C1 不是从 0 开始计数,而是从 1开始计算,因为 C1 在步骤已经递增的缘故。
在这里插入图片描述
                图2 41 读取一帧数据当中的数据位
  一帧数据的跟踪结果与读取结果如图2 41所示 … 除了起始位,我们使用了两个步骤采集并跟踪之余,接下来便用 8 个步骤数据一边跟踪一边采集所有数据位,然而采集的时候则是 1/4 周期,即每位数据的第 108 个时钟。最后的校验位及结束位则是跟踪而已。
  对此, Verilog 可以这样表示,结果如代码2 15所示:
                    代码2 15

1.	0:   
2.	if( RXD == 1'b0 ) begin i <= i + 1'b1; C1 <= C1 + 4'd1; end   
3.	  
4.	1: // start bit  
5.	if( C1 == BPS115K2 -1 ) begin C1 <= 8'd0; i <= i + 1'b1; end   
6.	else C1 <= C1 + 1'b1;  
7.	  
8.	2,3,4,5,6,7,8,9: //stalk and count 1~8 data's bit , sample data at 1/2 for bps  
9.	begin  
10.	if( C1 == SAMPLE ) D1[i-2] <= RXD;  
11.	   if( C1 == BPS115K2 -1 ) begin C1 <= 8'd0; i <= i + 1'b1; end  
12.	      else C1 <= C1 + 1'b1;          
13.	end  
14.	  
15.	10,11: // parity bit & stop bit  
16.	if( C1 == BPS115K2 -1 ) begin C1 <= 8'd0; i <= i + 1'b1; end  
17.	else C1 <= C1 + 1'b1;  
18.	  
19.	12:  
20.	begin isDone <= 1'b1; i <= i + 1'b1; end  
21.	  
22.	13:  
23.	begin isDone <= 1'b0; i <= 4'd0; end  

  如代码2 15所示,步骤 0~1 用来采集与跟踪起始位,步骤 2~9 则用来跟踪数据位,并且采集为 1/4 周期。步骤 10~11 则用来跟踪校验位于结束位。
在这里插入图片描述
                图2 42 RX功能模块的建模图
  图2 42是 RX 功能模块的建模图,左方链接至顶层信号 RXD,右方则是问答信号还有 8位的 oData。
                代码2 16 RX模块实现代码

1.	//****************************************************************************//  
2.	//# @Author: 碎碎思  
3.	//# @Date:   2019-04-21 22:46:15  
4.	//# @Last Modified by:   zlk  
5.	//# @WeChat Official Account: OpenFPGA  
6.	//# @Last Modified time: 2019-08-04 03:21:38  
7.	//# Description:   
8.	//# @Modification History: 2014-05-10 13:44:28  
9.	//# Date                By             Version             Change Description:   
10.	//# ========================================================================= #  
11.	//# 2014-05-10 13:44:28  
12.	//# ========================================================================= #  
13.	//# |                                                                       | #  
14.	//# |                                OpenFPGA                               | #  
15.	//****************************************************************************//  
16.	module rx_funcmod  
17.	(  
18.	    input CLOCK, RESET,   
19.	     input RXD,  
20.	     input iCall,  
21.	     output oDone,   
22.	     output [7:0]oData  
23.	);  
24.	    parameter BPS115K2 = 9'd434, SAMPLE = 9'd108;  
25.	        
26.	    reg [3:0]i;  
27.	     reg [8:0]C1;  
28.	     reg [7:0]D1;  
29.	     reg isDone;  
30.	       
31.	     always @ ( posedge CLOCK or negedge RESET )  
32.	         if( !RESET )  
33.	              begin  
34.	                    i <= 4'd0;  
35.	                     C1 <= 9'd0;  
36.	                     D1 <= 8'd0;  
37.	                     isDone <= 1'b0;  
38.	                end  
39.	          else if( iCall )  
40.	              case( i )  
41.	                       
42.	                     0:   
43.	                     if( RXD == 1'b0 ) begin i <= i + 1'b1; C1 <= C1 + 4'd1; end   
44.	                       
45.	                     1: // start bit  
46.	                     if( C1 == BPS115K2 -1 ) begin C1 <= 8'd0; i <= i + 1'b1; end   
47.	                     else C1 <= C1 + 1'b1;  
48.	                       
49.	                     2,3,4,5,6,7,8,9: //stalk and count 1~8 data's bit , sample data at 1/2 for bps  
50.	                     begin  
51.	                        if( C1 == SAMPLE ) D1[i-2] <= RXD;  
52.	                        if( C1 == BPS115K2 -1 ) begin C1 <= 8'd0; i <= i + 1'b1; end  
53.	                        else C1 <= C1 + 1'b1;            
54.	                     end  
55.	                       
56.	                     10,11: // parity bit & stop bit  
57.	                     if( C1 == BPS115K2 -1 ) begin C1 <= 8'd0; i <= i + 1'b1; end  
58.	                     else C1 <= C1 + 1'b1;  
59.	                       
60.	                     12:  
61.	                     begin isDone <= 1'b1; i <= i + 1'b1; end  
62.	                       
63.	                     13:  
64.	                     begin isDone <= 1'b0; i <= 4'd0; end  
65.	                  
66.	                endcase  
67.	                  
68.	    assign oDone = isDone;  
69.	    assign oData = D1;  
70.	      
71.	endmodule  

  第 16~24 行是相关的出入端声明,第 24 行则是波特率为 115200 的常量声明,其外还有采集的周期。
  第 26~38 行是相关的寄存器声明,第 32~38 行则是这些寄存器的复位操作。
  第 39~46 行是核心操作。第 39 行的 if( iCall ) 表示该模块不使能便不工作。步骤 0~1 用来判断与跟踪起始位;

  步骤 2~9 用来跟踪并且读取当中的数据位;步骤 10 至 11 则是用来跟踪校验位与停止位而已。步骤 12~13 则用来反馈完成信号,以示一次性的接收工作已经完成。
  第 68~69 行是输出驱动声明。
在这里插入图片描述

版权声明:本文来源CSDN,感谢博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/Pieces_thinking/article/details/99709567
站方申明:本站部分内容来自社区用户分享,若涉及侵权,请联系站方删除。

0 条评论

请先 登录 后评论

官方社群

GO教程

推荐文章

猜你喜欢