姓名:趙宗明
學(xué)號(hào):19021211230
轉(zhuǎn)載自:https://blog.csdn.net/hxs13551803230/article/details/78836098
【嵌牛導(dǎo)讀】:FPGA:實(shí)現(xiàn)串行接口?RS232
【嵌牛鼻子】:FPGA ?串行接口RS232
【嵌牛提問】:串行接口是連接FPGA和PC機(jī)的一種簡單方式治宣。這個(gè)項(xiàng)目向大家展示了如果使用FPGA來創(chuàng)建RS-232收發(fā)器侮邀。
整個(gè)項(xiàng)目包括5個(gè)部分
1.?RS232是怎樣工作的
3.?發(fā)送模塊
4.?接收模塊
【嵌牛正文】:
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í)鐘信號(hào)。接收端必須有某種方式靠瞎,使之與接收數(shù)據(jù)同步比庄。
對(duì)于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ù)?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ù)乃俾蕦?duì)于接收端有多么重要。(串口發(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接口绰垂,對(duì)不起占业,你很不幸運(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每秒
物理層
電纜上的信號(hào)使用正負(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í)鐘頻率上(對(duì)于今天的標(biāo)準(zhǔn)的來說RS-232真是太慢了),這就意味著我們需要用一個(gè)較高的時(shí)鐘來分頻產(chǎn)生盡量接近于115200Hz的時(shí)鐘信號(hào)。
從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;
}
這段代碼會(huì)精確的以平均每?"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)?Verilog?使用?32?位的默認(rèn)結(jié)果,?但實(shí)際計(jì)算過程中的某些數(shù)據(jù)超過了32位述么,所以改變一種計(jì)算方法蝌数。
parameter BaudGeneratorInc = ((Baud<<(BaudGeneratorAccWidth-4))+(ClkFrequency>>5))/(ClkFrequency>>4);
這行程序也使得結(jié)果成為整數(shù),從而避免截?cái)唷?/p>
這就是整個(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"信號(hào)有效垢乙,此時(shí)“TxD_start”信號(hào)被忽略.
RS-232模塊的參數(shù)是固定的: 8位數(shù)據(jù), 2個(gè)停止位,?無奇偶校驗(yàn).
數(shù)據(jù)串行化
假設(shè)我們已經(jīng)有了一個(gè)115200波特的"BaudTick"信號(hào).
我們需要產(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é)識(shí)別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ī)制與接收到的輸入信號(hào)同步(接收端沒有辦法得到發(fā)送斷的時(shí)鐘)毡证。這里采用如下辦法电爹。
???? 1.為了確定新數(shù)據(jù)的到來,即檢測(cè)開始位料睛,我們使用幾倍于波特率的采樣時(shí)鐘對(duì)接收到的信號(hào)進(jìn)行采樣丐箩。
???? 2.一旦檢測(cè)到"開始位"摇邦,再將采樣時(shí)鐘頻率降為已知的發(fā)送端的波特率。
典型的過采樣時(shí)鐘頻率為接收到的信號(hào)的波特率的16倍屎勘,這里我們使用8倍的采樣時(shí)鐘施籍。當(dāng)波特率為115200時(shí),采樣時(shí)鐘為921600Hz概漱。(115200*8=921600)
假設(shè)我們已經(jīng)有了一個(gè)8倍于波特率的時(shí)鐘信號(hào)?"Baud8Tick"丑慎,其頻率為?921600Hz。
具體設(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)行濾波铛绰,這樣可以防止毛刺信號(hào)被誤認(rèn)為是開始信號(hào)。
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
一旦檢測(cè)到"開始位"产喉,使用如下的狀態(tài)機(jī)可以檢測(cè)出接收到每一位數(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è)移位寄存器來存儲(chǔ)接受到的數(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í)都會(huì)更新這8個(gè)GPout?的值障涯。
2.?將FPGA的8個(gè)引腳作為輸入(稱為“通用輸入”)。FPGA收到任何數(shù)據(jù)后膳汪,都會(huì)將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í)鐘頻率痹换。