FPGA零基础学习之UART驱动教程

可编程逻辑

1366人已加入

描述

UART即通用异步收发器,是一种通用串行数据总线,用于异步通信。该总线为双向通信,可以实现数据的接收与发送。

在数据传输过程中,我们需要解释一下串行通信。假设现在我们传输数据的双方为A和B,每次传输8bit数据,这8bit的数据在传输时按照A与B之间的连线分为串行通信和并行通信。串行通信即A与B之间仅有一根数据线,在传输数据时需要一次发送1bit,总共发送8次。并行通信即A与B之间有8根线,传输数据时,将8bit数据通过8根线一起传输,这样一次就可以全部传输完成。

数据传输时,接收方和发送方使用的时钟不是同一个时钟域,这也就是异步传输。

在通信双方传输数据之前,需要通过串口线进行连接,然后再传输数据,常用的串口线为DB9接口,但是由于这种接口体积大,不易携带等缺点而慢慢淘汰。我们在B04的开发板上使用到的是一个USB转串口的芯片,这样我们的MINI USB接口不仅可以给开发板供电,还可以进行串口数据传输。芯片为CP2102(USB <-->UART(LVCMOS/LVTTL)),对于开发者来说,就不需要关注电平标准了。

芯片电路图如图所示:

LVCMOS

在电路图中我们可以发现,串口接口只有两根数据线,分别为RXD和TXD。那么在进行通信之前,我们需要先了解一下串口的传输规则。

在发送者没有发送数据时,接收方如果一直接收数据,那就会导致数据出错,所以,接收方在接收数据时需要有标志信号,然后启动接收。在我们的串口协议中是这样规定的:

1、空闲态数据线上为高电平。

2、发送数据时,先发送起始位,逻辑电平为低。

3、起始位结束之后,发送8bit数据,从低位开始传输。

4、数据传输完毕,是1bit的校验位,采用奇偶校验法。(可不使用)

5、停止位,为高电平,可以是1bit、1.5bit或者2bit。

那么我们清楚了数据传输规则之后,我们还需要明白一个内容,那就是1bit数据的时间长度。在算这个时间之前,我们需要了解一下波特率。波特率的单位是bit/s,也就是1秒时间内,传输的bit数。我们串口常用的波特率有9600、14400、19200等等。这个波特率是传输数据的双方,提前规定好的。那么在同一速度下传输数据,就会简单很多。那么根据波特率我们可以计算出来1bit数据的时长为104166ns。在清楚这些之后,接下来我们做一个回环测试。

首先我们先新建一个工程:

LVCMOS

选好代码存放位置,修改工程名字为uart。

选择我们的芯片型号:XC7A35TFGG484-2。

LVCMOS

新建好工程后,开始新建文件写代码。

LVCMOS

LVCMOS

LVCMOS

点击OK,顶层文件新建完成,后续各个模块新建方式相同。接收代码如下:

 

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

 

发送数据时,跟接收基本类似,按照数据格式发送数据,代码如下:

 

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

 

其中读使能我们只需在数据发送前将数据读出即可。

在做完两个模块之后,我们还需要使用一个FIFO来做数据缓存,FIFO配置参数如下:

LVCMOS

我们使用异步FIFO,深度选择2048,位宽为8,复位信号暂时不使用。

LVCMOS

生成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

 

功能部分写完之后,我们写一个仿真进行逻辑验证,写仿真时,我们按照数据顺序模拟给值,每1bit持续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

 

打开仿真波形:

LVCMOS

如图,我们可以看到,当我们的接收模块接收到数据时,会将数据写入FIFO,FIFO中有数据时,发送模块就会将数据读出并发送,仿真现象正确。

下板现象:

LVCMOS

我们随便写入几个数据,会发现我们的发送模块和接收模块的数据完全一致,即接收和发送正常。





审核编辑:刘清

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分