作者:李西銳??校對:陸輝
大俠好,歡迎來到FPGA技術江湖。本系列將帶來FPGA的系統(tǒng)性學習,從最基本的數(shù)字電路基礎開始,最詳細操作步驟,最直白的言語描述,手把手的“傻瓜式”講解,讓電子、信息、通信類專業(yè)學生、初入職場小白及打算進階提升的職業(yè)開發(fā)者都可以有系統(tǒng)性學習的機會。
系統(tǒng)性的掌握技術開發(fā)以及相關要求,對個人就業(yè)以及職業(yè)發(fā)展都有著潛在的幫助,希望對大家有所幫助。本次帶來Vivado系列,UART驅動教程。話不多說,上貨。
UART 驅動教程
UART即通用異步收發(fā)器,是一種通用串行數(shù)據(jù)總線,用于異步通信。該總線為雙向通信,可以實現(xiàn)數(shù)據(jù)的接收與發(fā)送。
在數(shù)據(jù)傳輸過程中,我們需要解釋一下串行通信。假設現(xiàn)在我們傳輸數(shù)據(jù)的雙方為A和B,每次傳輸8bit數(shù)據(jù),這8bit的數(shù)據(jù)在傳輸時按照A與B之間的連線分為串行通信和并行通信。串行通信即A與B之間僅有一根數(shù)據(jù)線,在傳輸數(shù)據(jù)時需要一次發(fā)送1bit,總共發(fā)送8次。并行通信即A與B之間有8根線,傳輸數(shù)據(jù)時,將8bit數(shù)據(jù)通過8根線一起傳輸,這樣一次就可以全部傳輸完成。
數(shù)據(jù)傳輸時,接收方和發(fā)送方使用的時鐘不是同一個時鐘域,這也就是異步傳輸。
在通信雙方傳輸數(shù)據(jù)之前,需要通過串口線進行連接,然后再傳輸數(shù)據(jù),常用的串口線為DB9接口,但是由于這種接口體積大,不易攜帶等缺點而慢慢淘汰。我們在B04的開發(fā)板上使用到的是一個USB轉串口的芯片,這樣我們的MINI USB接口不僅可以給開發(fā)板供電,還可以進行串口數(shù)據(jù)傳輸。芯片為CP2102(USB <-->UART(LVCMOS/LVTTL)),對于開發(fā)者來說,就不需要關注電平標準了。
芯片電路圖如圖所示:
在電路圖中我們可以發(fā)現(xiàn),串口接口只有兩根數(shù)據(jù)線,分別為RXD和TXD。那么在進行通信之前,我們需要先了解一下串口的傳輸規(guī)則。
在發(fā)送者沒有發(fā)送數(shù)據(jù)時,接收方如果一直接收數(shù)據(jù),那就會導致數(shù)據(jù)出錯,所以,接收方在接收數(shù)據(jù)時需要有標志信號,然后啟動接收。在我們的串口協(xié)議中是這樣規(guī)定的:
1、空閑態(tài)數(shù)據(jù)線上為高電平。
2、發(fā)送數(shù)據(jù)時,先發(fā)送起始位,邏輯電平為低。
3、起始位結束之后,發(fā)送8bit數(shù)據(jù),從低位開始傳輸。
4、數(shù)據(jù)傳輸完畢,是1bit的校驗位,采用奇偶校驗法。(可不使用)
5、停止位,為高電平,可以是1bit、1.5bit或者2bit。
那么我們清楚了數(shù)據(jù)傳輸規(guī)則之后,我們還需要明白一個內容,那就是1bit數(shù)據(jù)的時間長度。在算這個時間之前,我們需要了解一下波特率。波特率的單位是bit/s,也就是1秒時間內,傳輸?shù)腷it數(shù)。我們串口常用的波特率有9600、14400、19200等等。這個波特率是傳輸數(shù)據(jù)的雙方,提前規(guī)定好的。那么在同一速度下傳輸數(shù)據(jù),就會簡單很多。那么根據(jù)波特率我們可以計算出來1bit數(shù)據(jù)的時長為104166ns。在清楚這些之后,接下來我們做一個回環(huán)測試。
首先我們先新建一個工程:
選好代碼存放位置,修改工程名字為uart。
選擇我們的芯片型號:XC7A35TFGG484-2。
新建好工程后,開始新建文件寫代碼。
點擊OK,頂層文件新建完成,后續(xù)各個模塊新建方式相同。接收代碼如下:
1 module uart_rx(
2
3 input wire clk,
4 input wire rst_n,
5 input wire RXD,
6 output reg [7:0] data,
7 output reg wr_en
8 );
9
10 parameter t = 5208;
11
12 reg [14:0] cnt;
13 reg flag;
14 reg rxd_r, rxd_rr;
15 wire rx_en;
16 reg [3:0] num;
17 reg [7:0] data_r;
18
19 always @ (posedge clk) rxd_r <= RXD;
20 always @ (posedge clk) rxd_rr <= rxd_r;
21
22 assign rx_en = (~rxd_r) & rxd_rr;
23
24 always @ (posedge clk, negedge rst_n)
25 begin
26 if(rst_n == 1'b0)
27 cnt <= 15'd0;
28 else if(flag)
29 begin
30 if(cnt == t - 1)
31 cnt <= 15'd0;
32 else
33 cnt <= cnt + 1'b1;
34 end
35 else
36 cnt <= 15'd0;
37 end
38
39 always @ (posedge clk, negedge rst_n)
40 begin
41 if(rst_n == 1'b0)
42 flag <= 1'b0;
43 else if(rx_en)
44 flag <= 1'b1;
45 else if(num == 4'd10)
46 flag <= 1'b0;
47 else
48 flag <= flag;
49 end
50
51 always @ (posedge clk, negedge rst_n)
52 begin
53 if(rst_n == 1'b0)
54 num <= 4'd0;
55 else if(cnt == t / 2 - 1)
56 num <= num + 1'b1;
57 else if(num == 4'd10)
58 num <= 4'd0;
59 else
60 num <= num;
61 end
62
63 always @ (posedge clk, negedge rst_n)
64 begin
65 if(rst_n == 1'b0)
66 begin
67 data_r <= 8'd0;
68 data <= 8'd0;
69 end
70 else if(cnt == t / 2 - 1)
71 case(num)
72 4'd0 : ;
73 4'd1 : data_r[0] <= rxd_rr;
74 4'd2 : data_r[1] <= rxd_rr;
75 4'd3 : data_r[2] <= rxd_rr;
76 4'd4 : data_r[3] <= rxd_rr;
77 4'd5 : data_r[4] <= rxd_rr;
78 4'd6 : data_r[5] <= rxd_rr;
79 4'd7 : data_r[6] <= rxd_rr;
80 4'd8 : data_r[7] <= rxd_rr;
81 4'd9 : data <= data_r;
82 default : data <= data;
83 endcase
84 end
85
86 always @ (posedge clk, negedge rst_n)
87 begin
88 if(rst_n == 1'b0)
89 wr_en <= 1'b0;
90 else if(num == 4'd10)
91 wr_en <= 1'b1;
92 else
93 wr_en <= 1'b0;
94 end
95
96 endmodule
發(fā)送數(shù)據(jù)時,跟接收基本類似,按照數(shù)據(jù)格式發(fā)送數(shù)據(jù),代碼如下:
1 module uart_tx(
2
3 input wire clk,
4 input wire rst_n,
5 input wire empty,
6 input wire [7:0] data,
7 output wire rd_en,
8 output reg TXD
9 );
10
11 parameter t = 5208;
12
13 reg [14:0] cnt;
14 reg flag;
15 reg [3:0] num;
16
17
18 always @ (posedge clk, negedge rst_n)
19 begin
20 if(rst_n == 1'b0)
21 cnt <= 15'd0;
22 else if(flag)
23 begin
24 if(cnt == t - 1)
25 cnt <= 15'd0;
26 else
27 cnt <= cnt + 1'b1;
28 end
29 else
30 cnt <= 15'd0;
31 end
32
33 always @ (posedge clk, negedge rst_n)
34 begin
35 if(rst_n == 1'b0)
36 flag <= 1'b0;
37 else if(empty == 1'b0)
38 flag <= 1'b1;
39 else if(num == 4'd10)
40 flag <= 1'b0;
41 else
42 flag <= flag;
43 end
44
45 always @ (posedge clk, negedge rst_n)
46 begin
47 if(rst_n == 1'b0)
48 num <= 4'd0;
49 else if(cnt == t / 2 - 1)
50 num <= num + 1'b1;
51 else if(num == 4'd10)
52 num <= 4'd0;
53 else
54 num <= num;
55 end
56
57 assign rd_en = (num == 4'd0 && cnt == 15'd1) ? 1'b1 : 1'b0;
58
59 always @ (posedge clk, negedge rst_n)
60 begin
61 if(rst_n == 1'b0)
62 TXD <= 1'b1;
63 else if(cnt == t / 2 - 1)
64 case(num)
65 4'd0 : TXD <= 1'b0;
66 4'd1 : TXD <= data[0];
67 4'd2 : TXD <= data[1];
68 4'd3 : TXD <= data[2];
69 4'd4 : TXD <= data[3];
70 4'd5 : TXD <= data[4];
71 4'd6 : TXD <= data[5];
72 4'd7 : TXD <= data[6];
73 4'd8 : TXD <= data[7];
74 4'd9 : TXD <= 1'b1;
75 default : TXD <= 1'b1;
76 endcase
77 end
78
79 endmodule
其中讀使能我們只需在數(shù)據(jù)發(fā)送前將數(shù)據(jù)讀出即可。
在做完兩個模塊之后,我們還需要使用一個FIFO來做數(shù)據(jù)緩存,F(xiàn)IFO配置參數(shù)如下:
我們使用異步FIFO,深度選擇2048,位寬為8,復位信號暫時不使用。
生成FIFO后,將各個模塊例化到頂層當中,代碼如下:
1 module uart(
2
3 input wire clk,
4 input wire rst_n,
5 input wire RXD,
6 output wire TXD
7 );
8
9 wire [7:0] rx_data;
10 wire wr_en;
11 wire rd_en;
12 wire [7:0] tx_data;
13 wire empty;
14
15 uart_rx uart_rx_inst(
16
17 .clk (clk ),
18 .rst_n (rst_n),
19 .RXD (RXD ),
20 .data (rx_data),
21 .wr_en (wr_en)
22 );
23
24 fifo fifo_inst (
25 .wr_clk(clk), // input wire wr_clk
26 .rd_clk(clk), // input wire rd_clk
27 .din(rx_data), // input wire [7 : 0] din
28 .wr_en(wr_en), // input wire wr_en
29 .rd_en(rd_en), // input wire rd_en
30 .dout(tx_data), // output wire [7 : 0] dout
31 .full(), // output wire full
32 .empty(empty) // output wire empty
33 );
34
35 uart_tx uart_tx_inst(
36
37 .clk (clk ),
38 .rst_n (rst_n ),
39 .empty (empty ),
40 .data (tx_data),
41 .rd_en (rd_en ),
42 .TXD (TXD )
43 );
44
45 endmodule
功能部分寫完之后,我們寫一個仿真進行邏輯驗證,寫仿真時,我們按照數(shù)據(jù)順序模擬給值,每1bit持續(xù)104166ns的時間。代碼如下:
1 `timescale 1ns / 1ps
2
3 module uart_tb;
4
5 reg clk;
6 reg rst_n;
7 reg RXD;
8 wire TXD;
9
10 initial begin
11 clk = 0;
12 rst_n = 0;
13 RXD = 1;
14 #105;
15 rst_n = 1;
16
17 #1000;
18 RXD = 0;
19 #104166;
20
21 RXD = 1;
22 #104166;
23 RXD = 0;
24 #104166;
25 RXD = 1;
26 #104166;
27 RXD = 0;
28 #104166;
29 RXD = 1;
30 #104166;
31 RXD = 0;
32 #104166;
33 RXD = 0;
34 #104166;
35 RXD = 1;
36 #104166;
37
38 RXD = 1;
39 #104166;
40
41 #5000;
42 $stop;
43 end
44
45 always #10 clk = ~clk;
46
47 uart uart_inst(
48
49 .clk (clk ),
50 .rst_n (rst_n ),
51 .RXD (RXD ),
52 .TXD (TXD )
53 );
54
55 endmodule
打開仿真波形:
如圖,我們可以看到,當我們的接收模塊接收到數(shù)據(jù)時,會將數(shù)據(jù)寫入FIFO,F(xiàn)IFO中有數(shù)據(jù)時,發(fā)送模塊就會將數(shù)據(jù)讀出并發(fā)送,仿真現(xiàn)象正確。
下板現(xiàn)象:
我們隨便寫入幾個數(shù)據(jù),會發(fā)現(xiàn)我們的發(fā)送模塊和接收模塊的數(shù)據(jù)完全一致,即接收和發(fā)送正常。