基于FPGA的uart串口回环实验

文章详细介绍了UART通用异步收发传输器的工作原理,包括UART与RS232的关系、串口通信的时序特点,以及波特率和比特率的概念。在设计串口模块时,重点展示了接收和发送模块的Verilog代码实现,包括波特计数器、比特标志位和数据接收与发送的逻辑。最后,提到了如何通过FPGA实现串口回环实验,并给出了仿真结果。
摘要由CSDN通过智能技术生成

1.串口简介        

        UART通用异步收发传输器( Universal Asynchronous Receiver/Transmitter) ,通常称作
UART。 UART 是一种通用的数据通信协议,也是异步串行通信口(串口)的总称,它在发送数据时将并行数据转换成串行数据来传输,在接收数据时将接收到的串行数据转换成并行数据。 它包括了 RS232、 RS499、 RS423、 RS422 和 RS485 等接口标准规范和总线标准规范。        

        串口作为常用的三大低速总线(UART、 SPI、 IIC)之一,在设计众多通信接口和调试时占有重要地位。但 UART 和 SPI、 IIC 不同的是,它是异步通信接口,异步通信中的接收方并不知道数据什么时候会到达,所以双方收发端都要有各自的时钟,在数据传输过程中是不需要时钟的,发送方发送的时间间隔可以不均匀,接受方是在数据的起始位和停止位的帮助下实现信息同步的。而 SPI、 IIC 是同步通信接口,同步通信中双方使用频率一致的时钟,在数据传输过程中时钟伴随着数据一起传输,发送方和接收方使用的时钟都是由主机提供的。        

        UART 通信只有两根信号线,一根是发送数据端口线叫 tx(Transmitter),一根是接收数据端口线叫 rx(Receiver),对于 PC 来说它的 tx 要和对于 FPGA 来说的 rx 连接,同样 PC 的 rx 要和 FPGA 的 tx 连接,如果是两个 tx 或者两个 rx 连接那数据就不能正常被发送出去和接收到,所以不要弄混,记住 rx 和 tx 都是相对自身主体来讲的。UART 可以实现全双工,即可以同时进行发送数据和接收数据。

        串口回环模块如下图所示,使用电脑上位机串口模块完成对FPGA串口模块的环回实验。

2.串口时序

下面我们来看一下RS232协议:

1、 RS232 是 UART 的一种,没有时钟线,只有两根数据线,分别是 rx 和 tx,这两根线都是 1bit 位宽的。其中 rx 是接收数据的线, tx 是发送数据的线。
2、 rx 位宽为 1bit, PC 机通过串口调试助手往 FPGA 发 8bit 数据时, FPGA 通过串口线rx 一位一位地接收,从最低位到最高位依次接收,最后在 FPGA 里面位拼接成 8 比特数据,也就是我们常说的串转并。
3、 tx 位宽为 1bit, FPGA 通过串口往 PC 机发 8bit 数据时, FPGA 把 8bit 数据通过 tx线一位一位的传给 PC 机,从最低位到最高位依次发送,最后上位机通过串口助手按照RS232 协议把这一位一位的数据位拼接成 8bit 数据,并行数据转换成串行数据进行发送。
4、串口数据的发送与接收是基于帧结构的,即一帧一帧的发送与接收数据。每一帧除了中间包含 8bit 有效数据外,还在每一帧的开头都必须有一个起始位,且固定为 0;在每一帧的结束时也必须有一个停止位,且固定为 1,即最基本的帧结构(不包括校验等)有10bit。在不发送或者不接收数据的情况下, rx 和 tx 处于空闲状态,此时 rx 和 tx 线都保持高电平,如果有数据帧传输时,首先会有一个起始位,然后是 8bit 的数据位,接着有 1bit的停止位,然后 rx 和 tx 继续进入空闲状态,然后等待下一次的数据传输。

rs232时序图入下图所示:

 5、波特率:在信息传输通道中,携带数据信息的信号单元叫码元(因为串口是 1bit 进行传输的,所以其码元就是代表一个二进制数), 每秒钟通过信号传输的码元数称为码元的传输速率,简称波特率,常用符号“Baud”表示,其单位为“波特每秒(Bps)”。串口常见的波特率有 4800、9600、 115200 等。
6、比特率:每秒钟通信信道传输的信息量称为位传输速率,简称比特率,其单位为“每秒比特数(bps)”。比特率可由波特率计算得出,公式为:比特率=波特率 * 单个调制状态对应的二进制位数。如果使用的是 115200 的波特率,其串口的比特率为: 115200Bps *1bit= 115200bps。
7、由计算得串口发送或者接收 1bit 数据的时间为一个波特,即 1/9600 秒,如果用50MHz(周期为 20ns)的系统时钟来计数,需要计数的个数为 cnt = (1s * 10^9)ns /115200bit)ns / 20ns ≈434 个系统时钟周期,即每个 bit 数据之间的间隔要在 50MHz 的时钟频率下计数 434 次。

3.串口模块设计

        我们先绘制一下串口回环的整个模块图:

         由整个回环模块图,我们可以看出在整个回环实验中,我们需要再设计两个模块,一个接收模块和一个发送模块。

3.1接收模块

        先看一下接收模块的波形图:

         输入信号为系统时钟、复位信号以及接收信号;为了消除跨时钟域所带来的的亚稳态现象,需要对rx信号进行打3拍操作,跨时钟域处理推荐是3拍,当然有些人觉得2拍足矣,只要验证没问题,这个你随意。

        进行完打拍操作后,需要取第三拍的下降沿操作,也就是~rx_reg2 & rx_reg3。至于为何这样,可以参考边沿检测部分。

        得到start_flag信号之后,就可以进行一个rx的接收了,本次接收模块使用的波特率为115200,系统时钟为50M,所以波特率为50_000_000/115200=434。

        为了每位数据的接收的稳定,所以可以数据的标志信号可以在波特率信号的中间位置进行拉高。然后将每bit的数据都拼接到我们接收数据的最高位,以此类推,接收到的第8位数据,就拼接在了rx_data的最高位,最先接收的数据就存储在了rx_data的最低位。

        接收完8位数据之后,我们的接收标志信号拉高,表示已经完成一次8位数据的接收。在接收完成标志位拉高后,将我们寄存的串转并的数据赋值po_data,同样po_flag拉高一个周期。

根据波形图与文字分析,可以编写代码如下:

`timescale 1ns / 1ps

module uart_rx
#(
    parameter   BAUD_MAX    =   'd115_200,
    parameter   CLK_MAX     =   'd50_000_000
)
(
    input               sys_clk     ,
    input               sys_rst_n   ,
    input               rx          ,
    
    output  reg [7:0]   po_data     ,
    output  reg         po_flag
    );
    
  
localparam  BAUD_CNT = CLK_MAX / BAUD_MAX;
  
reg         rx_reg1;                            //接收寄存器1
reg         rx_reg2;                            //接收寄存器2
reg         rx_reg3;                            //接收寄存器3
reg         bit_flag;                           //比特标志位

reg         rx_flag;                            //接收标志位
reg [8:0]   baud_cnt;                           //波特计数器

reg         start_flag  ;                       //开始接收标志
reg         rx_en       ;                       //接收使能

reg  [7:0]  rx_data     ;                       //接收数据寄存器
reg  [3:0]  bit_cnt     ;                       //比特计数器
    
//打3拍
always@(posedge sys_clk or negedge sys_rst_n) begin
    if(!sys_rst_n) begin
        rx_reg1 <= 1'b1;
        rx_reg2 <= 1'b1;
        rx_reg3 <= 1'b1;
    end
    else begin
        rx_reg1 <= rx;
        rx_reg2 <= rx_reg1;
        rx_reg3 <= rx_reg2;
    end
end

//取第三拍下降沿作为开始接收标志信号
always@(posedge sys_clk or negedge sys_rst_n) begin
    if(!sys_rst_n)
        start_flag <= 1'b0;
    else if((rx_reg3) && (~rx_reg2))
        start_flag <= 1'b1;
    else
        start_flag <= 1'b0;
end

//接收使能
always@(posedge sys_clk or negedge sys_rst_n) begin
    if(!sys_rst_n)
        rx_en <= 1'b0;
    else if(start_flag == 1'b1)
        rx_en <= 1'b1;
    else if((bit_cnt == 4'd8) && (bit_flag == 1'b1))
        rx_en <= 1'b0;
end

//波特计数器
always@(posedge sys_clk or negedge sys_rst_n) begin
    if(!sys_rst_n)
        baud_cnt <= 9'd0;
    else if((baud_cnt == BAUD_CNT - 1'b1) || (rx_en == 1'b0))
        baud_cnt <= 9'd0;
    else if(rx_en == 1'b1)
        baud_cnt <= baud_cnt + 9'd1;
end

//比特标志位
always@(posedge sys_clk or negedge sys_rst_n) begin
    if(!sys_rst_n)
        bit_flag <= 1'b0;
    else if(baud_cnt == BAUD_CNT/2 - 1'b1)
        bit_flag <= 1'b1;
    else
        bit_flag <= 1'b0;
end

//比特计数器
always@(posedge sys_clk or negedge sys_rst_n) begin
    if(!sys_rst_n)
        bit_cnt <= 4'd0;
    else if((bit_cnt == 4'd8) && (bit_flag == 1'b1))
        bit_cnt <= 4'b0;
    else if(bit_flag == 1'b1)
        bit_cnt <= bit_cnt + 4'd1;
end

//接收数据寄存器
always@(posedge sys_clk or negedge sys_rst_n) begin
    if(!sys_rst_n)
        rx_data <= 8'd0;
    else if((bit_cnt >= 4'd1) && (bit_cnt <= 4'd8) && (bit_flag == 1'b1))
        rx_data <= {rx_reg3, rx_data[7:1]};
end

//接收标志位
always@(posedge sys_clk or negedge sys_rst_n) begin
    if(!sys_rst_n)
        rx_flag <= 1'b0;
    else if((bit_cnt == 4'd8) && (bit_flag == 1'b1))
        rx_flag <= 1'b1;
    else
        rx_flag <= 1'b0;
end

//接收的串转并数据
always@(posedge sys_clk or negedge sys_rst_n) begin
    if(!sys_rst_n)
        po_data <= 8'd0;
    else if(rx_flag == 1'b1)
        po_data <= rx_data;
end

//接收完成信号
always@(posedge sys_clk or negedge sys_rst_n) begin
    if(!sys_rst_n)
        po_flag <= 1'b0;
    else
        po_flag <= rx_flag;
end
    
endmodule

编写接收模块仿真代码:

`timescale 1ns / 1ns

module tb_uart_rx();

reg               sys_clk     ;
reg               sys_rst_n   ;
reg               rx          ;
                              
wire      [7:0]   po_data     ;
wire              po_flag     ;


initial begin
    sys_clk = 1'b1;
    sys_rst_n <= 1'b0;
    #201
    sys_rst_n <= 1'b1;
end

always #10 sys_clk = ~sys_clk;

//模拟发送8次数据,分别为0~7
initial begin
        #200
        rx_bit(8'd8);  //任务的调用,任务名+括号中要传递进任务的参数
        rx_bit(8'd1);
        rx_bit(8'd2);
        rx_bit(8'd3);
        rx_bit(8'd4);
        rx_bit(8'd5);
        rx_bit(8'd6);
        rx_bit(8'd7);
end
    

//定义一个名为rx_bit的任务
//任务以task开头,后面紧跟的是任务名,调用时使用
task rx_bit(
    //传递到任务中的参数,调用任务的适合从外部传进来一个8位的值
        input   [7:0]   data
);
    
        integer i;      //定义一个常量
        
        for(i=0; i<10; i=i+1) begin
            case(i)
                0: rx <= 1'b0;
                1: rx <= data[0];
                2: rx <= data[1];
                3: rx <= data[2];
                4: rx <= data[3];
                5: rx <= data[4];
                6: rx <= data[5];
                7: rx <= data[6];
                8: rx <= data[7];
                9: rx <= 1'b1;
            endcase
            #(434*20);  //每发送1位数据时434个时钟周期
        end
endtask

uart_rx
#(
    .BAUD_MAX    ('d115_200     ),
    .CLK_MAX     ('d50_000_000  )
)
uart_rx_inst
(
    .sys_clk     (sys_clk  ),
    .sys_rst_n   (sys_rst_n),
    .rx          (rx       ),
                           
    .po_data     (po_data  ),
    .po_flag     (po_flag  )
    );

endmodule

接收模块仿真图:

打拍取沿及接收使能部分波形

波特计数器以及比特标志位(434/2拉高)波形:

 数据接收端以及数据接收完成标志位波形:

 

3.2发送模块

        同理按照时序图绘制发送模块的波形图:

         因为设计一个串口回环,发送和接收模块的波特率是相同的,所以并没有队pi_flag信号进行打3拍处理。在接收信号拉高之后,tx_en拉高,进入发送模式。

        同样波特计数器为434,bit_flag每次拉高,bit_cnt计一个数,第0位为起始位,拉低,然后进行数据的传送,当计数到第九位时,使tx信号拉高,表示停止位。

        同样根据波形图可以编写出发送模块的代码:

`timescale 1ns / 1ns

module uart_tx
#(
    parameter   BAUD_MAX    =   'd115_200,
    parameter   CLK_MAX     =   'd50_000_000
)
(
    input                sys_clk     ,
    input                sys_rst_n   ,
        
    input       [7:0]    pi_data     ,              //接收的数据
    input                pi_flag     ,              //接收标志位
                                                    
    output  reg          tx                         //发送端
);


localparam  BAUD_CNT = CLK_MAX / BAUD_MAX;


reg         tx_en       ;                           //发送使能
reg [8:0]   baud_cnt    ;                           //波特率计数器
reg         bit_flag    ;                           //比特标志位
reg [3:0]   bit_cnt     ;                           //比特计数器


//发送使能
always@(posedge sys_clk or negedge sys_rst_n)
    if(!sys_rst_n)
        tx_en <= 1'b0;
    else if(bit_cnt == 4'd9 && bit_flag == 1'b1)
        tx_en <= 1'b0;
    else if(pi_flag == 1'b1)
        tx_en <= 1'b1;

//波特计数器
always@(posedge sys_clk or negedge sys_rst_n)
    if(!sys_rst_n)
        baud_cnt <= 9'd0;
    else if((baud_cnt == BAUD_CNT - 1'b1) || tx_en == 1'b0)
        baud_cnt <= 9'd0;
    else
        baud_cnt <= baud_cnt + 9'd1;


//比特数据标志位
always@(posedge sys_clk or negedge sys_rst_n)
    if(!sys_rst_n)
        bit_flag <= 1'b0;
    else if(baud_cnt == 9'd1)
        bit_flag <= 1'b1;
    else
        bit_flag <= 1'b0;
        
        
//比特计数器,数据位数个数计数, 10个有效数据(含起始位和停止位)到来后计数器清零
always@(posedge sys_clk or negedge sys_rst_n)
    if(!sys_rst_n)
        bit_cnt <= 4'd0;
    else if(bit_cnt == 4'd9 && bit_flag == 1'b1)
        bit_cnt <= 4'd0;
    else if(bit_flag == 1'b1 && tx_en == 1'b1)
        bit_cnt <= bit_cnt + 4'd1;
        
//tx:输出数据在满足 rs232 协议(起始位为 0,停止位为 1)的情况下一位一位输出        
always@(posedge sys_clk or negedge sys_rst_n)
    if(!sys_rst_n)
        tx <= 1'b1;
    else if(bit_flag == 1'b1)
        case(bit_cnt)
            0: tx <= 1'b0;
            1: tx <= pi_data[0];
            2: tx <= pi_data[1];
            3: tx <= pi_data[2];
            4: tx <= pi_data[3];
            5: tx <= pi_data[4];
            6: tx <= pi_data[5];
            7: tx <= pi_data[6];
            8: tx <= pi_data[7];
            9: tx <= 1'b1;
            default : 
                tx <= 1'b1;
        endcase


endmodule

同样进行发送代码的仿真编写:

`timescale 1ns / 1ns

module tb_uart_tx();

reg                sys_clk     ;
reg                sys_rst_n   ;                             
reg       [7:0]    pi_data     ;
reg                pi_flag     ;
        
wire               tx          ;


initial begin
    sys_clk = 1'b0;
    sys_rst_n <= 1'b0;
    #201
    sys_rst_n <= 1'b1;
end


always #10 sys_clk = ~sys_clk;

initial begin
    pi_flag <= 1'b0;
    pi_data <= 8'd0;
    #401
    pi_data <= 8'd0;
    pi_flag <= 1'd1;
    #20
    pi_flag <= 1'd0;
    #(434*10*20)
    pi_data <= 8'd15;
    pi_flag <= 1'd1;
    #20
    pi_flag <= 1'd0;
    #(434*10*20)
    pi_data <= 8'd2;
    pi_flag <= 1'd1;
    #20
    pi_flag <= 1'd0;
    #(434*10*20)
    pi_data <= 8'd3;
    pi_flag <= 1'd1;
    #20
    pi_flag <= 1'd0;
    #(434*10*20)
    pi_data <= 8'd4;
    pi_flag <= 1'd1;
    #20
    pi_flag <= 1'd0;
    #(434*10*20)
    pi_data <= 8'd5;
    pi_flag <= 1'd1;
    #20
    pi_flag <= 1'd0;
    #(434*10*20)
    pi_data <= 8'd6;
    pi_flag <= 1'd1;
    #20
    pi_flag <= 1'd0;
    #(434*10*20)
    pi_data <= 8'd7;
    pi_flag <= 1'd1;
    #20
    pi_flag <= 1'd0;
end

uart_tx
#(
    .BAUD_MAX('d115_200     )       ,
    .CLK_MAX ('d50_000_000  )
)uart_tx_inst
(
    .sys_clk     (sys_clk  ),
    .sys_rst_n   (sys_rst_n),
                           
    .pi_data     (pi_data  ),
    .pi_flag     (pi_flag  ),
                           
    .tx          (tx       )
);

endmodule

先看一下发送开始标志位波形:

 延迟一个时钟周期,发送使能拉高。

 波特计数器等于1是,比特标志位拉高,比特位数加1。

最后再看一下发送的数据:

 如接收到的数据为7,发送端从最低位1110_0000发送数据7,注意这里的顺序是先发送最低位,最后一位为最高位。

3.3例化回环模块

        最后将我们的两个模块例化到一起:

`timescale 1ns / 1ns

module uart_232(
    input               sys_clk     ,
    input               sys_rst_n   ,
    input               rx          ,

    output              tx
);

wire    [7:0]   po_data;
wire            po_flag;


uart_rx
#(
    .BAUD_MAX    ('d115_200     ),
    .CLK_MAX     ('d50_000_000  )
)
uart_rx_inst
(
    .sys_clk     (sys_clk  ),
    .sys_rst_n   (sys_rst_n),
    .rx          (rx       ),
                           
    .po_data     (po_data  ),
    .po_flag     (po_flag  )
    );
      

uart_tx
#(
    .BAUD_MAX('d115_200     )       ,
    .CLK_MAX ('d50_000_000  )
)uart_tx_inst
(
    .sys_clk     (sys_clk  ),
    .sys_rst_n   (sys_rst_n),
                           
    .pi_data     (po_data  ),
    .pi_flag     (po_flag  ),
                           
    .tx          (tx       )
);

endmodule

仿真代码编写:

`timescale 1ns / 1ns

module tb_uart_232();

reg               sys_clk     ;
reg               sys_rst_n   ;
reg               rx          ;
                              
wire              tx          ;

initial begin
    sys_clk = 1'b1;
    sys_rst_n <= 1'b0;
    #201
    sys_rst_n <= 1'b1;
end

always #10 sys_clk = ~sys_clk;




//模拟发送8次数据,分别为0~7
initial begin
        #200
        rx_bit(8'd0);  //任务的调用,任务名+括号中要传递进任务的参数
        rx_bit(8'd1);
        rx_bit(8'd2);
        rx_bit(8'd3);
        rx_bit(8'd4);
        rx_bit(8'd5);
        rx_bit(8'd6);
        rx_bit(8'd7);
end
    

//定义一个名为rx_bit的任务
//任务以task开头,后面紧跟的是任务名,调用时使用
task rx_bit(
    //传递到任务中的参数,调用任务的适合从外部传进来一个8位的值
        input   [7:0]   data
);
    
        integer i;      //定义一个常量
        
        for(i=0; i<10; i=i+1) begin
            case(i)
                0: rx <= 1'b0;
                1: rx <= data[0];
                2: rx <= data[1];
                3: rx <= data[2];
                4: rx <= data[3];
                5: rx <= data[4];
                6: rx <= data[5];
                7: rx <= data[6];
                8: rx <= data[7];
                9: rx <= 1'b1;
            endcase
            #(434*20);  //每发送1位数据时434个时钟周期
        end
endtask


uart_232    uart_232_inst(
    .sys_clk     (sys_clk   ),
    .sys_rst_n   (sys_rst_n ),
    .rx          (rx        ),
                            
    .tx          (tx        )
);

endmodule

最好波形参考发送模块以及接收模块做一个参考。

下载完成上位机验证结果如下:

 

FPGA中实现UART串口回环,需要设计两个模块,分别是uart_rx和uart_tx模块。其中,uart_rx模块负责接收串口数据,而uart_tx模块负责发送串口数据。在uart_rx模块中,可以使用线性序列机的设计方法,通过时序图来描述其功能。时序图中可以清楚地看到数据的传输过程,包括起始位、数据位和结束位的发送和接收。具体的代码实现可以参考引用\[1\]和引用\[2\]中的内容。在设计FPGA时,养成良好的设计习惯非常重要,可以先画出实验的框图,然后对每个小模块进行时序设计。这样可以避免在复杂项目中茫然无措。引用\[3\]中提供了一个实验框图的例子,可以作为参考。 #### 引用[.reference_title] - *1* *3* [基于FPGAUART回环设计(1)](https://blog.csdn.net/zhangningning1996/article/details/103836599)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [基于FPGA实现uart串口模块(Verilog)--------接收模块及思路总结](https://blog.csdn.net/qq_41467882/article/details/87027577)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

伊藤诚诚诚诚

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值