【接口時序】3、UART串口收發的原理與Verilog實現


一、軟件平台與硬件平台

  軟件平台:

    1、操作系統:Windows-8.1

    2、開發套件:ISE14.7

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

  硬件平台:

    1、FPGA型號:XC6SLX45-2CSG324

    2、USB轉UART芯片:Silicon Labs CP2102GM

二、原理介紹

  串口是串行接口(serial port)的簡稱,也稱為串行通信接口或COM接口。串口通信是指采用串行通信協議(serial communication)在一條信號線上將數據一個比特一個比特地逐位進行傳輸的通信模式。 串口按電氣標准及協議來划分,包括RS-232、RS-422、RS485等。其中最常用的就是RS-232接口。

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

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

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

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

  DB-9接口的實物圖如下圖所示(早期電腦主機后面可以看到這個接口,現在一般都用USB轉串口線進行串口通信):

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

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

    2、pin-3:TXD(transmit data),發送串行數據

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

  在串口通信中,數據在1位寬的單條線路上進行傳輸,一個字節的數據要分為8次,由低位到高位按順序一位一位的進行傳送,這個過程稱為數據的"串行化(serialized)"過程。由於串口通信是一種異步通信協議,並沒有時鍾信號隨着數據一起傳輸,而且空閑狀態(沒有數據傳輸的狀態)的時候,串行傳輸線為高電平1,所以發送方發送一個字節數據之前會先發送一個低電平0,接收方收到這個低電平0以后就知道有數據要來了,准備開始接收數據從而實現一次通信。串口通信的時序如下圖所示:

  串口通信的規范如下:

    1、空閑狀態(沒有數據傳輸的狀態)下,串行傳輸線上為高電平1

    2、發送方發送低電平0表示數據傳輸開始,這個低電平表示傳輸的起始位

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

    4、數據位的最高位發送完畢以后的下一位是奇偶校驗位,這一位可以省略不要,同時,當不發送奇偶校驗位的時候接收方也相應的不接收校驗位

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

  下面以發送字節0x55為例來說明整個的發送過程:

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

下面在給出一個波形,根據上面的規則也可以很容易判斷這是發送字節0x13的波形

接下來的最后一個問題是:串口傳輸的速度是多少?

  實際上,串口傳輸的速度用波特率(baudrate)來指定。波特率表示的是每秒發送的比特數,單位是bps(bits-per-seconds),例如,1000 bauds表示1秒鍾發送了1000個比特,或者說每個比特持續的時間是1ms。關於串口發送的波特率是有一組標准的規定的,並不是隨便一個數字。常用的波特率標准有:

    1、1200 bps

    2、9600 bps (常用)

    3、38400 bps

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

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

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

三、目標功能

  1、編寫發送模塊的verilog代碼,並往PC上連續不斷發送0x00~0xff這些數據,PC上用串口調試助手進行接收並以16進制顯示出來

  2、在第一個功能的基礎上編寫接收模塊的verilog代碼,接收模塊接收到第一個功能中發送模塊發送的數據以后,用接收到的並行數據的低四位驅動板上的四個LED燈

  3、編寫一個頂層模塊把發送模塊和接收模塊均例化進去,然后從PC的串口調試助手上發送數據到FPGA,FPGA接收到數據以后把接收的數據返回給串口調試助手顯示

四、設計思路與Verilog代碼編寫

4.1發送模塊波特率時鍾的設計與實現

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

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

  波特率產生模塊的框圖如下圖所示

  其中:

  I_clk是系統時鍾;

  I_rst_n是系統復位;

  I_tx_bps_en是發射模塊波特率使能信號,當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                  , // 系統50MHz時鍾
    input   I_rst_n                , // 系統全局復位
    input   I_bps_tx_clk_en        , // 串口發送模塊波特率時鍾使能信號
    input   I_bps_rx_clk_en        , // 串口接收模塊波特率時鍾使能信號
    output  O_bps_tx_clk           , // 發送模塊波特率產生時鍾
    output  O_bps_rx_clk             // 接收模塊波特率產生時鍾
);

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       ;

///////////////////////////////////////////////////////////    
// 功能:串口發送模塊的波特率時鍾產生邏輯
///////////////////////////////////////////////////////////
always @(posedge I_clk or negedge 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 ;        
end

assign O_bps_tx_clk = (R_bps_tx_cnt == 13'd1) ? 1'b1 : 1'b0 ;

///////////////////////////////////////////////////////////    
// 功能:串口接收模塊的波特率時鍾產生邏輯
///////////////////////////////////////////////////////////
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 ;        
end

assign O_bps_rx_clk = (R_bps_rx_cnt == C_BPS_SELECT >> 1'b1) ? 1'b1 : 1'b0 ;

endmodule

 

  波特率模塊的ModelSim仿真圖為

4.2發送模塊的設計與實現

  有了波特率時鍾以后,就可以開始編寫發送模塊的內部邏輯了。發送模塊的結構框圖如下圖所示

       其中:

  I_clk是系統時鍾;

  I_rst_n是系統復位;

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

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

  I_para_data[7:0]是並行的8-bit數據;

  O_rs232_txd是串行的bit數據流;

  O_bps_clk_en是發射波特率時鍾啟動信號,當它為1是波特率產生模塊才能產生發射模塊的波特率時鍾;

  O_tx_done是發送1字節數據完成的標志位,當一個字節發送完畢以后,O_tx_done產生一個高脈沖。

 

  以發送字節0x55為例,發送模塊幾個關鍵信號的時序圖如下圖所示

  發送模塊的代碼如下:

module uart_txd
(
    input          I_clk           , // 系統50MHz時鍾
    input          I_rst_n         , // 系統全局復位
    input          I_tx_start      , // 發送使能信號
    input          I_bps_tx_clk    , // 發送波特率時鍾
    input   [7:0]  I_para_data     , // 要發送的並行數據
    output  reg    O_rs232_txd     , // 發送的串行數據,在硬件上與串口相連
    output  reg    O_bps_tx_clk_en , // 波特率時鍾使能信號
    output  reg    O_tx_done         // 發送完成的標志
);

reg  [3:0]  R_state ;

reg R_transmiting ; // 數據正在發送標志

/////////////////////////////////////////////////////////////////////////////
// 產生發送 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

/////////////////////////////////////////////////////////////////////////////
// 發送數據狀態機
/////////////////////////////////////////////////////////////////////////////
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 ;  // 關掉波特率時鍾使能信號
        end 
    else if(R_transmiting) // 檢測發送標志被拉高,准備發送數據
        begin
            O_bps_tx_clk_en <= 1'b1 ;  // 發送數據前的第一件事就是打開波特率時鍾使能信號
            if(I_bps_tx_clk) // 在波特率時鍾的控制下把數據通過一個狀態機發送出去,並產生發送完成信號
                begin
                    case(R_state)
                        4'd0  : // 發送起始位
                            begin
                                O_rs232_txd  <= 1'b0            ;
                                O_tx_done    <= 1'b0            ; 
                                R_state      <= R_state + 1'b1  ;
                            end
                        4'd1  : // 發送 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  : // 發送 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  : // 發送 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  : // 發送 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  : // 發送 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  : // 發送 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  : // 發送 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  : // 發送 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  : // 發送 停止位
                            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 ; // 一幀數據發送完畢以后就關掉波特率時鍾使能信號
        R_state        <= 4'd0 ;
        O_tx_done      <= 1'b0 ;
        O_rs232_txd      <= 1'b1 ;
      end
end

endmodule

  其中當檢測到輸入信號I_tx_start為高電平以后,發送模塊立即把R_transmiting信號拉高,表示開始要發送數據了,在R_transmiting為高電平的期間,打開波特率時鍾使能信號並且在波特率時鍾的控制下通過一個狀態機把並行數據發送出去,並產生發送完成信號O_tx_done,等O_tx_done為高以后再把R_transmiting拉低表示一次發送結束。

  為了實現功能1的效果還需要編寫一個頂層模塊把波特率模塊和發送模塊例化進去並產生發送的信號,頂層模塊的代碼如下:

module uart_tx_top
(
    input          I_clk           , // 系統50MHz時鍾
    input          I_rst_n         , // 系統全局復位
    output         O_rs232_txd       // 發送的串行數據,在硬件上與串口相連
);

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        ;

/////////////////////////////////////////////////////////////////////
// 產生要發送的數據
/////////////////////////////////////////////////////////////////////
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             ;
        end
end

uart_txd U_uart_txd
(
    .I_clk               (I_clk                 ), // 系統50MHz時鍾
    .I_rst_n             (I_rst_n               ), // 系統全局復位
    .I_tx_start          (W_tx_start            ), // 發送使能信號
    .I_bps_tx_clk        (W_bps_tx_clk          ), // 波特率時鍾
    .I_para_data         (W_para_data           ), // 要發送的並行數據
    .O_rs232_txd         (O_rs232_txd           ), // 發送的串行數據,在硬件上與串口相連
    .O_bps_tx_clk_en     (W_bps_tx_clk_en       ), // 波特率時鍾使能信號
    .O_tx_done           (W_tx_done             )  // 發送完成的標志
);

baudrate_gen U_baudrate_gen
(
    .I_clk              (I_clk              ), // 系統50MHz時鍾
    .I_rst_n            (I_rst_n            ), // 系統全局復位
    .I_bps_tx_clk_en    (W_bps_tx_clk_en    ), // 串口發送模塊波特率時鍾使能信號
    .I_bps_rx_clk_en    (                   ), // 串口接收模塊波特率時鍾使能信號
    .O_bps_tx_clk       (W_bps_tx_clk       ), // 發送模塊波特率產生時鍾
    .O_bps_rx_clk       (                   )  // 接收模塊波特率產生時鍾
);

endmodule

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

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

4.3、接收模塊的設計與實現

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

  其中:

  I_clk是系統時鍾;

  I_rst_n是系統復位;

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

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

  I_rs232_rx是串行的bit數據流;

  O_para_data[7:0]是並行的8-bit數據;

  O_bps_rx_clk_en是發射波特率時鍾啟動信號,當它為1是波特率產生模塊才能產生接收模塊的波特率時鍾;

  O_rx_done是接收1字節數據完成的標志位,當一個字節接收完畢以后,O_rx_done產生一個高脈沖。

  接收模塊與發射模塊的邏輯結構非常類似,但是由於接收模塊需要判斷串行數據流的起始位,所以還要加一段檢測串行數據流下降沿的邏輯,檢測串行數據流下降沿的代碼如下:

 

////////////////////////////////////////////////////////////////////////////////
// 功能:把 I_rs232_rxd 打的前兩拍,是為了消除亞穩態
//      把 I_rs232_rxd 打的后兩拍,是為了產生下降沿標志位
////////////////////////////////////////////////////////////////////////////////
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
// 產生I_rs232_rxd信號的下降沿標志位
assign W_rs232_rxd_neg    =    (~R_rs232_rx_reg2) & R_rs232_rx_reg3 ;

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

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

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

  

module uart_rxd
(
    input                       I_clk               , // 系統50MHz時鍾
    input                       I_rst_n             , // 系統全局復位
    input                       I_rx_start          , // 接收使能信號
    input                       I_bps_rx_clk        , // 接收波特率時鍾
    input                       I_rs232_rxd         , // 接收的串行數據,在硬件上與串口相連  
    output    reg               O_bps_rx_clk_en     , // 波特率時鍾使能信號
    output    reg               O_rx_done           , // 接收完成標志
    output    reg   [7:0]       O_para_data           // 接收到的8-bit並行數據
);

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 打的前兩拍,是為了消除亞穩態
//          把 I_rs232_rxd 打的后兩拍,是為了產生下降沿標志位
////////////////////////////////////////////////////////////////////////////////
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
// 產生I_rs232_rxd信號的下降沿標志位
assign W_rs232_rxd_neg    =    (~R_rs232_rx_reg2) & R_rs232_rx_reg3 ;

////////////////////////////////////////////////////////////////////////////////
// 功能:產生發送信號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

////////////////////////////////////////////////////////////////////////////////
// 功能:用狀態機把串行的輸入數據接收,並轉化為並行數據輸出
////////////////////////////////////////////////////////////////////////////////
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 ; // 接收完畢以后關閉波特率時鍾使能信號
        end          
end

endmodule

 

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

 

module uart_top
(
    input             I_clk           , // 系統50MHz時鍾
    input             I_rst_n         , // 系統全局復位
    output    [3:0]   O_led_out       ,
    output            O_rs232_txd       // 發送的串行數據,在硬件上與串口相連
);

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] ;

/////////////////////////////////////////////////////////////////////
// 產生要發送的數據
/////////////////////////////////////////////////////////////////////
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                ;
        end
end

uart_txd U_uart_txd
(
    .I_clk               (I_clk                 ), // 系統50MHz時鍾
    .I_rst_n             (I_rst_n               ), // 系統全局復位
    .I_tx_start          (W_tx_start            ), // 發送使能信號
    .I_bps_tx_clk        (W_bps_tx_clk          ), // 波特率時鍾
    .I_para_data         (W_para_data           ), // 要發送的並行數據
    .O_rs232_txd         (O_rs232_txd           ), // 發送的串行數據,在硬件上與串口相連
    .O_bps_tx_clk_en     (W_bps_tx_clk_en       ), // 波特率時鍾使能信號
    .O_tx_done           (W_tx_done             )  // 發送完成的標志
);

baudrate_gen U_baudrate_gen
(
    .I_clk              (I_clk              ), // 系統50MHz時鍾
    .I_rst_n            (I_rst_n            ), // 系統全局復位
    .I_bps_tx_clk_en    (W_bps_tx_clk_en    ), // 串口發送模塊波特率時鍾使能信號
    .I_bps_rx_clk_en    (W_bps_rx_clk_en    ), // 串口接收模塊波特率時鍾使能信號
    .O_bps_tx_clk       (W_bps_tx_clk       ), // 發送模塊波特率產生時鍾
    .O_bps_rx_clk       (W_bps_rx_clk       )  // 接收模塊波特率產生時鍾
);

uart_rxd U_uart_rxd
(
    .I_clk              (I_clk              ), // 系統50MHz時鍾
    .I_rst_n            (I_rst_n            ), // 系統全局復位
    .I_rx_start         (1'b1               ), // 接收使能信號
    .I_bps_rx_clk       (W_bps_rx_clk       ), // 接收波特率時鍾
    .I_rs232_rxd        (O_rs232_txd        ), // 接收的串行數據,在硬件上與串口相連  
    .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並行數據
);

endmodule

  仿真圖如下圖所示

 

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

4.4串口回顯功能的設計與實現

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

 

module uart_top
(
    input            I_clk           , // 系統50MHz時鍾
    input            I_rst_n         , // 系統全局復位
    input            I_rs232_rxd     , // 接收的串行數據,在硬件上與串口相連
    output           O_rs232_txd     , // 發送的串行數據,在硬件上與串口相連
    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              ), // 系統50MHz時鍾
    .I_rst_n            (I_rst_n            ), // 系統全局復位
    .I_bps_tx_clk_en    (W_bps_tx_clk_en    ), // 串口發送模塊波特率時鍾使能信號
    .I_bps_rx_clk_en    (W_bps_rx_clk_en    ), // 串口接收模塊波特率時鍾使能信號
    .O_bps_tx_clk       (W_bps_tx_clk       ), // 發送模塊波特率產生時鍾
    .O_bps_rx_clk       (W_bps_rx_clk       )  // 接收模塊波特率產生時鍾
);

uart_txd U_uart_txd
(
    .I_clk               (I_clk                 ), // 系統50MHz時鍾
    .I_rst_n             (I_rst_n               ), // 系統全局復位
    .I_tx_start          (W_rx_done             ), // 發送使能信號
    .I_bps_tx_clk        (W_bps_tx_clk          ), // 波特率時鍾
    .I_para_data         (W_para_data           ), // 要發送的並行數據
    .O_rs232_txd         (O_rs232_txd           ), // 發送的串行數據,在硬件上與串口相連
    .O_bps_tx_clk_en     (W_bps_tx_clk_en       ), // 波特率時鍾使能信號
    .O_tx_done           (W_tx_done             )  // 發送完成的標志
);

uart_rxd U_uart_rxd
(
    .I_clk              (I_clk                ), // 系統50MHz時鍾
    .I_rst_n            (I_rst_n              ), // 系統全局復位
    .I_rx_start         (1'b1                 ), // 接收使能信號
    .I_bps_rx_clk       (W_bps_rx_clk         ), // 接收波特率時鍾
    .I_rs232_rxd        (I_rs232_rxd          ), // 接收的串行數據,在硬件上與串口相連  
    .O_bps_rx_clk_en    (W_bps_rx_clk_en      ), // 波特率時鍾使能信號
    .O_rx_done          (W_rx_done            ), // 接收完成標志
    .O_para_data        (W_para_data          )  // 接收到的8-bit並行數據
);

endmodule

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

五、進一步思考

5.1波特率模塊產生的O_bps_tx_clk滯后O_bps_rx_clk可能出現的問題

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

 

module baudrate_gen
(
    input   I_clk                  , // 系統50MHz時鍾
    input   I_rst_n                , // 系統全局復位
    input   I_bps_tx_clk_en        , // 串口發送模塊波特率時鍾使能信號
    input   I_bps_rx_clk_en        , // 串口接收模塊波特率時鍾使能信號
    output  O_bps_tx_clk           , // 發送模塊波特率產生時鍾
    output  O_bps_rx_clk           // 接收模塊波特率產生時鍾
);

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       ;

///////////////////////////////////////////////////////////    
// 功能:串口發送模塊的波特率時鍾產生邏輯
///////////////////////////////////////////////////////////
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             
end

assign O_bps_tx_clk = R_bps_tx_clk_reg ;

///////////////////////////////////////////////////////////    
// 功能:串口接收模塊的波特率時鍾產生邏輯
///////////////////////////////////////////////////////////
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 ;        
end

assign O_bps_rx_clk = (R_bps_rx_cnt == C_BPS_SELECT >> 1'b1) ? 1'b1 : 1'b0 ;

endmodule

  其仿真如下所示:

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

5.2發送數據的狀態機和接收數據的狀態機可以用移位的方式來做

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

 

歡迎關注我的公眾號:FPGA之禪

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM