首页 > 代码库 > 【黑金原创教程】【FPGA那些事儿-驱动篇I 】实验十三:串口模块② — 接收

【黑金原创教程】【FPGA那些事儿-驱动篇I 】实验十三:串口模块② — 接收

实验十三:串口模块② — 接收

我们在实验十二实现了串口发送,然而这章实验则要实现串口接收 ... 在此,笔者也会使用其它思路实现串口接收。

clip_image002

图13.1 模块之间的数据传输。

假设我们不考虑波特率,而且一帧数据之间的传输也只是发生在FPGA之间,即两只模块之间互转,并且两块模块都使用相同的时钟频率,结果如图13.1所示。只要成立上述的假设成立,串口传输不过是简单的数据传输活动而已,图中的发送模块经由TXD将一帧11位的数据发送至接收模块。

clip_image004

图13.2 发送与接收一帧数据。

至于两者之间的时序过程,则如图13.2所示 ... 发送方经由TXD,从T0~T10总共发送一帧为11位的数据,反之接收方则从T2~T9读取其中的8位数据而已(D为寄存器的暂存内容)。从图13.2当中,我们可以看见发送方,即TXD都是经由上升沿发送未来值,接收方D则是经由上升沿读取过去值。对此,Verilog可以这样描述,结果如代码13.1所示:

      //发送方
      reg [10:0]rTXD;
     always @(posedge CLOCK)
         case(i)
            0,1,2,3,4,5,6,7,8,9,10:      
            begin TXD <= rTXD[i]; i <= i + 1‘b1; end
            ......
         endcase
 
      //接收方
      reg [7:0]D1;
     always @(posedge CLOCK)
         case(i)
           2,3,4,5,6,7,8,9:      
            begin D1[i] <= TXD; i <= i + 1‘b1; end
            ......
         endcase
<style type="text/css">.csharpcode, .csharpcode pre{ font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/}.csharpcode pre { margin: 0em; }.csharpcode .rem { color: #008000; }.csharpcode .kwrd { color: #0000ff; }.csharpcode .str { color: #006080; }.csharpcode .op { color: #0000c0; }.csharpcode .preproc { color: #cc6633; }.csharpcode .asp { background-color: #ffff00; }.csharpcode .html { color: #800000; }.csharpcode .attr { color: #ff0000; }.csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em;}.csharpcode .lnum { color: #606060; }</style>

代码13.1

如代码13.1所示,发送方在步骤0~10一共发送一帧为11位的数据 ... 反之接收方,则在步骤2~9读取其中的数据[7:0]。心机重的朋友的一定会疑惑道,为什么笔者要换个角度去思考串口怎样接收呢?原因其实很简单,目的就是为了简化理解,脑补时序,实现精密控制。

对此,FPGA与其它设备互转数据,其实可以反映成两只模块正在互转数据,然而理想时序就是关键。因为Verilog无法描述理想以外的时序,对此所有时序活动都必须看成理想时序。

clip_image006

图13.3 FPGA接收一帧波特率为115200的数据。

当FPGA接收一帧数据为波特率115200之际,情况差不多如图13.3所示。50Mhz是FPGA的时钟源,也是一帧数据的采集时钟,RXD则是一帧数据的输入端。波特率为115200的一位数据经过50Mhz的时钟量化以后,每一位数据大约保持8.68us,即434个时钟。

串口传输没有自己的时钟信号,所以我们必须利用FPGA的时钟源“跟踪”每一位数据。对此,FPGA只能借用计数器“同步跟踪”而已,至于Verilog则可以这样描述,结果如代码13.2所示:

    0,1,2,3,4,5,6,7,8,9,10:  //同步跟踪中 ...        
    if( C1 == 434 -1 ) begin C1 <= 8‘d0; i <= i + 1‘b1; end
    else C1 <= C1 + 1‘b1; 
<style type="text/css">.csharpcode, .csharpcode pre{ font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/}.csharpcode pre { margin: 0em; }.csharpcode .rem { color: #008000; }.csharpcode .kwrd { color: #0000ff; }.csharpcode .str { color: #006080; }.csharpcode .op { color: #0000c0; }.csharpcode .preproc { color: #cc6633; }.csharpcode .asp { background-color: #ffff00; }.csharpcode .html { color: #800000; }.csharpcode .attr { color: #ff0000; }.csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em;}.csharpcode .lnum { color: #606060; }</style>

代码13.2

如代码13.2所示,所谓同步跟踪,就是利用计数器估计每一位数据 ... 期间,步骤0~10表示每一位数据,至于C1计数434个时钟则是同步跟踪中。其中 -1 考虑了步骤之间的跳转所耗掉的时钟。

clip_image008

图13.4 读取起始位。

我们都知道串口的一帧数据都是从拉低的起始位开始,然而为了完美尾行,亦即实现精密控时,起始位的读取往往都是关键。如图13.4所示,当我们在第一个时钟读取(采集)起始位的时候,由于Verilog的读取只能经过读取过去值而已,余下起始位还有433个时钟需要我们跟踪,为此Verilog可以这样描述,结果如代码13.3所示:

    0: 
    if( RXD == 1‘b0 ) begin i <= i + 1‘b1; C1 <= C1 + 4‘d1; end         
         
    1: // stalk start bit
    if( C1 == BPS115K2 -1 ) begin C1 <= 8‘d0; i <= i + 1‘b1; end 
    else C1 <= C1 + 1‘b1;
<style type="text/css">.csharpcode, .csharpcode pre{ font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/}.csharpcode pre { margin: 0em; }.csharpcode .rem { color: #008000; }.csharpcode .kwrd { color: #0000ff; }.csharpcode .str { color: #006080; }.csharpcode .op { color: #0000c0; }.csharpcode .preproc { color: #cc6633; }.csharpcode .asp { background-color: #ffff00; }.csharpcode .html { color: #800000; }.csharpcode .attr { color: #ff0000; }.csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em;}.csharpcode .lnum { color: #606060; }</style>

代码13.3

如代码13.3所示,步骤0用来检测起始位,如果RXD的电平为拉低状态,C1立即递增以示同步跟踪已经用掉一个时钟,同样也可以看成i进入下一个步骤用掉一个时钟。然而步骤1是用来跟踪余下的433个时钟,但是计数器C1不是从0开始计数,而是从1开始计算,因为C1在步骤已经递增的缘故。

clip_image010

图13.5 读取一帧数据当中的数据位。

一帧数据的跟踪结果与读取结果如图13.5所示 ... 除了起始位,我们使用了两个步骤采集并跟踪之余,接下来便用8个步骤数据一边跟踪一边采集所有数据位,然而采集的时候则是1/4周期,即每位数据的第108个时钟。最后的校验位及结束位则是跟踪而已。对此,Verilog 可以这样表示,结果如代码13.4所示:

    0: 
    if( RXD == 1‘b0 ) begin i <= i + 1‘b1; C1 <= C1 + 4‘d1; end 
                     
    1: // start bit
    if( C1 == 434 -1 ) begin C1 <= 8‘d0; i <= i + 1‘b1; end 
    else C1 <= C1 + 1‘b1;
                     
    2,3,4,5,6,7,8,9: 
    begin
        if( C1 == 108 ) D1[i-2] <= RXD;
        if( C1 == 434 -1 ) begin C1 <= 8‘d0; i <= i + 1‘b1; end
        else C1 <= C1 + 1‘b1;                           
    end
                 
    10,11: // parity bit & stop bit
    if( C1 == 434 -1 ) begin C1 <= 8‘d0; i <= i + 1‘b1; end
    else C1 <= C1 + 1‘b1;
<style type="text/css">.csharpcode, .csharpcode pre{ font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/}.csharpcode pre { margin: 0em; }.csharpcode .rem { color: #008000; }.csharpcode .kwrd { color: #0000ff; }.csharpcode .str { color: #006080; }.csharpcode .op { color: #0000c0; }.csharpcode .preproc { color: #cc6633; }.csharpcode .asp { background-color: #ffff00; }.csharpcode .html { color: #800000; }.csharpcode .attr { color: #ff0000; }.csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em;}.csharpcode .lnum { color: #606060; }</style>

代码13.4

如代码13.4所示,步骤0~1用来采集与跟踪起始位,步骤2~9则用来跟踪数据位,并且采集为1/4周期。步骤10~11则用来跟踪校验位于结束位。理解完毕以后,我们便可以开始建模了。

clip_image012

图13.6 实验十三的建模图。

图13.6是实验十三的建模图,rx_demo组合模块包含RX功能模块与核心操作。RX功能模块的左方链接至RXD顶层信号,它主要是负责一帧数据的接收,然后反馈给核心操作。核心操作则负责RX功能模块的使能工作,当它领取完成信号以后,变桨回收回来的数据再经由TXD顶层信号发送出去。

rx_funcmod.v

clip_image014

图13.7 RX功能模块的建模图。

图13.7是RX功能模块的建模图,左方链接至顶层信号RXD,右方则是问答信号还有8位的oData。

1.    module rx_funcmod
2.    (
3.         input CLOCK, RESET, 
4.         input RXD,
5.         input iCall,
6.         output oDone, 
7.         output [7:0]oData
8.    );
9.        parameter BPS115K2 = 9‘d434, SAMPLE = 9‘d108;
10.          
<style type="text/css">.csharpcode, .csharpcode pre{ font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/}.csharpcode pre { margin: 0em; }.csharpcode .rem { color: #008000; }.csharpcode .kwrd { color: #0000ff; }.csharpcode .str { color: #006080; }.csharpcode .op { color: #0000c0; }.csharpcode .preproc { color: #cc6633; }.csharpcode .asp { background-color: #ffff00; }.csharpcode .html { color: #800000; }.csharpcode .attr { color: #ff0000; }.csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em;}.csharpcode .lnum { color: #606060; }</style>

以上内容是相关的出入端声明,第9行则是波特率为115200的常量声明,其外还有采集的周期。

11.         reg [3:0]i;
12.         reg [8:0]C1;
13.         reg [7:0]D1;
14.         reg isDone;
15.         
16.         always @ ( posedge CLOCK or negedge RESET )
17.             if( !RESET )
18.                  begin
19.                         i <= 4‘d0;
20.                         C1 <= 9‘d0;
21.                         D1 <= 8‘d0;
22.                         isDone <= 1‘b0;
23.                    end
<style type="text/css">.csharpcode, .csharpcode pre{ font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/}.csharpcode pre { margin: 0em; }.csharpcode .rem { color: #008000; }.csharpcode .kwrd { color: #0000ff; }.csharpcode .str { color: #006080; }.csharpcode .op { color: #0000c0; }.csharpcode .preproc { color: #cc6633; }.csharpcode .asp { background-color: #ffff00; }.csharpcode .html { color: #800000; }.csharpcode .attr { color: #ff0000; }.csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em;}.csharpcode .lnum { color: #606060; }</style>

以上内容行是相关的寄存器声明,第17~22行则是这些寄存器的复位操作。

24.              else if( iCall )
25.                  case( i )
26.                         
27.                         0: 
28.                         if( RXD == 1‘b0 ) begin i <= i + 1‘b1; C1 <= C1 + 4‘d1; end 
29.                         
30.                         1: // start bit
31.                         if( C1 == BPS115K2 -1 ) begin C1 <= 8‘d0; i <= i + 1‘b1; end 
32.                         else C1 <= C1 + 1‘b1;
33.                         
34.                         2,3,4,5,6,7,8,9: //stalk and count 1~8 data‘s bit , sample data at 1/2 for bps
35.                         begin
36.                            if( C1 == SAMPLE ) D1[i-2] <= RXD;
37.                            if( C1 == BPS115K2 -1 ) begin C1 <= 8‘d0; i <= i + 1‘b1; end
38.                            else C1 <= C1 + 1‘b1;          
39.                         end
40.                         
41.                         10,11: // parity bit & stop bit
42.                         if( C1 == BPS115K2 -1 ) begin C1 <= 8‘d0; i <= i + 1‘b1; end
43.                         else C1 <= C1 + 1‘b1;
44.                         
45.                         12:
46.                         begin isDone <= 1‘b1; i <= i + 1‘b1; end
47.                         
48.                         13:
49.                         begin isDone <= 1‘b0; i <= 4‘d0; end
50.                    
51.                    endcase
52.                    
<style type="text/css">.csharpcode, .csharpcode pre{ font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/}.csharpcode pre { margin: 0em; }.csharpcode .rem { color: #008000; }.csharpcode .kwrd { color: #0000ff; }.csharpcode .str { color: #006080; }.csharpcode .op { color: #0000c0; }.csharpcode .preproc { color: #cc6633; }.csharpcode .asp { background-color: #ffff00; }.csharpcode .html { color: #800000; }.csharpcode .attr { color: #ff0000; }.csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em;}.csharpcode .lnum { color: #606060; }</style>

以上内容是核心操作。第24行的 if( iCall ) 表示该模块不使能便不工作。步骤0~1用来判断与跟踪起始位;步骤2~9用来跟踪并且读取当中的数据位;步骤10至11则是用来跟踪校验位与停止位而已。步骤12~13则用来反馈完成信号,以示一次性的接收工作已经完成。

53.        assign oDone = isDone;
54.        assign oData = http://www.mamicode.com/D1;
55.        
56.    endmodule
<style type="text/css">.csharpcode, .csharpcode pre{ font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/}.csharpcode pre { margin: 0em; }.csharpcode .rem { color: #008000; }.csharpcode .kwrd { color: #0000ff; }.csharpcode .str { color: #006080; }.csharpcode .op { color: #0000c0; }.csharpcode .preproc { color: #cc6633; }.csharpcode .asp { background-color: #ffff00; }.csharpcode .html { color: #800000; }.csharpcode .attr { color: #ff0000; }.csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em;}.csharpcode .lnum { color: #606060; }</style>

以上内容是输出驱动声明。

rx_demo.v

rx_demo的连线布局请浏览回图13.6,至于核心操作的内容请浏览代码。

1.    module rx_demo
2.    (
3.         input CLOCK, RESET, 
4.         input RXD,
5.         output TXD
6.    );
<style type="text/css">.csharpcode, .csharpcode pre{ font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/}.csharpcode pre { margin: 0em; }.csharpcode .rem { color: #008000; }.csharpcode .kwrd { color: #0000ff; }.csharpcode .str { color: #006080; }.csharpcode .op { color: #0000c0; }.csharpcode .preproc { color: #cc6633; }.csharpcode .asp { background-color: #ffff00; }.csharpcode .html { color: #800000; }.csharpcode .attr { color: #ff0000; }.csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em;}.csharpcode .lnum { color: #606060; }</style>

以上内容是相关的出入端声明。

7.         wire DoneU1;
8.         wire [7:0]DataU1;
9.        
10.        rx_funcmod U1
11.         (
12.             .CLOCK( CLOCK ),
13.              .RESET( RESET ),
14.              .RXD( RXD ),    // < top
15.              .iCall( isRX ),    // < core
16.              .oDone( DoneU1 ),  // > core
17.              .oData( DataU1 )   // > core
18.         );
19.         
<style type="text/css">.csharpcode, .csharpcode pre{ font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/}.csharpcode pre { margin: 0em; }.csharpcode .rem { color: #008000; }.csharpcode .kwrd { color: #0000ff; }.csharpcode .str { color: #006080; }.csharpcode .op { color: #0000c0; }.csharpcode .preproc { color: #cc6633; }.csharpcode .asp { background-color: #ffff00; }.csharpcode .html { color: #800000; }.csharpcode .attr { color: #ff0000; }.csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em;}.csharpcode .lnum { color: #606060; }</style>

以上内容为是RX功能模块的实例化,第7~8是连线声明。

20.         parameter B115K2 = 9‘d434, TXFUNC = 5‘d16;
21.         
22.         reg [4:0]i,Go;
23.         reg [8:0]C1;
24.         reg [10:0]D1;
25.         reg rTXD;
26.         reg isRX;
27.         
28.         always @ ( posedge CLOCK or negedge RESET )
29.             if( !RESET )
30.                  begin 
31.                         i <= 5‘d0;
32.                         C1 <= 9‘d0;
33.                         D1 <= 11‘d0;
34.                         rTXD <= 1‘b1;
35.                         isRX<= 1‘b0;
36.                    end
<style type="text/css">.csharpcode, .csharpcode pre{ font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/}.csharpcode pre { margin: 0em; }.csharpcode .rem { color: #008000; }.csharpcode .kwrd { color: #0000ff; }.csharpcode .str { color: #006080; }.csharpcode .op { color: #0000c0; }.csharpcode .preproc { color: #cc6633; }.csharpcode .asp { background-color: #ffff00; }.csharpcode .html { color: #800000; }.csharpcode .attr { color: #ff0000; }.csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em;}.csharpcode .lnum { color: #606060; }</style>

以上内容为相关的寄存器声明以及复位操作。第20行是波特率为115200常量声明之余还有伪函数的入口地址。第22~26行是相关的寄存器声明,第29~33行则是这些寄存器的复位操作。

37.              else
38.                  case( i )
39.                    
40.                         0:
41.                         if( DoneU1 ) begin isRX <= 1‘b0; D1 <= { 2‘b11,DataU1,1‘b0 }; i <= TXFUNC; Go <= 5‘d0; end
42.                         else isRX <= 1‘b1; 
43.                         
44.                         /**********/
45.                         
46.                         16,17,18,19,20,21,22,23,24,25,26:
47.                         if( C1 == B115K2 -1 ) begin C1 <= 8‘d0; i <= i + 1‘b1; end
48.                         else begin rTXD <= D1[i - 16]; C1 <= C1 + 1‘b1; end
49.                         
50.                         27:
51.                         i <= Go;
52.                        
53.                    endcase
54.                    
<style type="text/css">.csharpcode, .csharpcode pre{ font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/}.csharpcode pre { margin: 0em; }.csharpcode .rem { color: #008000; }.csharpcode .kwrd { color: #0000ff; }.csharpcode .str { color: #006080; }.csharpcode .op { color: #0000c0; }.csharpcode .preproc { color: #cc6633; }.csharpcode .asp { background-color: #ffff00; }.csharpcode .html { color: #800000; }.csharpcode .attr { color: #ff0000; }.csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em;}.csharpcode .lnum { color: #606060; }</style>

以上内容为核心操作。步骤16~27是发送一帧数据的伪函数,笔者直接将TX功能整合进来。步骤0则是用来接收完成反馈,并且准备好发送输数,然后i指向伪函数。

55.        assign TXD = rTXD;56.                               57.    endmodule
<style type="text/css">.csharpcode, .csharpcode pre{ font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/}.csharpcode pre { margin: 0em; }.csharpcode .rem { color: #008000; }.csharpcode .kwrd { color: #0000ff; }.csharpcode .str { color: #006080; }.csharpcode .op { color: #0000c0; }.csharpcode .preproc { color: #cc6633; }.csharpcode .asp { background-color: #ffff00; }.csharpcode .html { color: #800000; }.csharpcode .attr { color: #ff0000; }.csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em;}.csharpcode .lnum { color: #606060; }</style>

以上内容是相关的输出驱动声明。编译完毕便下载程序,串口调试助手设置为 115200 波特率,8位数据位,奇偶校验位随便,停止位1。事后,每当串口调试助手想FPGA发送什么数据,FPGA也会回馈串口调试助手,不过仅限于一帧又有间隔的数据而已。目前是实验十三还不能支持数据流的接收,因为实验十三没有空间缓冲数据流 ... 此外,核心操作没发送一帧数据也有一定的时间耽误。

细节一:完整的个体模块

实验十三的RX功能模块已经是完整的个体模块,可以直接拿来调用。

【黑金原创教程】【FPGA那些事儿-驱动篇I 】实验十三:串口模块② — 接收