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