UART(Universal Asynchronous Receiver Transmitter,通用非同步收發器)是一種非同步序列通訊協議,主要用於計算機和嵌入式系統之間的資料交換。
實現UART通訊的介面規範和匯流排標準包括RS-232、RS449、RS423和RS485等,介面標準規定了通訊標準的電氣特性、傳輸速率、連線特性和機械特性。
文章摘要:本篇文章目標設計一個格式為起始位+8位資料(無校驗)+停止位的串列埠收發,接收PC上位機RS232匯流排訊號後,重新打包轉發至PC端顯示(形成迴環),資料完整無錯碼情況。
關鍵詞: 非同步時鐘;亞穩態;非同步序列通訊;Verilog HDL
目標設計框圖:
【序列幀格式】
UART通訊使用兩條訊號線進行資料傳輸:傳送資料線(TX)和接收資料線(RX)。資料以資料幀的形式傳輸,每個資料幀由起始位、資料位(通常為5到9位)、校驗位(可選)和停止位(通常為1到2位)組成。
傳送方在傳送資料之前,會先傳送一個起始位(通常為低電平),然後按照設定的資料位和校驗位傳送資料,最後傳送一個或多個停止位(通常為高電平)以標識資料傳輸的結束。接收方在檢測到起始位後,會開始接收資料,直到檢測到停止位為止(起止式協議)。同時,接收方會根據約定的校驗方式對資料進行校驗。
1、起始位(Start Bit):通常是一個單獨的位,邏輯值為0(低電平),表示字元開始,接收端同步到傳送端的資料流。
2、資料位(Data Bits):幀的主體部分,包含傳輸資訊,資料位數量可以根據需要進行配置,常見的有5位、6位、7位、8位等。
3、校驗位(Parity Bit):可選位,校驗位可以是偶校驗(Even Parity)、奇校驗(Odd Parity)或無校驗(No Parity),偶校驗意味著資料位中1的個數(包括校驗位)應該是偶數;奇校驗則是奇數。
4、停止位(Stop Bits):標誌字元的結束,並允許接收器在接收下一個字元之前有時間復位。停止位的數量可以是1位、1.5位或2位。
5、空閒位(Idle Line):在兩個字元之間,線路通常保持在高電平狀態(邏輯值為1)稱為空閒線狀態,它允許接收端在字元之間有時間來檢測和響應線路狀態的變化。
資料傳輸中,波特率(Baud rate)是一個重要的引數,表示單位時間內傳輸的碼元符號的個數(通常指二進位制位)。波特率並不直接等同於位元率(bit rate),因為每個碼元符號可能包含多個位元。但在許多情況下,特別是在序列通訊中,一個碼元符號就是一個位元,此時波特率就等於位元率。
傳送速率為960字元/秒,而每個字元為10位(可能是1個起始位、8個資料位和1個停止位)。總的二進位制位數傳送速率是960字元/秒 × 10位/字元 = 9600位/秒,所以波特率就是9600。
【非同步傳輸存在亞穩態】
在數位電路設計中同步建立至關重要,非同步時鐘域之間的訊號通訊不可避免存在亞穩態問題。UART接收過程,起始位的檢測尤為關鍵,錯誤的起始位檢測可能導致整個資料包的接收失敗。為了避免因亞穩態導致的取樣錯誤和電路故障,設計者必須在介面處採取可靠的同步化措施。
亞穩態:簡單來說,亞穩態是指觸發器(Flip-Flop)或其他數位電路元件無法在某個規定的時間段內達到一個可確認的穩定狀態。
產生原因:主要由於違反了觸發器的建立和保持時間(Setup and Hold Time)要求。在時鐘上升沿前後的特定時間視窗內,如果資料輸入埠上的資料發生變化,就會產生時序違規,從而導致亞穩態的出現,如下圖。
表現特徵:在穩定期間,觸發器可能輸出一些中間級電平,或者處於振盪狀態。並且,這種無用的輸出電平可以沿訊號通道上的各個觸發器級聯式傳播下去,導致整個系統的不穩定。
減少亞穩態影響:
亞穩態震盪時間(Tmet)關係到後級暫存器的採集穩定問題,Tmet影響因素包括器件的生產工藝、溫度、環境等。
當Tmet1時間長到大於一個取樣週期後,那第二級暫存器就會採集到亞穩態,但是從第二級暫存器輸出的訊號就是相對穩定的了。由於暫存器本身就有減小Tmet時間讓資料快速穩定的作用,第二級暫存器的Tmet2的持續時間繼續延長到大於一個取樣週期這種情況雖然會存在,但是其機率是極小的。在第三級暫存器時,資料傳輸幾乎達到穩態。
單位元訊號從慢速時鐘域同步到快速時鐘域需要使用打兩拍的方式消除亞穩態。第一級暫存器產生亞穩態並經過自身後可以穩定輸出的機率為70%~80%,第二級暫存器可以穩定輸出的機率為99%左右,再多加暫存器的級數改善效果就不明顯了,Verilog HDL程式如下。
always @(posedge sys_clk or negedge sys_rst)begin
if(!sys_rst) rx_reg1 <= 1'b1;
else rx_reg1 <= rx;
end
always @(posedge sys_clk or negedge sys_rst)begin
if(!sys_rst) rx_reg2 <= 1'b1;
else rx_reg2 <= rx_reg1;
end
always @(posedge sys_clk or negedge sys_rst)begin
if(!sys_rst) rx_reg3 <= 1'b1;
else rx_reg3 <= rx_reg2;
end
【接收端處理邏輯】
在本篇中,通訊格式:起始位+8位資料+無校檢+1位停止位。sys_clk
為系統時鐘(在這裡僅作為時鐘參考)設定為50Mhz(20ns),非同步通訊的波特率設定為9600Baud,則波特率計數器Baud_cnt
最大值 = 1/9600x50_000_000 ≈ 5208。
bit_flag
和 bit_cnt
:位標誌和位計數器,用於跟蹤當前接收到的位的數量和狀態。rx_reg1
, rx_reg2
, rx_reg3
:接收暫存器,用於暫存接收到的資料,rx_flag
和 po_flag
:接收和傳送的標誌,用於指示接收或傳送過程的狀態。rx
和 rx_data_out
:接收到的資料和輸出資料。具體UART接收端時序圖如下。
資料流:當start
訊號被觸發時,UART開始接收資料,並使能work_en
訊號,資料位(XData[0]
到 XData[7]
)被連續接收並儲存在rx_reg1
、rx_reg2
、rx_reg3
等暫存器中。在資料接收過程中,Baud_cnt[12:0]
、bit_flag
和 bit_cnt
用於確保資料以正確的波特率接收,並跟蹤當前位的狀態。一旦所有資料位和停止位(Xstop
)都被接收,UART將rx_data_out
設定為接收到的資料,並可能設定rx_flag
以指示資料已準備好。
依據時序圖,接續完成其他型別訊號時序程式邏輯:
always @(posedge sys_clk or negedge sys_rst)begin//處理start_flag邏輯
if(!sys_rst) start_flag <= 1'b0;
else if((rx_reg3 == 1'b1)&&(rx_reg2 == 1'b0)&&(work_en == 1'b0))start_flag <= 1'b1;
else start_flag <= 1'b0;
end
always @(posedge sys_clk or negedge sys_rst)begin//處理work_en邏輯
if(!sys_rst) work_en <= 1'b0;
else if(start_flag) work_en <= 1'b1;
else if((bit_cnt == 4'd8)&&(bit_flag == 1'b1)) work_en <= 1'b0;
else work_en <= work_en;
end
always @(posedge sys_clk or negedge sys_rst)begin//處理baud_cnt邏輯
if(!sys_rst) baud_cnt <= 13'd0;
else if((baud_cnt == BAUD_CNT_MAX - 1'b1)||(work_en == 1'b0))baud_cnt <= 13'd0;
else baud_cnt <= baud_cnt + 1'b1;
end
always @(posedge sys_clk or negedge sys_rst)begin//處理bit_flag邏輯
if(!sys_rst) bit_flag <= 1'b0;
else if(baud_cnt == BAUD_CNT_MAX/2 - 1'b1) bit_flag <= 1'b1;
else bit_flag <= 1'b0;
end
always @(posedge sys_clk or negedge sys_rst)begin//處理bit_cnt邏輯
if(!sys_rst) bit_cnt <= 4'd0;
else if((bit_cnt == 4'd8)&&(bit_flag == 1'b1)) bit_cnt <= 4'd0;
else if(bit_flag) bit_cnt <= bit_cnt + 1'b1;
end
always @(posedge sys_clk or negedge sys_rst)begin//處理rx_data邏輯
if(!sys_rst) rx_data <= 8'b0;
else if((bit_cnt >= 4'd1)&&(bit_cnt<= 4'd8)&&(bit_flag))
rx_data <= {rx_reg3,rx_data[7:1]};
end
always @(posedge sys_clk or negedge sys_rst)begin//處理rx_flag邏輯
if(!sys_rst) rx_flag <= 1'd0;
else if((bit_cnt == 4'd8)&&(bit_flag)) rx_flag <= 1'b1;
else rx_flag <= 1'd0;
end
always @(posedge sys_clk or negedge sys_rst)begin//處理po_data邏輯
if(!sys_rst) po_data <= 8'd0;
else if(rx_flag) po_data <= rx_data;
end
always @(posedge sys_clk or negedge sys_rst)begin//處理po_flag邏輯
if(!sys_rst) po_flag <= 1'b0;
else po_flag <= rx_flag;
end
完備後,編寫了一個簡單的模擬程式,驗證接收的時序情況。從下圖可知,三級暫存器依次往後延遲了一個時鐘週期,即20ns,這實現了目標期望,用於減小亞穩態影響,保證時序準確。
再看接收rx訊號線資料的處理情況,待一輪字元資料處理完成後,po_data
得到了正確的序列輸入資料。波特率計數器在計數到13’d5027後歸零,等待使能訊號拉高。
【傳送端處理邏輯】
較於接收端,傳送端邏輯比較簡單。pi_data
:輸入的8位資料訊號,pi_flag
:輸入訊號標誌。Baud_cnt
為波特率計數器。輸入傳送標誌為高電平,傳送端將準備的資料,按位計數輸出到序列訊號匯流排tx
,依次拉低訊號線,傳送起始標誌+8位資料訊號。位元計數器計數到4'd9拉低使能線,表示單個字元資料輸出完畢,拉高訊號線標誌停止至空閒狀態。
always @(posedge sys_clk or negedge sys_rst)begin//處理work_en邏輯
if(!sys_rst) work_en <= 1'b0;
else if((bit_cnt == 4'd9)&&bit_flag) work_en <= 1'b0;
else if(pi_flag) work_en <= 1'b1;
end
always @(posedge sys_clk or negedge sys_rst)begin//處理baud_cnt邏輯
if(!sys_rst) baud_cnt <= 13'b0;
else if((baud_cnt == BAUD_CNT_MAX - 1'b1)||(!work_en)) baud_cnt <= 13'b0;
else if(work_en) baud_cnt <= baud_cnt + 1'b1;
end
always @(posedge sys_clk or negedge sys_rst)begin//處理bit_cnt邏輯
if(!sys_rst) bit_cnt <= 4'd0;
else if((bit_cnt == 4'd9)&&(bit_flag == 1'b1)) bit_cnt <= 4'd0;
else if((work_en == 1'b1)&&(bit_flag == 1'b1)) bit_cnt <= bit_cnt + 1'b1;
end
always @(posedge sys_clk or negedge sys_rst)begin//處理bit_flag邏輯
if(!sys_rst) bit_flag <= 1'b0;
else if(baud_cnt == 13'd1) bit_flag <= 1'd1;
else bit_flag <= 1'b0;
end
always @(posedge sys_clk or negedge sys_rst)begin//處理tx傳送邏輯
if(!sys_rst) tx <= 1'b0;
else if(bit_flag == 1'b1)begin
case(bit_cnt)
0 :tx <= 1'b0;
1 :tx <= pi_data[0];
2 :tx <= pi_data[1];
3 :tx <= pi_data[2];
4 :tx <= pi_data[3];
5 :tx <= pi_data[4];
6 :tx <= pi_data[5];
7 :tx <= pi_data[6];
8 :tx <= pi_data[7];
9 :tx <= 1'b1;
default: tx<= 1'b1;
endcase
end
end
完備後,編寫了模擬程式,驗證傳送的時序情況。檢測到輸入標誌為高電平,work_en
使能有效,波特率計數器開始計數,位元位計數器待其計數到13'd1時計數加1,這樣避免位元錯判。
從下面模擬圖可以看到,tx傳送輸出的序列訊號時序是與pi_data
保持一致的。
【整體驗證UART】
首先,建立五組任意字元資料,經UART傳送端向UART接收端轉發。透過模擬程式進行分析,由於接收端與PC源資料相比,對資料的處理要遲後一個字元週期,即5208x20x10ns,從下圖可得到,資料處理序列資料tran_data
後保持正確,並且輸出資料out_data
與源資料保持一致,即可驗證模組設計暫時是沒有大問題。
最後,將UART接收端和傳送端模組例項化為文章開頭的設計框架。在PC端傳送任意三組資料後,如下圖COM11埠接收資料並且正確,設計到此驗證成功,滿足期望。
文獻參考:
[1] 野火]FPGA Verilog開發實戰指南——基於Altera EP4CE10 征途Pro開發板 文件 (embedfire.com);
[2] FPGA中亞穩態——讓你無處可逃 - 屋簷下的龍捲風 - 部落格園 (cnblogs.com);
[3] ww1.microchip.com/downloads/en/DeviceDoc/70000582e.pdf;
[4]萬敏. 非同步時序電路中的亞穩態設計與分析[D]. 上海:上海交通大學,2008.
本篇文章中使用的Verilog程式模組,若有需見網頁左欄Gitee倉庫連結:憨大頭的妙妙屋: 真奇妙! (gitee.com)