FPGA:實(shí)現(xiàn)串行接口 RS232
串行接口(RS-232)
串行接口是連接FPGA和PC機(jī)的一種簡單方式,。這個(gè)項(xiàng)目向大家展示了如果使用FPGA來創(chuàng)建RS-232收發(fā)器,。
整個(gè)項(xiàng)目包括5個(gè)部分
RS-232接口是怎樣工作的
作為標(biāo)準(zhǔn)設(shè)備,,大多數(shù)的計(jì)算機(jī)都有1到2個(gè)RS-232串口,。
特性
RS-232有下列特性:
·
使用9針的"DB-9"插頭(舊式計(jì)算機(jī)使用25針的"DB-25"插頭).
·
允許全雙工的雙向通訊(也就是說計(jì)算機(jī)可以在接收數(shù)據(jù)的同時(shí)發(fā)送數(shù)據(jù)).
·
最大可支持的傳輸速率為10KBytes/s.
DB-9插頭
你可能已經(jīng)在你的計(jì)算機(jī)背后見到過這種插頭
它一共有9個(gè)引腳,,但是最重要的3個(gè)引腳是:
·
引腳2: RxD (接收數(shù)據(jù)).
·
引腳3: TxD (發(fā)送數(shù)據(jù)).
·
引腳5: GND (地).
僅使用3跟電纜,,你就可以發(fā)送和接收數(shù)據(jù).
串行通訊
數(shù)據(jù)以每次一位的方式傳輸,;每條線用來傳輸一個(gè)方向的數(shù)據(jù)。由于計(jì)算機(jī)通常至少需要若干位數(shù)據(jù),,因此數(shù)據(jù)在發(fā)送之前先“串行化”,。通常是以8位數(shù)據(jù)為1組的。,。先發(fā)送最低有效位,,最后發(fā)送最高有效位。
異步通訊
RS-232使用異步通訊協(xié)議,。也就是說數(shù)據(jù)的傳輸沒有時(shí)鐘信號,。接收端必須有某種方式,使之與接收數(shù)據(jù)同步,。
對于RS-232來說,,是這樣處理的:
1.
串行線纜的兩端事先約定好串行傳輸?shù)膮?shù)(傳輸速度、傳輸格式等)
2.
當(dāng)沒有數(shù)據(jù)傳輸?shù)臅r(shí)候,,發(fā)送端向數(shù)據(jù)線上發(fā)送"1"
3.
每傳輸一個(gè)字節(jié)之前,,發(fā)送端先發(fā)送一個(gè)"0"來表示傳輸已經(jīng)開始。這樣接收端便可以知道有數(shù)據(jù)到來了,。
4.
開始傳輸后,數(shù)據(jù)以約定的速度和格式傳輸,,所以接收端可以與之同步
5.
每次傳輸完成一個(gè)字節(jié)之后,,都在其后發(fā)送一個(gè)停止位("1")
讓我們來看看0x55是如何傳輸?shù)?/span>:01010101
0x55的二進(jìn)制表示為:01010101。
但是由于先發(fā)送的是最低有效位,,所以發(fā)送序列是這樣的: 1-0-1-0-1-0-1-0.
下面是另外一個(gè)例子 :
傳輸?shù)臄?shù)據(jù)為0xC4,,你能看出來嗎?11000100
從圖中很難看出來所傳輸?shù)臄?shù)據(jù),這也說明了事先知道傳輸?shù)乃俾蕦τ诮邮斩擞卸嗝粗匾?。?/span>串口發(fā)送的開始位為0,,結(jié)束位為1,,空閑line是為高)
數(shù)據(jù)傳輸可以多快?
數(shù)據(jù)的傳輸速度是用波特來描述的,亦即每秒鐘傳輸?shù)臄?shù)據(jù)位,,例如1000波特表示每秒鐘傳輸100比特的數(shù)據(jù),
或者說每個(gè)數(shù)據(jù)位持續(xù)1毫秒(1/1000) = 1ms,。
波特率不是隨意的,必須服從一定的標(biāo)準(zhǔn),,如果希望設(shè)計(jì)123456波特的RS-232接口,,對不起,你很不幸運(yùn),,這是不行的,。常用的串行傳輸速率值包括以下幾種:
·
1200 波特.
·
9600 波特.
·
38400 波特.
·
115200 波特 (通常情況下是你可以使用的最高速度).
在115200
波特傳輸速度下,
每位數(shù)據(jù)持續(xù) (1/115200) = 8.7μs.
如果傳輸8位數(shù)據(jù),共持續(xù)
8 x 8.7μs = 69μs。但是每個(gè)字節(jié)的傳輸又要求額外的“開始位”和“停止位”,所以實(shí)際上需要花費(fèi)10
x 8.7μs = 87μs的時(shí)間,。最大的有效數(shù)據(jù)傳輸率只能達(dá)到 11.5KBytes每秒,。
在115200
波特傳輸速度下,一些使用了不好的芯片的計(jì)算機(jī)要求一個(gè)長的停止位(1.5或2位數(shù)據(jù)的長度),這使得最大傳輸速度降到大約10.5KBytes每秒
物理層
電纜上的信號使用正負(fù)電壓的機(jī)制:
·
"1" 用 -10V
的電壓表示(或者在 -5V
與 -15V之間的電壓).
·
"0" 用 +10V
的電壓表示(或者在 5V
與 15V之間的電壓).
所以沒有數(shù)據(jù)傳輸?shù)碾娎|上的電壓應(yīng)該為-10V或-5到-10之間的某個(gè)電壓,。
波特率發(fā)生器
這里我們使用串行連接的最大速度115200波特,,其他較慢的波特也很容易由此產(chǎn)生。
FPGA通常運(yùn)行在遠(yuǎn)高于115200Hz的時(shí)鐘頻率上(對于今天的標(biāo)準(zhǔn)的來說RS-232真是太慢了),,這就意味著我們需要用一個(gè)較高的時(shí)鐘來分頻產(chǎn)生盡量接近于115200Hz的時(shí)鐘信號,。
從1.8432MHz的時(shí)鐘產(chǎn)生
通常RS-232芯片使用1.8432MHz的時(shí)鐘,以為這個(gè)時(shí)鐘很容易產(chǎn)生標(biāo)準(zhǔn)的波特率,,所以我們假設(shè)已經(jīng)擁有了一個(gè)這樣的時(shí)鐘源,。
只需要將 1.8432MHz 16分頻便可得到
115200Hz的時(shí)鐘,多方便??!
reg [3:0] BaudDivCnt;(1843200/16=115200
always @(posedge clk) BaudDivCnt <= BaudDivCnt + 1;
wire BaudTick = (BaudDivCnt==15);
所以 "BaudTick"
每16個(gè)時(shí)鐘周期需要置位一次,從而從1.8432MHz的時(shí)鐘得到115200Hz的時(shí)鐘,。
從任意頻率產(chǎn)生
早期的發(fā)生器假設(shè)使用1.8432MHz的時(shí)鐘,。但如果我們使用2MHz的時(shí)鐘怎么辦呢?要從2MHz的時(shí)鐘得到
115200Hz,,需要將時(shí)鐘 "17.361111111..."
分頻,,并不是一個(gè)整數(shù)。我的解決辦法是有時(shí)候17分頻,,有時(shí)候18分頻,,使得整體的分頻比保持在
"17.361111111"。這是很容易做到的,。
下面是實(shí)現(xiàn)這個(gè)想法的C語言代碼:
while(1) //
死循環(huán)
{
acc += 115200;
if(acc >=2000000) printf("*"); else printf(" ");
acc %= 2000000;
}
這段代碼會精確的以平均每 "17.361111111..."
個(gè)時(shí)鐘間隔打印出一個(gè)"*",。
為了從FPGA得到同樣的效果,考慮到串行接口可以容忍一定的波特率誤差,所以即使我們使用17.3或者17.4這樣的分頻比也是沒有關(guān)系的,。
FPGA波特率發(fā)生器
我們希望2000000是2的整數(shù)冪,,但很可惜,它不是,。所以我們改變分頻比,,"2000000/115200"
約等于 "1024/59" = 17.356.
這跟我們要求的分頻比很接近,并且使得在FPGA上實(shí)現(xiàn)起來相當(dāng)有效,。
//10
位的累加器 ([9:0]), 1位進(jìn)位輸出 ([10])
reg [10:0] acc; //一共11位!
always @(posedge clk)
acc <= acc[9:0] + 59; //我們使用上一次結(jié)果的低10位,,但是保留11位結(jié)果
wire BaudTick = acc[10]; //第11位作為進(jìn)位輸出,這里的方法用的非常好,可以作為分頻始終來用
使用 2MHz
時(shí)鐘, "BaudTick"
為 115234
波特,
跟理想的115200波特存在 0.03%
的誤差,。
參數(shù)化的FPGA波特率發(fā)生器
前面的設(shè)計(jì)我們使用的是10位的累加器,,如果時(shí)鐘頻率提高的話,需要更多的位數(shù),。
下面是一個(gè)使用 25MHz
時(shí)鐘和 16
位累加器的設(shè)計(jì),,該設(shè)計(jì)是參數(shù)化的,所以很容易根據(jù)具體情況修改,。
parameter ClkFrequency = 25000000; // 25MHz FPGA的工作頻率
parameter Baud = 115200;
parameter BaudGeneratorAccWidth = 16;
parameter BaudGeneratorInc = (Baud<<BaudGeneratorAccWidth)/ClkFrequency;//左移的話意味著乘以2的幾次方
reg [BaudGeneratorAccWidth:0] BaudGeneratorAcc; //留出一位用于分頻進(jìn)位用的
always @(posedge clk)
BaudGeneratorAcc <= BaudGeneratorAcc[BaudGeneratorAccWidth-1:0] + BaudGeneratorInc;
wire BaudTick = BaudGeneratorAcc[BaudGeneratorAccWidth];//這個(gè)式子就是我們分頻的結(jié)果,,達(dá)到了由25MHZ分頻到115200HZ的效果
上面的設(shè)計(jì)中存在一個(gè)錯(cuò)誤: "BaudGeneratorInc"的計(jì)算是錯(cuò)誤的,
因?yàn)?/span> Verilog
使用 32
位的默認(rèn)結(jié)果,
但實(shí)際計(jì)算過程中的某些數(shù)據(jù)超過了32位,所以改變一種計(jì)算方法,。
parameter BaudGeneratorInc = ((Baud<<(BaudGeneratorAccWidth-4))+(ClkFrequency>>5))/(ClkFrequency>>4);
這行程序也使得結(jié)果成為整數(shù),,從而避免截?cái)唷?/span>
這就是整個(gè)的設(shè)計(jì)方法了。
現(xiàn)在我們已經(jīng)得到了足夠精確的波特率,,可以繼續(xù)設(shè)計(jì)串行接收和發(fā)送模塊了,。
RS-232發(fā)送模塊
下面是我們所想要實(shí)現(xiàn)的:
它應(yīng)該能像這樣工作:
·
發(fā)送器接收8位的數(shù)據(jù),并將其串行輸出,。("TxD_start"置位后開始傳輸).
·
當(dāng)有數(shù)傳輸?shù)臅r(shí)候,,使"busy"信號有效,此時(shí)“TxD_start”信號被忽略.
RS-232模塊的參數(shù)是固定的: 8位數(shù)據(jù),
2個(gè)停止位,
無奇偶校驗(yàn).
數(shù)據(jù)串行化
假設(shè)我們已經(jīng)有了一個(gè)115200波特的"BaudTick"信號.
我們需要產(chǎn)生開始位,、8位數(shù)據(jù)以及停止位,。
用狀態(tài)機(jī)來實(shí)現(xiàn)看起來比較合適。
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
注意看這個(gè)狀態(tài)機(jī)是怎樣實(shí)現(xiàn)當(dāng)"TxD_start"有效就開始,但只在"BaudTick"有效的時(shí)候才轉(zhuǎn)換狀態(tài)的,。.
現(xiàn)在,,我們只需要產(chǎ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
//將開始位、數(shù)據(jù)以及停止位結(jié)合起來
assign TxD = (state<4) | (state[3] & muxbit);
RS232接收模塊
下面是我們想要實(shí)現(xiàn)的模塊:
我們的設(shè)計(jì)目的是這樣的:
1.當(dāng)RxD線上有數(shù)據(jù)時(shí),,接收模塊負(fù)責(zé)識別RxD線上的數(shù)據(jù)
2.當(dāng)收到一個(gè)字節(jié)的數(shù)據(jù)時(shí),,鎖存接收到的數(shù)據(jù)到"data"總線,并使"data_ready"有效一個(gè)周期,。
注意:只有當(dāng)"data_ready"有效時(shí),"data"總線的數(shù)據(jù)才有效,其他的時(shí)間里不要使用"data"總線上的數(shù)據(jù),,因?yàn)樾碌臄?shù)據(jù)可能已經(jīng)改變了其中的部分?jǐn)?shù)據(jù),。
過采樣
異步接收機(jī)必須通過一定的機(jī)制與接收到的輸入信號同步(接收端沒有辦法得到發(fā)送斷的時(shí)鐘)。這里采用如下辦法,。
1.為了確定新數(shù)據(jù)的到來,,即檢測開始位,我們使用幾倍于波特率的采樣時(shí)鐘對接收到的信號進(jìn)行采樣,。
2.一旦檢測到"開始位",,再將采樣時(shí)鐘頻率降為已知的發(fā)送端的波特率。
典型的過采樣時(shí)鐘頻率為接收到的信號的波特率的16倍,,這里我們使用8倍的采樣時(shí)鐘,。當(dāng)波特率為115200時(shí),采樣時(shí)鐘為921600Hz,。(115200*8=921600)
假設(shè)我們已經(jīng)有了一個(gè)8倍于波特率的時(shí)鐘信號
"Baud8Tick",,其頻率為 921600Hz。
具體設(shè)計(jì)
首先,,接受到的"RxD"信號與我們的時(shí)鐘沒有任何關(guān)系,,所以采用兩個(gè)D觸發(fā)器對其進(jìn)行過采樣,并且使之我我們的時(shí)鐘同步,。
reg [1:0] RxD_sync;
always @(posedge clk) if(Baud8Tick) RxD_sync <= {RxD_sync[0], RxD};
首先我們對接收到的數(shù)據(jù)進(jìn)行濾波,,這樣可以防止毛刺信號被誤認(rèn)為是開始信號。
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ī)可以檢測出接收到每一位數(shù)據(jù),。
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
注意,我們使用了"next_bit"
來遍歷所有數(shù)據(jù)位,。
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);
最后我們使用一個(gè)移位寄存器來存儲接受到的數(shù)據(jù),。
reg [7:0] RxD_data;
always @(posedge clk) if(Baud8Tick && next_bit && state[3]) RxD_data <= {RxD_bit, RxD_data[7:1]};
怎樣使用發(fā)送和接收模塊
這個(gè)設(shè)計(jì)似的我們可以通過計(jì)算機(jī)的串行口來控制FPGA的幾個(gè)引腳。
具體來說,,該設(shè)計(jì)完成以下功能,。
1.
將FPGA的8個(gè)引腳作為輸出(稱為“通用輸出”)。
FPGA收到任何數(shù)據(jù)時(shí)都會更新這8個(gè)GPout
的值,。
2.
將FPGA的8個(gè)引腳作為輸入(稱為“通用輸入”),。FPGA收到任何數(shù)據(jù)后,都會將GPin上的數(shù)值通過串行口發(fā)送出去,。
通用輸出可以用來通過計(jì)算機(jī)遠(yuǎn)程控制任何東西,,例如FPGA板上的LED,甚至可以再添加一個(gè)繼電器來控制咖啡機(jī),。
module serialfun(clk, RxD, TxD, GPout, GPin);
input clk;
input RxD;
output TxD;
output [7:0] GPout;
input [7:0] GPin;
///////////////////////////////////////////////////
wire RxD_data_ready;
wire [7:0] RxD_data;
async_receiver deserializer(.clk(clk), .RxD(RxD), .RxD_data_ready(RxD_data_ready), .RxD_data(RxD_data));
reg [7:0] GPout;
always @(posedge clk) if(RxD_data_ready) GPout <= RxD_data;
///////////////////////////////////////////////////
async_transmitter serializer(.clk(clk), .TxD(TxD), .TxD_start(RxD_data_ready), .TxD_data(GPin));
endmodule
記得包含異步發(fā)送和接收模塊的設(shè)計(jì)文件,,并更新里面的時(shí)鐘頻率。
|
|