FPGA之UART簡單通訊篇(1)

寫在前面,但願記錄一下本身動手作的事情,拿來主義永遠是別人的異步

一、UART串口簡介async

串口顧名思義,是將字節數據以bit位進行串行傳輸,特色是線路簡單,速度較慢。因此經常使用在工業控制,嵌入式等數據傳輸速度要求不高的場景中。測試

擴展:串行通訊分兩種:同步串行和異步串行通訊spa

同步串行:通訊雙方在同一時鐘控制下,同步傳輸數據debug

異步串行:通訊雙方使用各自時鐘控制數據發送與接收過程設計

二、UART串口接線與數據與數據包格式調試

 

接線:UART是一種採用異步串行通訊方式的通用異步收發傳輸器(universal asynchronous receiver-transmitter),它在發送數據時將並行數據轉換成串行數據來傳輸,在接收數據時將接收到的串行數據轉換成並行數據。code

UART串口通訊須要兩根信號線來實現,一根用於串口發送,另一根負責串口接收。blog

 串口接線ip

 

數據包格式: 在進行具體的串口設計以前,先了解串口通訊協議。一般串口的一次發送或接收由四個部分組成:起始位S(「通常爲邏輯‘0’)、數據位D0~D7(通常爲6位~8位之間可變,數據低位在前)、校驗位(奇校驗、偶檢驗或不須要校驗位)、中止位(一般爲1位、1.5位、2位)。中止位必須爲邏輯1。在一次串口通訊過程當中,數據接收與發送雙方沒有共享時鐘,所以,雙方必須協商好數據傳輸波特率。波特率即數據傳輸速率。根據雙方協議好的傳輸速率,接收端便可對發送端的數據進行採樣。經常使用的波特率有9600、19200、38400、57600以及115200等。固然更塊的速度意味着對採樣的要求更高,有可能誤碼率會逐漸提升。

      一般對串口進行數據採樣,採用更高頻的時鐘。這樣作的目的是採用高頻時鐘來鎖存低頻時鐘,減小數據的誤碼率,增長接收模塊的自糾錯能力。

具體的工做流程爲:

      發送端按照預先設定好的波特率,發送起始位(Start)+數據位(data)+奇偶校驗位+結束位。其中,起始位爲邏輯0,結束位爲邏輯1,發送端在空閒狀態爲1。發送數據包格式以下圖所示。

 

數據包格式

 

  數據的奇偶校驗位是能夠選擇的,若是不使用奇偶,那麼就沒有這個數據位,本設計中沒有用到奇偶校驗位。

 三、設計 

接收端經過檢測電平‘1’到‘0’的跳變來肯定一個數據包的開始。肯定開始位接收完成以後,依次接收數據,使用更高的採樣時鐘,完成數據採集。接收完數據位後,繼續接收奇偶校驗位和中止位。

串口的接收與發送,其主要時序設計包括兩個部分:一、波特率的產生時序;二、數據傳輸時序,包括接收與發送。

       波特率產生時序設計:假設FPGA輸入時鐘100Mhz,爲獲得經常使用的波特率,仍然採用計數分頻來獲得。BAUD_DIV=100_000000/波特率。其中採樣中心點爲發送或接收時鐘的中心點,即BAUD_DIV_CAP=100_000000/(2*波特率)。該部分在數據接收和發送部分均單獨完成。以下圖所示

 

 

 

 方案圖

       數據接收模塊:在設置好傳輸波特率的狀況下,根據串口傳輸時序,進行解串。空閒狀態時,接收 數據爲邏輯高電平,等待起始位邏輯低電平的到來。當起始位到達後,由低位到高位,依次採集8位數據,並進行相應的解串,存入臨時寄存器。接收有效數據完成後,判斷結束位,接收完畢。

       數據發送模塊:設置發送使能信號和待發送的數據。經過計數器,表示10個數據發送的週期。這10個數據,依次爲起始位+8位數據位+1位結束位,實現數據位的逐個發送。

RX模塊:

`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Company: 
// Engineer: 
// 
// Create Date: 2020/09/21 13:51:32
// Design Name: 
// Module Name: UART_RX
// Project Name: 
// Target Devices: 
// Tool Versions: 
// Description: 
// 
// Dependencies: 
// 
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
// 
//////////////////////////////////////////////////////////////////////////////////


module uart_rx_path(
    input            clk_100m,
    input            uart_rx_i,
    
    output    [7:0]    uart_rx_data_o,
    output            uart_rx_done,
    output            baud_bps_sim


);

    parameter    [13:0]    BAUD_DIV    =    14'd13020;    //9600bps    ,50Mhz
    parameter    [13:0]    BAUD_DIV_MID=    14'd6510;    //collection mid 
    
(*mark_debug="true"*)    reg    [13:0]    baud_div        =0;
(*mark_debug="true"*)    reg            baud_bps        =0;
(*mark_debug="true"*)    reg            bps_start        =0;
    
    always    @(posedge    clk_100m)
    begin
        if(baud_div==BAUD_DIV_MID)begin
            baud_bps<= 1'b1;
            baud_div<= baud_div +1'b1;
            end
        else if(baud_div<BAUD_DIV && bps_start)begin//當波特率啓動時,計數器累加
            baud_div <= baud_div +1'b1;
            baud_bps <= 0;
            end
        else begin
            baud_bps<=0;
            baud_div<=0;
            end
    end

(*mark_debug="true"*)    reg    [4:0]    uart_rx_i_r    =    5'b11111;    
    always @(posedge clk_100m)
    begin
        uart_rx_i_r <= {uart_rx_i_r[3:0],uart_rx_i};
    end

    wire    uart_rx_int    = uart_rx_i_r[4]|uart_rx_i_r[3]|uart_rx_i_r[2]|uart_rx_i_r[1]|uart_rx_i_r[0];
    
(*mark_debug="true"*)    reg[3:0]    bit_num    =    0;
(*mark_debug="true"*)    reg            uart_rx_done_r    =0;
(*mark_debug="true"*)    reg            state    =1'b0;
    
(*mark_debug="true"*)    reg    [7:0]    uart_rx_data_0_r0    =    0; //process
    reg    [7:0]    uart_rx_data_0_r1    =    0;    //done
    
    always    @(posedge clk_100m)
    begin
        uart_rx_done_r <= 1'b0;
    case (state)
    1'b0:
        if(!uart_rx_int)begin
            bps_start<=1'b1;
            state<=1'b1;
        end
    1'b1:
        if(baud_bps)
        begin
            bit_num<=bit_num+1'b1;    
            if(bit_num<4'd9)        
                uart_rx_data_0_r0[bit_num-1]<= uart_rx_i;
            //else
               //uart_rx_data_0_r0 <= uart_rx_data_0_r0;     
        end
        else if (bit_num==4'd10)begin
            bit_num <= 0;
            uart_rx_done_r <= 1'b1;
            uart_rx_data_0_r1 <= uart_rx_data_0_r0;
            state<=1'b0;//進入狀態0,再次循環檢測
            bps_start<=0;
        end
    default:;

    endcase

    end
    
    assign baud_bps_tb=baud_bps;//for simulation

    assign uart_rx_data_o=uart_rx_data_0_r1;

    assign uart_rx_done=uart_rx_done_r;

endmodule

TX模塊

module uart_tx_path(

input clk_i,



input [7:0] uart_tx_data_i, //待發送數據

input uart_tx_en_i, //發送發送使能信號



output uart_tx_o

);



parameter BAUD_DIV     = 14'd13020;//波特率時鐘,9600bps,100Mhz/9600=10416,波特率可調

parameter BAUD_DIV_CAP = 14'd6510;//波特率時鐘中間採樣點,100Mhz/9600/2=d208,波特率可調



(*mark_debug="true"*)reg [13:0] baud_div=0; //波特率設置計數器

(*mark_debug="true"*)reg baud_bps=0; //數據發送點信號,高有效

reg [9:0] send_data=10'b1111111111;//待發送數據寄存器,1bit起始信號+8bit有效信號+1bit結束信號

(*mark_debug="true"*)reg [3:0] bit_num=0; //發送數據個數計數器

(*mark_debug="true"*)reg uart_send_flag=0; //數據發送標誌位

(*mark_debug="true"*)reg uart_tx_o_r=1; //發送數據寄存器,初始狀態位高



always@(posedge clk_i)

begin

if(baud_div==BAUD_DIV_CAP) //當波特率計數器計數到數據發送中點時,產生採樣信號baud_bps,用來發送數據

begin

baud_bps<=1'b1;

baud_div<=baud_div+1'b1;

end

else if(baud_div<BAUD_DIV && uart_send_flag)//數據發送標誌位有效期間,波特率計數器累加,以產生波特率時鐘

begin

baud_div<=baud_div+1'b1;

baud_bps<=0;

end

else

begin

baud_bps<=0;

baud_div<=0;

end

end



always@(posedge clk_i)

begin

if(uart_tx_en_i) //接收數據發送使能信號時,產生數據發送標誌信號

begin

uart_send_flag<=1'b1;

send_data<={1'b1,uart_tx_data_i,1'b0};//待發送數據寄存器裝填,1bit起始信號0+8bit有效信號+1bit結束信號

end

else if(bit_num==4'd10) //發送結束時候,清楚發送標誌信號,並清楚待發送數據寄存器內部信號

begin

uart_send_flag<=1'b0;

send_data<=10'b1111_1111_11;

end

end



always@(posedge clk_i)

begin

if(uart_send_flag) //發送有效時候

begin

if(baud_bps)//檢測發送點信號

begin

if(bit_num<=4'd9)

begin

uart_tx_o_r<=send_data[bit_num]; //發送待發送寄存器內數據,從低位到高位

bit_num<=bit_num+1'b1;

end

end

else if(bit_num==4'd10)

bit_num<=4'd0;

end

else

begin

uart_tx_o_r<=1'b1; //空閒狀態時,保持發送端位高電平,以備發送時候產生低電平信號

bit_num<=0;

end

end



assign uart_tx_o=uart_tx_o_r;



endmodule

TOP模塊

module uart_top(

input clk_i_p,
input clk_i_n,

//input   clk_i,


input uart_rx_i,



output uart_tx_o

    );



wire [7:0] uart_rx_data_o;

wire uart_rx_done;
wire   clk_i;
   IBUFDS IBUFDS_inst (
      .O(clk_i),   // 1-bit output: Buffer output
      .I(clk_i_p),   // 1-bit input: Diff_p buffer input (connect directly to top-level port)
      .IB(clk_i_n)  // 1-bit input: Diff_n buffer input (connect directly to top-level port)
   );



    

uart_rx_path uart_rx_path_u (

    .clk_100m(clk_i),

    .uart_rx_i(uart_rx_i),



    .uart_rx_data_o(uart_rx_data_o),

    .uart_rx_done(uart_rx_done)

    );

    

uart_tx_path uart_tx_path_u (

    .clk_i(clk_i),

    .uart_tx_data_i(uart_rx_data_o),

    .uart_tx_en_i(uart_rx_done),

    .uart_tx_o(uart_tx_o)

    );

    

endmodule

Test_bench(本文開發板是KCU105,用的125差分,在仿真時,將頂層差分時鐘屏蔽,復原clk_i單端便可,波特率使用125Mhz/9600,改成100Mhz/9600便可)

 
 

`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Company:
// Engineer:
//
// Create Date: 2020/09/21 13:55:20
// Design Name:
// Module Name: uart_top_tb
// Project Name:
// Target Devices:
// Tool Versions:
// Description:
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
//////////////////////////////////////////////////////////////////////////////////

 
 


module uart_top_TB;

 
 

 

 
 

reg clk_i_p;

 
 

reg clk_i_n;

 
 

reg rst_n_i;

 
 

reg uart_rx_i;

 
 

wire [7:0] uart_tx_o;

 
 

 

 
 

uart_top u_uart_top

 
 

(

 
 

.clk_i_p (clk_i_p),
.clk_i_n (clk_i_n),
//.rst_n_i (rst_n_i),

 
 

.uart_rx_i (uart_rx_i),

 
 

.uart_tx_o (uart_tx_o)

 
 

);

 
 

 

 
 

initial

 
 

begin

 
 

clk_i_p = 0;
clk_i_n = 1;
rst_n_i = 0;

 
 

uart_rx_i = 1'b1;

 
 

 

 
 

// Wait 100 ns for global reset to finish

 
 

#96;

 
 

rst_n_i=1;

 
 

#104170

 
 

uart_rx_i = 1'b1;

 
 

#104170

 
 

uart_rx_i = 1'b0;//start

 
 

//1001_0101

 
 

#104170

 
 

uart_rx_i = 1'b1;

 
 

#104170

 
 

uart_rx_i = 1'b0;

 
 

#104170

 
 

uart_rx_i = 1'b1;

 
 

#104170

 
 

uart_rx_i = 1'b0;

 
 

#104170

 
 

uart_rx_i = 1'b1;

 
 

#104170

 
 

uart_rx_i = 1'b0;

 
 

#104170

 
 

uart_rx_i = 1'b0;

 
 

#104170

 
 

uart_rx_i = 1'b1;

 
 

#104170

 
 

uart_rx_i = 1'b1;//stop

 
 

#808320

 
 

//00000101

 
 

uart_rx_i = 1'b0;//start

 
 

#104170

 
 

uart_rx_i = 1'b1;

 
 

#104170

 
 

uart_rx_i = 1'b0;

 
 

#104170

 
 

uart_rx_i = 1'b1;

 
 

#104170

 
 

uart_rx_i = 1'b0;

 
 

#104170

 
 

uart_rx_i = 1'b0;

 
 

#104170

 
 

uart_rx_i = 1'b0;

 
 

#104170

 
 

uart_rx_i = 1'b0;

 
 

#104170

 
 

uart_rx_i = 1'b0;

 
 

#104170

 
 

uart_rx_i = 1'b1;//stop

 
 

 

 
 

#808320

 
 

//10000100

 
 

uart_rx_i = 1'b0;//start

 
 

#104170

 
 

uart_rx_i = 1'b0;

 
 

#104170

 
 

uart_rx_i = 1'b0;

 
 

#104170

 
 

uart_rx_i = 1'b1;

 
 

#104170

 
 

uart_rx_i = 1'b0;

 
 

#104170

 
 

uart_rx_i = 1'b0;

 
 

#104170

 
 

uart_rx_i = 1'b0;

 
 

#104170

 
 

uart_rx_i = 1'b0;

 
 

#104170

 
 

uart_rx_i = 1'b1;

 
 

#104170

 
 

uart_rx_i = 1'b1;//stop

 
 

end

 
 

 

 
 

always

 
 

begin

 
 

#2 clk_i_p = ~clk_i_p;
#2 clk_i_n = ~clk_i_n;
end

 
 

 

 
 

endmodule



仿真發送的第一個數據是,10010101,,對應的Hex爲95,第二個是00000101,hex爲05。

 

四、綜合佈線前仿真以下:

 

 

 

 

 接收模塊正確

 

 

 發送仿真ok

 

 

五、上板子調試

結果

 

測試結果

 有誤碼狀況:

波特率提升時,會有誤碼出現,後面有時間再來改善一下。

 

誤碼圖:

 

 

原先的Kcu105自帶USB轉串口,可是實際過程當中效果不理想(出現出廠自帶的測試東西),因此我用的之前的PL2303這個USB轉串口,外掛了兩個Pin管腳,效果還行。

相關文章
相關標籤/搜索