寫在前面的話
UART串行接口簡稱串口,是我們各類芯片最常用的一種異步通信接口,通過串口我們就可以建立起計算機和我們實驗板之間的通信和控制關系,也就是我們通常所說的上下位機通信。串口可以說是不同平台互相通信、控制的一個最基本的接口。
項目需求
設計一個UART控制器,當控制器從上位機接收到數據以后,馬上將數據輸出,發送回上位機,完成“回環測試”。
UART的原理分析
要實現UART通信,首先我們需要用到一個外部的電平轉換芯片MAX232,其具體配置電路如下:
注解:
MAX232芯片是美信(MAXIM)公司專為RS_232標准串口設計的單電源電平轉換芯片,使用+5V單電源供電
主要特點:
符合所有的RS_232技術標准
只需要單一+5V電源供電
片載電荷泵具有升壓、電源極性翻轉能力,能夠產生+10V和-10V電壓
功耗低,典型供電電流5MA
內部集成2個RS_232驅動器
高集成度,片外最低只需四個電容即可工作
由原理圖可以看出,最終我們FPGA需要控制的其實也就是兩條信號線:
RXD和TXD,分別為數據接收線和數據發送線。
那么接下來,問題就變得簡單了,既然只有兩條線,那么我們只需要關注其數據收發時序即可,時序圖如下:
UART數據格式:
說明:在此實驗中,無奇偶校驗位,則一幀數據為十位。(奇偶校驗是一種校驗代碼傳輸正確性的方法。根據被傳輸的一組二進制代碼的數位中“1”的個數是奇數或偶數來進行校驗。采用奇數的稱為奇校驗,反之,稱為偶校驗。采用何種校驗是事先規定好的。通常專門設置一個奇偶校驗位,用它使這組代碼中“1”的個數為奇數或偶數。若用奇校驗,則當接收端收到這組代碼時,校驗“1”的個數是否為奇數,從而確定傳輸代碼的正確性。)
在UART接收時,采集一幀數據的中間8位有效位,忽略開始位與停止位;在UART發送時,將發送的並行8位數據轉為串行數據,並添加開始位與停止位。
UART中的一幀數據為10位,空閑時均為高電平,在檢測到開始位(低電平)之后,開始采集8位有效數據位(低位在前),再將停止位置為高電平即可。
通過前面的學習,我們已經了解了UART的數據格式,那么,傳輸速率如何控制呢?這就涉及到了一個波特率的概念:
波特率是衡量數據傳輸速率的指針。表示為每秒鍾傳送的二進制位數(bit),例如資料傳送速率為120字符/秒,而每一個字符為10位,則其傳送的波特率為10*120=1200波特(bit)。此實驗中設置波特率為9600bit/s。
系統架構
模塊功能說明:bps_rx模塊為串口接收數據的速率控制模塊,當使能信號rx_en為高電平時,bps_rx模塊內部計數器開始計數,按照設定好的波特率,輸出控制數據采集的尖峰脈沖信號rx_sel_data和有效數據位的計數值rx_num。
模塊功能說明:uart_rx為串口串行數據的接收模塊,數據從端口rs232_rx輸入,在采集控制信號rx_sel_data和有效位計數器rx_num的控制下,進行串並轉換,從端口rx_d[7:0]輸出。tx_en為發送控制模塊的使能信號,當Uart_rx模塊接收數據完畢以后,置高信號tx_en啟動數據發送,將采集到的數據rx_d[7:0]發送到上位機。
模塊功能說明:bps_tx模塊為串口發送數據的速率控制模塊,當使能信號tx_en為高電平時,bps_tx模塊內部計數器開始計數,按照設定好的波特率,輸出控制數據發送的尖峰脈沖信號tx_sel_data和有效數據位的計數值tx_num。
模塊功能說明:uart_tx為串口串行數據的發送模塊,並行數據從端口rx_d[7:0]輸入,在采集控制信號tx_sel_data和有效位計數器tx_num的控制下,進行並串轉換,從端口rs232_tx輸出。
模塊功能介紹
模塊名 |
功能描述 |
bps_rx |
控制串口接收數據的速率 |
uart_rx |
接收串口串行數據 |
bps_tx |
控制串口發送數據的速率 |
uart_tx |
發送串口串行數據 |
uart |
頂層連接 |
端口和內部連線描述
頂層模塊端口介紹
端口名 |
端口說明 |
clk |
系統時鍾輸入 |
rst_n |
系統復位 |
rs232_tx |
數據輸出端口 |
rs232_rx |
數據輸入端口 |
內部連線
連線名 |
連線說明 |
rx_en |
bps_rx開始計數的使能信號 |
rx_sel_data |
控制數據采集的尖峰脈沖信號 |
rx_num |
接收有效數據位的計數值 |
rx_d |
接收到的數據 |
tx_en |
發送控制模塊的使能信號 |
tx_sel_data |
控制數據發送的尖峰脈沖信號 |
tx_num |
發送有效數據位的計數值 |
代碼解釋
數據接收模塊波特率生成
/**************************************************** * Engineer : 夢翼師兄 * QQ : 761664056 * The module function:控制串口接收數據的速率 *****************************************************/ 00 module bps_rx( 01 clk, //系統時鍾50MHz 02 rst_n, //低電平復位 03 rx_en, //使能信號:串口接收或發送開始 04 rx_sel_data, //波特率計數的中心點(采集數據的使能信號) 05 rx_num //一幀數據0-9 06 ); 07 //模塊輸入 08 input clk; //系統時鍾50MHz 09 input rst_n; //低電平復位 10 input rx_en; //使能信號:串口接收開始 11 //模塊輸出 12 output reg rx_sel_data; //波特率計數的中心點(采集數據的使能信號) 13 output reg [3:0] rx_num; //一幀數據0-9 14 //設置參數 15 parameter bps_div = 13'd5207, 16 bps_div_2 = 13'd2603; 17 18 //接收標志位:接收到使能信號rx_en后,將標志位flag拉高,當信號rx_num計完一幀數據后拉低 19 reg flag; 20 always@(posedge clk or negedge rst_n) 21 if(!rst_n) 22 flag <= 0; 23 else if(rx_en) 24 flag <= 1; 25 else if(rx_num == 4'd10) 26 flag <= 0; 27 28 //波特率計數 29 reg [12:0] cnt; 30 always@(posedge clk or negedge rst_n) 31 if(!rst_n) 32 cnt <= 13'd0; 33 else if(flag && cnt < bps_div) 34 cnt <= cnt + 1'b1; 35 else 36 cnt <= 13'd0; 37 38 //規定接收數據的范圍:即一幀數據(10位:1位開始位,8位數據位,1位結束位) 39 always@(posedge clk or negedge rst_n) 40 if(!rst_n) 41 rx_num <= 4'd0; 42 else if(rx_sel_data && flag) 43 rx_num <= rx_num + 1'b1; 44 else if(rx_num == 4'd10) 45 rx_num <= 1'd0; 46 47 //數據在波特率的中間部分采集:即接收數據的使能信號 48 always@(posedge clk or negedge rst_n) 49 if(!rst_n) 50 rx_sel_data <= 1'b0; 51 else if(cnt == bps_div_2)//中間取數是為了產生尖峰脈沖,尖峰脈沖為采集數據的使能信號,用來把握速率 52 rx_sel_data <= 1'b1; 53 else 54 rx_sel_data <= 1'b0; 55 56 endmodule |
本模塊20~26行負責控制flag的值,flag為高電平代表一幀數據正在傳輸。29~36行為波特率計數器,當flag有效時計數器開始計數。48~54行負責在波特率計數值的中間部位生成尖峰脈沖rx_sel_data,兩個尖峰脈沖之間的間隔為5207個系統時鍾周期,滿足9600bps波特率。39~45在尖峰脈沖作用下接收數據計數器開始標定有效幀比特位。
數據接收模塊
/**************************************************** * Engineer : 夢翼師兄 * QQ : 761664056 * The module function:接收串口串行數據 *****************************************************/ 00 module uart_rx( 01 clk, //50MHZ時鍾 02 rst_n, //低電平復位 03 rs232_rx, //輸入串行數據 04 rx_num, //一幀數據控制位 05 rx_sel_data, //波特率計數的中心點(采集數據的使能信號) 06 rx_en, //使能信號:啟動接收波特率計數 07 tx_en, //使能信號:在接收完數據后,開始啟動發送數據模塊 08 rx_d //將采集數據的有效8位串行數據轉化為並行數據 09 ); 10 //模塊輸入 11 input clk; //50MHZ時鍾 12 input rst_n; //低電平復位 13 input rs232_rx; //輸入串行數據 14 input [3:0] rx_num; //一幀數據控制位 15 input rx_sel_data; //波特率計數的中心點(采集數據的使能信號) 16 //模塊輸出 17 output rx_en; //使能信號:啟動接收波特率計數 18 output reg tx_en; //使能信號:在接收完數據后,開始啟動發送數據模塊 19 output reg [7:0] rx_d; //將采集數據的有效8位串行數據轉化為並行數據 20 //檢測低電平(開始位) 21 reg in_1,in_2; 22 always@(posedge clk or negedge rst_n) 23 if(!rst_n) 24 begin 25 in_1 <= 1'b1; 26 in_2 <= 1'b1; 27 end 28 else 29 begin 30 in_1 <= rs232_rx; 31 in_2 <= in_1; 32 end 33 34 assign rx_en = in_2 &(~in_1); //當檢測由高變低的過程后,使能信號拉高 35 36 //確保在一幀數據的中間8位進行數據的讀取,讀取完成后,使能信號tx_en控制串口發送模塊 37 reg [7:0] rx_d_r; 38 always@(posedge clk or negedge rst_n) 39 if(!rst_n) 40 begin 41 rx_d_r <= 8'd0; 42 rx_d <= 8'd0; 43 end 44 else if(rx_sel_data) 45 case(rx_num) 46 0:; //忽略開始位 47 1: rx_d_r[0] <= rs232_rx;//采集中間8位有效數據 48 2: rx_d_r[1] <= rs232_rx; 49 3: rx_d_r[2] <= rs232_rx; 50 4: rx_d_r[3] <= rs232_rx; 51 5: rx_d_r[4] <= rs232_rx; 52 6: rx_d_r[5] <= rs232_rx; 53 7: rx_d_r[6] <= rs232_rx; 54 8: rx_d_r[7] <= rs232_rx; 55 9: rx_d <= rx_d_r; //鎖存采集的8位有效位(忽略停止位) 56 default:; 57 endcase 58 //使能信號:在完成接收以后立即拉高tx_en(啟動發送模塊) 59 always@(posedge clk or negedge rst_n) 60 if(!rst_n) 61 tx_en <= 0; 62 else if(rx_num == 9 && rx_sel_data) //在接收停止位之后拉高一個時鍾 63 tx_en <= 1; 64 else 65 tx_en <= 0; 66 endmodule |
22~34行的作用是下降沿檢測,電路檢測到rs232_rx有下降沿出現說明一幀數據開始傳輸。38~57行在波特率采樣控制信號rx_sel_data的控制下,將串行數據進行串並轉換,逐位存儲到中間並行寄存器rx_d_r。59~65行當數據采集完畢以后,負責輸出發送使能信號tx_en,觸發數據發送模塊啟動,將采集到的數據發送到上位機。
數據發送模塊波特率生成
/**************************************************** * Engineer : 夢翼師兄 * QQ : 761664056 * The module function:控制串口發送數據的速率 *****************************************************/ 00 module bps_tx( 01 clk, //系統時鍾50MHz 02 rst_n, //低電平復位 03 tx_en, //使能信號:串口發送開始 04 05 tx_sel_data, //波特率計數的中心點(采集數據的使能信號) 06 tx_num //一幀數據0-9 07 ); 08 //模塊輸入 09 input clk; //系統時鍾50MHz 10 input rst_n; //低電平復位 11 input tx_en; //使能信號:串口發送開始 12 //模塊輸出 13 output reg tx_sel_data; //波特率計數的中心點 14 output reg [3:0] tx_num; //一幀數據0-9 15 //設置參數 16 parameter bps_div = 13'd5207, 17 bps_div_2 = 13'd2603; 18 19 //發送標志位:接收到使能信號tx_en后,將標志位flag拉高,當信號tx_num計完一幀數據后拉低 20 reg flag; 21 always@(posedge clk or negedge rst_n) 22 if(!rst_n) 23 flag <= 0; 24 else if(tx_en) 25 flag <= 1; 26 else if(tx_num == 4'd10) 27 flag <= 0; 28 29 //波特率計數 30 reg [12:0] cnt; 31 always@(posedge clk or negedge rst_n) 32 if(!rst_n) 33 cnt <= 13'd0; 34 else if(flag && cnt < bps_div) 35 cnt <= cnt + 1'b1; 36 else 37 cnt <= 13'd0; 38 39 //規定發送數據的范圍:即一幀數據(10位:1位開始位,8位數據位,1位結束位) 40 always@(posedge clk or negedge rst_n) 41 if(!rst_n) 42 tx_num <= 4'd0; 43 else if(tx_sel_data && flag) 44 tx_num <= tx_num + 1'b1; 45 else if(tx_num == 4'd10) 46 tx_num <= 1'd0; 47 48 //數據在波特率的中間部分采集:即發送數據的使能信號 49 always@(posedge clk or negedge rst_n) 50 if(!rst_n) 51 tx_sel_data <= 1'b0; 52 else if(cnt == bps_div_2)//中間取數是為了產生尖峰脈沖,尖峰脈沖為采集數據的使能信號,用來把握速率 53 tx_sel_data <= 1'b1; 54 else 55 tx_sel_data <= 1'b0; 56 57 endmodule |
本模塊確定了發送數據與接收數據的有效范圍並且進行分頻計數(在此例中,使用的波特率為9600bps,時鍾為50MHZ,則分頻計數值為5207)。該模塊功能和bps_rx模塊功能類似。
數據發送模塊
/**************************************************** * Engineer : 夢翼師兄 * QQ : 761664056 * The module function:發送串口串行數據 *****************************************************/ 00 module uart_tx( 01 clk, //50MHZ時鍾 02 rst_n, //低電平復位 03 tx_num, //一幀數據控制位 04 tx_sel_data, //波特率計數的中心點(采集數據的使能信號) 05 rx_d, //8位數據(即輸入數據) 06 rs232_tx //uart發送信號(一幀數據) 07 ); 08 //模塊輸入 09 input clk; //50MHZ時鍾 10 input rst_n; //低電平復位 11 input [3:0] tx_num; //一幀數據控制位 12 input tx_sel_data; //波特率計數的中心點(采集數據的使能信號) 13 input [7:0] rx_d; //8位數據(即輸入數據) 14 //模塊輸出 15 output reg rs232_tx; //uart發送信號(一幀數據) 16 //在串口發送的過程中,確保發送1位開始位,8位有效數據位,1位結束位 17 always@(posedge clk or negedge rst_n) 18 if(!rst_n) 19 rs232_tx <= 1'b1; 20 else if(tx_sel_data) 21 case(tx_num) 22 0: rs232_tx <= 1'b0; //開始位為低電平 23 1: rs232_tx <= rx_d[0]; 24 2: rs232_tx <= rx_d[1]; 25 3: rs232_tx <= rx_d[2]; 26 4: rs232_tx <= rx_d[3]; 27 5: rs232_tx <= rx_d[4]; 28 6: rs232_tx <= rx_d[5]; 29 7: rs232_tx <= rx_d[6]; 30 8: rs232_tx <= rx_d[7]; 31 9: rs232_tx <= 1'b1; //結束位為高電平 32 default: rs232_tx <= 1'b1; //串口的其它空閑位均要拉至高電平 33 endcase 34 35 endmodule |
20~31行在發送使能信號tx_sel_data作用下,rs232_tx首先發送啟動信號,然后將並行數據逐位輸出,完成並串轉換,八位數據發送完畢以后發送停止信號,結束數據傳輸。
頂層模塊
/**************************************************** * Engineer : 夢翼師兄 * QQ : 761664056 * The module function:負責頂層連接 *****************************************************/ 00 module uart( 01 clk, //系統時鍾50MHz 02 rst_n, //低電平復位 03 rs232_rx, //uart發送信號 04 rs232_tx //uart接收信號 05 ); 06 //系統輸入 07 input clk; //系統時鍾50MHz 08 input rst_n; //低電平復位 09 input rs232_rx; //uart發送信號 10 //系統輸出 11 output rs232_tx; //uart接收信號 12 13 //內部信號:模塊內部的接口信號,比如模塊bps_rx的輸入信號en,通過內部信號rx_en與模塊uart_rx的輸出信號en相連 14 15 wire rx_en; 16 wire tx_en; 17 wire [7:0] rx_d; 18 wire [3:0] rx_num,tx_num; 19 20 //模塊例化 21 bps_rx bps_rx( //串口接收波特率計數模塊 22 .clk(clk), 23 .rst_n(rst_n), 24 .rx_en(rx_en), 25 .rx_num(rx_num), 26 .rx_sel_data(rx_sel_data) 27 ); 28 29 uart_rx uart_rx( //串口接收模塊 30 .clk(clk), 31 .rx_d(rx_d), 32 .rst_n(rst_n), 33 .rs232_rx(rs232_rx), 34 .rx_en(rx_en), 35 .rx_num(rx_num), 36 .rx_sel_data(rx_sel_data), 37 .tx_en(tx_en) 38 ); 39 40 bps_tx bps_tx( //串口發送波特率計數模塊 41 .clk(clk), 42 .rst_n(rst_n), 43 .tx_en(tx_en), 44 .tx_num(tx_num), 45 .tx_sel_data(tx_sel_data) 46 ); 47 uart_tx uart_tx( //串口發送模塊 48 .clk(clk), 49 .rst_n(rst_n), 50 .rx_d(rx_d), 51 .rs232_tx(rs232_tx), 52 .tx_num(tx_num), 53 .tx_sel_data(tx_sel_data) 54 ); 55 56 endmodule |
編寫完可綜合代碼之后,查看RTL視圖如下:
由RTL視圖可以看出,代碼綜合以后得到的電路結構和我們設計的系統框圖一致,說明頂層邏輯連接正確,接下來編寫測試代碼如下:
/**************************************************** * Engineer : 夢翼師兄 * QQ : 761664056 * The module function:測試uart *****************************************************/ 00 `timescale 1ns/1ps //設置仿真時間單位與精度分別為1ns/1ns 01 02 module uart_tb; 03 //系統輸入 04 reg clk; //系統時鍾50MHz 05 reg rst_n; //低電平復位 06 reg rs232_rx; //uart發送信號 07 //系統輸出 08 wire rs232_tx;//uart接收信號 09 //例化 10 uart uart( 11 .clk(clk), //系統時鍾50MHz 12 .rst_n(rst_n), //低電平復位 13 .rs232_rx(rs232_rx), //uart發送信號 14 .rs232_tx(rs232_tx) //uart發送信號 15 ); 16 17 initial 18 begin 19 clk = 0; rst_n = 0; rs232_rx= 1; //在復位階段,將激勵賦初值 20 #200.1 rst_n = 1; //延時200ns后停止復位 21 //模擬發送一幀數據(發送時間的延時根據所設定的波特率計算) 22 #200 rs232_rx= 0;//開始位 23 #110000 rs232_rx= 0;//發送數據8'ha4 (8'b0110_0100) 24 #110000 rs232_rx= 1; 25 #110000 rs232_rx= 1; 26 #110000 rs232_rx= 0; 27 #110000 rs232_rx= 0; 28 #110000 rs232_rx= 1; 29 #110000 rs232_rx= 0; 30 #110000 rs232_rx= 0; 31 #110000 rs232_rx= 1;//結束位 32 #1500000 $stop; //仿真1500000ns后停止仿真 33 end 34 always #10 clk = ~clk; //時鍾的表示,即每隔10ns翻轉一次,一個周期的時間即為20ns,時鍾為1/20ns = 50MHZ 35 endmodule |
22~31行模擬了上位機數據的輸入,用來測試我們設計的串口模塊是否能夠進行正常的數據收發。
仿真分析
從波形上可以很清楚的看到,接收和發送的波形完全相同,說明我們的設計正確。