FPGA設計中RS232串口的Verilog實現(TX控制器)

RS232串口是一種簡單的異步串行通訊方式,雖然傳輸速率不太高,但因爲通訊協議簡單,實現起來非常容易,所以在對數據帶寬要求不太高的場合得到了非常廣泛的應用。今天我們在這裏討論一下RS232串口通訊的Verilog實現。

一.硬件電路:

下面是一個典型的計算機與串口設備的連接示意圖。RS232採用DB9或DB25的接口。最簡單的連接方法只需要TXD和RXD兩根信號線分別傳輸和接收數據。

clip_image002

現在常用的FPGA的IO電平一般都在1.8/2.5/3.3V,不滿足RS232的電平要求,所以我們要通過一個RS232的串口電平轉換芯片,將FPGA的IO輸出電平轉換爲RS232的電平。這裏我們採用一片MAX3232實現兩路串口的電平轉換。UART_*直接與FPGA的IO連接,EXT_*與DB9/DB25的RS232接口連接。

clip_image004

要注意的一點是,RS232的電平。邏輯“0”的電平是+5~+15V,邏輯“1”的電平是-5~-15V,是和TTL電平的定義相反的。

二.TXD(發送數據)方的Verilog實現

串口通訊是以字節爲單位的。每個字節的傳輸包括一個起始位,8bit數據位,一到兩個的結束位,以及可選的校驗位等。下圖是一次傳輸的邏輯波形的定義。在傳輸中,每一位的寬度相同的,由傳輸的波特率決定。

clip_image006

對於串口TXD的方向,控制器要根據所要傳輸的一個字節的數據,生成上面格式的邏輯波形併發送出去。

每次傳輸除了數據不同外,整個傳輸的基本格式是一樣的。在verilog的實現中,用一個包含有4個狀態的狀態機來實現一個字節數據的傳輸。四個狀態分別是UART_IDLE,UART_START,UART_DATA和UART_STOP。

當沒有數據傳輸時,串口處於IDLE狀態,TXD爲高電平。當有一個字節的數據需要傳輸時,進入UART_START狀態,同時生成一個bit的起始位(低電平),然後進入UART_DATA狀態,將要傳輸的一個字節的數據按照從bit0到bit7的順序,依次放到TXD上,然後進入結束位(這裏用一個bit的結束位),當該次傳輸結束以後,串口重新進入IDLE狀態。如果有多個字節的數據需要傳輸,那麼當前一個字節的數據傳輸結束時(UART_STOP結束),直接進入UART_START狀態,開始下一個字節的傳輸。

下面是狀態機的verilog實現(其中FIFO_EMPTY用於表示是否有數據需要傳輸,UART_DATA_CNT用來選擇當前需要傳輸的數據位)。UART_CLK是串口的時鐘,由串口波特率決定。

wire FIFO_EMPTY;

reg [2:0] UART_DATA_CNT;

parameter UART_IDLE = 4'b0001,

UART_START = 4'b0010,

UART_DATA = 4'b0100,

UART_STOP = 4'b1000;

reg [3:0] UARTSM, UARTSMNXT;

wire PHASE_IDLE = UARTSM[0];

wire PHASE_START = UARTSM[1];

wire PHASE_DATA = UARTSM[2];

wire PHASE_STOP = UARTSM[3];

always @(posedge UART_CLK or negedge RESET)

begin

if(~RESET)

UARTSM <= UART_IDLE;

else

UARTSM <= UARTSMNXT;

end

always @( UARTSM or FIFO_EMPTY or UART_DATA_CNT) begin

UARTSMNXT = UARTSM;

case (UARTSM)

UART_IDLE:

if(~FIFO_EMPTY)

UARTSMNXT = UART_START;

else

UARTSMNXT = UART_IDLE;

UART_START:

UARTSMNXT = UART_DATA;

UART_DATA:

if(UART_DATA_CNT == 3'b111)

UARTSMNXT = UART_STOP;

else

UARTSMNXT = UART_DATA;

UART_STOP:

if(~FIFO_EMPTY)

UARTSMNXT = UART_START;

else

UARTSMNXT = UART_IDLE;

default:

UARTSMNXT = UART_IDLE;

endcase

end

UART_DATA狀態需要持續8個bit的寬度,在這個狀態下,需要將8bit的數據(TX_DATA)一次放到TXD上。可以通過下面的邏輯來實現:

assign TX_DATA_EN = PHASE_START;

always @(posedge UART_CLK or negedge RESET)

begin

if(~RESET)

UART_DATA_CNT <= 3'b0;

else if(PHASE_DATA)

UART_DATA_CNT <= UART_DATA_CNT + 3'b1;

else

UART_DATA_CNT <= 3'b0;

end

reg UART_DATA_BIT;

always @( UART_DATA_CNT or TX_DATA)

begin

case(UART_DATA_CNT)

3'b000:

UART_DATA_BIT = TX_DATA[0];

3'b001:

UART_DATA_BIT = TX_DATA[1];

3'b010:

UART_DATA_BIT = TX_DATA[2];

3'b011:

UART_DATA_BIT = TX_DATA[3];

3'b100:

UART_DATA_BIT = TX_DATA[4];

3'b101:

UART_DATA_BIT = TX_DATA[5];

3'b110:

UART_DATA_BIT = TX_DATA[6];

3'b111:

UART_DATA_BIT = TX_DATA[7];

default:

UART_DATA_BIT = 1'b0;

endcase

end

wire TXD_PRE;

assign TXD_PRE = PHASE_START ? 1'b0 : (PHASE_DATA & UART_DATA_BIT) | PHASE_STOP | PHASE_IDLE;

爲了簡化時序設計,用下面的邏輯將TXD用register out的方式送到FPGA的IO上。

reg TXD;

always @(posedge UART_CLK or negedge RESET)

begin

if(~RESET)

TXD <= 1'b1;

else

TXD <= TXD_PRE;

End

一般情況下,串口作爲一個通訊模塊,都是要爲其他功能模塊服務的,功能模塊產生數據,送給串口,串口將數據發送給上位機或者其他設備。那麼在設計的過程中,就需要注意功能模塊與串口之間的數據傳輸的問題。一般情況下,功能模塊和串口的工作時鐘都不會相同,所以兩個模塊之間的數據可能要以異步方式傳輸。這裏我們在串口中直接實現一個8bit位寬的異步FIFO。功能模塊可以直接將數據寫入FIFO,當FIFO不爲空的情況下,串口就直接從FIFO中讀取數據併發送出去。

由於本文主要是介紹串口的實現的,對於異步FIFO的實現,就不做過多的討論,直接採用ALTERA的異步FIFO的IP核生成。

asyn_fifo uart_tx_fifo (

.aclr ( FIFO_CLR ), //FIFO Clear,可以由RESET生成

.data ( DATA_IN ), //數據輸入,由功能模塊生成,8bit位寬

.rdclk ( UART_CLK ), //串口時鐘,與波特率相關

.rdreq ( TX_DATA_EN ), //數據輸出使能信號,從FIFO中輸出數據

.wrclk ( CLK ), //數據輸入時鐘,與功能模塊的時鐘相關

.wrreq ( DATA_EN ), //數據輸入使能信號

.q ( FIFO_DATA_OUT ), //FIFO數據輸出,送串口

.rdempty ( FIFO_EMPTY ), //FIFO empty信號,非空時,串口即要啓動

.wrfull ( FIFO_FULL ) //FIFO full信號,如果出現該種情況,則說明波特率太低,串口無法滿足數據傳輸要求。

);

下圖是從示波器上抓取的串口單個字節的傳輸波形。波特率爲115200,白色爲FPGA IO的3.3V TTL輸出,紅色爲MAX232的RS232電平輸出。

clip_image008

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章