FPGA串行接口 1 - RS-232 串行接口的工作原理
串行接口RS-232是將FPGA連接到PC的簡(jiǎn)單方法。我們只需要一個(gè)發(fā)射器和接收器模塊。
異步發(fā)送器
它通過串行化要發(fā)送的數(shù)據(jù)來創(chuàng)建信號(hào)“ TxD”。
異步接收器
它從FPGA外部獲取信號(hào)“ RxD”,并對(duì)其進(jìn)行“反序列化”,以便在FPGA內(nèi)部輕松使用。
RS-232接口具有以下特點(diǎn):
使用 9 針連接器“DB-9”(較舊的 PC 使用 25 針“DB-25”)。
允許雙向全雙工通信(PC可以同時(shí)發(fā)送和接收數(shù)據(jù))。
可以以大約 10KBytes/s 的最大速度進(jìn)行通信。
DB-9 連接器
您可能已經(jīng)在 PC 背面看到了此連接器。
它有 9 個(gè)引腳,但 3 個(gè)重要的引腳是:
引腳 2:RxD(接收數(shù)據(jù))。
引腳 3:TxD(傳輸數(shù)據(jù))。
引腳 5:GND(接地)。
只需使用 3 根電線,您就可以發(fā)送和接收數(shù)據(jù)。
數(shù)據(jù)通常由 8 位(我們稱之為字節(jié))的塊發(fā)送,并且是“序列化”的:首先發(fā)送 LSB(數(shù)據(jù)位 0),然后發(fā)送位 1,...最后是 MSB(第 7 位)。
異步通信
此接口使用異步協(xié)議。 這意味著沒有時(shí)鐘信號(hào)沿?cái)?shù)據(jù)傳輸。 接收器必須有一種方法可以將自身“計(jì)時(shí)”到輸入的數(shù)據(jù)位。
在 RS-232 的情況下,這是這樣完成的:
電纜的兩端事先就通信參數(shù)(速度、格式等)達(dá)成一致。這是在通信開始之前手動(dòng)完成的。
當(dāng)線路處于空閑狀態(tài)時(shí),發(fā)射器會(huì)發(fā)送“空閑”(=“1”)。
發(fā)送器在發(fā)送每個(gè)字節(jié)之前發(fā)送“start”(=“0”),以便接收器可以確定一個(gè)字節(jié)即將到來。
發(fā)送字節(jié)數(shù)據(jù)的 8 位。
發(fā)送器在每個(gè)字節(jié)后發(fā)送“stop”(=“1”)。
讓我們看看字節(jié)在傳輸時(shí)0x55的樣子:
字節(jié) 0x55 以二進(jìn)制形式01010101。
但是由于它首先傳輸 LSB(bit-0),因此該行的切換方式如下:1-0-1-0-1-0-1-0。
下面是另一個(gè)示例:
這里的數(shù)據(jù)是0xC4,你能看到它嗎?
這些位更難看到。 這說明了接收方知道數(shù)據(jù)以何種速度發(fā)送是多么重要。
我們發(fā)送數(shù)據(jù)的速度有多快?
速度以波特率為單位,即每秒可以發(fā)送多少位。 例如,1000 波特意味著每秒 1000 位,或者每個(gè)位持續(xù) <> 毫秒。
RS-232 接口的常見實(shí)現(xiàn)(如 PC 中使用的接口)不允許使用任何速度。 如果你想使用123456波特率,你就不走運(yùn)了。 你必須滿足于一些“標(biāo)準(zhǔn)”速度。常見值包括:
1200波特。
9600波特。
38400波特。
115200 波特(通常是你能做到的最快速度)。
在 115200 波特時(shí),每個(gè)比特持續(xù) (1/115200) = 8.7μs。 如果傳輸 8 位數(shù)據(jù),則持續(xù)時(shí)間為 8 x 8.7μs = 69μs。 但是每個(gè)字節(jié)都需要一個(gè)額外的起始位和停止位,因此實(shí)際上需要 10 x 8.7μs = 87μs。 這意味著最大速度為每秒 11.5KB。
在 115200 波特率下,一些帶有錯(cuò)誤芯片的 PC 需要一個(gè)“長(zhǎng)”停止位(1.5 或 2 位長(zhǎng)...),這使得最大速度降至每秒 10.5KB 左右。
物理層
電線上的信號(hào)使用正/負(fù)電壓方案。
“1”使用 -10V(或介于 -5V 和 -15V 之間)發(fā)送。
“0”使用+10V(或5V至15V之間)發(fā)送。
因此,空閑線路的電壓約為 -10V。
串行接口 2 - 波特發(fā)生器
在這里,我們希望以最大速度使用串行鏈路,即 115200 波特(較慢的速度也很容易生成)。 FPGA 通常以 MHz 的速度運(yùn)行,遠(yuǎn)高于 115200Hz(按照今天的標(biāo)準(zhǔn),RS-232 相當(dāng)慢)。 我們需要找到一種方法來生成(從FPGA時(shí)鐘)盡可能接近每秒115200次的“滴答聲”。
傳統(tǒng)上,RS-232芯片使用1.8432MHz時(shí)鐘,因?yàn)檫@使得生成標(biāo)準(zhǔn)波特頻率變得非常容易。 1.8432MHz 除以 16 得到 115200Hz。
假設(shè)FPGA時(shí)鐘信號(hào)運(yùn)行頻率為1.8432MHz
//我們創(chuàng)建一個(gè)4位計(jì)數(shù)器
reg [3:0] BaudDivCnt;always @(posedge clk) BaudDivCnt <= BaudDivCnt + 1; // count forever from 0 to 15
/ 以及每 16 個(gè)時(shí)鐘斷言一次的滴答信號(hào)(即每秒 115200 次)
wire BaudTick = (BaudDivCnt==15);這很容易。但是,如果你有一個(gè)1MHz的時(shí)鐘,而不是8432.2MHz,你會(huì)怎么做? 要從 115200MHz 時(shí)鐘生成 2Hz,我們需要將時(shí)鐘除以“17.361111111...” 不完全是一個(gè)整數(shù)。 解決方案是有時(shí)除以 17,有時(shí)除以 18,確保比率保持“17.361111111”。 這實(shí)際上很容易做到。
請(qǐng)看下面的“C”代碼:
while(1) // repeat forever{
acc += 115200;
if(acc>=2000000) printf("*"); else printf(" ");
acc %= 2000000;
}
它以精確的比例打印“*”,平均每“17.361111111...”循環(huán)一次。
為了在FPGA中有效地獲得相同的結(jié)果,我們依賴于這樣一個(gè)事實(shí),即串行接口可以容忍波特頻率發(fā)生器中幾%的誤差。
希望 2000000 是 2000000 的冪。 顯然 2000000 不是。 所以我們改變了比例...... 讓我們使用“115200/1024”= 59.17,而不是“356/10”。 這非常接近我們的理想比率,并實(shí)現(xiàn)了高效的 FPGA 實(shí)現(xiàn): 我們使用一個(gè) 59 位累加器,遞增 <>,每次累加器溢出時(shí)都會(huì)標(biāo)記一個(gè)刻度。
// let's assume the FPGA clock signal runs at 2.0000MHz// we use a 10-bit accumulator plus an extra bit for the accumulator carry-out
reg [10:0] acc; // 11 bits total!
// add 59 to the accumulator at each clock
always @(posedge clk)
acc <= acc[9:0] + 59; // use 10 bits from the previous accumulator result, but save the full 11 bits result
wire BaudTick = acc[10]; // so that the 11th bit is the accumulator carry-out
使用我們的 2MHz 時(shí)鐘,“BaudTick”每秒置位 115234 次,與理想的 0 相差 03.115200%。
參數(shù)化 FPGA 波特率發(fā)生器
以前的設(shè)計(jì)使用 10 位累加器,但隨著時(shí)鐘頻率的增加,需要更多的位。
這是一個(gè)具有 25MHz 時(shí)鐘和 16 位累加器的設(shè)計(jì)。 設(shè)計(jì)是參數(shù)化的,因此易于定制。
parameter ClkFrequency = 25000000; // 25MHzparameter 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];
最后一個(gè)實(shí)現(xiàn)問題: “BaudGeneratorInc”計(jì)算是錯(cuò)誤的,因?yàn)?Verilog 使用 32 位中間結(jié)果,并且計(jì)算超出了這個(gè)范圍。 更改該行,如下所示以獲得解決方法。
parameter BaudGeneratorInc = ((Baud<<(BaudGeneratorAccWidth-4))+(ClkFrequency>>5))/(ClkFrequency>>4);這條線還有一個(gè)額外的優(yōu)勢(shì),可以對(duì)結(jié)果進(jìn)行舍入而不是截?cái)唷?/p>
現(xiàn)在我們有了足夠精確的波特發(fā)生器,我們可以繼續(xù)使用 RS-232 發(fā)射器和接收器模塊。
串行接口 3 - RS-232 發(fā)送器
我們正在構(gòu)建一個(gè)具有固定參數(shù)的“異步發(fā)射器”:8 個(gè)數(shù)據(jù)位、2 個(gè)停止位、非奇偶校驗(yàn)。
它的工作原理是這樣的:
發(fā)送器在 FPGA 內(nèi)部獲取 8 位數(shù)據(jù)并將其串行化(從“TxD_start”信號(hào)置位時(shí)開始)。
“忙”信號(hào)在傳輸發(fā)生時(shí)被置位(在此期間忽略“TxD_start”信號(hào))。
序列化數(shù)據(jù)
要遍歷起始位、8 個(gè)數(shù)據(jù)位和停止位,狀態(tài)機(jī)似乎是合適的。
reg [3:0] state;
// the state machine starts when "TxD_start" is asserted, but advances when "BaudTick" is asserted (115200 times a second)
always @(posedge clk)
case(state)
4'b0000: if(TxD_start) state <= 4'b0100;
4'b0100: if(BaudTick) state <= 4'b1000; // start
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; // stop1
4'b0010: if(BaudTick) state <= 4'b0000; // stop2
default: if(BaudTick) state <= 4'b0000;
endcase
現(xiàn)在,我們只需要生成“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
// combine start, data, and stop bits together
assign TxD = (state<4) | (state[3] & muxbit);
串行接口 4 - RS-232 接收器
我們正在構(gòu)建一個(gè)“異步接收器”:
我們的實(shí)現(xiàn)是這樣工作的:
該模塊在收到 RxD 線時(shí)收集數(shù)據(jù)。
當(dāng)一個(gè)字節(jié)被接收到時(shí),它出現(xiàn)在“數(shù)據(jù)”總線上。一旦接收到一個(gè)完整的字節(jié),就會(huì)為一個(gè)時(shí)鐘置位“data_ready”。
請(qǐng)注意,“data”僅在斷言“data_ready”時(shí)有效。 其余時(shí)間,不要使用它,因?yàn)樾聰?shù)據(jù)可能會(huì)洗牌。
過采樣
異步接收器必須以某種方式與輸入信號(hào)保持同步(它通常無(wú)法訪問發(fā)射器使用的時(shí)鐘)。
為了確定新的數(shù)據(jù)字節(jié)何時(shí)到來,我們通過以波特率頻率的倍數(shù)對(duì)信號(hào)進(jìn)行過采樣來尋找“開始”位。
一旦檢測(cè)到“起始”位,我們以已知的波特率對(duì)線路進(jìn)行采樣,以獲取數(shù)據(jù)位。
接收器通常以波特率的 16 倍對(duì)輸入信號(hào)進(jìn)行過采樣。 我們?cè)谶@里使用了 8 次...... 對(duì)于 115200 波特,采樣率為 921600Hz。
假設(shè)我們有一個(gè)可用的“Baud8Tick”信號(hào),每秒斷言 921600 次。
設(shè)計(jì)
首先,傳入的“RxD”信號(hào)與我們的時(shí)鐘沒有關(guān)系。
我們使用兩個(gè)D觸發(fā)器對(duì)其進(jìn)行過采樣,并將其同步到我們的時(shí)鐘域。
reg [1:0] RxD_sync;
always @(posedge clk) if(Baud8Tick) RxD_sync <= {RxD_sync[0], RxD};
我們對(duì)數(shù)據(jù)進(jìn)行過濾,以便 RxD 線上的短尖峰不會(huì)與起始位混淆。
reg [1:0] RxD_cnt;
reg RxD_bit;
always @(posedge clk)
if(Baud8Tick)
begin
if(RxD_sync[1] && RxD_cnt!=2'b11) RxD_cnt <= RxD_cnt + 1;
else
if(~RxD_sync[1] && RxD_cnt!=2'b00) RxD_cnt <= RxD_cnt - 1;
if(RxD_cnt==2'b00) RxD_bit <= 0;
else
if(RxD_cnt==2'b11) RxD_bit <= 1;
end
狀態(tài)機(jī)允許我們?cè)跈z測(cè)到“開始”后檢查接收到的每個(gè)位。
reg [3:0] state;
always @(posedge clk)
if(Baud8Tick)
case(state)
4'b0000: if(~RxD_bit) state <= 4'b1000; // start bit found?
4'b1000: if(next_bit) state <= 4'b1001; // bit 0
4'b1001: if(next_bit) state <= 4'b1010; // bit 1
4'b1010: if(next_bit) state <= 4'b1011; // bit 2
4'b1011: if(next_bit) state <= 4'b1100; // bit 3
4'b1100: if(next_bit) state <= 4'b1101; // bit 4
4'b1101: if(next_bit) state <= 4'b1110; // bit 5
4'b1110: if(next_bit) state <= 4'b1111; // bit 6
4'b1111: if(next_bit) state <= 4'b0001; // bit 7
4'b0001: if(next_bit) state <= 4'b0000; // stop bit
default: state <= 4'b0000;
endcase
請(qǐng)注意,我們使用了“next_bit”信號(hào),從一個(gè)位到另一個(gè)位。
reg [2:0] bit_spacing;
always @(posedge clk)
if(state==0)
bit_spacing <= 0;
else
if(Baud8Tick)
bit_spacing <= bit_spacing + 1;
wire next_bit = (bit_spacing==7);
最后,移位寄存器收集數(shù)據(jù)位。
reg [7:0] RxD_data;
always @(posedge clk) if(Baud8Tick && next_bit && state[3]) RxD_data <= {RxD_bit, RxD_data[7:1]};
串行接口 5 - 如何使用 RS-232 發(fā)射器和接收器
此設(shè)計(jì)允許從 PC 控制幾個(gè) FPGA 引腳(通過 PC 的串行端口)。
它在FPGA(名為“GPout”的端口)上創(chuàng)建8個(gè)輸出。GPout由FPGA接收到的任何字符進(jìn)行更新。
FPGA 上還有 8 個(gè)輸入(名為“GPin”的端口)。每次FPGA接收到字符時(shí),都會(huì)發(fā)送GPin。
GP 輸出可用于從您的 PC 遠(yuǎn)程控制任何東西,可能是 LED 或咖啡機(jī)......
module serialGPIO(
input clk,
input RxD,
output TxD,
output reg [7:0] GPout, // general purpose outputs
input [7:0] GPin // general purpose inputs
);
wire RxD_data_ready;
wire [7:0] RxD_data;
async_receiver RX(.clk(clk), .RxD(RxD), .RxD_data_ready(RxD_data_ready), .RxD_data(RxD_data));
always @(posedge clk) if(RxD_data_ready) GPout <= RxD_data;
async_transmitter TX(.clk(clk), .TxD(TxD), .TxD_start(RxD_data_ready), .TxD_data(GPin));
endmodule
評(píng)論