當前位置:網站首頁>基於FPGA的UART接口設計

基於FPGA的UART接口設計

2022-07-23 18:59:35qq_44985628

一、頂層設計思路:

    UART即通用异步收發傳輸接口(Universal Asynchronous Receiver/Transmitter),簡稱串口,是一種常用的通信接口,其協議原理就不贅述了,不了解的可以自己查閱資料。(不贅述不代錶不重要,相反,對於每一個FPGA設計,充分理解原理是基礎和前提,而FPGA和Verilog只是工具。)用FPGA來實現UART,關鍵就是要將UART收發數據時的時序用Verilog描述出來。

    根據UART協議的原理,可以將整個UART分為兩個模塊:串口接收模塊“UART_RX”和串口發送模塊“UART_TX”,前者將接收到的1比特串行數據“uart_rxd”轉化為8比特並行數據“data[7:0]”,後者又將8比特並行數據“data[7:0]”轉化回1比特串行數據“uart_txd”輸出,最終實現串行數據的收發。UART頂層功能框圖如圖1所示:

在這裏插入圖片描述
二、串口接收模塊設計思路:

該模塊實現將接收到的1比特串行數據轉化為8比特並行數據,而只有當有數據輸入時,該模塊才去工作,所以需要一個使能信號“rxd_en”來控制該模塊,當“rxd_en”有效時,該模塊才工作。根據UART協議原理,接收到的1比特串行數據最開始的一比特為起始比特(“0”),而“uart_rxd”在空閑時為“1”,故剛開始接收到串行數據時,“uart_rxd”必定會產生一個下降沿,所以可以檢測這個下降沿,每當下降沿到來的同時將“rxd_en”置為有效,從而使該模塊開始工作;每當所有串行數據都被接收完畢時,再把“rxd_en”置為無效,同時發出接收完成標志“rx_done”,從而關閉該模塊。(關於在Verilog裏怎麼實現實現邊沿檢測,可以看我寫的關於DDS信號發生器的那篇博文。)

在數據接收的過程中,速率以串口波特率為准,常用的串口波特率有:1200、2400、4800、9600、14400、57600、115200等等。所以需要一個波特率計數器“baud_cnt”在特定的波特率下對主時鐘進行計數,每當這個波特率計數器計滿時,接收一比特數據。例如,主時鐘為50MHz,當波特率為9600時,波特率計數器的最大值應該為:50000000/9600-1=5207,此時,每當波特率計數器計到5207時就清零,同時接收一比特串行數據。

在本次設計中,每個數據的數據比特共有10比特(1比特起始比特、8比特數據比特、1比特停止比特),故在接收過程中,還需要一個比特計數器“bit_cnt”來對每個串行數據的數據比特進行計數,具體操作為:每當波特率計數器計滿時,比特計數器就自增1,直到比特計數器的值為9時清零。

    在接收過程中,為了接收到穩定的串行數據,本設計在每一比特串行數據的中間對其進行采樣和接收,具體操作為:每當波特率計數器計到最大值的一半時,就對當前的串行數據進行采樣,然後根據比特計數器的值,將采樣後的值賦給相應的並行數據比特。此外,為了消除亞穩態,待接收的串行數據應該先通過一個兩比特的寄存器進行緩沖後再進行邊沿檢測。

    根據以上分析,做出串口接收模塊的時序圖如圖2所示(“uart_rxd_r”是“uart_rxd”消除亞穩態後再通過邊沿檢測寄存器後的信號):

在這裏插入圖片描述
根據時序圖,就可以進行串口接收模塊的RTL描述,編寫的Verilog代碼如下:

`timescale 1ns / 1ps
 
module UART_RX(
    input clk, //主時鐘,50MHz
    input rst, //複比特,高電平有效
    input uart_tx_data, //發送給串口的串行數據
    
    output reg [7:0] uart_rx_data, //串口接收後的並行數據
    output reg rx_done //接收完成標志
    );
    
    parameter CLK_F = 50000000; //主時鐘頻率
    parameter UART_B = 9600; //串口波特率
    parameter B_CNT = CLK_F / UART_B; //波特率計數器的最大值
    
    reg [1:0] uart_tx_data_r1; //用於消除輸入串行數據的亞穩態
    reg [1:0] uart_tx_data_r2; //輸入串行數據邊沿檢測寄存器
    reg rxd_en; //接收使能信號
    reg [15:0] baud_cnt = 16'd0; //115200波特率計數器
    reg [3:0] bit_cnt = 4'd0; //比特計數器
    reg [7:0] rx_data; //接收數據寄存器
 
    
/***************消除輸入串行數據亞穩態*************/
    always @ (posedge clk)
    begin
        uart_tx_data_r1 <= {
    uart_tx_data_r1[0] , uart_tx_data};
    end
 
/***************輸入串行數據邊沿檢測*************/
    always @ (posedge clk)
    begin
        uart_tx_data_r2 <= {
    uart_tx_data_r2[0] , uart_tx_data_r1[1]};
    end
 
/***************接收使能信號控制*************/
    always @ (posedge clk or negedge rst)
    begin
        if (rst)
            rxd_en <= 1'd0;
        else
        begin 
            if (uart_tx_data_r2 == 2'b10)
                rxd_en <= 1'd1;
            else if ((bit_cnt == 4'd9) && (baud_cnt == B_CNT / 2))
                rxd_en <= 1'd0;
            else 
                rxd_en <= rxd_en;
        end
    end
 
/***************波特率計數器控制*************/
    always @ (posedge clk or negedge rst)
    begin
        if (rst)
            baud_cnt <= 16'd0;
        else if (rxd_en)
        begin
            if (baud_cnt == B_CNT - 1)
                baud_cnt <= 16'd0;
            else
                baud_cnt <= baud_cnt + 1'b1;
        end
        else
            baud_cnt <= 16'd0;
    end
 
/***************比特計數器控制*************/
    always @ (posedge clk or negedge rst)
    begin
        if (rst)
            bit_cnt <= 4'd0;
        else if (rxd_en)
        begin
            if (baud_cnt == B_CNT - 1)
                bit_cnt <= bit_cnt + 1'b1;
            else
                bit_cnt <= bit_cnt;
        end
        else
            bit_cnt <= 4'd0;
    end
 
/***************接收緩存*************/
    always @ (posedge clk or negedge rst)
    begin
        if (rst)
            rx_data <= 8'd0;
        else if (rxd_en)
        begin
            if (baud_cnt == B_CNT / 2)
            begin
                case (bit_cnt)
                    4'd1:rx_data[0] <= uart_tx_data_r2[1];
                    4'd2:rx_data[1] <= uart_tx_data_r2[1];
                    4'd3:rx_data[2] <= uart_tx_data_r2[1];
                    4'd4:rx_data[3] <= uart_tx_data_r2[1];
                    4'd5:rx_data[4] <= uart_tx_data_r2[1];
                    4'd6:rx_data[5] <= uart_tx_data_r2[1];
                    4'd7:rx_data[6] <= uart_tx_data_r2[1];
                    4'd8:rx_data[7] <= uart_tx_data_r2[1];
                    default:;
                endcase
            end
            else
                rx_data <= rx_data;
        end
        else
            rx_data <= 8'd0;
    end
 
/***************接收*************/
    always @ (posedge clk or negedge rst)
    begin
        if (rst)
            uart_rx_data <= 8'd0;
        else if (bit_cnt == 4'd9)
            uart_rx_data <= rx_data;
        else
            uart_rx_data <= 8'd0;
    end
 
/***************接收完成標志控制*************/
    always @ (posedge clk or negedge rst)
    begin
        if (rst)
            rx_done <= 1'd0;
        else if (bit_cnt == 4'd9)
            rx_done <= 1'd1;
        else
            rx_done <= 1'd0;
    end
    
endmodule 

編寫的testbeach如下:

`timescale 1ns / 1ps
module tb_uart_rx();
 
    reg clk; //主時鐘,50MHz
    reg rst; //複比特,低電平有效
    reg uart_tx_data; //發送給串口的串行數據
    
    wire [7:0] uart_rx_data; //串口接收後的並行數據
    wire rx_done; //接收完成標志
   
/***************模塊例化*************/ 
    UART_RX tb_uart_rx(
        .clk(clk),      
        .rst(rst),     
        .uart_tx_data(uart_tx_data), 
                             
        .uart_rx_data(uart_rx_data), 
        .rx_done(rx_done)  
    );
 
/***************產生主時鐘*************/
    always #10 clk = ~clk;
    
/***************初始化*************/
    initial 
    begin
        clk = 1'd0;
        rst = 1'd0;
        uart_tx_data = 1'b1;
        #1000000
        uart_tx_data = 1'b0;
        #200000
        uart_tx_data = 1'b1;
        #200000
        uart_tx_data = 1'b0;
        #200000
        uart_tx_data = 1'b1;
    end
   
endmodule

仿真結果如圖3所示,可以看到接收功能已實現:
在這裏插入圖片描述
三、串口發送模塊設計思路:

    該模塊實現將8比特並行數據轉化回1比特串行數據輸出,與接收模塊一樣,串口發送模塊也需要一個使能信號“txd_en”來控制,當“txd_en”有效時,模塊才工作。根據UART原理,只有接收模塊接收完成後,發送模塊才能開始工作,故接收模塊裏的接收完成標志即是發送模塊的發送開始標志“txd_start”,所以可以通過檢測發送開始標志的上昇沿來使能“txd_en”,從而使能發送模塊。

    根據以上分析,做出串口發送模塊的時序圖如圖4所示:

在這裏插入圖片描述
根據時序圖,編寫的Verilog代碼如下:

 `timescale 1ns / 1ps
 
module UART_TX(
    input clk, //主時鐘,50MHz
    input rst, //複比特,高電平有效
    input txd_start, //發送開始標志
    input [7:0] uart_rx_data, //串口接收到的並行數據
    
    output reg uart_tx_data //串口發送的串行數據
    );
    
    parameter CLK_F = 50000000; //主時鐘頻率
    parameter UART_B = 9600; //串口波特率
    parameter B_CNT = CLK_F / UART_B; //波特率計數器的最大值
    
    reg [1:0] txd_start_r; //發送開始標志邊沿檢測寄存器 
    reg txd_en; //發送使能信號
    reg [15:0] baud_cnt = 16'd0; //115200波特率計數器
    reg [3:0] bit_cnt = 4'd0; //比特計數器
    reg [7:0] tx_data; //發送數據寄存器
 
/***************發送開始標志邊沿檢測*************/
    always @ (posedge clk)
    begin
        txd_start_r <= {
    txd_start_r[0] , txd_start};
    end
    
/***************發送使能信號控制*************/
    always @ (posedge clk or negedge rst)
    begin
        if (rst)
            txd_en <= 1'd0;
        else
        begin 
            if (txd_start_r == 2'b01)
                txd_en <= 1'd1;
            else if ((bit_cnt == 4'd9) && (baud_cnt == B_CNT / 2))
                txd_en <= 1'd0;
            else 
                txd_en <= txd_en;
        end
    end
    
/***************發送緩存*************/
    always @ (posedge clk or negedge rst)
    begin
        if (rst)
            tx_data <= 8'd0;
        else
        begin 
            if (txd_start_r == 2'b01)
                tx_data <= uart_rx_data;
            else if ((bit_cnt == 4'd9) && (baud_cnt == B_CNT / 2))
                tx_data <= 8'd0;
            else 
                tx_data <= tx_data;
        end
    end
    
/***************波特率計數器控制*************/
    always @ (posedge clk or negedge rst)
    begin
        if (rst)
            baud_cnt <= 16'd0;
        else if (txd_en)
        begin
            if (baud_cnt == B_CNT - 1)
                baud_cnt <= 16'd0;
            else
                baud_cnt <= baud_cnt + 1'b1;
        end
        else
            baud_cnt <= 16'd0;
    end
 
/***************比特計數器控制*************/
    always @ (posedge clk or negedge rst)
    begin
        if (rst)
            bit_cnt <= 4'd0;
        else if (txd_en)
        begin
            if (baud_cnt == B_CNT - 1) 
                bit_cnt <= bit_cnt + 1'b1;
            else
                bit_cnt <= bit_cnt;
        end
        else
            bit_cnt <= 4'd0;
    end
 
/***************發送*************/
    always @ (posedge clk or negedge rst)
    begin
        if (rst)
            uart_tx_data <= 1'd1;
        else if (txd_en)
        begin
            case (bit_cnt)
                4'd0:uart_tx_data <= 1'd0;
                4'd1:uart_tx_data <= tx_data[0];
                4'd2:uart_tx_data <= tx_data[1];
                4'd3:uart_tx_data <= tx_data[2];
                4'd4:uart_tx_data <= tx_data[3];
                4'd5:uart_tx_data <= tx_data[4];
                4'd6:uart_tx_data <= tx_data[5];
                4'd7:uart_tx_data <= tx_data[6];
                4'd8:uart_tx_data <= tx_data[7];
                4'd9:uart_tx_data <= 1'd1;
                default:;
            endcase
        end
        else
            uart_tx_data <= 1'd1;
    end
    
endmodule

編寫的testbeach如下:

`timescale 1ns / 1ps
module tb_uart_tx();
 
    reg clk; //主時鐘,50MHz
    reg rst; //複比特,低電平有效
    reg txd_start; //發送開始標志
    reg [7:0] uart_rx_data; //串口接收到的並行數據
    
    wire uart_tx_data; //串口發送的串行數據
   
/***************模塊例化*************/ 
    UART_TX tb_uart_tx(
        .clk(clk),      
        .rst(rst),     
        .txd_start(txd_start),    
        .uart_rx_data(uart_rx_data), 
                             
        .uart_tx_data(uart_tx_data)
    );
 
/***************產生主時鐘*************/
    always #10 clk = ~clk;
    
/***************初始化*************/
    initial 
    begin
        clk = 1'd0;
        rst = 1'd0;
        txd_start = 1'd0;
        uart_rx_data = 8'h5a;
        #20
        txd_start = 1'd1;
        #20
        txd_start = 1'd0;
        #100000
        uart_rx_data = 8'h3f;
        #100000
        uart_rx_data = 8'he6;
    end
   
endmodule

仿真結果如圖5所示,可以看到發送功能已實現:

在這裏插入圖片描述 圖5
四、頂層代碼及其上板調試:

兩個子模塊設計完成後,按照頂層功能框圖可以編寫頂層的RTL描述,編寫的Verilog代碼如下(由於我的開發板上的時鐘是差分時鐘,故需要調用一個差分信號轉單端信號的設計原語“IBUFDS”,該原語的使用很簡單,在這裏就不專門介紹了,不了解的可以自己查閱資料):
`timescale 1ns / 1ps

module UART_TOP(
    input clk_p, //差分主時鐘正端,50MHz
    input clk_n, //差分主時鐘負端,50MHz
    input rst, //複比特,高電平有效
    input uart_rxd, //接收端
    
    output uart_txd //發送端
    );
    
    parameter  CLK_FREQ = 50000000; //主時鐘頻率
    parameter  UART_BPS = 460800; //串口波特率
    
    wire clk; //主時鐘,50MHz
    wire [7:0] uart_rx_data_w; //串口裏的並行數據線
    wire rx_done_w; //接收完成信號線
 
/***************差分時鐘轉單端時鐘*************/
    IBUFDS #(
          .DIFF_TERM("FALSE"), 
          .IBUF_LOW_PWR("TRUE"), 
          .IOSTANDARD("DEFAULT") 
       ) IBUFDS_inst (
          .O(clk), 
          .I(clk_p), 
          .IB(clk_n)
       );
    
/***************調用串口接收模塊*************/
    UART_RX #(
    .CLK_F(CLK_FREQ),
    .UART_B(UART_BPS)
    )
    uut_rxd(
        .clk(clk),
        .rst(rst),
        .uart_tx_data(uart_rxd),
        .uart_rx_data(uart_rx_data_w),
        .rx_done(rx_done_w)
    );
    
/***************調用串口發送模塊*************/
    UART_TX #(
    .CLK_F(CLK_FREQ), 
    .UART_B(UART_BPS)
    )
    uut_txd(
        .clk(clk),
        .rst(rst),
        .txd_start(rx_done_w),
        .uart_rx_data(uart_rx_data_w),
        .uart_tx_data(uart_txd) 
    );
     
endmodule

綜合、實現後,進行上板調試,為了簡單起見,本設計采用回環的方式來調試驗證,即PC發送數據到FPGA上,FPGA通過串口接收數據後再通過串口發送回PC。調試結果如圖6所示,可以看到,所設計的串口工作正常:

在這裏插入圖片描述

版權聲明
本文為[qq_44985628]所創,轉載請帶上原文鏈接,感謝
https://cht.chowdera.com/2022/204/202207231641422375.html

隨機推薦