资讯详情

FPGA零基础学习:UART协议驱动设计

通用异步收发传输器(Universal Asynchronous Receiver / Transmitter),通常称作UART,是一种异步收发传输器。它在串行通信和并行通信之间转换要传输的数据。作为将并行输入信号转换为串行输出信号的芯片,UART通常集成在其他通信接口的连接上。

UART异步通信是一种通用串行数据总线。该总线的双向通信可实现全双工传输和接收。嵌入式设计,UART用于通信主机和辅助设备,如汽车音响和外部AP通信,和PC通信包括监控调试器和其他设备。

两个芯片或设备之间的传输信息称为通信。可能有很多信息,比如传输ASCII码(8bit)。在设计中,我们可以在两个通信设备之间设计8条数据线bit同时发送数据,对方同时接收8位数据。这种同时发送多个数据的传输方式称为并行通信。

由于某些原因,设备A和设备B不能设计多条数据线,只能设计一条数据线。如果此时仍需要传输ASCII码,那该怎么办?

可将设备AASCII代码的8位,按一定顺序逐一发送到数据线。设备B按设备A发送的顺序逐一接收,然后拼接成8位。这种通信方式成为串行通信。

将8个或多个数据分成一个发送的过程称为并转串。将一个接收到的数据合并为8个或多个数据的过程称为串转并。

对于串行通信设备,发送方正在执行并转串,接收方正在执行串行并转串。

UART该设备是串行通信设备。

全双工通信是指信息可以同时双向传输。例如:打电话,同时听。

半双工通信是指信息只能同时单向传输,双方都可以发送和接收,但不能同时发送和接收。例如:对讲机通信。

单工通信是指在通信过程中,只能发送设备A,接收设备B。例如:听收音机。

SANXIN – B01的开发板上的UART接口设备可实现半双工通信。

两个设备之间相互通信的基本条件是相同的电平标准。UART接口标准有很多,包括RS232、RS485等等。

台式PC一般上面会有一个DB9、接口标准为RS232。

各工业板上也有许多接口。随着技术的发展,PC上的DB9的接口逐渐被淘汰,换成了USB接口。

选择使用我们的开发板USB界面,方便大家学习,方便和谐PC进行通信。

FPGA芯片RS485、RS232、USB接口电平等。在大多数板卡设计中FPGA将电平转换器添加到外围,并将其添加到外围FPGA将电平标准转换为通信电平标准。

在我们的开发板上使用USB <->UART(LVCOMS/LVTTL)电平转换芯片CP2102。因此,开发板上的供电端口不仅可以供电,还可以通信。

对于开发者来说,不需要考虑线路的电平标准,只需要考虑如何发送和接收逻辑。

当双方进行通信时,他们并不总是进行通信,而且大多数通信都是突然的。此时,发送设备需要在发送有效性前提前通知接收设备接收信息。有些人会有问题,发送者有信息发送,没有信息停止;接收设备检测信息发送接收,没有信息不接收;不是吗?为什么要提前通知信息?

由于双方可以进行通信,通信线路必然会在中间建立。当发送人有信息时,发送信息;停止发送无信息。对于发送人来说,没有问题。接收人相对困难。在通信线路中,当发送人不发送信息时,接收人也会在线路上收到信息,因为接收人不知道发送人是否发送信息,这将导致接收人无法判断信息或噪音。

为解决上述问题,我们规定了通信协议。

在UART在通信协议中,我们规定:

  1. 不通信时,发送高电平。
  2. 发送信息时,位(1)bit、低电平)。可以理解为告诉接收方应该接收信息。
  3. 发送数据位,由于是串行通信,规定从低开始(协议规定信息位可以是4、5、6、7、8)。
  4. 校验位(1bit)。可采用奇校验、偶校验、直发1、直发0、不发等五种情况。
  5. 停止位(1bit、1.5bit、2bit。高电平)。
  6. 空闲位(1bit,高电平)

1bit时间宽度是多少?

在UART在协议中,常用的波特率(BAUD)为300、600、1200、2400、4800、9600、19200、38400、43000、56000、57600、115200。1秒除以波特率。bit时间宽度。

验证位有什么作用?如何验证?

在发送信息时,由于需要通过长线,中间很可能会受到干扰,导致某些信息位置反转,最终导致通信失败。验证位的作用是在接收到数据后进行检查。如果检查失败,则视为接收数据错误,可直接丢弃。

在验证过程中,您可以选择奇怪的验证和偶像验证。奇怪的验证是要求发送的数据位和验证位1的数量,偶像验证是要求发送的数据位和验证位1的数量。

在发送器中添加缓冲器。即上游模块将要发送的数据写入发送器FIFO检测到发送器的控制逻辑FIFO当有数据时,读取并发送。由于发送器发送速度慢,添加FIFO之后,上游模块可以直接写入后续数据,无需等待上一个数据发送完成。

发送器的控制逻辑根据事先约定的波特率和验证方法进行读取FIFO按数据UART向外发送协议。

在接收器中添加缓冲器。也就是说,接收器的控制逻辑在接收到信息后发送到缓冲器。由于缓冲器的存在,主控制器可以检查缓冲器中是否有数据,而无需始终检查接收状态。

在接收过程中,开始位置的低电平持续时间应超过半个周期,以避免线路干扰和错误接收。

在接收数据位、验证位和停止位时,采用倍频(16倍频)采样,中间采样值为6、7、8、9、10。当采样依据。当五次全部为同一电平时,本位为此电平值;当四次相同,一次不同时,本位与四次相同的电平值相同;的电平值相同;当发生其他情况时,认为线路干扰

接收完成后,进行帧检测和验证,满足设计要求时,将数据写入FIFO中。

该模块命名为uart_drive,由四个模块组成。

tx_fifo模块:发送缓冲区256深度,宽度为8,设计高电平有效复位。负责缓存上游要发送的数据。

tx_ctrl模块:发送逻辑控制部分。tx_fifo根据中间的数据UART发送协议规定。

rx_ctrl模块:接收逻辑控制部分。根据外部数据在线数据负责UART解析协议规定,存储到tx_fifo中。

rx_fifo模块:接收缓冲区256的深度和宽度为8,设计高电平有效复位。负责缓存接收逻辑控制部分分析的数据,等待控制器件的读取。

调用tx_fifo和7.四节方法相似,其他步骤不同,下面给出具体说明。

如果在设计中不使用许多标志信号,则不再引出。

引出清除信号(高电平有效),使清除信号与读时钟同步。

调用rx_fifo和7.四节方法相似,其他步骤不同,下面给出具体说明。

如果在设计中不使用许多标志信号,则不再引出。

引出清除信号(高电平有效),使清除信号与写时钟同步。

参数PARITY为选择的验证方法,1为奇验证,0为偶验证。

参数BAUD波特率的选择。

参数F_clk参考时钟频率。

参数T是波特率规定的时间,需要计数多个参考时钟周期。

设计代码为:

module tx_ctrl (    input   wire              clk,   input   wire              rst_n,      input   wire              tx_fifo_rdempty,   output  reg               tx_fifo_rden,   input   wire    [7:0]     tx_fifo_rdata,      output  reg               uart_txd );    parameter   PARITY   =    1;   parameter   BAUD     =    9600;   parameter   F_clk    =    50_000_000;      localparam  T        =    F_clk/BAUD;      reg                       tx_en;   reg                       tx_done;   reg         [25:0]        baud_cnt;   reg         [3:0]         bit_cnt;      always @ (posedge clk, negedge rstn) begin
    if (rst_n == 1'b0)
      baud_cnt <= 26'd0;
    else
      if (tx_en == 1'b1 && baud_cnt < T - 1'b1)
        baud_cnt <= baud_cnt + 1'b1;
      else
        baud_cnt <= 26'd0;
  end
  
  always @ (posedge clk, negedge rst_n) begin
    if (rst_n == 1'b0)
      bit_cnt <= 4'd0;
    else
      if (tx_en == 1'b1)
        if (baud_cnt == 26'd1)
          bit_cnt <= bit_cnt + 1'b1;
        else
          bit_cnt <= bit_cnt;
      else
        bit_cnt <= 4'd0;
  end
  
  always @ (posedge clk, negedge rst_n) begin
    if (rst_n == 1'b0)
      tx_done <= 1'b0;
    else
      if (bit_cnt == 4'd13 && baud_cnt == 26'd1)
        tx_done <= 1'b1;
      else
        tx_done <= 1'b0;
  end
  
  always @ (posedge clk, negedge rst_n) begin
    if (rst_n == 1'b0)
      tx_en <= 1'b0;
    else
      if (tx_en == 1'b0 && tx_fifo_rdempty == 1'b0)
        tx_en <= 1'b1;
      else
        if (tx_done == 1'b1)
          tx_en <= 1'b0;
        else
          tx_en <= tx_en;
  end
  
  always @ (posedge clk, negedge rst_n) begin
    if (rst_n == 1'b0)
      tx_fifo_rden <= 1'b0;
    else
      if (tx_en == 1'b0 && tx_fifo_rdempty == 1'b0)
        tx_fifo_rden <= 1'b1;
      else
        tx_fifo_rden <= 1'b0;
  end
  
  always @ (posedge clk, negedge rst_n) begin
    if (rst_n == 1'b0)
      uart_txd <= 1'b1;
    else  
      case (bit_cnt)
        4'd0    :   uart_txd <= 1'b1;  //  no busy
        4'd1    :   uart_txd <= 1'b0;  //  start
        4'd2    :   uart_txd <= tx_fifo_rdata[0];  // bit0
        4'd3    :   uart_txd <= tx_fifo_rdata[1];
        4'd4    :   uart_txd <= tx_fifo_rdata[2];
        4'd5    :   uart_txd <= tx_fifo_rdata[3];
        4'd6    :   uart_txd <= tx_fifo_rdata[4];
        4'd7    :   uart_txd <= tx_fifo_rdata[5];
        4'd8    :   uart_txd <= tx_fifo_rdata[6];
        4'd9    :   uart_txd <= tx_fifo_rdata[7]; // bit7
        4'd10   :   uart_txd <= PARITY ? ~(^tx_fifo_rdata) : ^tx_fifo_rdata; // parity bit
        4'd11   :   uart_txd <= 1'b1; // stop 
        4'd12   :   uart_txd <= 1'b1; // no busy
        4'd13   :   uart_txd <= 1'b1; // no busy
        default :   uart_txd <= 1'b1;
      endcase
  end

endmodule

tx_en为发送标志信号,当发送逻辑处于不发送状态时,并且tx_fifo中不空,就将tx_en拉高,启动发送逻辑。当发送完成后,拉高tx_done,将tx_en拉低。其他时间tx_en保持不变。

tx_fifo_rden为tx_fifo的读使能信号,拉高一拍,读出一个数据,所以每次只能拉高一拍。在tx_en为低器件,且外部tx_fifo中有数据时,拉高tx_fifo_rden。tx_fifo_rden和tx_en拉高的条件相同,故而会同步拉高,下一拍时,tx_en会变为高电平,所以此时tx_fifo_rden只会拉高一拍。

baud_cnt是为了记录每发送1bit时间宽度的计数器。在发送使能tx_en拉高后,baud_cnt就开始不断的计数即可。

bit_cnt为此时应该发送UART协议中哪一位的计数器,此计数器在发送使能拉高后,baud_cnt每次计数到1时,bit_cnt进行加1。由于baud_cnt为循环计数,无论在什么时刻bit_cnt加1,后续加1的时间间隔都是一个bit时间宽度。为了能够使tx_en一旦拉高,发送逻辑能够快速发送起始位,所以本设计中选择1。

tx_done信号为发送完成信号,当bit_cnt等于13(起始位1bit、数据位8bit、校验位1bit,停止位1bit和空闲位1bit,共计12bit。本设计中bit_cnt为1时,发送起始位;bit_cnt为12时,发送空闲位)时,证明所有的bit位都已经发送完成,将tx_done拉高。

在算术运算中,假设data的位宽为3,^data=data[1] ^ data[1] ^ data[0],这种运算规则称为缩减运算符。缩减运算符还有“&”和“|”。如果data中1的个数为奇数个,那么缩减异或之后的记过为1,否则为0。当采用奇校验时,数据位和校验位的1个数为奇数,所以校验位应该是~(^tx_fifo_rdata)。当采用偶校验时,数据位和校验位的1个数为偶数,所以校验位应该是^tx_fifo_rdata。

参数PARITY为选择的校验方式,1表示为奇校验,0表示为偶校验。

参数BAUD为选择的波特率。

参数F_clk为参考的时钟频率。

参数T为需要计数多个参考时钟周期才可以到16倍波特率规定的时间。

由于外部uart_rxd的信号为异步信号,首先需要打两拍。

设计代码为:

module rx_ctrl (

  input     wire                clk,
  input     wire                rst_n,
  
  input     wire                uart_rxd,
  
  output    reg     [7:0]       rx_fifo_data,
  output    reg                 rx_fifo_wren
);

  parameter   PARITY   =    1;
  parameter   BAUD     =    9600;
  parameter   F_clk    =    50_000_000;

  localparam  T        =    F_clk/(16*BAUD);
  
  reg                           rxd_r;
  reg                           rxd_rr;
  reg                           rx_en;
  reg                           rx_done;
  reg               [25:0]      start_cnt;
  reg               [25:0]      baudx16_cnt;
  reg               [7:0]       cap_cnt;
  reg               [151:0]     cap_buf;
  reg               [8:0]       rx_buf;
  reg               [8:0]       rx_error;
  reg                           rx_done_r;

  initial rxd_r = 1'b1;
  initial rxd_rr = 1'b1;
  
  always @ (posedge clk) rxd_r <= uart_rxd;
  always @ (posedge clk) rxd_rr <= rxd_r;
  
  always @ (posedge clk, negedge rst_n) begin
    if (rst_n == 1'b0)
      start_cnt <= 26'd0;
    else
      if (rx_en == 1'b0 && rxd_rr == 1'b0)
        start_cnt <= start_cnt + 1'b1;
      else
        start_cnt <= 26'd0;
  end
  
  always @ (posedge clk, negedge rst_n) begin
    if (rst_n == 1'b0)
      rx_en <= 1'b0;
    else
      if (start_cnt == 8 * T)
        rx_en <= 1'b1;
      else
        if (rx_done == 1'b1)
          rx_en <= 1'b0;
        else
          rx_en <= rx_en;
  end
  
  always @ (posedge clk, negedge rst_n) begin
    if (rst_n == 1'b0)
      baudx16_cnt <= 26'd0;
    else
      if (rx_en == 1'b1 && baudx16_cnt < T - 1'b1)
        baudx16_cnt <= baudx16_cnt + 1'b1;
      else
        baudx16_cnt <= 26'd0;
  end
  
  always @ (posedge clk, negedge rst_n) begin
    if (rst_n == 1'b0)
      cap_buf <= 152'd0;
    else
      if (rx_en == 1'b1 && baudx16_cnt == T - 1'b1)
        cap_buf <= {cap_buf[150:0], rxd_rr};
      else
        cap_buf <= cap_buf;
  end
  
  always @ (posedge clk, negedge rst_n) begin
    if (rst_n == 1'b0)
      cap_cnt <= 8'd0;
    else
      if (rx_en == 1'b1)
        if (baudx16_cnt == T - 1'b1)
          cap_cnt <= cap_cnt + 1'b1;
        else  
          cap_cnt <= cap_cnt;
      else
        cap_cnt <= 8'd0;
  end
  
  always @ (posedge clk, negedge rst_n) begin
    if (rst_n == 1'b0)
      rx_done <= 1'b0;
    else
      if (cap_cnt == 8'd152 && baudx16_cnt == 26'd2)
        rx_done <= 1'b1;
      else
        rx_done <= 1'b0;
  end
  
  always @ (posedge clk, negedge rst_n) begin
    if (rst_n == 1'b0) begin
      rx_buf <= 9'd0;
      rx_error <= 9'd0;
    end
    else 
      if (rx_done == 1'b1) begin
        {rx_error[8],rx_buf[8]} <= decoder_rxd(cap_buf[10:6]);
        {rx_error[7],rx_buf[7]} <= decoder_rxd(cap_buf[26:22]);
        {rx_error[6],rx_buf[6]} <= decoder_rxd(cap_buf[42:38]);
        {rx_error[5],rx_buf[5]} <= decoder_rxd(cap_buf[58:54]);
        {rx_error[4],rx_buf[4]} <= decoder_rxd(cap_buf[74:70]);
        {rx_error[3],rx_buf[3]} <= decoder_rxd(cap_buf[90:86]);
        {rx_error[2],rx_buf[2]} <= decoder_rxd(cap_buf[106:102]);
        {rx_error[1],rx_buf[1]} <= decoder_rxd(cap_buf[122:118]);
        {rx_error[0],rx_buf[0]} <= decoder_rxd(cap_buf[138:134]);
      end
      else begin
        rx_error <= rx_error;
        rx_buf <= rx_buf;
      end
  end
  
  function [1:0] decoder_rxd;
    
    input   [4:0]   data;
    
    reg     [2:0]   num;
    
    begin
      
      num = data[4] + data[3] + data[2] + data[1] + data[0];
      decoder_rxd[1] = (num < 3'd4)&&(num > 3'd1) ? 1'b1 : 1'b0;
      decoder_rxd[0] = (num > 3'd3) ? 1'b1 : 1'b0;
      
    end
  endfunction
  
  initial rx_done_r = 1'b0;
  
  always @ (posedge clk) rx_done_r <= rx_done;
  
  always @ (posedge clk, negedge rst_n) begin
    if (rst_n == 1'b0)
      rx_fifo_data <= 8'd0;
    else
      if (rx_done_r == 1'b1)
        rx_fifo_data <= rx_buf[7:0];
      else
        rx_fifo_data <= rx_fifo_data;
  end
  
  always @ (posedge clk, negedge rst_n) begin
    if (rst_n == 1'b0)
      rx_fifo_wren <= 1'b0;
    else
      if (rx_done_r == 1'b1 && (|rx_error == 1'b0) && ^rx_buf == PARITY)
        rx_fifo_wren <= 1'b1;
      else
        rx_fifo_wren <= 1'b0;
  end
endmodule

start_cnt为记录在没有启动接收时,低电平的持续时间。

当start_cnt的低电平持续时间等于8个T时(16个T为一个bit的时间宽度),认为此起始位有效,拉高rx_en。当接收完成后,rx_en拉低,其他时间,rx_en保持不变。

当rx_en拉高后,baudx16_cnt不断开始计数,最大值为16倍频的宽度值。

当rx_en拉高后,且baudx16_cnt为最大值时,就开始进行移位采样。由于起始位只判断一半,所以半个起始位、8个数据位、1个奇偶校验位,在16倍频采样的情况下,一共会采样152次。

cap_cnt为采样的计数,当采样到152时,且baudx16_cnt等于2时,认为采样结束。利用baudx16_cnt等于2只是为了产生的tx_done为一个脉冲。

采样结束后,利用函数的特性,得出8个数据位、1个校验位,并且得出9个线路是不是出现干扰的标志rx_error。

得到最终结果后,将数据进行输出。

产生rx_fifo_wren时,进行了线路干扰检测判断,停止位判断,以及奇偶校验判断,当都符合预期后,输出为1。其他情况输出为0。

顶层设计只负责将上述四个模块按照架构图的方式进行连接。

设计代码为:

module uart_drive (

  input   wire            clk,
  input   wire            rst_n,
  
  input   wire            tx_clk,
  input   wire            tx_en,
  input   wire    [7:0]   tx_data,
  output  wire            tx_full,
  output  wire            uart_txd,
  
  input   wire            uart_rxd,
  input   wire            rx_clk,
  output  wire            rx_empty,
  input   wire            rx_en,
  output  wire    [7:0]   rx_data
);
   
  parameter   PARITY   =    1;
  parameter   BAUD     =    9600;
  parameter   F_clk    =    50_000_000;

  wire                    tx_fifo_rdempty;
  wire                    tx_fifo_rden;
  wire            [7:0]   tx_fifo_rdata;
  wire                    rx_fifo_wren;
  wire            [7:0]   rx_fifo_data;
  
  tx_fifo  tx_fifo_inst (
      .aclr         ( ~rst_n ),
      .data         ( tx_data ),
      .rdclk        ( clk ),
      .rdreq        ( tx_fifo_rden ),
      .wrclk        ( tx_clk ),
      .wrreq        ( tx_en ),
      .q            ( tx_fifo_rdata ),
      .rdempty      ( tx_fifo_rdempty ),
      .wrfull       ( tx_full )
    );
  
  tx_ctrl # (
      .PARITY       (PARITY),
      .BAUD         (BAUD),  
      .F_clk        (F_clk)
    )tx_ctrl_inst(
      .clk          (clk),
      .rst_n        (rst_n),
      
      .tx_fifo_rdempty(tx_fifo_rdempty),
      .tx_fifo_rden (tx_fifo_rden),
      .tx_fifo_rdata(tx_fifo_rdata),
      
      .uart_txd     (uart_txd)
    );
    
  rx_ctrl # (
      .PARITY       (PARITY),
      .BAUD         (BAUD),  
      .F_clk        (F_clk)
    ) rx_ctrl_inst (

      .clk          (clk),
      .rst_n        (rst_n),
      
      .uart_rxd     (uart_rxd),
      
      .rx_fifo_data (rx_fifo_data),
      .rx_fifo_wren (rx_fifo_wren)
    );
  
  rx_fifo  rx_fifo_inst (
      .aclr         ( ~rst_n ),
      .data         ( rx_fifo_data ),
      .rdclk        ( rx_clk ),
      .rdreq        ( rx_en ),
      .wrclk        ( clk ),
      .wrreq        ( rx_fifo_wren ),
      .q            ( rx_data ),
      .rdempty      ( rx_empty )
    );
    
endmodule

parameter所定义的参数,在例化时,可以对它进行重新赋值,方便我们参数化设计。

在仿真中,将uart_rxd和uart_txd相连接,实现自发自收。

对于tx_clk和rx_clk都采用clk连接。

仿真代码如下:

`timescale 1ns/1ps

module uart_drive_tb;
  
  reg                 clk;
  reg                 rst_n;
  
  reg                 tx_en;
  reg     [7:0]       tx_data;
  wire                tx_full;
  wire                uart_sda;
  
  reg                 rx_en;
  wire                rx_empty;
  wire    [7:0]       rx_data;
  
  uart_drive uart_drive_inst(

      .clk            (clk),
      .rst_n          (rst_n),
      
      .tx_clk         (clk),
      .tx_en          (tx_en),
      .tx_data        (tx_data),
      .tx_full        (tx_full),
      .uart_txd       (uart_sda),
      
      .uart_rxd       (uart_sda),
      .rx_clk         (clk),
      .rx_empty       (rx_empty),
      .rx_en          (rx_en),
      .rx_data        (rx_data)
    );
    
  initial clk = 1'b0;
  always # 10 clk = ~clk;
  
  initial begin
    rst_n = 1'b0;
    tx_data = 8'd0;
    tx_en = 1'b0;
    rx_en = 1'b0;
    # 201
    rst_n = 1'b1;
    # 200;
    repeat (5) begin
      @ (posedge clk);
      # 2;
      tx_data = {$random} % 256;
      tx_en = 1'b1;
    end
    
    @ (posedge clk);
    # 2;
    tx_data = 8'd0;
    tx_en = 1'b0;
    
    repeat (5) begin
      @ (negedge rx_empty);
      # 2;
      rx_en = 1'b1;
      @ (posedge clk);
      # 2;
      rx_en = 1'b0;
      # 100;
    end
    
    # 50000;
    $stop;
  end

endmodule

复位结束后,采用写入随机数的方式,写入了五个数据。从RTL仿真图中可以看到这个五个数据为24、81、09、63、0d。

接收端口时刻监测rx_empty是不是为假值,一旦为假值,就证明有接收到数据,立刻拉高一拍rx_en,进行读出。大概经过十几毫秒后,仿真会自动停止。

从RTL仿真图中可以看到,读出的数据为24、81、09、63、0d这五个数据,和我们写入的相同。

由于此设计外设接口众多,并且在使用时,都是由上游控制器进行控制。本小节编写上游控制器,实现回环测试(将接收到的数据,全部在发送出去)。

在测试时,rx_clk和tx_clk都采用系统时钟。

本模块命名为uart_drive_example。

test_ctrl模块负责监控rx_empty是否为假值,一旦有数据接收到就可以读出,发送到发送缓冲区中。

此模块采用状态实现。共分为WAIT_RX(等待UART接收数据),WAIT_RD(等待读数据),SEND(发送数据)。

将rx_en置高后,rx_data需要等待一拍才会有效。

状态转移图如下:

设计代码如下:

module test_ctrl (

  input   wire              clk,
  input   wire              rst_n,
  
  input   wire              rx_empty,
  output  reg               rx_en,
  input   wire      [7:0]   rx_data,
  
  output  reg               tx_en,
  output  reg       [7:0]   tx_data
);

  localparam  WAIT_RX     =   3'b001;
  localparam  WAIT_RD     =   3'b010;
  localparam  SEND        =   3'b100;
  
  reg               [2:0]   c_state;
  reg               [2:0]   n_state;
  
  always @ (posedge clk, negedge rst_n) begin
    if (rst_n == 1'b0)
      c_state <= 3'd0;
    else
      c_state <= n_state;
  end

  always @ * begin
    case (c_state)
      WAIT_RX   :   begin
        if (rx_empty == 1'b1)
          n_state = WAIT_RX;
        else
          n_state = WAIT_RD;
      end
      
      WAIT_RD   :   begin
        n_state = SEND;
      end
      
      SEND      :   begin
        n_state = WAIT_RX;
      end
      
      default   :   n_state = WAIT_RX;
    endcase
  end

  always @ (posedge clk, negedge rst_n) begin
    if (rst_n == 1'b0)
      rx_en <= 1'b0;
    else
      if (c_state == WAIT_RX && rx_empty == 1'b0)
        rx_en <= 1'b1;
      else
        rx_en <= 1'b0;
  end
  
  always @ (posedge clk, negedge rst_n) begin
    if (rst_n == 1'b0)
      tx_en <= 1'b0;
    else
      if (c_state == SEND)
        tx_en <= 1'b1;
      else
        tx_en <= 1'b0;
  end
  
  always @ (posedge clk, negedge rst_n) begin
    if (rst_n == 1'b0)
      tx_data <= 8'd0;
    else
      if (c_state == SEND)
        tx_data <= rx_data;
      else
        tx_data <= 8'd0;
  end
  
endmodule

uart_drive_example负责将test_ctrl和uart_drive联系起来。

设计代码如下:

module uart_drive_example (

  input   wire              clk,
  input   wire              rst_n,
  
  input   wire              uart_rxd,
  output  wire              uart_txd
);

  wire                      rx_empty;
  wire                      rx_en;
  wire          [7:0]       rx_data;
  wire                      tx_en;
  wire          [7:0]       tx_data;
  
  test_ctrl test_ctrl_inst(

      .clk          (clk),
      .rst_n        (rst_n),
      
      .rx_empty     (rx_empty),
      .rx_en        (rx_en),
      .rx_data      (rx_data),
      
      .tx_en        (tx_en),
      .tx_data      (tx_data)
    );

  uart_drive uart_drive_inst(

      .clk            (clk),
      .rst_n          (rst_n),
      
      .tx_clk         (clk),
      .tx_en          (tx_en),
      .tx_data        (tx_data),
      .tx_full        (),
      .uart_txd       (uart_txd),
      
      .uart_rxd       (uart_rxd),
      .rx_clk         (clk),
      .rx_empty       (rx_empty),
      .rx_en          (rx_en),
      .rx_data        (rx_data)
    );
    
endmodule

将uart_drive_example设置为顶层。

在file界面,右击uart_drive_example文件,选择set as top level……。

进行综合分析后,分配管脚,形成配置文件。

将开发板与电脑相连接,打开设备管理器。可以看到在其他设备中出现了CP2102 USB to UART Bridge Controller,并且前面有一个黄色的感叹号,标志着此端口还不能使用。

在我们的开发板上,使用的USB <->UART的芯片就是CP2102,所以在此需要安装驱动。

打开04_串口驱动,安装CP210x_windows_drivers。

文件中有两个安装程序。一个是CP210xVCPInstaller_x64,另外一个是CP210xVCPInstaller_x86。此时我们需要查看自己电脑的系统是多少位的,打开控制面板中的系统就可以看自己的电脑是多少位的操作系统。

64位的操作系统,安装CP210xVCPInstaller_x64,32位的操作系统安装CP210xVCPInstaller_x86。

双击对应的安装程序后,点击下一步。 

 点击“我接受”,点击下一步。

 等待一段时间后,选择完成即可。

此时对开发板进行断电再上电的处理,就可以在设备管理的端口(COM和LPT)中看到安装好的程序,并且记住后面的COM口的编号,一会儿需要使用。在此,笔者的PC上的COM口为COM3。大家好,我是【FPGA功夫熊猫】精益求精,不断推荐好文章。

标签: usb连接器ut1111c

锐单商城拥有海量元器件数据手册IC替代型号,打造 电子元器件IC百科大全!

锐单商城 - 一站式电子元器件采购平台