大俠好,歡迎來到FPGA技術江湖,江湖偌大,相見即是緣分。大俠可以關注FPGA技術江湖,在“闖蕩江湖”、"行俠仗義"欄里獲取其他感興趣的資源,或者一起煮酒言歡。
今天給大俠帶來基于FPGA的UART設計,附源碼,獲取源碼,請在“FPGA技術江湖”公眾號內回復“UART設計源碼”,可獲取源碼文件。話不多說,上貨。
設計背景
串口的出現是在1980年前后,數據傳輸率是115kbps~230kbps。串口出現的初期是為了實現連接計算機外設的目的,初期串口一般用來連接鼠標和外置Modem以及老式攝像頭和寫字板等設備。串口也可以應用于兩臺計算機(或設備)之間的互聯及數據傳輸。由于串口(COM)不支持熱插拔及傳輸速率較低,部分新主板和大部分便攜電腦已開始取消該接口。串口多用于工控和測量設備以及部分通信設備中。
串口是串行接口的簡稱,也稱串行通信接口或串行通訊接口(通常指COM接口),是采用串行通信方式的擴展接口。串行接口(Serial Interface)是指數據一位一位地順序傳送。其特點是通信線路簡單,只要一對傳輸線就可以實現雙向通信(可以直接利用電話線作為傳輸線),從而大大降低了成本,特別適用于遠距離通信,但傳送速度較慢。
通信協議是指通信雙方的一種約定。約定包括對數據格式、同步方式、傳送速度、傳送步驟、檢糾錯方式以及控制字符定義等問題做出統一規定,通信雙方必須共同遵守。串口通信的兩種最基本的方式為:同步串行通信方式和異步串行通信方式。
同步串行通信是指SPI(Serial Peripheral interface)的縮寫,顧名思義就是串行外圍設備接口。SPI是一種高速的全雙工通信總線。封裝芯片上總共有四根線,PCB布局布線也簡單,所以現在很多芯片集成了這個協議。主要用于CPU和各種外圍器件進行通信,TRM450是SPI接口。
異步串行通信是指UART(Universal Asynchronous Receiver/Transmitter),通用異步接收/發送。UART是一個并行輸入成為串行輸出的芯片,通常集成在主板上。UART包含TTL電平的串口和RS232電平的串口。RS232也稱標準串口,也是最常用的一種串行通訊接口。RS-232-C 標準對兩個方面作了規定,即信號電平標準和控制信號線的定義。RS-232-C 采用負邏輯規定邏輯電平,信號電平與通常的TTL電平也不兼容,RS-232-C 將-5V~-15V 規定為“1”,+5V~+15V 規定為“0”。
設計原理
uart的示意圖如下:

其端口對應的功能表如下:

在設計過程中只需要關心RS232_TXD和RS232_RXD兩個信號, RS232_TXD是數據發送端口,RS232_RXD是數據接收端口。
本設計將通過串口建立起計算機和實驗板(ZX_1)之間的通信和控制關系,也就是通常所說的上下位機通信。要實現這樣的通信,首先需要用到一個外部的電平轉換芯片MAX232,其具體配置電路原理圖如下:

解析:
MAX232芯片是美信(MAXIM)公司專為RS-232標準串口設計的單電源電平轉換芯片,使用+5v單電源供電。
主要特點:
1、符合所有的RS-232C技術標準;
2、只需要單一+5V電源供電;
3、片載電荷泵具有升壓、電壓極性反轉能力,能夠產生+10V和-10V電壓V+、V-;
4、功耗低,典型供電電流5mA;
5、內部集成2個RS-232C驅動器;
6、高集成度,片外最低只需4個電容即可工作。
本設計還需要分析在通信過程中,UART所對應的數據格式如下:
起始位:線路空閑時為高電平,當截獲第一個低電平比特時,則為起始位;
信息位:在起始位之后,按照低位首發原則,順序發送信息位的最低位到最高位,信息位的寬度可以是4、5、6、7、8中的一個;
奇偶校驗位:信息位之后則是一個可選的奇偶校驗位,它可以是無校驗(NONE)、奇校驗(ODD)、偶校驗(EVEN)中的任意一個,無校驗時,信息位之后就是停止位。奇偶校驗是,使得信息位和校驗位的所有1的個數保持奇數或者偶數位;
停止位:停止位的長度可以是1、1.5或2中的任意一個,它為高電平;
空閑位:持續的高電平。
波特率:每秒傳輸的數據位(bit)數為波特率。RS-232-C的波特率可以是50、75、100、150、300、600、1200、2400、4800、9600、19200波特。
通過分析上述的數據格式,在本設計中,將波特率設置為9600,起始位設置為1比特,信息位設置為8比特,奇偶校驗位設置為0比特,停止位設置為2比特,空閑位設置為1比特。
因為在設計中只需要關注RS232_TXD和RS232_RXD這兩個信號,既然只有兩條線,所以只需要關注其數據收發時序即可,時序圖如下:

設計架構
設計總架構圖如下:

uart_pll模塊是一個鎖相環,通過50M的外部時鐘(ref_clk),倍頻得到100M的上游接口的100M系統時鐘(sys_clk);divider模塊為UART的分頻模塊,通過用100M的sys_clk作為輸入,分頻得到波特率為9600的uart_clk時鐘。
transmitter模塊為串口發送模塊,并配合與其對應的trans_fifo發送數據緩存FIFO進行使用,將儲存在FIFO中的數據通過RS232-C協議發送出去;
receiver模塊為串口接收模塊,并配合與其對應的rec_fifo接收數據緩存FIFO進行使用,將儲存在FIFO中的數據通過RS232-C協議接收進來;
UART發送器(transmitter)設計
UART發送器的時序如下圖:


UART接收器(receiver)設計
根據對UART時序的分析可以得到如下的狀態轉移表(SMF):

設計代碼
頂層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; //全局時鐘復位 input [7:0] tdata; //發送fifo輸入數據 input twrreq; //發送fifo寫請求 output tfull; //發送fifo輸出寫滿 output [7:0] rdata; //接收fifo輸出數據 input rrdreq; //接收fifo的輸入讀請求 output rempty; //接收fifo的輸出入空 output uart_txd; //輸出發送線信號 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( //發送fifo .data(tdata), .rdclk(uart_clk), .rdreq(tf_rdreq), .wrclk(sys_clk), .wrreq(twrreq), .q(tf_data), .rdempty(tf_empty), .wrfull(tfull) ); transmitter trans( //發送模塊 .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( //鎖相環產生系統時鐘,作用于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發送模塊LSM(線性序列機) module transmitter(clk, rst_n, empty, data, rdreq, txd); input clk, rst_n; //輸入時鐘復位 input empty; //來自fifo的輸入空標志信號 input [7:0] data; //來自fifo的輸入數據 output reg rdreq; //輸出到fifo的讀請求 output reg txd; //輸出發送線信號 reg [7:0] temp; //中間寄存器 reg [7:0] count; //8位計數 `define EP 192 //終止符 always @ (posedge clk or negedge rst_n) begin : lsm_2s1 //線性序列機一段閉節點 if (!rst_n) //復位 count <= `EP; else if ((count >= `EP) && !empty) //計數大于終止符和非空(empty=0) count <= 0; else if (count < `EP) //計數小于終止符 count <= count + 1; end always @ (posedge clk or negedge rst_n) begin : lsm_2s2 //線性序列機一段閉節點 if (!rst_n) //復位 begin txd <= 1; //發送線為高 rdreq <= 0; //讀請求為0 temp <= 0; //中間寄存器為0 end else if ((count >= `EP) && !empty) //計數大于終止符fifo為非空,讀請求拉高 rdreq <= 1; else case (count) 0 : begin rdreq <= 0; //讀請求拉低 txd <= 0; end 1 : temp[7:0] <= data[7:0]; //輸入數據給中間寄存器 1*16 : txd <= temp[0]; //中間寄存器按位給發送線發送 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; //輸入時鐘復位
output reg [7:0] data; //輸出數據
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 //線性序列機一段閉節點
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 //線性序列機二段閉節點
if(!rst_n)
begin
data <= 0;
wrreq <= 0; //寫請求為0
end
else
case(count)
`GET0 : data[0] <= rxd; //將接收的數據通過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
參數宏的頭文件代碼:
/////uart_lsm_head.v //////////定義時標//////////// `timescale 1us/1ns /////////定義設計參數///////// `define BAUD_RATE 9600 //波特率=9600 `define SYS_CLK 100000000 //系統時鐘sys_clk 頻率=100M `define REF_CLK 50000000 //系統時鐘ref_clk頻率=50M //////////使用宏自動計算的諸參數//////////// `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) //參考時鐘半周期 //////////使用宏自動計算的分頻數(占空比50%)//////////// `define DW (`SYS_CLK/(2*`UART_CLK))
仿真測試
transmitter(發送)模塊的測試代碼:
`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]; //發送第一個信息位(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位的中間寄存器,產生激勵
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 //數據使用波特率的周期
rxd = temp[0]; //發送一個信息位(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]; //發送最后一個信息位(HSB)
#`TBAUD_RATE
rxd = 1;
#`TUART_CLK100 $stop; //100倍uart_clk周期
end
always #`TUART_CLK_HALF clk = ~clk; // uart_clk 的時鐘,使用uart_clk的半周期
endmodule
仿真圖:分別為發送和接收做仿真測試。
發送的仿真波形如下:

接收的仿真波形如下:

根據以上兩個仿真波形,可以發現設計是正確的,之后則可利用串口獵人的上位機軟件,實現自發自收。
-
FPGA
+關注
關注
1660文章
22412瀏覽量
636339 -
接口
+關注
關注
33文章
9521瀏覽量
157040 -
串口
+關注
關注
15文章
1619瀏覽量
82827 -
uart
+關注
關注
22文章
1314瀏覽量
106646 -
串行通信
+關注
關注
4文章
608瀏覽量
37141
原文標題:源碼系列:基于FPGA的串口UART設計(附源工程)
文章出處:【微信號:HXSLH1010101010,微信公眾號:FPGA技術江湖】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
基于FPGA開發板TSP的串口通信設計
用FPGA/CPLD設計UART
用FPGA/CPLD設計UART
FPGA與CPLD實現UART
uart是什么意思?認識uart串口
UART串口WiFi模塊的工作原理及應用
基于FPGA的串口UART設計
評論