1. 串口发送字节数据——基于FPGA的串口发送数据实验

发布时间 2023-09-22 16:59:24作者: daqiaobugong

1. 通用异步收发传输器(universal asynchronous receiver/transmitter, UART)传输一个字节的数据

1.1 设计前的思考

  • 首先进行单字节模块设计
  1. 串口通信模块设计的目的是用来发送数据的,因此需要有一个数据输入端口
  2. 串口通信,支持不同的波特率,所以需要有一个波特率设置端口
    • 根据通信环境选择(容易受到干扰就慢一些,波特率小一些)
  3. 串口通信的本质就是将8位的并行数据通过一根信号线,在不同的时刻传输并行数据的不同位,通过多个时刻,最终将8位并行数据全部传出
  4. 串口通信以1位的低电平标志串行传输的开始,待8位数据传输完成之后,再以1位的高电平标志传输的结束
  5. 控制信号,控制并转串模块什么时候开始工作。什么时候一个数据发送完成?需要有一个发送开始信号,以及一个发送完成信号

1.2 设计开始

  1. 首先设计Uart_Byte_Tx(单字节发送)模块
    选择以下变量作为模块Uart_Byte_Tx输入
  • Clk 时钟信号
  • Reset_N 复位信号
  • Data 数据信号
  • Send_En 发送使能
  • Baud_Set 波特率设置信号
  • Uart_Tx 串口输出
  • Tx_Done 字节发送完成信号
    使用的变量
  • Bps_Dr 波特率
  1. 时钟设计(默认9600波特率)
条目
Baud_set 0 1 2 3 4
Bps_Dr 9600 19200 38400 57600 115200
  • 仿真时钟默认为50MHz。
  • 先低电平,码字,再高电平。因此需要10个码字。
  • 默认状态下为发送高电平
  • 发送时序图如下所示
    图1-1_发送一个字节的时序图
    .9.22串口发送总结\图1-1_发送一个字节的时序图.png)

代码如下:

module Uart_Byte_Tx(
    Clk,
    Reset_N,
    Data,
    Send_En,
    Baud_Set,
    Uart_Tx,
    Tx_Done
);

    input Clk;
    input Reset_N;
    input [7:0]Data;
    input Send_En;
    input [2:0]Baud_Set;
    output reg Uart_Tx;
    output reg Tx_Done;
    
    reg [12:0]Bps_Dr;
    always@(*)
        case(Baud_Set)
            0: Bps_Dr <= 1000000000 / 9600 / 20;
            1: Bps_Dr <= 1000000000 / 19200 / 20;
            2: Bps_Dr <= 1000000000 / 38400 / 20;
            3: Bps_Dr <= 1000000000 / 57600 / 20;
            4: Bps_Dr <= 1000000000 / 115200 / 20;
            default: Bps_Dr <= 1000000000 / 9600 / 20;
        endcase
    
    reg [12:0]Div_cnt;
    always@(posedge Clk or negedge Reset_N)
        if(!Reset_N)
            Div_cnt <= 0;
        else if(Send_En)
            if(Div_cnt == Bps_Dr - 1)
                Div_cnt <= 0;
            else
                Div_cnt <= Div_cnt + 1'b1;
        else
            Div_cnt <= 0;
    
    reg [3:0]Bps_cnt;
    always@(posedge Clk or negedge Reset_N)
        if(!Reset_N)
            Bps_cnt <= 0;
        else if(Div_cnt == Bps_Dr - 1)begin
            if(Bps_cnt == 10)
                Bps_cnt <= 0;
            else
                Bps_cnt <= Bps_cnt + 1'b1;
        end
        else
            Bps_cnt <= Bps_cnt;
    
    always@(posedge Clk or negedge Reset_N)
        if(!Reset_N)
            Uart_Tx <= 1'b1;
        else
        case(Bps_cnt)
            0: begin
                Uart_Tx <= 0;
                Tx_Done <= 0;
            end
            1: Uart_Tx <= Data[0];
            2: Uart_Tx <= Data[1];
            3: Uart_Tx <= Data[2];
            4: Uart_Tx <= Data[3];
            5: Uart_Tx <= Data[4];
            6: Uart_Tx <= Data[5];
            7: Uart_Tx <= Data[6];
            8: Uart_Tx <= Data[7];
            9: Uart_Tx <= 1'b1;
            10: begin
                Tx_Done <= 1'b1;
                Uart_Tx <= 1'b1;
            end
            default: Uart_Tx <= 1'b1;
        endcase
            
    

endmodule

仿真代码:

`timescale 1ns / 1ns
module Uart_Byte_Tx_tb;

    reg Clk;
    reg Reset_N;
    reg [7:0]Data;
    reg Send_En;
    reg [2:0]Baud_Set;
    wire Uart_Tx;
    wire Tx_Done;

    Uart_Byte_Tx Uart_Byte_Tx(
        .Clk(Clk),
        .Reset_N(Reset_N),
        .Data(Data),
        .Send_En(Send_En),
        .Baud_Set(Baud_Set),
        .Uart_Tx(Uart_Tx),
        .Tx_Done(Tx_Done)
    );
    
    initial Clk = 1;
    always #10 Clk = ~Clk;
    
    initial begin
        Reset_N = 0;
        Data = 0;
        Send_En = 0;
        Baud_Set = 4;
        #201;
        Reset_N = 1;
        #100;
        Send_En = 1;
        Data = 8'h57;
        @(posedge Tx_Done)
        Send_En = 0;
        #2000;
        Send_En = 1;
        Data = 8'h75;
        @(posedge Tx_Done)
        Send_En = 0;
        $stop;
    end
    
endmodule

1.3 仿真结果

  1. 基本上能够传输,但是遇到了一些问题:

    • 问题1:在第一个码字发送前,Send_En还没有拉高时,Uart_Tx已经变0。
      图1-2

    • 问题2:在第二个码字发送前,Send_En已经拉高,但还是等待了一个时间间隔(序号10多持续了一个Bps_cnt)才开始发送码字。
      图1-3

  2. 问题思考

    • 对问题1来说,Uart_Tx提前变0是因为在传输片段中,命中了情况case(0),因此在Reset_N拉高之后的一个电平就开始发送。
    • 对问题2来说,10多持续了一个时间间隔,是因为Send_En在拉高时,Bps_cnt还是10。
  3. 解决方案

    • 既然命中case(0),那就从1开始计数
    • Send_En在拉高时,让Bps_cnt为0。
  4. 仿真结果

    • 成功解决问题1。
      图1-4
    • 可以看到Bps_cnt=11持续了很短一段时间,当Send_En拉高时计数已经从0开始。可以认为基本解决问题2。
    • 但是由于不再使用Bps_cnt=0的情况,导致产生了一个间隔的时间浪费。
      图1-5

1.4 仿真后问题的思考及解决

  • 不再使用Bps_cnt=0,那么可以修改Bps_cnt计数的起始信号,之前是(Div_cnt == Bps_Dr - 1)触发,修改为(Div_cnt == 1)触发。修改之后即可解决。
    图1-6

  • 附修改后的源码(添加Bps_Clk)

module Uart_Byte_Tx(
    Clk,
    Reset_N,
    Data,
    Send_En,
    Baud_Set,
    Uart_Tx,
    Tx_Done
);

    input Clk;
    input Reset_N;
    input [7:0]Data;
    input Send_En;
    input [2:0]Baud_Set;
    output reg Uart_Tx;
    output reg Tx_Done;
    
    reg [12:0]Bps_Dr;
    always@(*)
        case(Baud_Set)
            0: Bps_Dr <= 1000000000 / 9600 / 20;
            1: Bps_Dr <= 1000000000 / 19200 / 20;
            2: Bps_Dr <= 1000000000 / 38400 / 20;
            3: Bps_Dr <= 1000000000 / 57600 / 20;
            4: Bps_Dr <= 1000000000 / 115200 / 20;
            default: Bps_Dr <= 1000000000 / 9600 / 20;
        endcase
    
    reg [12:0]Div_cnt;
    always@(posedge Clk or negedge Reset_N)
        if(!Reset_N)
            Div_cnt <= 0;
        else if(Send_En)
            if(Div_cnt == Bps_Dr - 1)
                Div_cnt <= 0;
            else
                Div_cnt <= Div_cnt + 1'b1;
        else
            Div_cnt <= 0;
    
    reg [3:0]Bps_cnt;
    wire Bps_Clk;
    assign Bps_Clk = (Div_cnt == 1);
    always@(posedge Clk or negedge Reset_N)
        if(!Reset_N)
            Bps_cnt <= 0;
        else if(Send_En)begin
            if(Bps_Clk)begin
                if(Bps_cnt == 11)
                    Bps_cnt <= 0;
                else
                    Bps_cnt <= Bps_cnt + 1'b1;
            end
        end
        else
            Bps_cnt <= 0;
    
    always@(posedge Clk or negedge Reset_N)
        if(!Reset_N)begin
            Uart_Tx <= 1'b1;
            Tx_Done <= 0;
        end
        else
        case(Bps_cnt)
            1: begin
                Uart_Tx <= 0;
                Tx_Done <= 0;
            end
            2: Uart_Tx <= Data[0];
            3: Uart_Tx <= Data[1];
            4: Uart_Tx <= Data[2];
            5: Uart_Tx <= Data[3];
            6: Uart_Tx <= Data[4];
            7: Uart_Tx <= Data[5];
            8: Uart_Tx <= Data[6];
            9: Uart_Tx <= Data[7];
            10: Uart_Tx <= 1'b1;
            11: begin
                Tx_Done <= 1'b1;
                Uart_Tx <= 1'b1;
            end
            default: Uart_Tx <= 1'b1;
        endcase
endmodule