實驗十三:串口模塊② — 接收
我們在實驗十二實現了串口發送,然而這章實驗則要實現串口接收 ... 在此,筆者也會使用其它思路實現串口接收。
圖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功能模塊已經是完整的個體模塊,可以直接拿來調用。