寫在前面,但願記錄一下本身動手作的事情,拿來主義永遠是別人的異步
一、UART串口簡介async
串口顧名思義,是將字節數據以bit位進行串行傳輸,特色是線路簡單,速度較慢。因此經常使用在工業控制,嵌入式等數據傳輸速度要求不高的場景中。測試
擴展:串行通訊分兩種:同步串行和異步串行通訊spa
同步串行:通訊雙方在同一時鐘控制下,同步傳輸數據debug
異步串行:通訊雙方使用各自時鐘控制數據發送與接收過程設計
二、UART串口接線與數據與數據包格式調試
接線:UART是一種採用異步串行通訊方式的通用異步收發傳輸器(universal asynchronous receiver-transmitter),它在發送數據時將並行數據轉換成串行數據來傳輸,在接收數據時將接收到的串行數據轉換成並行數據。code
UART串口通訊須要兩根信號線來實現,一根用於串口發送,另一根負責串口接收。blog
串口接線ip
數據包格式: 在進行具體的串口設計以前,先了解串口通訊協議。一般串口的一次發送或接收由四個部分組成:起始位S(「通常爲邏輯‘0’)、數據位D0~D7(通常爲6位~8位之間可變,數據低位在前)、校驗位(奇校驗、偶檢驗或不須要校驗位)、中止位(一般爲1位、1.5位、2位)。中止位必須爲邏輯1。在一次串口通訊過程當中,數據接收與發送雙方沒有共享時鐘,所以,雙方必須協商好數據傳輸波特率。波特率即數據傳輸速率。根據雙方協議好的傳輸速率,接收端便可對發送端的數據進行採樣。經常使用的波特率有9600、19200、38400、57600以及115200等。固然更塊的速度意味着對採樣的要求更高,有可能誤碼率會逐漸提升。
一般對串口進行數據採樣,採用更高頻的時鐘。這樣作的目的是採用高頻時鐘來鎖存低頻時鐘,減小數據的誤碼率,增長接收模塊的自糾錯能力。
具體的工做流程爲:
發送端按照預先設定好的波特率,發送起始位(Start)+數據位(data)+奇偶校驗位+結束位。其中,起始位爲邏輯0,結束位爲邏輯1,發送端在空閒狀態爲1。發送數據包格式以下圖所示。
數據包格式
數據的奇偶校驗位是能夠選擇的,若是不使用奇偶,那麼就沒有這個數據位,本設計中沒有用到奇偶校驗位。
三、設計
接收端經過檢測電平‘1’到‘0’的跳變來肯定一個數據包的開始。肯定開始位接收完成以後,依次接收數據,使用更高的採樣時鐘,完成數據採集。接收完數據位後,繼續接收奇偶校驗位和中止位。
串口的接收與發送,其主要時序設計包括兩個部分:一、波特率的產生時序;二、數據傳輸時序,包括接收與發送。
波特率產生時序設計:假設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。
四、綜合佈線前仿真以下:
接收模塊正確
發送仿真ok
五、上板子調試
結果
測試結果
有誤碼狀況:
波特率提升時,會有誤碼出現,後面有時間再來改善一下。
誤碼圖:
原先的Kcu105自帶USB轉串口,可是實際過程當中效果不理想(出現出廠自帶的測試東西),因此我用的之前的PL2303這個USB轉串口,外掛了兩個Pin管腳,效果還行。