FPGA之UART簡單通信篇(1)


寫在前面,希望記錄一下自己動手做的事情,拿來主義永遠是別人的

1、UART串口簡介

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

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

同步串行:通信雙方在同一時鍾控制下,同步傳輸數據

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

2、UART串口接線與數據與數據包格式

 

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

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

 串口接線

 

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

      通常對串口進行數據采樣,采用更高頻的時鍾。這樣做的目的是采用高頻時鍾來鎖存低頻時鍾,減少數據的誤碼率,增加接收模塊的自糾錯能力。

具體的工作流程為:

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

 

數據包格式

 

  數據的奇偶校驗位是可以選擇的,如果不使用奇偶,那么就沒有這個數據位,本設計中沒有用到奇偶校驗位。

 3、設計 

接收端通過檢測電平‘1’到‘0’的跳變來確定一個數據包的開始。確定開始位接收完成之后,依次接收數據,使用更高的采樣時鍾,完成數據采集。接收完數據位后,繼續接收奇偶校驗位和停止位。

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

       波特率產生時序設計:假設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。

 

4、綜合布線前仿真如下:

 

 

 

 

 接收模塊正確

 

 

 發送仿真ok

 

 

5、上板子調試

結果

 

測試結果

 有誤碼情況:

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

 

誤碼圖:

 

 

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

 


免責聲明!

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



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