加入星計劃,您可以享受以下權(quán)益:

  • 創(chuàng)作內(nèi)容快速變現(xiàn)
  • 行業(yè)影響力擴散
  • 作品版權(quán)保護
  • 300W+ 專業(yè)用戶
  • 1.5W+ 優(yōu)質(zhì)創(chuàng)作者
  • 5000+ 長期合作伙伴
立即加入
  • 正文
    • 設(shè)計背景
    • 設(shè)計原理
    • 設(shè)計架構(gòu)
    • UART發(fā)送器(transmitter)設(shè)計
    • 設(shè)計代碼
    • 仿真測試
  • 相關(guān)推薦
申請入駐 產(chǎn)業(yè)圖譜

源碼系列:基于FPGA的串口UART設(shè)計(附源工程)

2024/12/31 來源:wechat
476
加入交流群
掃碼加入
獲取工程師必備禮包
參與熱點資訊討論

大俠好,歡迎來到FPGA技術(shù)江湖,江湖偌大,相見即是緣分。大俠可以關(guān)注FPGA技術(shù)江湖,在“闖蕩江湖”、"行俠仗義"欄里獲取其他感興趣的資源,或者一起煮酒言歡。

今天給大俠帶來基于FPGA的UART設(shè)計,附源碼,獲取源碼,請在“FPGA技術(shù)江湖”公眾號內(nèi)回復(fù)“UART設(shè)計源碼”,可獲取源碼文件。話不多說,上貨。

設(shè)計背景

串口的出現(xiàn)是在1980年前后,數(shù)據(jù)傳輸率是115kbps~230kbps。串口出現(xiàn)的初期是為了實現(xiàn)連接計算機外設(shè)的目的,初期串口一般用來連接鼠標(biāo)和外置Modem以及老式攝像頭和寫字板等設(shè)備。串口也可以應(yīng)用于兩臺計算機(或設(shè)備)之間的互聯(lián)及數(shù)據(jù)傳輸。由于串口(COM)不支持熱插拔及傳輸速率較低,部分新主板和大部分便攜電腦已開始取消該接口。串口多用于工控和測量設(shè)備以及部分通信設(shè)備中。

串口是串行接口的簡稱,也稱串行通信接口或串行通訊接口(通常指COM接口),是采用串行通信方式的擴展接口。串行接口(Serial Interface)是指數(shù)據(jù)一位一位地順序傳送。其特點是通信線路簡單,只要一對傳輸線就可以實現(xiàn)雙向通信(可以直接利用電話線作為傳輸線),從而大大降低了成本,特別適用于遠(yuǎn)距離通信,但傳送速度較慢。

通信協(xié)議是指通信雙方的一種約定。約定包括對數(shù)據(jù)格式、同步方式、傳送速度、傳送步驟、檢糾錯方式以及控制字符定義等問題做出統(tǒng)一規(guī)定,通信雙方必須共同遵守。串口通信的兩種最基本的方式為:同步串行通信方式和異步串行通信方式。

同步串行通信是指SPI(Serial Peripheral interface)的縮寫,顧名思義就是串行外圍設(shè)備接口。SPI是一種高速的全雙工通信總線。封裝芯片上總共有四根線,PCB布局布線也簡單,所以現(xiàn)在很多芯片集成了這個協(xié)議。主要用于CPU和各種外圍器件進行通信,TRM450是SPI接口。

異步串行通信是指UART(Universal Asynchronous Receiver/Transmitter),通用異步接收/發(fā)送。UART是一個并行輸入成為串行輸出的芯片,通常集成在主板上。UART包含TTL電平的串口和RS232電平的串口。RS232也稱標(biāo)準(zhǔn)串口,也是最常用的一種串行通訊接口。RS-232-C 標(biāo)準(zhǔn)對兩個方面作了規(guī)定,即信號電平標(biāo)準(zhǔn)和控制信號線的定義。RS-232-C 采用負(fù)邏輯規(guī)定邏輯電平,信號電平與通常的TTL電平也不兼容,RS-232-C 將-5V~-15V 規(guī)定為“1”,+5V~+15V 規(guī)定為“0”。

設(shè)計原理

uart的示意圖如下:

其端口對應(yīng)的功能表如下:

在設(shè)計過程中只需要關(guān)心RS232_TXD和RS232_RXD兩個信號, RS232_TXD是數(shù)據(jù)發(fā)送端口,RS232_RXD是數(shù)據(jù)接收端口。

本設(shè)計將通過串口建立起計算機和實驗板(ZX_1)之間的通信和控制關(guān)系,也就是通常所說的上下位機通信。要實現(xiàn)這樣的通信,首先需要用到一個外部的電平轉(zhuǎn)換芯片MAX232,其具體配置電路原理圖如下:

解析:

MAX232芯片是美信(MAXIM)公司專為RS-232標(biāo)準(zhǔn)串口設(shè)計的單電源電平轉(zhuǎn)換芯片,使用+5v單電源供電。

主要特點:

1、符合所有的RS-232C技術(shù)標(biāo)準(zhǔn);

2、只需要單一+5V電源供電;

3、片載電荷泵具有升壓、電壓極性反轉(zhuǎn)能力,能夠產(chǎn)生+10V和-10V電壓V+、V-;

4、功耗低,典型供電電流5mA;

5、內(nèi)部集成2個RS-232C驅(qū)動器;

6、高集成度,片外最低只需4個電容即可工作。

本設(shè)計還需要分析在通信過程中,UART所對應(yīng)的數(shù)據(jù)格式如下:

起始位:線路空閑時為高電平,當(dāng)截獲第一個低電平比特時,則為起始位;

信息位:在起始位之后,按照低位首發(fā)原則,順序發(fā)送信息位的最低位到最高位,信息位的寬度可以是4、5、6、7、8中的一個;

奇偶校驗位:信息位之后則是一個可選的奇偶校驗位,它可以是無校驗(NONE)、奇校驗(ODD)、偶校驗(EVEN)中的任意一個,無校驗時,信息位之后就是停止位。奇偶校驗是,使得信息位和校驗位的所有1的個數(shù)保持奇數(shù)或者偶數(shù)位;

停止位:停止位的長度可以是1、1.5或2中的任意一個,它為高電平;

空閑位:持續(xù)的高電平。

波特率:每秒傳輸?shù)臄?shù)據(jù)位(bit)數(shù)為波特率。RS-232-C的波特率可以是50、75、100、150、300、600、1200、2400、4800、9600、19200波特。

通過分析上述的數(shù)據(jù)格式,在本設(shè)計中,將波特率設(shè)置為9600,起始位設(shè)置為1比特,信息位設(shè)置為8比特,奇偶校驗位設(shè)置為0比特,停止位設(shè)置為2比特,空閑位設(shè)置為1比特。

因為在設(shè)計中只需要關(guān)注RS232_TXD和RS232_RXD這兩個信號,既然只有兩條線,所以只需要關(guān)注其數(shù)據(jù)收發(fā)時序即可,時序圖如下:

設(shè)計架構(gòu)

設(shè)計總架構(gòu)圖如下:

uart_pll模塊是一個鎖相環(huán),通過50M的外部時鐘(ref_clk),倍頻得到100M的上游接口的100M系統(tǒng)時鐘(sys_clk);divider模塊為UART的分頻模塊,通過用100M的sys_clk作為輸入,分頻得到波特率為9600的uart_clk時鐘。

transmitter模塊為串口發(fā)送模塊,并配合與其對應(yīng)的trans_fifo發(fā)送數(shù)據(jù)緩存FIFO進行使用,將儲存在FIFO中的數(shù)據(jù)通過RS232-C協(xié)議發(fā)送出去;

receiver模塊為串口接收模塊,并配合與其對應(yīng)的rec_fifo接收數(shù)據(jù)緩存FIFO進行使用,將儲存在FIFO中的數(shù)據(jù)通過RS232-C協(xié)議接收進來;

UART發(fā)送器(transmitter)設(shè)計

UART發(fā)送器的時序如下圖:

UART接收器(receiver)設(shè)計

根據(jù)對UART時序的分析可以得到如下的狀態(tài)轉(zhuǎn)移表(SMF):

設(shè)計代碼

頂層uart_lsm模塊代碼:

`include "uart_lsm_head.v"
module uart_lsm(ref_clk, global_reset,tdata, twrreq,       tfull, rdata, rrdreq, rempty, uart_txd, uart_rxd);
  input ref_clk, global_reset;  //全局時鐘復(fù)位  input [7:0] tdata;  //發(fā)送fifo輸入數(shù)據(jù)  input twrreq;  //發(fā)送fifo寫請求   output tfull;   //發(fā)送fifo輸出寫滿  output [7:0] rdata;  //接收fifo輸出數(shù)據(jù)  input rrdreq;   //接收fifo的輸入讀請求  output rempty;   //接收fifo的輸出入空   output uart_txd;  //輸出發(fā)送線信號  input uart_rxd;  //輸入接收線信號
  wire trxd;
  wire [7:0] tf_data, rf_data;  wire tf_rdreq, tf_empty, rf_wrreq;  wire sys_clk, uart_clk, rst_n;

  assign rst_n = ~global_reset;
  trans_fifo t_fifo(    //發(fā)送fifo    .data(tdata),    .rdclk(uart_clk),    .rdreq(tf_rdreq),    .wrclk(sys_clk),    .wrreq(twrreq),    .q(tf_data),    .rdempty(tf_empty),    .wrfull(tfull)  );
  transmitter trans(   //發(fā)送模塊    .clk(uart_clk),     .rst_n(rst_n),     .empty(tf_empty),     .data(tf_data),     .rdreq(tf_rdreq),     .txd(trxd)  );
  rec_fifo r_fifo(     //接收fifo    .data(rf_data),    .rdclk(sys_clk),    .rdreq(rrdreq),    .wrclk(uart_clk),    .wrreq(rf_wrreq),    .q(rdata),    .rdempty(rempty)  );
  receiver rece(     //接收模塊    .clk(uart_clk),     .rst_n(rst_n),     .data(rf_data),     .wrreq(rfwrreq),     .rxd(trxd)  );
  uart_pll u_pll(         //鎖相環(huán)產(chǎn)生系統(tǒng)時鐘,作用于fifo、divider    .areset(global_reset),    .inclk0(ref_clk),    .c0(sys_clk)  );
  divider_ebd_1s_mealy   //分頻模塊分頻uart_clk,作用于receiver transmitter  #(.HW(`DW), .LW(`DW))  div(    .clk_in(sys_clk),     .rst_n(rst_n),     .clk_out(uart_clk)  );
endmodule

transmitter模塊代碼:

//uart發(fā)送模塊LSM(線性序列機)module transmitter(clk, rst_n, empty, data, rdreq, txd);
  input clk, rst_n;  //輸入時鐘復(fù)位  input empty;       //來自fifo的輸入空標(biāo)志信號  input [7:0] data;  //來自fifo的輸入數(shù)據(jù)  output reg rdreq;  //輸出到fifo的讀請求  output reg txd;    //輸出發(fā)送線信號
  reg [7:0] temp;      //中間寄存器  reg [7:0] count;    //8位計數(shù)
  `define EP 192   //終止符
  always @ (posedge clk or negedge rst_n)  begin  : lsm_2s1   //線性序列機一段閉節(jié)點    if (!rst_n)   //復(fù)位      count <= `EP;    else if ((count >= `EP) && !empty)  //計數(shù)大于終止符和非空(empty=0)      count <= 0;    else if (count < `EP) //計數(shù)小于終止符      count <= count + 1;  end 
  always @ (posedge clk or negedge rst_n)  begin  : lsm_2s2  //線性序列機一段閉節(jié)點    if (!rst_n)  //復(fù)位      begin        txd <= 1;   //發(fā)送線為高        rdreq <= 0;  //讀請求為0        temp <= 0;  //中間寄存器為0      end     else if ((count >= `EP) && !empty) //計數(shù)大于終止符fifo為非空,讀請求拉高        rdreq <= 1;    else       case (count)        0  :  begin              rdreq <= 0;  //讀請求拉低              txd <= 0;            end         1  :  temp[7:0] <= data[7:0];  //輸入數(shù)據(jù)給中間寄存器        1*16  :  txd <= temp[0];   //中間寄存器按位給發(fā)送線發(fā)送        2*16  :  txd <= temp[1];        3*16  :  txd <= temp[2];        4*16  :  txd <= temp[3];        5*16  :  txd <= temp[4];        6*16  :  txd <= temp[5];        7*16  :  txd <= temp[6];        8*16  :  txd <= temp[7];        9*16  :  txd <= 1;    //拉高      endcase       end   
endmodule

接收模塊receiver代碼:

`include "uart_lsm_head.v"
module receiver(clk, rst_n, data, wrreq, rxd);   //uart接收模塊LSM(線性序列機)
  input clk, rst_n;  //輸入時鐘復(fù)位  output reg [7:0] data;  //輸出數(shù)據(jù)  output reg wrreq;   //輸出寫請求  input rxd;    //輸入接收線信號
  reg [7:0] count;  //宏定義  `define EP   184   //終止符  `define GET0 24  `define GET1 `GET0+16  `define GET2 `GET1+16  `define GET3 `GET2+16  `define GET4 `GET3+16  `define GET5 `GET4+16  `define GET6 `GET5+16  `define GET7 `GET6+16  `define GETW `GET7+16  //wrreq=1  `define GLRW `GETW+1   //wrreq=0
  always @ (posedge clk or negedge rst_n)  begin : lsm_2s1   //線性序列機一段閉節(jié)點    if(!rst_n)      count <= `EP;    else if((count >= `EP) && !rxd) //rxd=0      count <= 0;    else if (count < `EP)      count <= count + 1;  end
  always @ (posedge clk or negedge rst_n)    begin  : lsm_2s2   //線性序列機二段閉節(jié)點           if(!rst_n)        begin                data <= 0;          wrreq <= 0;      //寫請求為0        end       else        case(count)          `GET0  :  data[0] <= rxd;   //將接收的數(shù)據(jù)通過data輸出          `GET1  :  data[1] <= rxd;          `GET2 :   data[2] <= rxd;          `GET3 :   data[3] <= rxd;          `GET4 :   data[4] <= rxd;          `GET5 :   data[5] <= rxd;          `GET6 :   data[6] <= rxd;          `GET7 :   data[7] <= rxd;          `GETW :   wrreq <= 1;   //寫請求拉高一拍,寫進fifo          `GLRW :   wrreq <= 0;   //一拍后寫請求為0        endcase    end
endmodule

參數(shù)宏的頭文件代碼

/////uart_lsm_head.v
//////////定義時標(biāo)////////////`timescale 1us/1ns
/////////定義設(shè)計參數(shù)/////////`define BAUD_RATE 9600   //波特率=9600`define SYS_CLK 100000000 //系統(tǒng)時鐘sys_clk 頻率=100M`define REF_CLK 50000000 //系統(tǒng)時鐘ref_clk頻率=50M
//////////使用宏自動計算的諸參數(shù)////////////`define TBAUD_RATE (1000000.0/`BAUD_RATE)//波特率周期`define UART_CLK (16*`BAUD_RATE)        //uart_clk 等于16倍波特率`define TUART_CLK (1000000.0/`UART_CLK) //uart_clk周期`define TEN_TUART_CLK (10.0*`TUART_CLK) //10倍uart_clk周期`define TUART_CLK100 (100.0*`TUART_CLK) //100倍uart_clk周期
`define TUART_CLK_HALF (`TUART_CLK/2.0) //uart_clk半周期`define TREF_CLK (1000000.0/`REF_CLK)  //參考時鐘周期`define TREF_CLK_HALF (`TREF_CLK/2.0)  //參考時鐘半周期
//////////使用宏自動計算的分頻數(shù)(占空比50%)////////////`define DW (`SYS_CLK/(2*`UART_CLK))

仿真測試

transmitter(發(fā)送)模塊的測試代碼:

`include "uart_lsm_head.v"
module transmitter_tb;
  reg clk, rst_n;  reg empty;  reg [7:0] data;  wire rdreq;  wire txd;    reg [7:0] temp;
  transmitter transmitter_dut(      .clk(clk),      .rst_n(rst_n),      .empty(empty),      .data(data),      .rdreq(rdreq),      .txd(txd)    );
  initial begin    clk = 1;    rst_n = 0;    data = 0;    empty = 1;    temp = 0;    #200.1 rst_n = 1;        #200.1 empty=1;temp=8'h55;    #`TBAUD_RATE    data[0] = temp[0];  //發(fā)送第一個信息位(LSB)    #`TBAUD_RATE    data[1] = temp[1];    #`TBAUD_RATE    data[2] = temp[2];    #`TBAUD_RATE    data[3] = temp[3];    #`TBAUD_RATE    data[4] = temp[4];    #`TBAUD_RATE    data[5] = temp[5];    #`TBAUD_RATE    data[6] = temp[6];    #`TBAUD_RATE    data[7] = temp[7];    #`TBAUD_RATE    empty = 0;      #2000 $stop;        end    always #`TUART_CLK_HALF clk = ~clk;  endmodule 

receiver(接收)模塊的測試代碼:

`include "uart_lsm_head.v"
module receiver_tb;
  reg clk, rst_n;  reg rxd;  wire [7:0] data;  wire wrreq;    reg [7:0] temp; //8位的中間寄存器,產(chǎn)生激勵    receiver receiver_dut(    .clk(clk),    .rst_n(rst_n),    .data(data),    .wrreq(wrreq),    .rxd(rxd)  );    initial begin    clk = 1;    rst_n = 0;    temp = 0;    rxd = 1;    #`TEN_TUART_CLK  //*代表異步 //10倍uart_clk周期    rst_n = 1;        #`TEN_TUART_CLK  //啟動一個停止位      rxd = 0;      temp = 8'h55;      #`TBAUD_RATE    //數(shù)據(jù)使用波特率的周期    rxd = temp[0];  //發(fā)送一個信息位(LSB)    #`TBAUD_RATE    rxd = temp[1];    #`TBAUD_RATE    rxd = temp[2];    #`TBAUD_RATE    rxd = temp[3];    #`TBAUD_RATE    rxd = temp[4];    #`TBAUD_RATE    rxd = temp[5];    #`TBAUD_RATE    rxd = temp[6];    #`TBAUD_RATE    rxd = temp[7];   //發(fā)送最后一個信息位(HSB)    #`TBAUD_RATE    rxd = 1;        #`TUART_CLK100 $stop;   //100倍uart_clk周期  end      always #`TUART_CLK_HALF clk = ~clk;  // uart_clk 的時鐘,使用uart_clk的半周期
endmodule 

仿真圖:分別為發(fā)送和接收做仿真測試。

發(fā)送的仿真波形如下:

接收的仿真波形如下:

根據(jù)以上兩個仿真波形,可以發(fā)現(xiàn)設(shè)計是正確的,之后則可利用串口獵人的上位機軟件,實現(xiàn)自發(fā)自收。

相關(guān)推薦

登錄即可解鎖
  • 海量技術(shù)文章
  • 設(shè)計資源下載
  • 產(chǎn)業(yè)鏈客戶資源
  • 寫文章/發(fā)需求
立即登錄

任何技術(shù)的學(xué)習(xí)就好比一個江湖,對于每一位俠客都需要不斷的歷練,從初入江湖的小白到歸隱山林的隱世高人,需要不斷的自我感悟自己修煉,讓我們一起仗劍闖FPGA乃至更大的江湖。