UART串口收發(fā)的原理與Verilog設(shè)計實現(xiàn)

2023-08-12 11:21:40 來源:FPGA之家

一、軟件平臺與硬件平臺

軟件平臺:

1、操作系統(tǒng):Windows-8.1


(資料圖片)

2、開發(fā)套件:ISE14.7

3、仿真工具:ModelSim-10.4-SE

硬件平臺:

1、FPGA型號:XC6SLX45-2CSG324

2、USB轉(zhuǎn)UART芯片Silicon LabsCP2102GM

二、原理介紹

串口是串行接口(serial port)的簡稱,也稱為串行通信接口或COM接口。串口通信是指采用串行通信協(xié)議(serial communication)在一條信號線上將數(shù)據(jù)一個比特一個比特地逐位進行傳輸?shù)耐ㄐ拍J健4诎?u>電氣標準及協(xié)議來劃分,包括RS-232、RS-422、RS485等。其中最常用的就是RS-232接口。

RS-232接口有以下三個特性:

1、用了一個9針的連接器"DB-9"(早期的電腦有用25針的連接器"DB-25")

2、允許全雙工通信(即通過串口發(fā)送數(shù)據(jù)和接收數(shù)據(jù)可以同時進行)

3、通信的最大速率大約在10KBytes/s左右

現(xiàn)在一般都用USB轉(zhuǎn)串口線進行串口通信):

雖然DB-9接頭一共有9根線,但是實現(xiàn)串口通信只需要其中的3根線就可以了,分別是:

1、pin-2:RXD(receive data),接收串行數(shù)據(jù)

2、pin-3:TXD(transmit data),發(fā)送串行數(shù)據(jù)

3、pin-5:GND(ground),地線

在串口通信中,數(shù)據(jù)在1位寬的單條線路上進行傳輸,一個字節(jié)的數(shù)據(jù)要分為8次,由低位到高位按順序一位一位的進行傳送,這個過程稱為數(shù)據(jù)的"串行化(serialized)"過程。由于串口通信是一種異步通信協(xié)議,并沒有時鐘信號隨著數(shù)據(jù)一起傳輸,而且空閑狀態(tài)(沒有數(shù)據(jù)傳輸?shù)臓顟B(tài))的時候,串行傳輸線為高電平1,所以發(fā)送方發(fā)送一個字節(jié)數(shù)據(jù)之前會先發(fā)送一個低電平0,接收方收到這個低電平0以后就知道有數(shù)據(jù)要來了,準備開始接收數(shù)據(jù)從而實現(xiàn)一次通信。串口通信的時序如下圖所示:

串口通信的規(guī)范如下:

1、空閑狀態(tài)(沒有數(shù)據(jù)傳輸?shù)臓顟B(tài))下,串行傳輸線上為高電平1

2、發(fā)送方發(fā)送低電平0表示數(shù)據(jù)傳輸開始,這個低電平表示傳輸?shù)钠鹗嘉?/p>

3、8-bit的數(shù)據(jù)位(1 Byte)是從最低位開始發(fā)送,最高位最后發(fā)送

4、數(shù)據(jù)位的最高位發(fā)送完畢以后的下一位是奇偶校驗位,這一位可以省略不要,同時,當不發(fā)送奇偶校驗位的時候接收方也相應(yīng)的不接收校驗位

5、最后一位是停止位,用高電平1表示停止位

下面以發(fā)送字節(jié)0x55為例來說明整個的發(fā)送過程:

先把0x55轉(zhuǎn)化成二進制為:01010101。顯然0x55的最低位bit 0是1,次低位bit 1是0,……..,最高位bit 7是0,由于串口是從最低位開始發(fā)送一個字節(jié),所以0x55各個位的發(fā)送順序是1-0-1-0-1-0-1-0,波形如下圖所示

下面在給出一個波形,根據(jù)上面的規(guī)則也可以很容易判斷這是發(fā)送字節(jié)0x13的波形

接下來的最后一個問題是:串口傳輸?shù)乃俣仁嵌嗌伲?/p>

實際上,串口傳輸?shù)乃俣扔貌ㄌ芈?baudrate)來指定。波特率表示的是每秒發(fā)送的比特數(shù),單位是bps(bits-per-seconds),例如,1000 bauds表示1秒鐘發(fā)送了1000個比特,或者說每個比特持續(xù)的時間是1ms。關(guān)于串口發(fā)送的波特率是有一組標準的規(guī)定的,并不是隨便一個數(shù)字。常用的波特率標準有:

1、1200 bps

2、9600 bps (常用)

3、38400 bps

4、115200 bps (常用,而且通常情況下是我們能用的最快的波特率)

波特率為115200 bps時,每個比特持續(xù)的時間為(1/115200)=8.7us,所以發(fā)送8個bit(1 Byte)需要的時間是8*8.7us=69us。在不考慮奇偶校驗位的情況下,發(fā)送一個字節(jié)還需要發(fā)送額外的1個起始位和1個停止位,所以發(fā)送1個字節(jié)實際所需要的最少時間是10*8.7us=87us,這意味著1s(1000000us)中能發(fā)送的字節(jié)數(shù)為(1000000/87) = 11494,所以在波特率為115200bps的情況下,串口傳輸數(shù)據(jù)的速率約為11.5KB/s。而有些電腦的串口有時候需要一個更長的停止位,比如1.5位或2位的停止位,那么發(fā)送一個字節(jié)所需要的時間比只有一個比特停止位的情況所耗費的時間更長,在這種情況下,串口的傳輸速率會低于10.5KB/s。

通過上面一系列的總結(jié)以后,可以得出FPGA與PC之間的串口通信主要包括三個模塊:波特率產(chǎn)生模塊、發(fā)射模塊和接收模塊。

三、目標功能

1、編寫發(fā)送模塊的verilog代碼,并往PC上連續(xù)不斷發(fā)送0x00~0xff這些數(shù)據(jù),PC上用串口調(diào)試助手進行接收并以16進制顯示出來

2、在第一個功能的基礎(chǔ)上編寫接收模塊的verilog代碼,接收模塊接收到第一個功能中發(fā)送模塊發(fā)送的數(shù)據(jù)以后,用接收到的并行數(shù)據(jù)的低四位驅(qū)動板上的四個LED

3、編寫一個頂層模塊把發(fā)送模塊和接收模塊均例化進去,然后從PC的串口調(diào)試助手上發(fā)送數(shù)據(jù)到FPGA,F(xiàn)PGA接收到數(shù)據(jù)以后把接收的數(shù)據(jù)返回給串口調(diào)試助手顯示

四、設(shè)計思路與Verilog代碼編寫

4.1、發(fā)送模塊波特率時鐘的設(shè)計與實現(xiàn)

本節(jié)以波特率為115200bps為例來說明波特率模塊設(shè)計方法,其余波特率可以以此類推。由于我的開發(fā)板上的時鐘為50MHz,周期T=20ns,而波特率為115200bps,所以1個bit持續(xù)的時間是8.7us,那么每個bit占用的周期數(shù)N=(8.7us / 20ns) = 434,所以可以定義一個計數(shù)器,每當計數(shù)器從0計數(shù)到433的時候就把計數(shù)器清零,然后在計數(shù)值為1(這個計數(shù)值最好比433的一半要小,這篇博客的最后一部分分析了原因)的情況下產(chǎn)生一個高脈沖。發(fā)射模塊只要檢測到這個高脈沖的到來就發(fā)送一個bit,這樣就實現(xiàn)了波特率為115200bps的串口數(shù)據(jù)發(fā)送。

而接收模塊的波特率時鐘產(chǎn)生邏輯與發(fā)送的波特率時鐘相比稍有不同。不同之處在于當接收模塊檢測到I_rs232_rxd的下降沿以后,表示有數(shù)據(jù)過來,準備開始接收數(shù)據(jù)了,由于一個bit持續(xù)的時間為434個時鐘周期,所以為了保證接收模塊接收數(shù)據(jù)的準確性,我們需要在434/2=217個周期,也就是數(shù)據(jù)的正中間位置的時候把輸入的數(shù)據(jù)接收并存起來。也就是說接收模塊的波特率時鐘要比發(fā)射模塊的波特率時鐘滯后數(shù)個周期

波特率產(chǎn)生模塊的框圖如下圖所示

其中:

I_clk是系統(tǒng)時鐘;

I_rst_n是系統(tǒng)復(fù)位;

I_tx_bps_en是發(fā)射模塊波特率使能信號,當I_tx_bps_en為1時O_bps_tx_clk才有時鐘信號輸出;

I_rx_bps_en是接收模塊波特率使能信號,當I_rx_bps_en為1時O_bps_rx_clk才有時鐘信號輸出。

波特率模塊的完整代碼如下:

module baudrate_gen(    input   I_clk                  , // 系統(tǒng)50MHz時鐘    input   I_rst_n                , // 系統(tǒng)全局復(fù)位    input   I_bps_tx_clk_en        , // 串口發(fā)送模塊波特率時鐘使能信號    input   I_bps_rx_clk_en        , // 串口接收模塊波特率時鐘使能信號    output  O_bps_tx_clk           , // 發(fā)送模塊波特率產(chǎn)生時鐘    output  O_bps_rx_clk             // 接收模塊波特率產(chǎn)生時鐘);parameter       C_BPS9600         = 5207         ,    //波特率為9600bps                C_BPS19200        = 2603         ,    //波特率為19200bps                C_BPS38400        = 1301         ,    //波特率為38400bps                C_BPS57600        = 867          ,    //波特率為57600bps                C_BPS115200       = 433          ;    //波特率為115200bps                parameter       C_BPS_SELECT      = C_BPS115200  ;    //波特率選擇                reg [12:0]  R_bps_tx_cnt       ;reg [12:0]  R_bps_rx_cnt       ;///////////////////////////////////////////////////////////// 功能:串口發(fā)送模塊的波特率時鐘產(chǎn)生邏輯///////////////////////////////////////////////////////////always @(posedge I_clk ornegedge I_rst_n)begin    if(!I_rst_n)        R_bps_tx_cnt <= 13"d0 ;    else if(I_bps_tx_clk_en == 1"b1)        begin            if(R_bps_tx_cnt == C_BPS_SELECT)                R_bps_tx_cnt <= 13"d0 ;            else                R_bps_tx_cnt <= R_bps_tx_cnt + 1"b1 ;        end        else        R_bps_tx_cnt <= 13"d0 ;endassign O_bps_tx_clk = (R_bps_tx_cnt == 13"d1) ? 1"b1 : 1"b0 ;///////////////////////////////////////////////////////////// 功能:串口接收模塊的波特率時鐘產(chǎn)生邏輯///////////////////////////////////////////////////////////always @(posedge I_clk or negedge I_rst_n)begin    if(!I_rst_n)        R_bps_rx_cnt <= 13"d0 ;    else if(I_bps_rx_clk_en == 1"b1)        begin            if(R_bps_rx_cnt == C_BPS_SELECT)                R_bps_rx_cnt <= 13"d0 ;            else                R_bps_rx_cnt <= R_bps_rx_cnt + 1"b1 ;        end        else        R_bps_rx_cnt <= 13"d0 ;endassign O_bps_rx_clk = (R_bps_rx_cnt == C_BPS_SELECT >> 1"b1) ? 1"b1 : 1"b0 ;endmodule

波特率模塊的ModelSim仿真圖為

4.2、發(fā)送模塊的設(shè)計與實現(xiàn)

有了波特率時鐘以后,就可以開始編寫發(fā)送模塊的內(nèi)部邏輯了。發(fā)送模塊的結(jié)構(gòu)框圖如下圖所示

其中:

I_clk是系統(tǒng)時鐘;

I_rst_n是系統(tǒng)復(fù)位;

I_tx_start是開始發(fā)送信號,當檢測到I_tx_start為高電平時,立馬把輸入I_para_data[7:0]的數(shù)據(jù)串行化成單bit的發(fā)出去;

I_bps_tx_clk是發(fā)送模塊波特率時鐘信號,當檢測到I_bps_tx_clk為高的時候就發(fā)送1個bit;

I_para_data[7:0]是并行的8-bit數(shù)據(jù);

O_rs232_txd是串行的bit數(shù)據(jù)流;

O_bps_clk_en是發(fā)射波特率時鐘啟動信號,當它為1是波特率產(chǎn)生模塊才能產(chǎn)生發(fā)射模塊的波特率時鐘;

O_tx_done是發(fā)送1字節(jié)數(shù)據(jù)完成的標志位,當一個字節(jié)發(fā)送完畢以后,O_tx_done產(chǎn)生一個高脈沖。

以發(fā)送字節(jié)0x55為例,發(fā)送模塊幾個關(guān)鍵信號的時序圖如下圖所示

發(fā)送模塊的代碼如下:

module uart_txd(    input          I_clk           , // 系統(tǒng)50MHz時鐘    input          I_rst_n         , // 系統(tǒng)全局復(fù)位    input          I_tx_start      , // 發(fā)送使能信號    input          I_bps_tx_clk    , // 發(fā)送波特率時鐘    input   [7:0]  I_para_data     , // 要發(fā)送的并行數(shù)據(jù)    output  reg    O_rs232_txd     , // 發(fā)送的串行數(shù)據(jù),在硬件上與串口相連    output  reg    O_bps_tx_clk_en , // 波特率時鐘使能信號    output  reg    O_tx_done         // 發(fā)送完成的標志);reg  [3:0]  R_state ;reg R_transmiting ; // 數(shù)據(jù)正在發(fā)送標志/////////////////////////////////////////////////////////////////////////////// 產(chǎn)生發(fā)送 R_transmiting 標志位/////////////////////////////////////////////////////////////////////////////always @(posedge I_clk or negedge I_rst_n)begin    if(!I_rst_n)        R_transmiting <= 1"b0 ;    else if(O_tx_done)        R_transmiting <= 1"b0 ;    else if(I_tx_start)        R_transmiting <= 1"b1 ;end/////////////////////////////////////////////////////////////////////////////// 發(fā)送數(shù)據(jù)狀態(tài)機/////////////////////////////////////////////////////////////////////////////always @(posedge I_clk or negedge I_rst_n)begin    if(!I_rst_n)        begin            R_state      <= 4"d0 ;            O_rs232_txd  <= 1"b1 ;            O_tx_done    <= 1"b0 ;            O_bps_tx_clk_en <= 1"b0 ;  // 關(guān)掉波特率時鐘使能信號        end     else if(R_transmiting) // 檢測發(fā)送標志被拉高,準備發(fā)送數(shù)據(jù)        begin            O_bps_tx_clk_en <= 1"b1 ;  // 發(fā)送數(shù)據(jù)前的第一件事就是打開波特率時鐘使能信號            if(I_bps_tx_clk) // 在波特率時鐘的控制下把數(shù)據(jù)通過一個狀態(tài)機發(fā)送出去,并產(chǎn)生發(fā)送完成信號                begin                    case(R_state)                        4"d0  : // 發(fā)送起始位                            begin                                O_rs232_txd  <= 1"b0            ;                                O_tx_done    <= 1"b0            ;                                R_state      <= R_state + 1"b1  ;                            end                        4"d1  : // 發(fā)送 I_para_data[0]                            begin                                O_rs232_txd  <= I_para_data[0]  ;                                O_tx_done    <= 1"b0            ;                                R_state      <= R_state + 1"b1  ;                            end                         4"d2  : // 發(fā)送 I_para_data[1]                            begin                                O_rs232_txd  <= I_para_data[1]   ;                                O_tx_done    <= 1"b0             ;                                R_state      <= R_state + 1"b1   ;                            end                        4"d3  : // 發(fā)送 I_para_data[2]                            begin                                O_rs232_txd  <= I_para_data[2]   ;                                O_tx_done    <= 1"b0             ;                                R_state      <= R_state + 1"b1   ;                            end                        4"d4  : // 發(fā)送 I_para_data[3]                            begin                                O_rs232_txd  <= I_para_data[3]   ;                                O_tx_done    <= 1"b0             ;                                R_state      <= R_state + 1"b1   ;                            end                         4"d5  : // 發(fā)送 I_para_data[4]                            begin                                O_rs232_txd  <= I_para_data[4]   ;                                O_tx_done    <= 1"b0             ;                                R_state      <= R_state + 1"b1   ;                            end                        4"d6  : // 發(fā)送 I_para_data[5]                            begin                                O_rs232_txd  <= I_para_data[5]   ;                                O_tx_done    <= 1"b0             ;                                R_state      <= R_state + 1"b1   ;                            end                        4"d7  : // 發(fā)送 I_para_data[6]                            begin                                O_rs232_txd  <= I_para_data[6]   ;                                O_tx_done    <= 1"b0             ;                                R_state      <= R_state + 1"b1   ;                            end                        4"d8  : // 發(fā)送 I_para_data[7]                            begin                                O_rs232_txd  <= I_para_data[7]   ;                                O_tx_done    <= 1"b0             ;                                R_state      <= R_state + 1"b1   ;                            end                         4"d9  : // 發(fā)送 停止位                            begin                                O_rs232_txd  <= 1"b1             ;                                O_tx_done    <= 1"b1             ;                                R_state      <= 4"d0          ;                            end                        default :R_state      <= 4"d0            ;
endcase         end       end     else       begin         O_bps_tx_clk_en    <= 1"b0  ; // 一幀數(shù)據(jù)發(fā)送完畢以后就關(guān)掉波特率時鐘使能信號         R_state         <= 4"d0  ;         O_tx_done        <= 1"b0  ;         O_rs232_txd      <= 1"b1  ;        end end endmodule

其中當檢測到輸入信號I_tx_start為高電平以后,發(fā)送模塊立即把R_transmiting信號拉高,表示開始要發(fā)送數(shù)據(jù)了,在R_transmiting為高電平的期間,打開波特率時鐘使能信號并且在波特率時鐘的控制下通過一個狀態(tài)機把并行數(shù)據(jù)發(fā)送出去,并產(chǎn)生發(fā)送完成信號O_tx_done,等O_tx_done為高以后再把R_transmiting拉低表示一次發(fā)送結(jié)束。

為了實現(xiàn)功能1的效果還需要編寫一個頂層模塊把波特率模塊和發(fā)送模塊例化進去并產(chǎn)生發(fā)送的信號,頂層模塊的代碼如下:

module uart_tx_top(    input          I_clk           , // 系統(tǒng)50MHz時鐘    input          I_rst_n         , // 系統(tǒng)全局復(fù)位    output         O_rs232_txd       // 發(fā)送的串行數(shù)據(jù),在硬件上與串口相連);wire            W_bps_tx_clk             ;wire            W_bps_tx_clk_en          ;wire            W_tx_start               ;wire            W_tx_done                ;wire  [7:0]     W_para_data              ;        reg   [7:0]     R_data_reg               ;reg   [31:0]    R_cnt_1s                 ;reg             R_tx_start_reg           ;assign W_tx_start     =    R_tx_start_reg    ;assign W_para_data    =    R_data_reg        ;/////////////////////////////////////////////////////////////////////// 產(chǎn)生要發(fā)送的數(shù)據(jù)/////////////////////////////////////////////////////////////////////always @(posedge I_clk or negedge I_rst_n)begin     if(!I_rst_n)        begin             R_cnt_1s         <= 31"d0     ;             R_data_reg       <= 8"d0      ;             R_tx_start_reg  <= 1"b0      ;        end     else if(R_cnt_1s == 31"d24_999_999)        begin             R_cnt_1s         <= 31"d0                 ;             R_data_reg       <= R_data_reg + 1"b1      ;             R_tx_start_reg <= 1"b1                 ;        end     else        begin          R_cnt_1s             <= R_cnt_1s + 1"b1     ;          R_tx_start_reg     <= 1"b0             ;        endenduart_txd U_uart_txd(    .I_clk               (I_clk                 ), // 系統(tǒng)50MHz時鐘    .I_rst_n             (I_rst_n               ), // 系統(tǒng)全局復(fù)位    .I_tx_start          (W_tx_start            ), // 發(fā)送使能信號    .I_bps_tx_clk        (W_bps_tx_clk          ), // 波特率時鐘    .I_para_data         (W_para_data           ), // 要發(fā)送的并行數(shù)據(jù)    .O_rs232_txd         (O_rs232_txd           ), // 發(fā)送的串行數(shù)據(jù),在硬件上與串口相連    .O_bps_tx_clk_en     (W_bps_tx_clk_en       ), // 波特率時鐘使能信號    .O_tx_done           (W_tx_done             )  // 發(fā)送完成的標志);baudrate_gen U_baudrate_gen(    .I_clk              (I_clk              ), // 系統(tǒng)50MHz時鐘    .I_rst_n            (I_rst_n            ), // 系統(tǒng)全局復(fù)位    .I_bps_tx_clk_en    (W_bps_tx_clk_en    ), // 串口發(fā)送模塊波特率時鐘使能信號    .I_bps_rx_clk_en    (                   ), // 串口接收模塊波特率時鐘使能信號    .O_bps_tx_clk       (W_bps_tx_clk       ), // 發(fā)送模塊波特率產(chǎn)生時鐘    .O_bps_rx_clk       (                   )  // 接收模塊波特率產(chǎn)生時鐘);endmodule

下載到板之前先用Modelsim仿一下看邏輯是否正確,仿之前把R_cnt_1s這個參數(shù)的上限值設(shè)置小一點,比如5000,可以加快仿真速度,下圖是仿真時序圖,顯然完全滿足設(shè)計要求。

仿真結(jié)束以后就綁定管腳然后下載到FPGA中,接著打開電腦的串口調(diào)試助手,下圖是我的電腦上的顯示效果:

4.3、接收模塊的設(shè)計與實現(xiàn)

波特率模塊和發(fā)送模塊都沒問題以后,就可以開始編寫接收模塊的代碼了。接收模塊的結(jié)構(gòu)框圖如下圖所示

其中:

I_clk是系統(tǒng)時鐘;

I_rst_n是系統(tǒng)復(fù)位;

I_rx_start是開始發(fā)送信號,當I_rx_start一直為高電平時,接收模塊檢測到有數(shù)據(jù)就會接收;

I_bps_rx_clk是接收模塊波特率時鐘信號,當檢測到I_bps_rx_clk為高的時候就接收1個bit;

I_rs232_rx是串行的bit數(shù)據(jù)流;

O_para_data[7:0]是并行的8-bit數(shù)據(jù);

O_bps_rx_clk_en是發(fā)射波特率時鐘啟動信號,當它為1是波特率產(chǎn)生模塊才能產(chǎn)生接收模塊的波特率時鐘;

O_rx_done是接收1字節(jié)數(shù)據(jù)完成的標志位,當一個字節(jié)接收完畢以后,O_rx_done產(chǎn)生一個高脈沖。

接收模塊與發(fā)射模塊的邏輯結(jié)構(gòu)非常類似,但是由于接收模塊需要判斷串行數(shù)據(jù)流的起始位,所以還要加一段檢測串行數(shù)據(jù)流下降沿的邏輯,檢測串行數(shù)據(jù)流下降沿的代碼如下:

////////////////////////////////////////////////////////////////////////////////// 功能:把 I_rs232_rxd 打的前兩拍,是為了消除亞穩(wěn)態(tài)//      把 I_rs232_rxd 打的后兩拍,是為了產(chǎn)生下降沿標志位////////////////////////////////////////////////////////////////////////////////always @(posedge I_clk or negedge I_rst_n)begin    if(!I_rst_n)        begin            R_rs232_rx_reg0 <= 1"b0 ;            R_rs232_rx_reg1 <= 1"b0 ;            R_rs232_rx_reg2 <= 1"b0 ;            R_rs232_rx_reg3 <= 1"b0 ;        end     else        begin              R_rs232_rx_reg0 <= I_rs232_rxd      ;            R_rs232_rx_reg1 <= R_rs232_rx_reg0  ;            R_rs232_rx_reg2 <= R_rs232_rx_reg1  ;            R_rs232_rx_reg3 <= R_rs232_rx_reg2  ;        end   end// 產(chǎn)生I_rs232_rxd信號的下降沿標志位assign W_rs232_rxd_neg    =    (~R_rs232_rx_reg2) & R_rs232_rx_reg3 ;

這段邏輯一共把I_rs232_rxd信號打了四拍,其中前兩排是為了消除I_rs232_rxd的亞穩(wěn)態(tài)(后面有時間專門討論亞穩(wěn)態(tài)問題),后兩排用來產(chǎn)生I_rs232_rxd信號下降沿的標志位。

這里以接收0x55這個字節(jié)為例來演示接收模塊的幾個重要信號的時序圖如下圖所示:

接收模塊的完整代碼如下:

module uart_rxd(    input                       I_clk               , // 系統(tǒng)50MHz時鐘    input                       I_rst_n             , // 系統(tǒng)全局復(fù)位    input                       I_rx_start          , // 接收使能信號    input                       I_bps_rx_clk        , // 接收波特率時鐘    input                       I_rs232_rxd         , // 接收的串行數(shù)據(jù),在硬件上與串口相連    output    reg               O_bps_rx_clk_en     , // 波特率時鐘使能信號    output    reg               O_rx_done           , // 接收完成標志    output    reg   [7:0]       O_para_data           // 接收到的8-bit并行數(shù)據(jù));reg         R_rs232_rx_reg0 ;reg         R_rs232_rx_reg1 ;reg         R_rs232_rx_reg2 ;reg         R_rs232_rx_reg3 ;reg         R_receiving     ;reg [3:0]   R_state         ;reg [7:0]   R_para_data_reg ;wire        W_rs232_rxd_neg ;////////////////////////////////////////////////////////////////////////////////// 功能:把 I_rs232_rxd 打的前兩拍,是為了消除亞穩(wěn)態(tài)//          把 I_rs232_rxd 打的后兩拍,是為了產(chǎn)生下降沿標志位////////////////////////////////////////////////////////////////////////////////always @(posedge I_clk or negedge I_rst_n)begin    if(!I_rst_n)        begin            R_rs232_rx_reg0 <= 1"b0 ;            R_rs232_rx_reg1 <= 1"b0 ;            R_rs232_rx_reg2 <= 1"b0 ;            R_rs232_rx_reg3 <= 1"b0 ;        end     else        begin              R_rs232_rx_reg0 <= I_rs232_rxd      ;            R_rs232_rx_reg1 <= R_rs232_rx_reg0  ;            R_rs232_rx_reg2 <= R_rs232_rx_reg1  ;            R_rs232_rx_reg3 <= R_rs232_rx_reg2  ;        end   end// 產(chǎn)生I_rs232_rxd信號的下降沿標志位assign W_rs232_rxd_neg    =    (~R_rs232_rx_reg2) & R_rs232_rx_reg3 ;////////////////////////////////////////////////////////////////////////////////// 功能:產(chǎn)生發(fā)送信號R_receiving////////////////////////////////////////////////////////////////////////////////always @(posedge I_clk or negedge I_rst_n)begin    if(!I_rst_n)        R_receiving <= 1"b0 ;    else if(O_rx_done)        R_receiving <= 1"b0 ;    else if(I_rx_start && W_rs232_rxd_neg)        R_receiving <= 1"b1 ;end////////////////////////////////////////////////////////////////////////////////// 功能:用狀態(tài)機把串行的輸入數(shù)據(jù)接收,并轉(zhuǎn)化為并行數(shù)據(jù)輸出////////////////////////////////////////////////////////////////////////////////always @(posedge I_clk or negedge I_rst_n)begin    if(!I_rst_n)        begin            O_rx_done       <= 1"b0 ;            R_state         <= 4"d0 ;            R_para_data_reg <= 8"d0 ;            O_bps_rx_clk_en <= 1"b0 ;        end     else if(R_receiving)        begin            O_bps_rx_clk_en <= 1"b1 ; // 打開波特率時鐘使能信號            if(I_bps_rx_clk)                begin                    case(R_state)                        4"d0  : // 接收起始位,但不保存                            begin                                R_para_data_reg     <= 8"d0             ;                                O_rx_done           <= 1"b0             ;                                R_state             <= R_state + 1"b1   ;                            end                        4"d1  : // 接收第0位,保存到R_para_data_reg[0]                            begin                                R_para_data_reg[0]  <= I_rs232_rxd      ;                                O_rx_done           <= 1"b0             ;                                R_state             <= R_state + 1"b1   ;                            end                        4"d2  : // 接收第1位,保存到R_para_data_reg[1]                            begin                                R_para_data_reg[1]  <= I_rs232_rxd      ;                                O_rx_done           <= 1"b0             ;                                R_state             <= R_state + 1"b1   ;                            end                        4"d3  : // 接收第2位,保存到R_para_data_reg[2]                            begin                                R_para_data_reg[2]  <= I_rs232_rxd      ;                                O_rx_done           <= 1"b0             ;                                R_state             <= R_state + 1"b1   ;                            end                         4"d4  : // 接收第3位,保存到R_para_data_reg[3]                            begin                                R_para_data_reg[3]  <= I_rs232_rxd      ;                                O_rx_done           <= 1"b0             ;                                R_state             <= R_state + 1"b1   ;                            end                         4"d5  : // 接收第4位,保存到R_para_data_reg[4]                            begin                                R_para_data_reg[4]  <= I_rs232_rxd      ;                                O_rx_done           <= 1"b0             ;                                R_state             <= R_state + 1"b1   ;                            end                        4"d6  : // 接收第5位,保存到R_para_data_reg[5]                            begin                                R_para_data_reg[5]  <= I_rs232_rxd      ;                                O_rx_done           <= 1"b0             ;                                R_state             <= R_state + 1"b1   ;                            end                        4"d7  :// 接收第6位,保存到R_para_data_reg[6]                            begin                                R_para_data_reg[6]  <= I_rs232_rxd      ;                                O_rx_done           <= 1"b0             ;                                R_state             <= R_state + 1"b1   ;                            end                        4"d8  : // 接收第7位,保存到R_para_data_reg[7]                            begin                                R_para_data_reg[7]  <= I_rs232_rxd      ;                                O_rx_done           <= 1"b0             ;                                R_state             <= R_state + 1"b1   ;                            end                         4"d9  : // 接收停止位,但不保存,并把R_para_data_reg給輸出                            begin                                O_para_data         <= R_para_data_reg  ;                                O_rx_done           <= 1"b1             ;                                R_state             <= 4"d0             ;                            end                                                     default:R_state <= 4"d0                         ;                    endcase                 end        end    else        begin            O_rx_done           <= 1"b0 ;            R_state             <= 4"d0 ;            R_para_data_reg     <= 8"d0 ;            O_bps_rx_clk_en     <= 1"b0 ; // 接收完畢以后關(guān)閉波特率時鐘使能信號        end          endendmodule

在下載到開發(fā)板測試之前,可以先用ModelSim軟件對模塊進行一個功能仿真,方法是直接把接收模塊例化到上一小節(jié)測試發(fā)送模塊的例子中,例化的頂層代碼如下:

module uart_top(    input             I_clk           , // 系統(tǒng)50MHz時鐘    input             I_rst_n         , // 系統(tǒng)全局復(fù)位    output    [3:0]   O_led_out       ,    output            O_rs232_txd       // 發(fā)送的串行數(shù)據(jù),在硬件上與串口相連);wire            W_bps_tx_clk                 ;wire            W_bps_tx_clk_en              ;wire            W_bps_rx_clk                 ;wire            W_bps_rx_clk_en              ;wire            W_tx_start                   ;wire            W_tx_done                    ;wire            W_rx_done                    ;wire  [7:0]     W_para_data                  ;wire  [7:0]     W_rx_para_data               ;            reg   [7:0]     R_data_reg                   ;reg   [31:0]    R_cnt_1s                     ;reg             R_tx_start_reg               ;    assign W_tx_start     =    R_tx_start_reg      ;assign W_para_data    =    R_data_reg          ;assign O_led_out     =    W_rx_para_data[3:0] ;/////////////////////////////////////////////////////////////////////// 產(chǎn)生要發(fā)送的數(shù)據(jù)/////////////////////////////////////////////////////////////////////always @(posedge I_clk or negedge I_rst_n)begin     if(!I_rst_n)        begin             R_cnt_1s         <= 31"d0     ;             R_data_reg       <= 8"d0      ;             R_tx_start_reg   <= 1"b0      ;        end     else if(R_cnt_1s == 31"d5000)        begin             R_cnt_1s         <= 31"d0                 ;             R_data_reg       <= R_data_reg + 1"b1     ;             R_tx_start_reg   <= 1"b1                  ;        end     else        begin          R_cnt_1s           <= R_cnt_1s + 1"b1     ;          R_tx_start_reg     <= 1"b0                ;        endenduart_txd U_uart_txd(    .I_clk               (I_clk                 ), // 系統(tǒng)50MHz時鐘    .I_rst_n             (I_rst_n               ), // 系統(tǒng)全局復(fù)位    .I_tx_start          (W_tx_start            ), // 發(fā)送使能信號    .I_bps_tx_clk        (W_bps_tx_clk          ), // 波特率時鐘    .I_para_data         (W_para_data           ), // 要發(fā)送的并行數(shù)據(jù)    .O_rs232_txd         (O_rs232_txd           ), // 發(fā)送的串行數(shù)據(jù),在硬件上與串口相連    .O_bps_tx_clk_en     (W_bps_tx_clk_en       ), // 波特率時鐘使能信號    .O_tx_done           (W_tx_done             )  // 發(fā)送完成的標志);baudrate_gen U_baudrate_gen(    .I_clk              (I_clk              ), // 系統(tǒng)50MHz時鐘    .I_rst_n            (I_rst_n            ), // 系統(tǒng)全局復(fù)位    .I_bps_tx_clk_en    (W_bps_tx_clk_en    ), // 串口發(fā)送模塊波特率時鐘使能信號    .I_bps_rx_clk_en    (W_bps_rx_clk_en    ), // 串口接收模塊波特率時鐘使能信號    .O_bps_tx_clk       (W_bps_tx_clk       ), // 發(fā)送模塊波特率產(chǎn)生時鐘    .O_bps_rx_clk       (W_bps_rx_clk       )  // 接收模塊波特率產(chǎn)生時鐘);uart_rxd U_uart_rxd(    .I_clk              (I_clk              ), // 系統(tǒng)50MHz時鐘    .I_rst_n            (I_rst_n            ), // 系統(tǒng)全局復(fù)位    .I_rx_start         (1"b1               ), // 接收使能信號    .I_bps_rx_clk       (W_bps_rx_clk       ), // 接收波特率時鐘    .I_rs232_rxd        (O_rs232_txd        ), // 接收的串行數(shù)據(jù),在硬件上與串口相連    .O_bps_rx_clk_en    (W_bps_rx_clk_en    ), // 波特率時鐘使能信號    .O_rx_done          (W_rx_done          ), // 接收完成標志    .O_para_data        (W_rx_para_data     )  // 接收到的8-bit并行數(shù)據(jù));endmodule

仿真圖如下圖所示

由圖可以看到接收數(shù)據(jù)與發(fā)送的數(shù)據(jù)完全一致,說明邏輯沒有問題,接下來就綁定管腳然后把代碼下載到FPGA看看效果,正常的效果是,PC的串口調(diào)試助手一直按順序顯示00~FF這些數(shù)據(jù),板上的LED燈的狀態(tài)與數(shù)據(jù)的低四位狀態(tài)相同。至此,功能二也全部實現(xiàn)完畢。

4.4、串口回顯功能的設(shè)計與實現(xiàn)

有了發(fā)射模塊和接收模塊以后,功能三的要求就很簡單了,直接寫一個頂層模塊,把串口的發(fā)送模塊與接收模塊例化進去就可以了,唯一要做的就是把接收模塊的接收完成標志位O_rx_done連接到發(fā)送模塊的I_tx_start上,把接收模塊的8-bit并行輸出總線O_para_data連接到發(fā)送模塊的8-bit并行輸入總線I_para_data上,下面直接給出頂層的代碼:

module uart_top(    input            I_clk           , // 系統(tǒng)50MHz時鐘    input            I_rst_n         , // 系統(tǒng)全局復(fù)位    input            I_rs232_rxd     , // 接收的串行數(shù)據(jù),在硬件上與串口相連    output           O_rs232_txd     , // 發(fā)送的串行數(shù)據(jù),在硬件上與串口相連    output    [3:0]  O_led_out);wire            W_bps_tx_clk                 ;wire            W_bps_tx_clk_en              ;wire            W_bps_rx_clk                 ;wire            W_bps_rx_clk_en              ;wire            W_rx_done                    ;wire            W_tx_done                    ;wire  [7:0]     W_para_data                  ;assign    O_led_out = W_para_data[3:0]       ;baudrate_gen U_baudrate_gen(    .I_clk              (I_clk              ), // 系統(tǒng)50MHz時鐘    .I_rst_n            (I_rst_n            ), // 系統(tǒng)全局復(fù)位    .I_bps_tx_clk_en    (W_bps_tx_clk_en    ), // 串口發(fā)送模塊波特率時鐘使能信號    .I_bps_rx_clk_en    (W_bps_rx_clk_en    ), // 串口接收模塊波特率時鐘使能信號    .O_bps_tx_clk       (W_bps_tx_clk       ), // 發(fā)送模塊波特率產(chǎn)生時鐘    .O_bps_rx_clk       (W_bps_rx_clk       )  // 接收模塊波特率產(chǎn)生時鐘);uart_txd U_uart_txd(    .I_clk               (I_clk                 ), // 系統(tǒng)50MHz時鐘    .I_rst_n             (I_rst_n               ), // 系統(tǒng)全局復(fù)位    .I_tx_start          (W_rx_done             ), // 發(fā)送使能信號    .I_bps_tx_clk        (W_bps_tx_clk          ), // 波特率時鐘    .I_para_data         (W_para_data           ), // 要發(fā)送的并行數(shù)據(jù)    .O_rs232_txd         (O_rs232_txd           ), // 發(fā)送的串行數(shù)據(jù),在硬件上與串口相連    .O_bps_tx_clk_en     (W_bps_tx_clk_en       ), // 波特率時鐘使能信號    .O_tx_done           (W_tx_done             )  // 發(fā)送完成的標志);uart_rxd U_uart_rxd(    .I_clk              (I_clk                ), // 系統(tǒng)50MHz時鐘    .I_rst_n            (I_rst_n              ), // 系統(tǒng)全局復(fù)位    .I_rx_start         (1"b1                 ), // 接收使能信號    .I_bps_rx_clk       (W_bps_rx_clk         ), // 接收波特率時鐘    .I_rs232_rxd        (I_rs232_rxd          ), // 接收的串行數(shù)據(jù),在硬件上與串口相連    .O_bps_rx_clk_en    (W_bps_rx_clk_en      ), // 波特率時鐘使能信號    .O_rx_done          (W_rx_done            ), // 接收完成標志    .O_para_data        (W_para_data          )  // 接收到的8-bit并行數(shù)據(jù));endmodule

建立工程并綁定管腳以后下載到開發(fā)板中,利用串口調(diào)試助手的自動發(fā)送(自動發(fā)送的周期最好在200ms以上)功能我對波特率為9600bps和115200bps分別進行了測試,在9600bps的情況下我一共發(fā)送了1002512個字節(jié),全部接受正確,115200bps波特率情況下一共發(fā)送了512325字節(jié),也全部接受正確,邏輯基本穩(wěn)定,歡迎大家繼續(xù)測。我第一次寫串口代碼的時候出現(xiàn)過在115200bps的情況下發(fā)送字節(jié)達到10萬以上的時候出現(xiàn)誤碼的情況,原因下一小節(jié)再說,上面的代碼已經(jīng)把這個問題修復(fù)了,原因就出在波特率模塊上。至此,功能三已全部完成。

五、進一步思考

5.1、波特率模塊產(chǎn)生的O_bps_tx_clk滯后O_bps_rx_clk可能出現(xiàn)的問題

我最開始寫的波特率模塊如下:

module baudrate_gen(    input   I_clk                  , // 系統(tǒng)50MHz時鐘    input   I_rst_n                , // 系統(tǒng)全局復(fù)位    input   I_bps_tx_clk_en        , // 串口發(fā)送模塊波特率時鐘使能信號    input   I_bps_rx_clk_en        , // 串口接收模塊波特率時鐘使能信號    output  O_bps_tx_clk           , // 發(fā)送模塊波特率產(chǎn)生時鐘    output  O_bps_rx_clk           // 接收模塊波特率產(chǎn)生時鐘);parameter         C_BPS9600         = 5207         ,    //波特率為9600bps                  C_BPS19200        = 2603         ,    //波特率為19200bps                  C_BPS38400        = 1301         ,    //波特率為38400bps                  C_BPS57600        = 867          ,    //波特率為57600bps                  C_BPS115200       = 433          ;    //波特率為115200bps                parameter         C_BPS_SELECT      = C_BPS115200  ; //波特率選擇                reg [12:0]  R_bps_tx_cnt       ;reg         R_bps_tx_clk_reg   ;reg [12:0]  R_bps_rx_cnt       ;///////////////////////////////////////////////////////////// 功能:串口發(fā)送模塊的波特率時鐘產(chǎn)生邏輯///////////////////////////////////////////////////////////always @(posedge I_clk or negedge I_rst_n)begin    if(!I_rst_n)        begin            R_bps_tx_cnt       <= 13"d0 ;            R_bps_tx_clk_reg   <= 1"b0  ;        end     else if(I_bps_tx_clk_en == 1"b1)        begin            if(R_bps_tx_cnt == C_BPS_SELECT)                begin                    R_bps_tx_cnt       <= 13"d0 ;                    R_bps_tx_clk_reg   <= 1"b1  ;                end                               else                begin                    R_bps_tx_cnt       <= R_bps_tx_cnt + 1"b1 ;                    R_bps_tx_clk_reg   <= 1"b0                ;                end            end       else        begin            R_bps_tx_cnt       <= 13"d0 ;            R_bps_tx_clk_reg   <= 1"b0  ;        end             endassign O_bps_tx_clk = R_bps_tx_clk_reg ;///////////////////////////////////////////////////////////// 功能:串口接收模塊的波特率時鐘產(chǎn)生邏輯///////////////////////////////////////////////////////////always @(posedge I_clk or negedge I_rst_n)begin    if(!I_rst_n)        R_bps_rx_cnt <= 13"d0 ;    else if(I_bps_rx_clk_en == 1"b1)        begin            if(R_bps_rx_cnt == C_BPS_SELECT)                R_bps_rx_cnt <= 13"d0 ;            else                R_bps_rx_cnt <= R_bps_rx_cnt + 1"b1 ;        end        else        R_bps_rx_cnt <= 13"d0 ;endassign O_bps_rx_clk = (R_bps_rx_cnt == C_BPS_SELECT >> 1"b1) ? 1"b1 : 1"b0 ;endmodule

其仿真如下所示:

當發(fā)送波特率時鐘使能信號打開以后,計數(shù)值計滿C_BPS_SELECT后才產(chǎn)生一個發(fā)送時鐘脈沖,而接收波特率時鐘只需要計滿C_BPS_SELECT的一半就產(chǎn)生了一個時鐘脈沖,這就導(dǎo)致在回顯實驗中,O_bps_tx_clk滯后于O_bps_rx_clk,而回顯實驗中我們直接把接收完成的標志直接接在了發(fā)送開始標志上,所以這就有可能導(dǎo)致,上一次的數(shù)據(jù)還沒發(fā)送完的時候這一次的數(shù)據(jù)已經(jīng)來了,經(jīng)過我的測試,使用上面的波特率邏輯,如果不做回顯實驗,一般沒問題,如果做回顯實驗,在波特率較高,比如115200bps和57600bps的情況下,數(shù)據(jù)量少的時候不會出錯,數(shù)據(jù)量大的時候一般都會有數(shù)據(jù)丟失,而在波特率較低的情況下,比如9600bps和2400bps,數(shù)據(jù)直接是接收一幀漏一幀,比如發(fā)送字符串a(chǎn)bcdef,接收回來的是ace。我用ChipScope才抓出了這個原因。今后使用的時候要注意這個問題。

5.2、發(fā)送數(shù)據(jù)的狀態(tài)機和接收數(shù)據(jù)的狀態(tài)機可以用移位的方式來做

事實上那個狀態(tài)機的發(fā)送8-bit數(shù)據(jù)和接收8-bit數(shù)據(jù)的部分可以用移位的方法來做,這樣寫的代碼會更短更精煉。今后有空的時候自己在重新寫一次。

審核編輯:劉清

標簽:

上一篇:C語言關(guān)系運算符詳解
下一篇:最后一頁