1. 程式人生 > >基於FPGA的RS232序列介面的實現

基於FPGA的RS232序列介面的實現

3、序列通訊
資料以每次一位的方式傳輸;每條線用來傳輸一個方向的資料。由於計算機通常至少需要若干位資料,因此資料在傳送之前先“序列化”。通常是以8位資料為1組的。 。先發送最低有效位,最後傳送最高有效位。

4、非同步通訊
RS-232使用非同步通訊協議。也就是說資料的傳輸沒有時鐘訊號。接收端必須有某種方式,使之與接收資料同步。
對於RS-232來說,是這樣處理的:
  • 序列線纜的兩端事先約定好序列傳輸的引數(傳輸速度、傳輸格式等)
  • 當沒有資料傳輸的時候,傳送端向資料線上傳送"1"
  • 每傳輸一個位元組之前,傳送端先發送一個"0"來表示傳輸已經開始。這樣接收端便可以知道有資料到來了。
  • 開始傳輸後,資料以約定的速度和格式傳輸,所以接收端可以與之同步
  • 每次傳輸完成一個位元組之後,都在其後傳送一個停止位("1")

5、資料傳輸可以多快
資料的傳輸速度是用波特來描述的,亦即每秒鐘傳輸的資料位,例如1000波特表示每秒鐘傳輸100位元的資料, 或者說每個資料位持續1毫秒。
波特率不是隨意的,必須服從一定的標準,如果希望設計123456波特的RS-232介面,對不起,你很不幸運,這是不行的。常用的序列傳輸速率值包括以下幾種:
  • 1200 波特.
  • 9600 波特.
  • 38400 波特.
  • 115200 波特 (通常情況下是你可以使用的最高速度).
在115200 波特傳輸速度下, 每位資料持續 (1/115200) = 8.7μs. 如果傳輸8位資料,共持續 8 x 8.7μs = 69μs。但是每個位元組的傳輸又要求額外的“開始位”和“停止位”,所以實際上需要花費10 x 8.7μs = 87μs的時間。最大的有效資料傳輸率只能達到 11.5KBytes每秒。

在115200 波特傳輸速度下,一些使用了不好的晶片的計算機要求一個長的停止位(1.5或2位資料的長度),這使得最大傳輸速度降到大約10.5KBytes每秒

6、物理層
電纜上的訊號使用正負電壓的機制:
  • "1" 用 -10V 的電壓表示(或者在 -5V 與 -15V之間的電壓).
  • "0" 用 +10V 的電壓表示(或者在 5V 與 15V之間的電壓).
所以沒有資料傳輸的電纜上的電壓應該為-10V或-5到-10之間的某個電壓。

7、波特率發生器
這裡我們使用序列連線的最大速度115200波特,其他較慢的波特也很容易由此產生。
FPGA通常執行在遠高於115200Hz的時鐘頻率上(對於今天的標準的來說RS-232真是太慢了),這就意味著我們需要用一個較高的時鐘來分頻產生儘量接近於115200Hz的時鐘訊號。

從1.8432MHz的時鐘產生
通常RS-232晶片使用1.8432MHz的時鐘,以為這個時鐘很容易產生標準的波特率,所以我們假設已經擁有了一個這樣的時鐘源。
只需要將 1.8432MHz 16分頻便可得到 115200Hz的時鐘,多方便啊!
reg [3:0] BaudDivCnt;
always @(posedge clk) BaudDivCnt <= BaudDivCnt + 1;
wire BaudTick = (BaudDivCnt==15);
所以 "BaudTick" 每16個時鐘週期需要置位一次,從而從1.8432MHz的時鐘得到115200Hz的時鐘。

從任意頻率產生
早期的發生器假設使用1.8432MHz的時鐘。但如果我們使用2MHz的時鐘怎麼辦呢?要從2MHz的時鐘得到 115200Hz,需要將時鐘 "17.361111111..." 分頻,並不是一個整數。我的解決辦法是有時候17分頻,有時候18分頻,使得整體的分頻比保持在 "17.361111111"。這是很容易做到的。
下面是實現這個想法的C語言程式碼:
while(1) // 死迴圈
{
acc += 115200;
if(acc >=2000000) printf("*"); else printf(" ");
acc %= 2000000;
}
這段程式碼會精確的以平均每 "17.361111111..." 個時鐘間隔打印出一個"*"。
為了從FPGA得到同樣的效果,考慮到序列介面可以容忍一定的波特率誤差,所以即使我們使用17.3或者17.4這樣的分頻比也是沒有關係的。

8、FPGA波特率發生器
我們希望2000000是2的整數冪,但很可惜,它不是。所以我們改變分頻比,"2000000/115200" 約等於 "1024/59" = 17.356. 這跟我們要求的分頻比很接近,並且使得在FPGA上實現起來相當有效。
//10 位的累加器 ([9:0]), 1位進位輸出 ([10])
reg [10:0] acc; //一共11位!
always @(posedge clk)
acc <= acc[9:0] + 59; //我們使用上一次結果的低10位,但是保留11位結果
wire BaudTick = acc[10]; //第11位作為進位輸出
使用 2MHz 時鐘, "BaudTick" 為 115234 波特, 跟理想的115200波特存在 0.03% 的誤差。

9、引數化的FPGA波特率發生器
前面的設計我們使用的是10位的累加器,如果時鐘頻率提高的話,需要更多的位數。
下面是一個使用 25MHz 時鐘和 16 位累加器的設計,該設計是引數化的,所以很容易根據具體情況修改。
parameter ClkFrequency = 25000000; // 25MHz
parameter Baud = 115200;
parameter BaudGeneratorAccWidth = 16;
parameter BaudGeneratorInc = (Baud<<BaudGeneratorAccWidth)/ClkFrequency;
reg [BaudGeneratorAccWidth:0] BaudGeneratorAcc;
always @(posedge clk)
BaudGeneratorAcc <= BaudGeneratorAcc[BaudGeneratorAccWidth-1:0] + BaudGeneratorInc;
wire BaudTick = BaudGeneratorAcc[BaudGeneratorAccWidth];
上面的設計中存在一個錯誤: "BaudGeneratorInc"的計算是錯誤的, 因為 Verilog 使用 32 位的預設結果, 但實際計算過程中的某些資料超過了32位,所以改變一種計算方法。
parameter BaudGeneratorInc = ((Baud<<(BaudGeneratorAccWidth-4))+(ClkFrequency>>5))/(ClkFrequency>>4);
這行程式也使得結果成為整數,從而避免截斷。
這就是整個的設計方法了。
現在我們已經得到了足夠精確的波特率,可以繼續設計序列接收和傳送模組了。
RS-232傳送模組

10、資料序列化
假設我們已經有了一個115200波特的"BaudTick"訊號.
我們需要產生開始位、8位資料以及停止位。
用狀態機來實現看起來比較合適。
reg [3:0] state;
always @(posedge clk)
case(state)
4'b0000: if(TxD_start) state <= 4'b0100;
4'b0100: if(BaudTick) state <= 4'b1000; // 開始位
4'b1000: if(BaudTick) state <= 4'b1001; // bit 0
4'b1001: if(BaudTick) state <= 4'b1010; // bit 1
4'b1010: if(BaudTick) state <= 4'b1011; // bit 2
4'b1011: if(BaudTick) state <= 4'b1100; // bit 3
4'b1100: if(BaudTick) state <= 4'b1101; // bit 4
4'b1101: if(BaudTick) state <= 4'b1110; // bit 5
4'b1110: if(BaudTick) state <= 4'b1111; // bit 6
4'b1111: if(BaudTick) state <= 4'b0001; // bit 7
4'b0001: if(BaudTick) state <= 4'b0010; // 停止位1
4'b0010: if(BaudTick) state <= 4'b0000; // 停止位2
default: if(BaudTick) state <= 4'b0000;
endcase
注意看這個狀態機是怎樣實現當"TxD_start"有效就開始,但只在"BaudTick"有效的時候才轉換狀態的。.
現在,我們只需要產生"TxD"輸出即可.
reg muxbit;
always @(state[2:0])
case(state[2:0])
0: muxbit <= TxD_data[0];
1: muxbit <= TxD_data[1];
2: muxbit <= TxD_data[2];
3: muxbit <= TxD_data[3];
4: muxbit <= TxD_data[4];
5: muxbit <= TxD_data[5];
6: muxbit <= TxD_data[6];
7: muxbit <= TxD_data[7];
endcase
//將開始位、資料以及停止位結合起來
assign TxD = (state<4) | (state[3] & muxbit);