打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
一天一个设计实例-3万字讲解UART和实例

用异步收发传输器(Universal Asynchronous Receiver/Transmitter),通常称作UART。它将要传输的资料在串行通信与并行通信之间加以转换。作为把并行输入信号转成串行输出信号的芯片,UART通常被集成于其他通讯接口的连结上

1.1.1串口信号定义和接线方法-5针串口-9针串口-全功能串口

1.串口、并口接口定义
并行口与串行口的区别是交换信息的方式不同,并行口能同时通过8条数据线传输信息,一次传输一个字节;而串行口只能用1条线传输一位数据,每次传输一个字节的一位。并行口由于同时传输更多的信息,速度明显高于串行口,但串行口可以用于比并行口更远距离的数据传输。
2.25针并行口插口的针脚功能:
23 25针并行口插口的针脚功能
针脚
功能
传输方向
针脚
功能
传输方向
1
选通 (STROBE低电平)
PC->Printer 
10
确认 (ACKNLG低电平)
Printer->PC
2
数据位0 (DATAO)
PC->Printer 
11
忙 (BUSY)
Printer->PC
3
数据位1 (DATA1)
PC->Printer 
12
却纸 (PE)
Printer->PC
4
数据位2 (DATA2)
PC->Printer 
13
选择 (SLCT)
Printer->PC
5
数据位3 (DATA3)
PC->Printer 
14
自动换行 (AUTOFEED低电平)
PC->Printer
6
数据位4 (DATA4)
PC->Printer 
15
错误观点(ERROR低电平)
Printer->PC
7
数据位5 (DATA5)
PC->Printer 
16
初始化成(INIT低电平)
PC->Printer
8
数据位6 (DATA6)
PC->Printer 
17
选择输入 (SLCTIN低电平)
PC->Printer
9
数据位7 (DATA7)
PC->Printer 
18-25
地线路(GND)
-
3.串行口的典型代表是RS-232C及其兼容插口,有9针和25针两类。
25针串行口具有20mA电流环接口功能,用9、11、18、25针来实现。其针脚功能如下:
24 25针串行口插口的针脚功能
针脚
功能
针脚
功能
1
未用


2
发出数据(TXD)
11
数据发送
3
接受数据(RXD)
12-17
未用
4
请求发送(RTS)
18
数据接收
5
清除发送(CTS)
19
未用
6
数据准备好(DSR)
20
数据终端准备好比(DTR)
7
信号地线路 (SG)
21
未用
8
载波检测 (DCD)
22
振铃指示精神 (RI)
9
发送返回
23-24
未用
10
未用
25
接收返回
4.9针串行口的针脚功能
25 9针串行口插口的针脚功能
针脚
功能
针脚
功能
1
载波检测(DCD)
6
数据准备好(DSR)
2
接受数据(RXD)
7
请求发送(RTS)
3
发出数据(TXD)
8
清除发送(CTS)
4
数据终端准备好(DTR)
9
振铃指示(RI)
5
信号地线(SG)


5.串口通信基本原理及接线方法
目前较为常用的串口有9针串口(DB9)和25针串口(DB25),通信距离较近时(<12m),可以用电缆线直接连接标准rs232端口(rs422< span="">RS485较远),若距离较远,需附加调制解调器(MODEM)。最为简单且常用的是三线制接法,即地、接收数据和发送数据三脚相连,本文只涉及到最为基本的接法,且直接用RS232相连。2‑6中为DB9和DB25的常用信号脚说明
26 DB9和DB25的常用信号脚说明
9针串口(DB9)
共同部分
25针串口(DB25)
针号
功能说明
缩写
针号
1
数据载波检测
DCD
8
2
接收数据
RXD
3
3
发送数据
TXD
2
4
数据终端准备
DTR
20
5
信号地
GND
7
6
数据设备准备好
DSR
6
7
请求发送
RTS
4
8
清除发送
CTS
5
9
振铃指示
DELL
22
RS232C串口通信接线方法(三线制),串口传输数据只要有接收数据针脚和发送针脚就能实现:同一个串口的接收脚和发送脚直接用线相连,两个串口相连或一个串口和多个串口相连同一个串口的接收脚和发送脚直接用线相连 对9针串口和25针串口,均是2与3直接相连两个不同串口(不论是同一台计算机的两个串口或分别是不同计算机的串口)
上面分析是对微机标准串行口而言的,还有许多非标准设备,如接收GPS数据或电子罗盘数据,只要记住一个原则:接收数据针脚(或线)与发送数据针脚(或线)相连,彼此交叉,信号地对应相接,就能百战百胜。
232 常用串口电缆
串口调试中要注意的几点:
串口调试时,准备一个好用的调试工具,如串口调试助手、串口精灵等,有事半功倍之效果; 强烈建议不要带电插拨串口,插拨时至少有一端是断电的,否则串口易损坏。
6.单工、半双工和全双工的定义
如果在通信过程的任意时刻,信息只能由一方A传到另一方B,则称为单工。如果在任意时刻,信息既可由A传到B,又能由B传A,但只能由一个方向上的传输存在,称为半双工传输。如果在任意时刻,线路上存在A到B和B到A的双向信号传输,则称为全双工。
电话线就是二线全双工信道。由于采用了回波抵消技术,双向的传输信号不致混淆不清。双工信道有时也将收、发信道分开,采用分离的线路或频带传输相反方向的信号,如回线传输。
7.奇偶校验
串行数据在传输过程中,由于干扰可能引起信息的出错,例如,传输字符‘E’,其各位为:
0100
0101=45H
由于干扰,可能使位变为1,这种情况,我们称为出现了“误码”。我们把如何发现传输中的错误,叫“检错”。发现错误后,如何消除错误,叫“纠错”。最简单的检错方法是“奇偶校验”,即在传送字符的各位之外,再传送1位奇/偶校验位。可采用奇校验或偶校验。
奇校验:所有传送的数位(含字符的各数位和校验位)中,“1”的个数为奇数,如:
1
0110
0101
0
0110
0001
偶校验:所有传送的数位(含字符的各数位和校验位)中,“1”的个数为偶数,如:
1
0100
0101
0
0100
0001
奇偶校验能够检测出信息传输过程中的部分误码(1位误码能检出,2位及2位以上误码不能检出),同时,它不能纠错。在发现错误后,只能要求重发。但由于其实现简单,仍得到了广泛使用。
有些检错方法,具有自动纠错能力。如循环冗余码(CRC)检错等。
8.串口通讯流控制
在串行通讯处理中,常常看到RTS/CTS和XON/XOFF这两个选项(尤其在上位机中),这就是两个流控制的选项,目前流控制主要应用于调制解调器的数据通讯中,但对普通RS232编程,了解一点这方面的知识是有好处的。那么,流控制在串行通讯中有何作用,在编制串行通讯程序怎样应用呢?这里我们就来谈谈这个问题。
流控制在串行通讯中的作用
这里讲到的“流”,当然指的是数据流。数据在两个串口之间传输时,常常会出现丢失数据的现象,或者两台计算机的处理速度不同,如台式机与单片机之间的通讯,接收端数据缓冲区已满,则此时继续发送来的数据就会丢失。现在我们在网络上通过MODEM进行数据传输,这个问题就尤为突出。流控制能解决这个问题,当接收端数据处理不过来时,就发出“不再接收”的信号,发送端就停止发送,直到收到“可以继续发送”的信号再发送数据。因此流控制可以控制数据传输的进程,防止数据的丢失。PC机中常用的两种流控制是硬件流控制(包括RTS/CTS、DTR/CTS等)和软件流控制XON/XOFF(继续/停止),下面分别说明。
硬件流控制
硬件流控制常用的有RTS/CTS流控制和DTR/DSR(数据终端就绪/数据设置就绪)流控制。硬件流控制必须将相应的电缆线连上,用RTS/CTS(请求发送/清除发送)流控制时,应将通讯两端的RTS、CTS线对应相连,数据终端设备(如计算机)使用RTS来起始调制解调器或其它数据通讯设备的数据流,而数据通讯设备(如调制解调器)则用CTS来起动和暂停来自计算机的数据流。这种硬件握手方式的过程为:我们在编程时根据接收端缓冲区大小设置一个高位标志(可为缓冲区大小的75%)和一个低位标志(可为缓冲区大小的25%),当缓冲区内数据量达到高位时,我们在接收端将CTS线置低电平(送逻辑0),当发送端的程序检测到CTS为低后,就停止发送数据,直到接收端缓冲区的数据量低于低位而将CTS置高电平。RTS则用来标明接收设备有没有准备好接收数据。常用的流控制还有还有DTR/DSR(数据终端就绪/数据设置就绪)。我们在此不再详述。由于流控制的多样性,我个人认为,当软件里用了流控制时,应做详细的说明,如何接线,如何应用。
软件流控制
由于电缆线的限制,我们在普通的控制通讯中一般不用硬件流控制,而用软件流控制。一般通过XON/XOFF来实现软件流控制。常用方法是:当接收端的输入缓冲区内数据量超过设定的高位时,就向数据发送端发出XOFF字符(十进制的19或Control-S,设备编程说明书应该有详细阐述),发送端收到XOFF字符后就立即停止发送数据;当接收端的输入缓冲区内数据量低于设定的低位时,就向数据发送端发出XON字符(十进制的17或Control-Q),发送端收到XON字符后就立即开始发送数据。一般可以从设备配套源程序中找到发送的是什么字符。应该注意,若传输的是二进制数据,标志字符也有可能在数据流中出现而引起误操作,这是软件流控制的缺陷,而硬件流控制不会有。
9.额外说明:
UART含义
随着计算机的日益普及,很多非RS232的串口也要接入PC机,如果为每一种新出现的串口都增加一个新的I/O口显然不现实,因为PC后面板位置有限,因此,将RS232串口和非RS232串口都通过RS232口接入是最佳方案。UART的U(通用)指的就是这个意思。早期ROM BIOS和DOS里的通信软件都是为RS232设计的,在没有检测到DCD有效前不会发送数据,因此,就连发送一个字符这样朴素的应用也要给出DCD、DTR、DSR等控制信号。因此,串口接头上要将一些控制线短接,或者干脆绕过系统软件自己写通信程序。
到此,UART的涵义就总结为:通用的 异步 (串行) I/O口。就在UART冠以通用二字,准备一统江湖的时候,制造商们不满于它的速度、体积和灵活性(软件可配置),推出了USB和1394串口。目前,笔记本上的UART串口有被取消的趋势,因而有网友发出了“没有串口,吾谁与归”的慨叹,古今多少事,都付笑谈中,USB取代UART是后话,暂且不表。
话说自从贺氏(Hayes)公司推出了聪明猫(SmartModem),他们制定的MODEM接口就成了业界标准,自此以后,所有公司制造的兼容猫都符合贺氏标准(连AT指令也兼容)。
细观贺氏制定的MODEM串口,与RS232标准大不相同。DTR在整个通信过程中一直保持有效,DSR在MODEM上电后/可以拨号前有效(取决于软件对DSR的理解),在通信过程的任意时刻,只要DTR/DSR无效,通信过程立即终止。在某种意义上,这也可以算是流控,但肯定不是RS232所指的那种主流控。如果拘泥于RS232,你是不会理解DTR和DSR的用途的。
贺氏不但改了DTR和DSR,竟然连RTS和CTS的涵义也重新定义了。因此,RTS和CTS已经不具有最开始的意义了。从字面理解RTS和CTS,是用于半双工通信的,当DTE想从收模式改为发模式时,就有效RTS请求发送,DCE收到RTS请求后不能立即完成转换,需要一段时间,然后有效CTS通知DTE:DCE已经转到发模式,DTE可以开始发送了。在全双工时,RTS和CTS都缺省置为有效即可。然而,在贺氏的MODEM串口定义中,RTS和CTS用于硬件流控,和什么全双工/半双工一点关系也没有。注意,硬件流控是靠软件实现的,之所以强调“硬件”二字,仅仅是因为硬件流控提供了用于流量情况指示的硬件连线,并不是说,你只要把线连上,硬件就能自己流控。如果软件不支持,光连上RTS和CTS是没有用的。
RTS和CTS硬件流控的软件算法如下:(RTS有效表示PC机可以收,CTS有效表示MODEM可以收,这两个信号互相独立,分别指示一个方向的流量情况) 。
PC端处理:
当发现(不一定及时发现) CTS (-3v to -15v)无效时,停止发送,
    当发现(不一定及时发现) CTS (3v to 15v)有效时,恢复发送;
当接收buffers中的bytes当接收buffers中的bytes>N 时,给 RTS 无效信号(-3v to -15v);
MODEM端处理:同上,但RTS与CTS交换。
在RS232中本来CTS 与RTS 有明确的意义,但自从贺氏(HAYES ) 推出了聪明猫(SmartModem)后就有点混淆了。在RS232中RTS 与CTS 是用来半双工模式下的方向切换;HAYES Modem中的RTS ,CTS 是用来进 行硬件流控的。通常UART的RTC、CTS 的含义指后者,即用来做硬流控的。
如果UART只有RX、TX两个信号,要流控的话只能是软流控;如果有RX,TX,CTS ,RTS 四个信号,则多半是支持硬流控的UART;如果有 RX,TX,CTS ,RTS ,DTR,DSR 六个信号的话,RS232标准的可能性比较大。
硬流控的RTS 、CTS :RTS (Require To Send,发送请求)为输出信号,用于指示本设备准备好可接收;CTS(Clear To Send,发送清除)为输入信号,有效时停止发送。假定A、B两设备通信,A设备的RTS 连接B设备的CTS ;A设备的CTS 连接B设备 的RTS 。前一路信号控制B设备的发送,后一路信号控制A设备的发送。对B设备的发送(A设备接收)来说,如果A设备接收缓冲快满的时发出RTS 信号(意思 通知B设备停止发送),B设备通过CTS 检测到该信号,停止发送;一段时间后A设备接收缓冲有了空余,发出RTS 信号,指示B设备开始发送数据。A设备发(B设备接收) 类似。上述功能也能在数据流中插入Xoff(特殊字符)和Xon(另一个特殊字符)信号来实现。A设备一旦接收到B设备发送过来的Xoff,立刻停止发 送;反之,如接收到B设备发送过来的Xon,则恢复发送数据给B设备。同理,B设备也类似,从而实现收发双方的速度匹配。
半双工的方向切换:RS232中使用DTR(Date Terminal Ready,数据终端准备)与DSR(Data Set Ready ,数据设备准备好)进行主流控,类似上述的RTS 与CTS 。对半双工的通信的DTE(Date Terminal Equipment,数据终端设备)与DCE(Data circuit Equipment )来说,默认的方向是DTE接收,DCE发送。如果DTE要发送数据,必须发出RTS 信号,请求发送数据。DCE收到后如果 空闲则发出CTS 回 应RTS 信 号,表示响应请求,这样通信方向就变为DTE->TCE,同时RTS 与CTS 信号必须一直保持。从这里可以看出,CTS ,TRS虽 然也有点流控的意思(如CTS 没有发出,DTE也不能发送数据),但主要是用来进行方向切换的。

1.1.2UART传输时序分析

串口传输数据都是一帧数据 11 位,参考2‑33中的串口时序。
233 串口时序
27 串口时序说明表
位作用
0
起始位
1~7
数据位
9
校验位
10
停止位
在串口的总线上“高电平”是默认的状态,当一帧数据的开始传输必须先拉低电平,这就是第 0 位的作用。第 0 位过后就是 8 个数据位,这八个数据位才是一帧数据中最有意义的东西。最后的两位是校验位和停止位,作用如同命名般一样。
串口传输还有另一个重要参数就是“波特率”。“波特率”在宏观上理解就是串口传输的传输速度;在微观上“波特率”就是串口传输中“一个位的周期”,换句话说亦是“一个位所逗留的时间”。这个概念在后期编写程序是很重要的。
常用的波特率有 9600 bps 和 115200 bps ( bit per second )。“9600 bps” 表示每秒可以传输 9600 位。但是经过公式计算“一个位的周期”就会暴露出来。
一个位的周期 = 1 / bps
= 1/ 9600
= 0.000104166666666667
从上述的公式,我们明白一个事实 9600 bps ,传输一位数据占用 0.000104166666666667s 时间。如果是一帧 11 位的数据,就需要
0.000104166666666667 x 11 = 0.00114583333333334
那么一秒钟内可以传输
1 / 0.00114583333333334 = 872.727272727268
872.727272727268 个帧数据。
当然这只是在数字上计算出来而已,但是实际上还有许多看不见的延迟因数。
当使用115200 bps ,
一个位的周期 = 1 / bps
= 1/ 115200
= 0.000086805555555555555555555555555556
传输一位数据占用 0.000086805555555555555555555555555556s 时间。如果是一帧 11 位的数据,就需要
0.000086805555555555556 x 11 = 0.00095486111111111111111111
那么一秒钟内可以传输
1 / 0.00095486111111111111111111 = 1047.2727272727272727272739459174
1047.2727272727272727272 个帧数据。
如果用 50Mhz 的时钟频率去量化的话:
( 1/115200 ) / (1/50E+6) = 8.68E-6 / 20E-9
= 434

1.1.3简单UART传输FPGA实现

234 FPGA发送一帧串口数据(考虑波特率)
如果2‑34考虑 115200 的波特率,结果如2‑34所示,每一位数据都保持 434 个时钟,为此 Verilog 可以这样表示,如代码2‑11所示:
代码211
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所示:
28 自定义一帧数据
自定义数据位
自定义内容
数据位
5~9
校验位
有/无
停止位
1~2
2‑8所示,可以自定义的数据其中便包含数据位,默认下为 1 字节,自定义内容则 5~9 位,校验位也可以设置为有或者无( 默认下是有),停止位也可以增至 2 位(默认下是 1 位)。
235 TX 功能模块的建模图
2‑35所示,该模块的左方有问答信号,还有 8 位的 iData,至于右方则是 TXD 顶层信号。此外,一帧数据的波特率为 115200 bps。
代码212 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 用来反馈完成信号并返回步骤。
这样发送模块就完成了,理想时序如下图所示:
236 串口发送模块功能逻辑示意图
由上图可知,串口发送模块是“定时发送”的过程。波特率模块产生的时间间隔是通过计数器实现的,由2‑36可知,每隔一定时间波特率模块就会产生一个高脉冲给TX_Pin_Out引脚。
串口发送的框图如2‑36所示:
237 串口模块RTL框图
对于FPGA实现UART的RX模块功能主要就是电平采集。那么它到底是如何实现采集的呢?
238 RX模块电平采集示意图
说到底,在发送模块中一位数据发送的时间间隔是通过波特率进行控制的,同理,在接受模块中采集一位数据的间隔同样也要通过波特率进行控制,具体如上图所示。
上图中,数据采集都是在“每位数据的中间”进行着。RX_Pin_In 输入一帧数据,当检测到低电平(起始位), 在第 0 位数据,采取忽略的态度,然后接下来的 8 位数据位都被采集,最后校验位和停止位,却是采取了忽略的操作。有一点必须好好注意,串口传输数据“从最低位开始,到最高位结束”。
因为 Verilog 无法描述理想以外的时序, 对此所有时序活动都必须看成理想时序。
239 FPGA接收一帧波特率为115200的数据
当FPGA接收一帧数据为波特率115200之际,情况差不多如2‑39所示。50Mhz是FPGA的时钟源,也是一帧数据的采集时钟, RXD 则是一帧数据的输入端。波特率为 115200的一位数据经过 50Mhz 的时钟量化以后,每一位数据大约保持 8.68us,即 434 个时钟。串口传输没有自己的时钟信号,所以我们必须利用 FPGA 的时钟源“跟踪”每一位数据。对此, FPGA 只能借用计数器“同步跟踪”而已,至于 Verilog 则可以这样描述,结果如代码2‑13所示:
代码213
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考虑了步骤之间的跳转所耗掉的时钟。
240 读取起始位
知道串口的一帧数据都是从拉低的起始位开始,然而为了完美尾行,亦即实现精密控时,起始位的读取往往都是关键。如2‑40所示,当我们在第一个时钟读取(采集)起始位的时候,由于 Verilog 的读取只能经过读取过去值而已,余下起始位还有 433 个时钟需要我们跟踪,为此 Verilog 可以这样描述,结果如代码2‑14所示:
代码214
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 在步骤已经递增的缘故。
241 读取一帧数据当中的数据位
一帧数据的跟踪结果与读取结果如2‑41所示 ... 除了起始位,我们使用了两个步骤采集并跟踪之余,接下来便用 8 个步骤数据一边跟踪一边采集所有数据位,然而采集的时候则是 1/4 周期,即每位数据的第 108 个时钟。最后的校验位及结束位则是跟踪而已。
对此, Verilog 可以这样表示,结果如代码2‑15所示:
代码215
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 则用来跟踪校验位于结束位。
242 RX功能模块的建模图
2‑42是 RX 功能模块的建模图,左方链接至顶层信号 RXD,右方则是问答信号还有 8位的 oData。
代码216 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 行是输出驱动声明。

1.1.4实用UART传输FPGA实现

上一节设计实现的UART只是简易的实现,没考虑诸如抖动(起始位抖动会导致数据位传输或接收错误)等问题,但是对于理解UART传输协议却很有帮助。在单片机中使用时一般串口都可以进行大量数据的传输,这得益于单片机在串口传输时会有“缓存”空间用于数据的存储,下面来看看单片机内部串口结构,如2‑43所示:
243 80C51串口组成示意图
所以本节设计串口时会参考单片机内部串口结构,主要是将上一节的UART功能拆解并完善部分功能,其中缓冲部分会在后面章节介绍完FIFO(First Input First Output)后,在将最终版本的UART进行封装,但是本节的串口设计也可以单独使用,也具有实用性。
2.3.4.1 电平检测模块
在进行实用型UART设计之前,需要先了解在FPGA中怎么进行电平检测。
在FPGA中最常见的就是利用“阻塞”“非阻塞”赋值语句进行电平检测,在一些特定场合还需要利用延迟达到精确检测的目的。
为了更好地理解“阻塞”“非阻塞”赋值要点,我们需要对Verilog语言中的阻塞赋值和非阻塞赋值的功能和执行时间上的差别有深入的理解。我们定义下面的两个关键字:
RHS——方程式右手方向的表达式或变量可分别缩写成 RHS表达式或RHS变量;
LHS ——方程式左手方向的表达式或变量可分别缩写成LHS 表达式或LHS变量。
IEEE Verilog标准定义了有些语句有确定的执行时间,有些语句没有确定的执行时间。若有两条或两条以上的语句准备在同一时间执行,但由于语句的排列顺序不同,却产生了不同的输出结果。这就是造成Verilog模块冒险和竞争的原因。为了避免产生竞争,理解阻塞和非阻塞赋值在执行时间上的差别是至关重要的。
1、阻塞赋值
阻塞赋值用等号(=)表示。为什么称这种赋值为阻塞赋值呢?因为在赋值时先计算RHS部分的值,这是赋值语句不允许任何别的Verilog语言的干扰,直到现行的赋值完成时刻,即把RHS赋值给LHS的时刻,它才允许别的赋值语句的执行。
一般可综合的赋值操作在RHS不能设定延时(即使是0延时也不允许)。从理论上讲,它与后面的赋值语句只有概念上的先后,而无实质的延迟。若在RHS上加延迟,则在延迟时间会阻止赋值语句的执行,延迟后才进行赋值,这种赋值语句是不可综合的,在需要综合的模块设计中不可使用这种风格的代码。
所谓阻塞的概念是指在同一个always块中,其后面的赋值语句从概念上是在前一句赋值语句结束之后再开始赋值的。
2、非阻塞赋值
非阻塞赋值用小于等于号(<=)表示。为什么称这种赋值为非阻塞赋值呢?因为在赋值开始时计算RHS表达式,赋值操作时刻结束时更新LHS。在计算非阻塞赋值的RHS表达式和更新LHS期间,其他的Verilog语句,包括其他的非阻塞赋值语句都可能计算RHS表达式和更新LHS。非阻塞赋值允许其他的Verilog语句同时进行操作。非阻塞赋值可以看作两个步骤的过程:
(1)在赋值开始时,计算非阻塞赋值RHS表达式;
(2)在赋值结束时,更新非阻塞赋值LHS表达式。
非阻塞赋值操作只能用于对寄存器类型变量进行赋值,因此只能用在“initial”块和“always”块等过程块中,而非阻塞赋值不允许用于连续赋值。
*重点:
1)时序电路建模时,用非阻塞赋值;
2)锁存器电路建模时,用非阻塞赋值;
3)用always块建立组合逻辑模型时,用阻塞赋值;
4)在同一个always块中建立时序和组合逻辑电路时,用非阻塞赋值;
5)在同一个alway块中,不要即用非阻塞又用阻塞赋值;
6)不要在一个以上的always块中为同一个变量赋值;
7)用$strobe系统任务来显示用非阻塞赋值的变量值;
8)在赋值时不要使用#0延迟。*
9)在描述组合逻辑的always块中用阻塞赋值,则综合成组合逻辑的电路结构;
10)在描述时序逻辑的always块中用非阻塞赋值,则综合成时序逻辑的电路结构。 
2‑44中是最基本的门电路设计的上升沿、下降沿捕获电路。由图可知,Trigger作为外部触发信号的输入,通过FPGA内部的clk与rst_n全局时钟信号,同步寄存输出。上升沿与下降沿的边沿捕获信号与当前的输入信号(Trigger)、上一时刻的寄存信号(Trigger_r)有关,主要关系如2‑9所示。

244 一级寄存实现的上升沿、下降沿捕获电路
29 上升沿、下降沿边沿检测条件
序号
Trigger_r
Trigger
检测边沿
1
上升沿
2
下降沿
边沿检测的实现很好理解,当上一时刻为低电平,而当前时刻为高电平时,显而易见这是外部信号的上升沿;反之,当上一时刻为高电平,而当前时刻为低电平时,为外部信号的下降沿。
与上述原理图中使用了2输入与门,通过反相器的辅助,作为2个时钟信号的对比。可以采用逆向思维进行推断;当pos_edge为高,即捕获到上升沿时,2输入与门输入了2个“1”,由反相器可知D触发器寄存输出后的信号为0,而当前信号为1。结果分析与设计的一样。neg_edge的设计也是如此。
外部输入的信号有效时,会保持一段时间的高电平,但FPGA不能通过判断高电平使能信号去进行逻辑分析。由于在FPGA中不便于处理类似的触发信号(除非外部输入信号作为全局时钟使用),所以通过边沿采样技术实现上升沿时刻的使能信号的捕捉,进而实现外部信号的上升沿触发功能。
一级的D触发器寄存在比较时,前一时刻的信号已经同步到同一时钟域,而当前时刻直接从外部输入,与FPGA整体逻辑电路不再同一时钟域。但希望我们的设计能够全部通过同步电路设计,以提高系统的可靠性,因此可以采用2级D触发器作为信号的寄存,同时比较第一级与第二级D触发器输出的信号,来检测外部输入信号的上升沿或下降沿。根据原理图设计的电路如2‑45所示。
245 二级寄存器实现的上升沿、下降沿捕获电路
对于电平检测模块要实现的电路如下:
246 电平检测模块建模图
输入管脚
作用/说明
输出管脚
作用/说明
Pin_in
待检测电平输入引脚
H2L_Sig
输入电平由高到低输出标志


L2H_Sig
输入电平由低到高输出标志
时序
代码217 detect_module.v
1.module detect_module  
2.(  
3.CLK, RSTn, Pin_In, H2L_Sig, L2H_Sig  
4.);  
5.  
6.input CLK;  
7.input RSTn;  
8.input Pin_In;  
9.output H2L_Sig;  
10.output L2H_Sig;  
11./**********************************/  
12.// 开发板使用的晶振为 50MHz, 50M*0.0001-1=4_999  
13.parameter T100US = 11'd4999;  
14./**********************************/  
15.reg [10:0]Count1;  
16.reg isEn;  
17.always @ ( posedge CLK or negedge RSTn )  
18.if( !RSTn )  
19.    begin  
20.        Count1 <= 11'd0;  
21.        isEn <= 1'b0;  
22.    end  
23.else if( Count1 == T100US )  
24.        isEn <= 1'b1;  
25.else  
26.        Count1 <= Count1 + 1'b1;  
27./********************************************/  
28.reg H2L_F1;  
29.reg H2L_F2;  
30.reg L2H_F1;  
31.reg L2H_F2;  
32.always @ ( posedge CLK or negedge RSTn )  
33.if( !RSTn )  
34.    begin  
35.        H2L_F1 <= 1'b1;  
36.        H2L_F2 <= 1'b1;  
37.        L2H_F1 <= 1'b0;  
38.        L2H_F2 <= 1'b0;  
39.    end  
40.else  
41.    begin  
42.        H2L_F1 <= Pin_In;  
43.        H2L_F2 <= H2L_F1;  
44.        L2H_F1 <= Pin_In;  
45.        L2H_F2 <= L2H_F1;  
46.end  
47./***********************************/  
48.assign H2L_Sig = isEn ? ( H2L_F2 & !H2L_F1 ) : 1'b0;  
49.assign L2H_Sig = isEn ? ( !L2H_F2 & L2H_F1 ) : 1'b0;  
50./***********************************/  
51.endmodule  
13 行定义了 100us 的常量,而第 17~26 行是用于延迟 100us。因为电平检测模块是非常敏感,在复位的一瞬间,电平容易处于不稳定的状态,我们需要延迟 100us。isEn = 1 寄存器是表示 100us 的延迟已经完成( 26 行)。
28~31 行,声明了四个寄存器。H2L_F1, H2L_F2,是针对检测电平由高变低。相反 L2H_F1, L2H_F2,则是针对检测电平由低变高。在 35~38 行,对各个寄存器初始化了,由于 H2L_Fx 是为了检测由高变低的电平,所以初始化为逻辑 1。L2H_Fx 是为了检测由低变高的电平,初值被设置为逻辑 0。
代码218
1.//初始化  
2.H2L_F1 <= 1'b1;  
3.H2L_F2 <= 1'b1;  
4.//每一个时间的操作  
5.H2L_F1 <= Pin_In;  
6.H2L_F2 <= H2L_F1;  
7.//每一个时间的布尔运算输出  
8.Pin_Out = H2L_F2 & !H2L_F1  
上面代码是用来检测电平由高变低。 H2L_F1 和 H2L_F2 的初值都是逻辑 1。假设第一个时间 Pin_In 为低电平, H2L_F1 就会被赋值位逻辑 0,而 H2L_F1 则是被赋值为 H2L_F1上一个时间的值(也就是 H2L_F1 的初值)。
我们知道在第一个时间, H2L_F1 为逻辑 0, H2L_F2 位逻辑 1。由于对 H2L_F1 的取反操作, H2L_F1 与 H2L_F2“求与”运算,所以这个表达式的输出是逻辑 1。
再假设第二个时间 Pin_In 保持为低电平, H2L_F1 同样会被赋值为逻辑 0, 而 H2L_F2则是被赋值为 H2L_F1 上一个时间的值,亦即逻辑 0 (第一个时间的值)。再经过布尔表达式的运算, 在第二个时间, H2L_F1 是逻辑 0, H2L_F2 是逻辑 0, 所以输出的结果是逻辑 0.
210 逻辑说明
时间
H2L_F1
H2L_F2
Pin_Out = ( !H2L_F1 ) & H2L_F2
Initial
1
1
0
T1
0
1
1
T2
0
0
0
41~56 行,正是执行如上的操作,无论是检测电平由高变低或者由低变高,思路基本都是一样。而最后的 48~49 行,是关于“电平检测”的布尔表达式。但是有一点不同的是,Pin_Out 的输出,是发生在 100us 之后,因为 100us 之前被 isEn 寄存器所限制(原因之前已经说了)。换一句话说,电平检测模块的有效是发生在 100us 的延迟之后。
2.3.4.2 UART发送模块
发送模块的时序及相关介绍前面都已经写的很清楚了,就是将波特率模块分开,这样方便后期维护及修改,同时也为了下一节接收模块的设计做统一处理。
整个模块要实现的结构如下:
247 发送模块(tx_module.v)整体结构图
首先,有一个顶层模块(tx_module.v),这个模块会包含(例化)成下面的两个子模块(bps_module.v和tx_control_module.v)。
bps_module.v波特率产生模块,简单说就是分频模块。
tx_control_module.v发送控制模块,就是利用状态机(简化状态机)进行发送开始位数据位和停止位。
211 发送模块(tx_module.v)模块间信号定义
模块间信号
管脚
传输方向
作用/说明
BPS_CLK
tx_bps_module.v模块传输给tx_control_module.v
由波特率计算得出的时钟传输给发送控制模块,用于传输数据位。
En_Sig
与TX_En_Sig引脚连接,送给tx_bps_module.v模块
TX_En_Sig 拉低的时候,tx_bps_module.v是处于随眠的
状态。一旦 TX_En_Sig 拉高电平,那么 tx_bps_module.v 就开始计数
时序
没有特殊要求,BPS_CLK就是时钟分频信号。



212 发送模块(tx_module.v)顶层模块信号定义
顶层模块
输入管脚
作用/说明
输出管脚
作用/说明
TX_En_Sig
当 TX_En_Sig 拉高电平,同时tx_bps_module.v 也 会 开 始 计 数,tx_control_module.v就会按照tx_bps_module.v给的时钟往TX_Pin_Out传输数据(TX_Data)。
TX_Pin_Out(TXD)
UART数据输出引脚,即TXD。
TX_Data
待传输的数据
TX_Done_Sig
一帧数据传输完毕标志信号。
时序





从根本上说,tx_module.v 是实现定时发送的功能。假设我配置的波特率为9600 bps,那么每隔0.000104166666666667s bps_module.v 这个模块就会产生一个高脉冲给tx_control_module.v,该控制模块会按这个节拍将数据一位一位的数据发送出去。假设配 置的波特率为115200 bps,那么每隔0.000086805555555555555555555555555556s  tx_bps_module.v 这个模块就会产生一个高脉冲给tx_control_module.v该控制模块会按这个节拍将数据一位一位的数据发送出去。一帧数据有 11 位,那么 bps_module.v 需要产生 12 次定时。
首先看下bps_module.v模块波特率产生模块设计和之前任意波形发生器一样。bps_module.v 是作为“定时”的功能。当 TX_En_Sig/En_Sig 拉低的时候,它是处于随眠的状态。一旦 TX_En_Sig/En_Sig 拉高电平,那么 bps_module.v 就开始计数。然后定时产生一个高脉冲经 BPS_CLK 给 tx_control_module.v。整体结构如下:
248 波特率产生模块(bps_module)整体结构图
213 波特率产生模块(bps_module)引脚说明
输入管脚
作用/说明
输出管脚
作用/说明
CLOCK
全局时钟输入管脚(50MHz)
BPS_CLK
分频波特率时钟
RST_n
全局复位引脚(低电平有效)
BPS_CLKen
使能波特率时钟
En_Sig
波特率产生模块的使能信号


时序
时序说明
见时序
按照1.5.5任意分频器设计进行bps_module模块设计
第一步,相位计数器-32位K步计数器,如代码2‑19所示
代码219 相位计数器-32位K步计数器
1.//------------------------------------------------------  
2.//RTL1: Precise fractional frequency for uart bps clock   
3.reg [31:0]  cnt;  
4.always@(posedge CLOCK or negedge RST_n)  
5.begin  
6.    if(!RST_n)  
7.        cnt <= 0;  
8.    else if(En_Sig)  
9.        cnt <= cnt + BPS_CNT;          
10.end  
11.  
12.//------------------------------------------------------  
代码2‑19中,在全局时钟CLOCK驱动下,进行K步计数,cnt为0~232-1的地址。BPS_CNT是一个16倍波特率(16*115200)的宏定义的分频参数,方便例化或者维护。
第二步,合成频率方波的生成。
在第一步完成了0~232-1寻址后,需要对地址进行比较、划分,得到一个方波信号。如代码2‑20所示。
代码220 合成频率方波的生成
1.//RTL2: Equal division of the Frequency division clock  
2.reg cnt_equal;  
3.always@(posedge CLOCK or negedge RST_n)  
4.begin  
5.    if(!RST_n)  
6.        cnt_equal <= 0;  
7.    else if(En_Sig)  
8.    begin  
9.        if(cnt < 32'h7FFF_FFFF)  
10.            cnt_equal <= 0;  
11.        else  
12.            cnt_equal <= 1;  
13.    end  
14.end  
15.  
16.//------------------------------------------------------  
代码2‑20中,9行,32‘h7FFF_FFFF为232-1的中点,因此它可以作为一种“门限电压”来实现合成频率的方波输出。
第四步,分频时钟使能信号的生成
主要利用边沿检测技术生成使能时钟信号,如代码2‑21所示。
代码221 分频时钟使能信号的生成
1.//RTL3: Generate enable clock for clock  
2.reg cnt_equal_r;  
3.always@(posedge CLOCK or negedge RST_n)  
4.begin  
5.    if(!RST_n)  
6.        cnt_equal_r <= 0;  
7.    else if(En_Sig)  
8.        cnt_equal_r <= cnt_equal;  
9.end  
10.  
11.assign  BPS_CLKen = (~cnt_equal_r & cnt_equal) ? 1'b1 : 1'b0;   
12.assign  BPS_CLK = cnt_equal_r;  
通过边沿检测技术,捕获了合成时钟的上升沿作为时钟使能信号,同时输出cut_equel_r信号作为分频时钟BPS_CLK。
完整的代码如下:
代码222 波特率产生模块(bps_module)完整代码
1.//****************************************************************************//  
2.//# @Author: 碎碎思  
3.//# @Date:   2019-05-03 01:32:40  
4.//# @Last Modified by:   zlk  
5.//# @WeChat Official Account: OpenFPGA  
6.//# @Last Modified time: 2019-05-06 21:22:18  
7.//# Description:   
8.//# @Modification History: 2019-05-06 21:22:18  
9.//# Date                By             Version             Change Description:   
10.//# ========================================================================= #  
11.//# 2019-05-06 21:22:18  
12.//# ========================================================================= #  
13.//# |                                                                       | #  
14.//# |                                OpenFPGA                               | #  
15.//****************************************************************************//  
16./*********************************************************************** 
17.    fc  :   Refrence clock = 50MHz = 50*10^6 
18.    fo  :   Output  clock 
19.    K   :   Counter Step 
20.    fo  =   fc*[K/(2^32)] 
21.100MHz  K   =   fo*(2^32)/fc = fo*(2^32)/(100*10^6) = 42.94967296 * fo 
22.50MHz   K   =   fo*(2^32)/fc = fo*(2^32)/(50*10^6)  = 85.89934592 * fo 
23.***********************************************************************/  
24.`timescale 1ns/1ps  
25.module  bps_module  
26.#(  
27.    //BPS_CNT = 85.89934592 * fo    for 50Mhz  
28.//  parameter       BPS_CNT = 32'd21990233  //256000bps 21990233  
29.//  parameter       BPS_CNT = 32'd10995116  //128000bps 10995116  
30.//  parameter       BPS_CNT = 32'd9895605   //115200bps 9895605    
31.    parameter       BPS_CNT = 32'd824634//9600bps 824634      
32.  //BPS_CNT = 42.949 67296 * fo    for 100Mhz  
33.//  parameter       BPS_CNT = 32'd175921860 //256000bps   
34.//  parameter       BPS_CNT = 32'd87960930  //128000bps   
35.//  parameter       BPS_CNT = 32'd79164837  //115200bps   
36.//  parameter       BPS_CNT = 32'd412317    //9600bps   
37.)  
38.(  
39.    //global clock  
40.    input           CLOCK,  
41.    input           RST_n,  
42.      
43.    //user interface  
44.    input          En_Sig,  
45.    output          BPS_CLK,  
46.    output          BPS_CLKen  
47.);  
48.  
49.  
50./********************************/  
51.  
52.//------------------------------------------------------  
53.//RTL1: Precise fractional frequency for uart bps clock   
54.reg [31:0]  cnt;  
55.always@(posedge CLOCK or negedge RST_n)  
56.begin  
57.    if(!RST_n)  
58.        cnt <= 0;  
59.    else if(En_Sig)  
60.        cnt <= cnt + BPS_CNT;          
61.end  
62.  
63.//------------------------------------------------------  
64.//RTL2: Equal division of the Frequency division clock  
65.reg cnt_equal;  
66.always@(posedge CLOCK or negedge RST_n)  
67.begin  
68.    if(!RST_n)  
69.        cnt_equal <= 0;  
70.    else if(En_Sig)  
71.    begin  
72.        if(cnt < 32'h7FFF_FFFF)  
73.            cnt_equal <= 0;  
74.        else  
75.            cnt_equal <= 1;  
76.    end  
77.end  
78.  
79.//------------------------------------------------------  
80.//RTL3: Generate enable clock for clock  
81.reg cnt_equal_r;  
82.always@(posedge CLOCK or negedge RST_n)  
83.begin  
84.    if(!RST_n)  
85.        cnt_equal_r <= 0;  
86.    else if(En_Sig)  
87.        cnt_equal_r <= cnt_equal;  
88.end  
89.  
90.assign  BPS_CLKen = (~cnt_equal_r & cnt_equal) ? 1'b1 : 1'b0;   
91.assign  BPS_CLK = cnt_equal_r;  
92.  
93.endmodule  
波特率产生后会将时钟信号送给tx_control_module模块,模块的整体结构如下:
249 发送控制模块(tx_control_module)整体结构图
214 发送控制模块(tx_control_module)引脚说明
输入管脚
作用/说明
输出管脚
作用/说明
CLOCK
全局时钟输入管脚(50MHz)
Tx_Done_Sig
UART数据发送完毕标志信号
RST_n
全局复位引脚(低电平有效)
TXD
UART串行数据输出
BPS_CLKen
波特率使能时钟输入引脚


TX_En_Sig
UART数据发送开始信号


TX_Data
UART待发送的数据


时序
时序说明
见时序
tx_control_module.v 控制模块是最为中心的一部分,当 TX_En_Sig 拉高电平,同时间bps_module.v 也会开始计数。tx_control_module.v 将 TX_Data 的值,按bps_module.v 产生的定时,有节奏的往 TXD 发送。当一帧数据发送完毕后,就反馈一个 TX_Done_Sig 的高脉冲。
代码223 发送控制模块(tx_control_module)完整代码
1.//****************************************************************************//  
2.//# @Author: 碎碎思  
3.//# @Date:   2019-05-04 00:47:08  
4.//# @Last Modified by:   zlk  
5.//# @WeChat Official Account: OpenFPGA  
6.//# @Last Modified time: 2019-05-05 22:30:21  
7.//# Description:   
8.//# @Modification History: 2019-05-05 22:30:21  
9.//# Date                By             Version             Change Description:   
10.//# ========================================================================= #  
11.//# 2019-05-05 22:30:21  
12.//# ========================================================================= #  
13.//# |                                                                       | #  
14.//# |                                OpenFPGA                               | #  
15.//****************************************************************************//  
16./************************************************************************** 
17...IDLE...Start...............UART DATA........................End...IDLE... 
18.________                                                     ______________ 
19.        |____< D0 >< D1 >< D2 >< D3 >< D4 >< D5 >< D6 >< D7 > 
20.        Bit0  Bit1  Bit2  Bit3  Bit4  Bit5  Bit6  Bit7  Bit8  Bit9 
21.**************************************************************************/  
22.module tx_control_module  
23.(  
24.    CLOCK, RST_n,  
25.    TX_En_Sig,   
26.    TX_Data,   
27.    BPS_CLK,   
28.    TX_Done_Sig,   
29.    TXD  
30.       
31.);  
32.  
33.    input CLOCK;  
34.    input RST_n;  
35.       
36.    input TX_En_Sig;  
37.    input [7:0]TX_Data;  
38.    input BPS_CLK;  
39.       
40.    output TX_Done_Sig;  
41.    output TXD;  
42.       
43.    /********************************************************/  
44.  
45.     reg [3:0]i;  
46.     reg rTX;  
47.     reg isDone;  
48.      
49.     always @ ( posedge CLOCK or negedge RST_n )  
50.         if( !RST_n )  
51.              begin  
52.                 i <= 4'd0;  
53.                    rTX <= 1'b1;  
54.                    isDone  <= 1'b0;  
55.                end  
56.          else if( TX_En_Sig )  
57.              case ( i )  
58.                  
59.                   4'd0 :  
60.                     if( BPS_CLK ) begin i <= i + 1'b1; rTX <= 1'b1; end  
61.                     4'd1 :  
62.                     if( BPS_CLK ) begin i <= i + 1'b1; rTX <= 1'b0; end  
63.                       
64.                     4'd2, 4'd3, 4'd4, 4'd5, 4'd6, 4'd7, 4'd8, 4'd9 :  
65.                     if( BPS_CLK ) begin i <= i + 1'b1; rTX <= TX_Data[ i - 2 ]; end  
66.                       
67.                     4'd10 :  
68.                     if( BPS_CLK ) begin i <= i + 1'b1; rTX <= 1'b1; end  
69.                                   
70.                     4'd11 :  
71.                     if( BPS_CLK ) begin i <= i + 1'b1; rTX <= 1'b1; end  
72.                       
73.                     4'd12 :  
74.                     if( BPS_CLK ) begin i <= i + 1'b1; isDone <= 1'b1; end  
75.                       
76.                     4'd13 :  
77.                     begin i <= 4'd0; isDone <= 1'b0; end  
78.                       
79.                     //default:i <= 4'd0;  
80.                   
81.                endcase  
82.                  
83.    /********************************************************/  
84.       
85.     assign TXD = rTX;  
86.     assign TX_Done_Sig = isDone;  
87.       
88.     /*********************************************************/  
89.       
90.endmodule  
代码2‑23中的设计的核心思想是状态机,状态机部分在1.6节已经介绍过了,只不过在写代码的时候将状态机精简。
当 TX_En_Sig 被拉高(36行)该控制模块就会开始工作了(同时间bps_module.v 也会开始计数)。每当bps_module.v 产生一个定时的高脉冲 BPS_CLKen, tx_control_module.v 都会发送一位数据。
第 0 位数据是起始位,所以 rTX 寄存器被赋值与逻辑 0(40 行)。接下来的八位都是数据位, tx_control_module.v 按 TX_Data 的值从最低位到最高位,将数据赋值给 rTX 寄存器(43 行)。最后两位数据则是校验位和停止位,如果没有什么特别需求,就随便填上逻辑 1 就行了(41~45 行)。最后产生一个 TX_Done_Sig 的高脉冲(51~55 行)。在 62 行 TX_Done_Sig 的输出是被 isDone 这个标志寄存器驱动着,然而 TXD的输出是由 rTX 这个寄存器驱动。
接下来是将上两个模块进行例化,完成整个发送模块的设计。电路的整体结构如2‑49所示。顶层模块只需要按照整体结构图进行“连线”就可以了。代码如下:
代码224 发送顶层模块(tx_module.v)完整代码
1.//****************************************************************************//  
2.//# @Author: 碎碎思  
3.//# @Date:   2019-05-03 02:18:37  
4.//# @Last Modified by:   zlk  
5.//# @WeChat Official Account: OpenFPGA  
6.//# @Last Modified time: 2019-05-06 22:13:07  
7.//# Description:   
8.//# @Modification History: 2019-05-06 22:13:07  
9.//# Date                By             Version             Change Description:   
10.//# ========================================================================= #  
11.//# 2019-05-06 22:13:07  
12.//# ========================================================================= #  
13.//# |                                                                       | #  
14.//# |                                OpenFPGA                               | #  
15.//****************************************************************************//  
16.module tx_module  
17.(  
18.    CLOCK, RST_n,  
19.    TX_Data,   
20.    TX_En_Sig,  
21.    TX_Done_Sig,   
22.    TXD  
23.);  
24.   
25.   input CLOCK;  
26.    input RST_n;  
27.    input [7:0]TX_Data;  
28.    input TX_En_Sig;  
29.    output TX_Done_Sig;  
30.    output TXD;  
31.        
32./********************************/  
33.        
34.    wire BPS_CLOCK;  
35.    wire BPS_CLOCKen;   
36.      
37.bps_module //BPS   
38.#(  
39.    //BPS_CNT = 85.89934592 * fo    for 50Mhz  
40.//  .BPS_CNT(32'd21990233)  //256000bps  
41.//  .BPS_CNT(32'd10995116)  //128000bps  
42.//  .BPS_CNT(32'd9895605)   //115200bps  
43.    .BPS_CNT(32'd824634)    //9600bps * 16 824634  
44.  //BPS_CNT = 42.949 67296 * fo    for 100Mhz  
45.//  .BPS_CNT(32'd10995116)  //256000bps 10995116  
46.//  .BPS_CNT(32'd5497558)   //128000bps 5497558  
47.//  .BPS_CNT(32'd4947802)   //115200bps 4947802  
48.//  .BPS_CNT(32'd412317)    //9600bps   412317  
49.)  
50.U1_bps_module  
51.(  
52.       .CLOCK( CLOCK ),  
53.        .RST_n( RST_n ),  
54.        .En_Sig( TX_En_Sig ),    // input - from U2  
55.        .BPS_CLK( BPS_CLOCK ),  // output - to X  
56.        .BPS_CLKen( BPS_CLOCKen ) // output - to U2  
57.);    
58./*********************************/  
59.        
60.tx_control_module U2_tx_control_module  
61.    (  
62.       .CLOCK( CLOCK ),  
63.        .RST_n( RST_n ),  
64.        .TX_En_Sig( TX_En_Sig ),    // input - from top  
65.        .TX_Data( TX_Data ),        // input - from top  
66.        .BPS_CLK( BPS_CLOCKen ),  // input - from U2  
67.        .TX_Done_Sig( TX_Done_Sig ), // output - to top  
68.        .TXD( TXD )     // output - to top  
69.    );  
70./***********************************/  
71.  
72.endmodule  
完成后的RTL如下:
250 UART发送模块RTL
2‑49整体设计一样。
上述代码的ModelSim仿真和2.3.3节仿真代码一样,结果如下:
251 ModelSim仿真
为了测试代码的实用性及使用方法,编写代码2‑25
代码225 UART发送实例代码
1.//****************************************************************************//  
2.//# @Author: 碎碎思  
3.//# @Date:   2019-05-03 02:18:37  
4.//# @Last Modified by:   zlk  
5.//# @WeChat Official Account: OpenFPGA  
6.//# @Last Modified time: 2019-05-06 21:38:04  
7.//# Description:   
8.//# @Modification History: 2019-05-06 21:38:04  
9.//# Date                By             Version             Change Description:   
10.//# ========================================================================= #  
11.//# 2019-05-06 21:38:04  
12.//# ========================================================================= #  
13.//# |                                                                       | #  
14.//# |                                OpenFPGA                               | #  
15.//****************************************************************************//  
16.module tx_module_demo  
17.(  
18.    CLOCK, RST_n,  
19.    TXD  
20.);  
21.  
22.   input CLOCK;  
23.    input RST_n;  
24.    output TXD;  
25.       
26./***************************/  
27.    reg [7:0]rData;   
28.    reg isTX;   
29.    wire DoneU1;  
30./****************************/  
31.  
32.  
33.      
34.tx_module dut  
35.    (  
36.        .CLOCK( CLOCK),  
37.        .RST_n( RST_n ),  
38.        .TX_Data( rData ),  
39.        .TX_En_Sig( isTX ),  
40.        .TX_Done_Sig( DoneU1 ),  
41.        .TXD( TXD )  
42.    );  
43.      
44./*******************************/  
45.  
46.    reg [3:0]i;  
47.  
48.  
49./**************************/  
50.       
51.     parameter T1S = 26'd49_999_999;  
52.       
53./*************************** 
54.      
55.     reg [25:0]Count_Sec; 
56.      
57.always @ ( posedge CLOCK or negedge RST_n ) 
58.         if( !RST_n ) 
59.              Count_Sec <= 26'd0; 
60.          else if( Count_Sec == T1S ) 
61.              Count_Sec <= 26'd0; 
62.          else 
63.              Count_Sec <= Count_Sec + 1'b1; 
64.                 
65./******************************** 
66. 
67. 
68.always @ ( posedge CLOCK or negedge RST_n ) 
69.        if( !RST_n ) 
70.             begin 
71.               isTX <= 1'b0; 
72.                rData <= 8'h00; 
73.             end     
74.          else if( DoneU1 ) 
75.             begin 
76.                  isTX <= 1'b0; 
77.                  rData <= 8'hAB;                 
78.              end 
79.          else if( Count_Sec == T1S ) 
80.              isTX <= 1'b1; 
81./******************************/  
82.      
83.      
84./******************************/  
85.  
86.always @ ( posedge CLOCK or negedge RST_n )  
87.         if( !RST_n )  
88.              begin  
89.                     i <= 4'd0;  
90.                     rData <= 8'd0;  
91.                     isTX <= 1'b0;  
92.                end  
93.          else  
94.              case( i )  
95.                  
96.                    0:  
97.                     if( DoneU1 ) begin isTX <= 1'b0; i <= i + 1'b1; end  
98.                     else begin isTX <= 1'b1; rData <= 8'hAB; end  
99.                       
100.                     1:  
101.                     if( DoneU1 ) begin isTX <= 1'b0; i <= i + 1'b1; end  
102.                     else begin isTX <= 1'b1; rData <= 8'hCD; end  
103.                       
104.                     2:  
105.                     if( DoneU1 ) begin isTX <= 1'b0; i <= i + 1'b1; end  
106.                     else begin isTX <= 1'b1; rData <= 8'hEF; end  
107.                       
108.                     3: // Stop  
109.                     i <= i;  
110.                  
111.                endcase  
112./******************************/  
113.  
114.endmodule  
2.3.4.3 UART接收模块
串口发送模块是“定时发送”的功能,那么串口接收模块就是“定时采集”的功能,这里的“定时”都是指波特率,“发送”就是按照“定时”的时间将数据一位一位的送到TXD引脚,那么“采集”呢?所谓的采集就是采集RXD引脚上的电平。
在一定波特率下,UART数据采集的步骤如下:
(1)空闲状态,等待数据起始位0;
(2)根据起始位的标志,判断UART时序起始;
(3)由低位到高位串行,接收8位数据;
(4)判断结束位1,一帧数据接收完毕。
UART接收模块的整体结构如2‑52所示:
252 UART接收模块的整体结构图
外观上和发送模块相比,就是多了一个电平检测模块。
215 接收模块(rx_module.v)模块间信号定义
模块间信号
管脚
传输方向
作用/说明
H2L_Sig
由电平检测模块传输给接收控制模块
由电平检测模块检测到输入电平由高到低输出标志传输给接收控制模块,用于后续的数据处理。



En_Sig
波特率定时模块和接收控制模块交互信号
当rx_control_module.v拉高Count_Sig, bps_module.v经BPS_CLK对 rx_control_module.v
产生定时
BPS_CLK
时序
没有特殊要求。



216 接收模块(rx_module.v)顶层模块信号定义
顶层模块Uart_Tx.v
输入管脚
作用/说明
输出管脚
作用/说明
RX_En_Sig
当 RX_En_Sig 拉高,整个模块就开始工作,它将采集来自 RX_Pin_In 的数据,当完成一帧数据接收的时候,就会产生一个高
脉冲给 RX_Done_Sig
RXD
UART串口接收数据引脚

RX_Data
接收到党的数据
TX_Done_Sig
一帧数据传输完毕标志信号。
CLOCK
全局时钟输入管脚(50MHz)


RST_n
全局复位引脚(低电平有效)


时序
时序说明
数据采集都是在“每位数据的中间”进行着(这个时候数据是最稳定的)。在上图中 RXD 输入一帧数据,当 detect_module.v 检测到低电平(起始位), rx_control_module.v 和 rx_bps_module.v 就产生定时(与 RX_Pin_In的波特率是一致)。然而 rx_bps_module.v 产生的定时是在每个位时间的中间。在第 0 位数据,采取忽略的态度,然后接下来的 8 位数据位都被采集,最后校验位和停止位,却是采取了忽略的操作。有一点你必须好好注意,串口传输数据“从最低位开始,到最高位结束”。




一、detect_module.v模块设计
detect_module.v模块输入是连结物理引脚 RXD,主要实现异步数据的同步逻辑及检测一帧数据的第 0 位,也就是起始位,然后产生一个高脉冲经 H2L_Sig 给 rx_control_module.v ,以表示一帧数据接收工作已经开始。
由于PC与FPGA端数据不同步,为了保证数据的同步,数据进入FPGA之后,首先需要将异步数据进行同步逻辑处理,使用的方式就是简单的D触发器时序电路,如下:
代码226 异步数据的同步逻辑
1./******************************/  
2.//sync the rxd data: rxd_sync  
3.reg rxd_sync;                 
4.always@(posedge CLOCK or negedge RST_n)  
5.begin  
6.    if(!RST_n)  
7.        rxd_sync <= 1;  
8.    else  
9.        rxd_sync <= RXD;  
10.end  
11.  
12./******************************/  
代码227 detect_module.v模块代码
1.//****************************************************************************//  
2.//# @Author: 碎碎思  
3.//# @Date:   2019-05-12 16:32:52  
4.//# @Last Modified by:   zlk  
5.//# @WeChat Official Account: OpenFPGA  
6.//# @Last Modified time: 2019-05-12 20:00:33  
7.//# Description:   
8.//# @Modification History: 2019-05-12 20:00:33  
9.//# Date                By             Version             Change Description:   
10.//# ========================================================================= #  
11.//# 2019-05-12 20:00:33  
12.//# ========================================================================= #  
13.//# |                                                                       | #  
14.//# |                                OpenFPGA                               | #  
15.//****************************************************************************//  
16.module detect_module   
17.(  
18.    CLOCK, RST_n,  
19.     RXD,  
20.     H2L_Sig  
21.);  
22.    input CLOCK;  
23.    input RST_n;  
24.    input RXD;  
25.    output H2L_Sig;  
26.       
27.  
28.    /******************************/  
29.    //sync the rxd data: rxd_sync  
30.    reg rxd_sync;                 
31.    always@(posedge CLOCK or negedge RST_n)  
32.    begin  
33.    if(!RST_n)  
34.        rxd_sync <= 1;  
35.    else  
36.        rxd_sync <= RXD;  
37.    end  
38.  
39.    /******************************/  
40.       
41.     reg H2L_F1;  
42.     reg H2L_F2;  
43.       
44.     always @ ( posedge CLOCK or negedge RST_n )  
45.         if( !RST_n )  
46.              begin  
47.                    H2L_F1 <= 1'b1;  
48.                     H2L_F2 <= 1'b1;  
49.                end  
50.          else  
51.              begin  
52.                    H2L_F1 <= rxd_sync;  
53.                    H2L_F2 <= H2L_F1;  
54.                end  
55.                  
56.    /***************************************/  
57.      
58.    assign H2L_Sig = H2L_F2 & !H2L_F1;  
59.      
60.    /***************************************/  
61.      
62.endmodule  
代码2‑27,和2.3.4.2节非常相似,针对性的修改了几个参数,理解起来应该不难。
二、bps_module.v模块设计
和上一节设计一致在此就不再赘述。
三、rx_control_module.v模块设计
rx_control_module.v 是核心控制模块。针对串口的配置主要是 1 帧 11 位的数据,重视八位数据位,无视起始位,校验位和结束位。当 RX_En_Sig 拉高,这个模块就开始工作,它将采集来自 RXD 的数据,当完成一帧数据接收的时候,就会产生一个高脉冲给 RX_Done_Sig。代码前端也需要加入异步逻辑数据的同步逻辑。
代码228 rx_control_module.v模块代码
1.//****************************************************************************//  
2.//# @Author: 碎碎思  
3.//# @Date:   2019-05-12 16:32:52  
4.//# @Last Modified by:   zlk  
5.//# @WeChat Official Account: OpenFPGA  
6.//# @Last Modified time: 2019-05-12 20:00:32  
7.//# Description:   
8.//# @Modification History: 2019-05-12 20:00:32  
9.//# Date                By             Version             Change Description:   
10.//# ========================================================================= #  
11.//# 2019-05-12 20:00:32  
12.//# ========================================================================= #  
13.//# |                                                                       | #  
14.//# |                                OpenFPGA                               | #  
15.//****************************************************************************//  
16.module rx_control_module  
17.(  
18.    CLOCK, RST_n,  
19.     H2L_Sig,   
20.     RXD,   
21.     BPS_CLK,   
22.     RX_En_Sig,  
23.    BPS_En_Sig,   
24.     RX_Data,   
25.     RX_Done_Sig  
26.       
27.);  
28.  
29.    input CLOCK;  
30.    input RST_n;  
31.       
32.    input H2L_Sig;  
33.    input RX_En_Sig;  
34.    input RXD;  
35.    input BPS_CLK;  
36.       
37.    output BPS_En_Sig;  
38.    output [7:0]RX_Data;  
39.    output RX_Done_Sig;  
40.       
41.       
42.     /********************************************************/  
43.    //sync the rxd data: rxd_sync  
44.    reg rxd_sync;                 
45.    always@(posedge CLOCK or negedge RST_n)  
46.    begin  
47.    if(!RST_n)  
48.        rxd_sync <= 1;  
49.    else  
50.        rxd_sync <= RXD;  
51.    end  
52.  
53.  
54.     /********************************************************/  
55.  
56.     reg [3:0]i;  
57.     reg [7:0]rData;  
58.     reg isCount;  
59.     reg isDone;  
60.      
61.     always @ ( posedge CLOCK or negedge RST_n )  
62.         if( !RST_n )  
63.              begin   
64.                  i <= 4'd0;  
65.                     rData <= 8'd0;  
66.                     isCount <= 1'b0;  
67.                     isDone <= 1'b0;    
68.                end  
69.          else if( RX_En_Sig )  
70.              case ( i )  
71.                  
72.                   4'd0 :  
73.                     if( H2L_Sig ) begin i <= i + 1'b1; isCount <= 1'b1; end  
74.                       
75.                     4'd1 :   
76.                     if( BPS_CLK ) begin i <= i + 1'b1; end  
77.                       
78.                     4'd2, 4'd3, 4'd4, 4'd5, 4'd6, 4'd7, 4'd8, 4'd9 :  
79.                     if( BPS_CLK ) begin i <= i + 1'b1; rData[ i - 2 ] <= rxd_sync; end  
80.                       
81.                     4'd10 :  
82.                     if( BPS_CLK ) begin i <= i + 1'b1; end  
83.                       
84.                     4'd11 :  
85.                     if( BPS_CLK ) begin i <= i + 1'b1; end  
86.                       
87.                     4'd12 :  
88.                     begin i <= i + 1'b1; isDone <= 1'b1; isCount <= 1'b0; end  
89.                       
90.                     4'd13 :  
91.                     begin i <= 4'd0; isDone <= 1'b0; end  
92.                   
93.                endcase  
94.                  
95.    /********************************************************/  
96.       
97.     assign BPS_En_Sig = isCount;  
98.     assign RX_Data = rData;  
99.     assign RX_Done_Sig = isDone;  
100.       
101.       
102.     /*********************************************************/  
103.       
104.endmodule  
69~91 行,是 rx_control_module.v 的核心控制功能。在 69 行,表示了如果 RX_En_Sig如果没有拉高,这个模块是不会工作。(在 58 行定义了 isCount 标志寄存器,为了使能bps_module.v 输出采集定时信号)当 rx_control_module.v 模块被使能该模块就会处于就绪状态,一旦 detect_module.v 检查到由高变低的电平变化(73 行),会使步骤 i 进入第 0 位采集,然而 isCount 标志寄存器同时也会被设置为逻辑 1, bps_module.v便会开始产生波特率的定时。
bps_module.v 产生的定时是在“每位数据的中间”。在 75~76 行,第一次的定时采集时第 0 位数据(起始位),保持忽略态度。在 78~79 行,定时采集的是八位数据位,每一位数据位会依低位到最高位储存入 rData 寄存器。81~85 行,是最后两位的定时采集(校验位,停留位),同时采取忽略的态度。当进入87~91 行,这表示一帧数据的采集工作已经结束。最后会产生一个完成信号的高脉冲,同时间 isCount 会被设置为逻辑 0,亦即停止bps_module.v 的操作。
至于 74~76 行, isCount 标志寄存器是 BPS_En_Sig 输出的驱动器, rData 寄存器是RX_Data 输出的驱动器,最后 isDone 标志寄存器是 RX_Done_Sig 输出的驱动寄存器。
四、rx_module.v顶层模块设计
接下来是将上几个模块进行例化,完成整个接收模块的设计。电路的整体结构如2‑52所示。顶层模块只需要按照整体结构图进行“连线”就可以了。代码如下:
代码229 rx_module.v顶层模块代码
1.//****************************************************************************//  
2.//# @Author: 碎碎思  
3.//# @Date:   2019-05-12 16:32:52  
4.//# @Last Modified by:   zlk  
5.//# @WeChat Official Account: OpenFPGA  
6.//# @Last Modified time: 2019-05-12 20:22:33  
7.//# Description:   
8.//# @Modification History: 2019-05-12 20:22:33  
9.//# Date                By             Version             Change Description:   
10.//# ========================================================================= #  
11.//# 2019-05-12 20:22:33  
12.//# ========================================================================= #  
13.//# |                                                                       | #  
14.//# |                                OpenFPGA                               | #  
15.//****************************************************************************//  
16.module rx_module  
17.(  
18.    CLOCK, RST_n,  
19.    RXD,   
20.    RX_En_Sig,  
21.    RX_Done_Sig,   
22.    RX_Data  
23.);  
24.  
25.    input CLOCK;  
26.     input RST_n;  
27.       
28.     input RXD;  
29.     input RX_En_Sig;  
30.       
31.     output [7:0]RX_Data;  
32.     output RX_Done_Sig;  
33.       
34.       
35.     /**********************************/  
36.       
37.     wire H2L_Sig;  
38.       
39.     detect_module U1  
40.     (  
41.         .CLOCK( CLOCK ),  
42.          .RST_n( RST_n ),  
43.          .RXD( RXD ),    // input - from top  
44.          .H2L_Sig( H2L_Sig )         // output - to U3  
45.     );  
46.       
47.     /**********************************/  
48.       
49.     wire BPS_CLK;  
50.     wire BPS_CLKen;  
51.       
52.    bps_module //BPS   
53.    #(  
54.        //BPS_CNT = 85.89934592 * fo    for 50Mhz  
55.    //  .BPS_CNT(32'd21990233)  //256000bps  
56.    //  .BPS_CNT(32'd10995116)  //128000bps  
57.    //  .BPS_CNT(32'd9895605)   //115200bps  
58.        .BPS_CNT(32'd824634)    //9600bps * 16 824634  
59.      //BPS_CNT = 42.949 67296 * fo    for 100Mhz  
60.    //  .BPS_CNT(32'd10995116)  //256000bps 10995116  
61.    //  .BPS_CNT(32'd5497558)   //128000bps 5497558  
62.    //  .BPS_CNT(32'd4947802)   //115200bps 4947802  
63.    //  .BPS_CNT(32'd412317)    //9600bps   412317  
64.    )  
65.    U1_bps_module  
66.    (  
67.           .CLOCK( CLOCK ),  
68.            .RST_n( RST_n ),  
69.            .En_Sig( BPS_En_Sig ),    // input - from U2  
70.            .BPS_CLK( BPS_CLK ),  // output - to X  
71.            .BPS_CLKen( BPS_CLKen ) // output - to U2  
72.    );   
73.       
74.     /**********************************/  
75.       
76.     wire Count_Sig;  
77.       
78.     rx_control_module U2  
79.     (  
80.         .CLOCK( CLOCK ),  
81.          .RST_n( RST_n ),  
82.            
83.          .H2L_Sig( H2L_Sig ),      // input - from U1  
84.          .RX_En_Sig( RX_En_Sig ),  // input - from top  
85.          .RXD( RXD ),  // input - from top  
86.          .BPS_CLK( BPS_CLKen ),      // input - from U2  
87.            
88.          .BPS_En_Sig( BPS_En_Sig ),    // output - to U2  
89.          .RX_Data( RX_Data ),        // output - to top  
90.          .RX_Done_Sig( RX_Done_Sig ) // output - to top  
91.            
92.     );  
93.       
94.     /************************************/  
95.       
96.  
97.endmodule  
完成后的RTL如下:
253 UART接收代码RTL
2‑52整体设计一样。
上述代码的ModelSim仿真和2.3.3节仿真代码一样,结果如下:
254 ModelSim仿真
为了测试代码的实用性及使用方法,建立测试代码的模型。如下图所示:
255 测试代码的模型
建立如上图 rx_module_demo.v 组合模块 ,它里边包含了一个控制模块对rx_module.v 的调用。一开始 control_module.v 会拉高 RX_En_Sig 使能 rx_module.v 。当有一阵数据经 RXD 传入rx_module.v 就会接收,然后过滤后的数据出输出 RX_Data , 然后再产生一个高脉冲给 RX_Done_Sig。当 control_module.v 接收到RX_Done_Sig 的高脉冲,拉低 RX_En_Sig,并且将 RX_Data 的“前四位”输出致 4 位LED 资源。然后过程再一次重复不断。
代码230 control_module.v实例代码
1.//****************************************************************************//  
2.//# @Author: 碎碎思  
3.//# @Date:   2019-05-12 21:11:18  
4.//# @Last Modified by:   zlk  
5.//# @WeChat Official Account: OpenFPGA  
6.//# @Last Modified time: 2019-05-12 21:26:44  
7.//# Description:   
8.//# @Modification History: 2019-05-12 21:26:44  
9.//# Date                By             Version             Change Description:   
10.//# ========================================================================= #  
11.//# 2019-05-12 21:26:44  
12.//# ========================================================================= #  
13.//# |                                                                       | #  
14.//# |                                OpenFPGA                               | #  
15.//****************************************************************************//  
16.module control_module  
17.(  
18.    CLOCK, RST_n,  
19.     RX_Done_Sig,  
20.     RX_Data,  
21.     RX_En_Sig,  
22.     Number_Data  
23.);  
24.  
25.    input CLOCK;  
26.     input RST_n;  
27.     input RX_Done_Sig;  
28.     input [7:0]RX_Data;  
29.     output RX_En_Sig;  
30.     output [7:0]Number_Data;  
31.       
32.     /******************************/  
33.       
34.     reg [7:0]rData;  
35.     reg isEn;  
36.       
37.    always @ ( posedge CLOCK or negedge RST_n )  
38.         if( !RST_n )  
39.              rData <= 8'd0;  
40.          else if( RX_Done_Sig )       
41.              begin rData <= RX_Data; isEn <= 1'b0; end  
42.          else isEn <= 1'b1;  
43.  
44.    /*********************************/  
45.      
46.    assign Number_Data = rData;  
47.    assign RX_En_Sig = isEn;  
48.      
49.    /*********************************/  
50.       
51.endmodule  
37~49 行就是这个控制模块的核心功能了。一开始的时候(42 行)就将 isEn 设置为逻辑 1, 这个标志寄存器在 47 行驱动着 RX_En_Sig。换句话说,此时的 rx_module.v 已经进入就绪状态,然后 control_module.v 等待着 RX_Done_Sig 反馈(40 行)。一旦一帧数据接收完毕, RX_Done_Sig 就会产生高脉冲然后 rData 就会寄存 RX_Data的值。同时间 isEn 被设置为逻辑 0(41行)。在下一个来临control_module.v 会再一次设置 isEn 为逻辑 1,已做好接收下一组数据的准备。
代码231 rx_module_demo.v实例代码
1.//***************************************//  
2.//# @Author: 碎碎思  
3.//# @Date:   2019-04-06 22:44:48  
4.//# @Last Modified by:   zlk  
5.//# @Last Modified time: 2019-05-12 21:36:15  
6.//****************************************//  
7.module rx_module_demo  
8.(  
9.    CLOCK, RST_n,  
10.     RXD,  
11.     LED  
12.);  
13.  
14.    input CLOCK;  
15.     input RST_n;  
16.     input RXD;  
17.    output [3:0]LED;      
18.       
19.     /**********************************/  
20.       
21.     wire RX_Done_Sig;  
22.     wire [7:0]RX_Data;  
23.       
24.  
25.     rx_module U1  
26.     (  
27.         .CLOCK( CLOCK ),  
28.          .RST_n( RST_n ),  
29.          .RXD( RXD ),   // input - from top  
30.          .RX_En_Sig( RX_En_Sig ),   // input - from U2  
31.          .RX_Done_Sig( RX_Done_Sig ),  // output - to U2  
32.          .RX_Data( RX_Data )           // output - to U2  
33.     );  
34.       
35.     /***********************************/  
36.       
37.     wire RX_En_Sig;  
38.     wire [7:0]Output_Data;  
39.       
40.     control_module U2  
41.     (  
42.         .CLOCK( CLOCK ),  
43.         .RST_n( RST_n ),  
44.          .RX_Done_Sig( RX_Done_Sig ),  // input - from U1  
45.          .RX_Data( RX_Data ),          // input - from U1  
46.          .RX_En_Sig( RX_En_Sig ),      // output - to U1  
47.          .Number_Data( Output_Data )   // output - to top  
48.     );  
49.       
50.     /***********************************/  
51.  
52.     assign LED = Output_Data[3:0];  
53.       
54.endmodule  
RTL电路如下:
256 RTL电路图
连线关系也和“图形”一样。在 52 行,对LED的输出驱动,取致 Output_Data 前四位。

257 串口调试助手测试
这个演示主要是在“串口调试助手”以 “十六进制”发送数据。如果发送 0F ,那么四位
LED 就全部都会亮起来;如果输出 0A么只有第四位和第二位 LED 亮了起来。
2.3.4.4 UART模块测试
为了测试上面设计的TX模块和RX模块功能,这部分主要完成和PC端串口通讯的设计。
PC端串口和芯片(FPGA、DSP、ARM等)通讯的几种方式:串口通讯主要考虑的是传输两端电平匹配, RS232电平(高电平:+12V 低电平:-12V),TTL电平(高电平:芯片端I/O引脚高电平 低电平:0 V)。
验证结构图如下图所示:
258 UART模块测试结构图
首先PC以一定的波特率发送数据,在FPGA接收完UART数据后,通过RX_DataRX_En_Sig信号激励rx_module。接着由FPGA发送接收到的串转并数据,再发送UART数据给PC,最后通过PC接收验证。整个设计的结构如下:
259 UART模块测试建模图
中间控制模块作用如同中介,将数据从一方移去另一方,完全没有介入串口接收|发送接口的操作之中。从另一个角度去看,在 rx_tx_interface_demo.v 中的接口(串口接收接口和串口发送接口)和中间模块控制模块,完全是独立的。
代码232 inter_control_module.v实例代码
1.//***************************************//  
2.//# @Author: 碎碎思  
3.//# @Date:   2019-05-12 22:32:32  
4.//# @Last Modified by:   zlk  
5.//# @Last Modified time: 2019-05-12 23:25:55  
6.//****************************************//  
7.module inter_control_module  
8.(  
9.    input CLOCK,  
10.    input RST_n,  
11.       
12.     //TX control signal  
13.     input TX_Done_Sig,  
14.     output TX_En_Sig,  
15.     output [7:0]TX_Data,  
16.     //RX control signal  
17.     input RX_Done_Sig,  
18.     output RX_En_Sig,  
19.     input [7:0]RX_Data  
20.);  
21.  
22.    /********************************/  
23.       
24.     reg [2:0]i;  
25.     reg isTX_En_Sig;  
26.     reg isRX_En_Sig;     
27.     reg [7:0]isRX_Data;  
28.     reg [7:0]isTX_Data;  
29.       
30.     always @ ( posedge CLOCK or negedge RST_n )  
31.         if( !RST_n )  
32.              begin  
33.                    i <= 3'd0;  
34.                     isRX_En_Sig <= 1'b0;  
35.                     isTX_En_Sig <= 1'b0;  
36.                end  
37.          else   
38.              case( i )  
39.                  
40.                    0:  
41.                     if( RX_Done_Sig ) begin isRX_En_Sig <= 1'b0;i <= i + 1'b1; end  
42.                     else begin isRX_En_Sig <= 1'b1;isRX_Data <= RX_Data;   end  
43.                       
44.                    1:  
45.                     if( TX_Done_Sig  ) begin isTX_En_Sig <= 1'b0; i <= i + 1'b1; end  
46.                     else begin isTX_En_Sig <= 1'b1; isTX_Data<=isRX_Data; end                       
47.                       
48.                     2:  
49.                     begin isRX_En_Sig <= 1'b0; i <= 3'd0; end  
50.                       
51.                  
52.                endcase   
53.                  
54.    /********************************/  
55.       
56.     assign RX_En_Sig = isRX_En_Sig;  
57.     assign TX_En_Sig= isTX_En_Sig;  
58.     assign TX_Data = isTX_Data;  
59.       
60.     /********************************/  
61.       
62.endmodule  
29~50 行是中间控制模块的主要功能。在步骤 0,先判断 rx_interface.v 的 FIFO 是否为空,如果不为空 if 条件就成立, i 就递增以示下一个步骤(32 行)。
34~38 行,表示从 rx_interface.v 读取一个深度的数据。当完成从 rx_interface.v 的 FIFO 读取一个深度的数据以后,数据在 FIFO_Read_Data 信号上已经就绪。在步骤 3,先判断 tx_inerface.v 的 FIFO 是否为满状态?如果tx_interface.v 的 FIFO 不为满状态, i 会递增以示下一个步骤。在这里请注意, FIFO_Write_Data 是直接由 FIFO_Read_Data 驱动(56 行)。在 43~47行,主要是将数据写入 tx_interface.v 的 FIFO 内。
最后 i 被赋予 0,以示重复上述的动作
本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
VHDL实验三:设计UART串行传输模块 [CPLD/FPGA]
【接口时序】3、UART串口收发的原理与Verilog实现
凔海笔记之FPGA(八):Verilog描述RS232 UART
UART串口收发控制器设计详解
基于Verilog下的串口通信实验
小课堂10——基于FPGA 的串口监视系统设计
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服