实验十三:串口模块② — 接收
我们在实验十二实现了串口发送,然而这章实验则要实现串口接收 ... 在此,笔者也会使用其它思路实现串口接收。
图13.1 模块之间的数据传输。
假设我们不考虑波特率,而且一帧数据之间的传输也只是发生在FPGA之间,即两只模块之间互转,并且两块模块都使用相同的时钟频率,结果如图13.1所示。只要成立上述的假设成立,串口传输不过是简单的数据传输活动而已,图中的发送模块经由TXD将一帧11位的数据发送至接收模块。
图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
代码13.1
如代码13.1所示,发送方在步骤0~10一共发送一帧为11位的数据 ... 反之接收方,则在步骤2~9读取其中的数据[7:0]。心机重的朋友的一定会疑惑道,为什么笔者要换个角度去思考串口怎样接收呢?原因其实很简单,目的就是为了简化理解,脑补时序,实现精密控制。
对此,FPGA与其它设备互转数据,其实可以反映成两只模块正在互转数据,然而理想时序就是关键。因为Verilog无法描述理想以外的时序,对此所有时序活动都必须看成理想时序。
图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;
代码13.2
如代码13.2所示,所谓同步跟踪,就是利用计数器估计每一位数据 ... 期间,步骤0~10表示每一位数据,至于C1计数434个时钟则是同步跟踪中。其中 -1 考虑了步骤之间的跳转所耗掉的时钟。
图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;
代码13.3
如代码13.3所示,步骤0用来检测起始位,如果RXD的电平为拉低状态,C1立即递增以示同步跟踪已经用掉一个时钟,同样也可以看成i进入下一个步骤用掉一个时钟。然而步骤1是用来跟踪余下的433个时钟,但是计数器C1不是从0开始计数,而是从1开始计算,因为C1在步骤已经递增的缘故。
图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;
代码13.4
如代码13.4所示,步骤0~1用来采集与跟踪起始位,步骤2~9则用来跟踪数据位,并且采集为1/4周期。步骤10~11则用来跟踪校验位于结束位。理解完毕以后,我们便可以开始建模了。
图13.6 实验十三的建模图。
图13.6是实验十三的建模图,rx_demo组合模块包含RX功能模块与核心操作。RX功能模块的左方链接至RXD顶层信号,它主要是负责一帧数据的接收,然后反馈给核心操作。核心操作则负责RX功能模块的使能工作,当它领取完成信号以后,变桨回收回来的数据再经由TXD顶层信号发送出去。
rx_funcmod.v
图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.
以上内容是相关的出入端声明,第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
以上内容行是相关的寄存器声明,第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.
以上内容是核心操作。第24行的 if( iCall ) 表示该模块不使能便不工作。步骤0~1用来判断与跟踪起始位;步骤2~9用来跟踪并且读取当中的数据位;步骤10至11则是用来跟踪校验位与停止位而已。步骤12~13则用来反馈完成信号,以示一次性的接收工作已经完成。
53. assign oDone = isDone;
54. assign oData = D1;
55.
56. endmodule
以上内容是输出驱动声明。
rx_demo.v
rx_demo的连线布局请浏览回图13.6,至于核心操作的内容请浏览代码。
1. module rx_demo
2. (
3. input CLOCK, RESET,
4. input RXD,
5. output TXD
6. );
以上内容是相关的出入端声明。
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.
以上内容为是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
以上内容为相关的寄存器声明以及复位操作。第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.
以上内容为核心操作。步骤16~27是发送一帧数据的伪函数,笔者直接将TX功能整合进来。步骤0则是用来接收完成反馈,并且准备好发送输数,然后i指向伪函数。
55. assign TXD = rTXD; 56. 57. endmodule
以上内容是相关的输出驱动声明。编译完毕便下载程序,串口调试助手设置为 115200 波特率,8位数据位,奇偶校验位随便,停止位1。事后,每当串口调试助手想FPGA发送什么数据,FPGA也会回馈串口调试助手,不过仅限于一帧又有间隔的数据而已。目前是实验十三还不能支持数据流的接收,因为实验十三没有空间缓冲数据流 ... 此外,核心操作没发送一帧数据也有一定的时间耽误。
细节一:完整的个体模块
实验十三的RX功能模块已经是完整的个体模块,可以直接拿来调用。