FPGA实现UART串口发送和接收

  • 串口通信

        串口(UART)全称通用异步收发传输器(Universal Asynchronous Receiver/Transmitter),主要用于数据间的串行传递,是一种全双工传输模式。它在发送数据时将并行数据转换成串行数据来传输,在接收数据时将接收到的串行数据转换成并行数据。

        串口通信只有两根信号线,一根是发送数据端口线叫 tx(Transmitter),用于发送数据一根是接收数据端口线叫 rx(Receiver),用于接收数据,如图所示。当主机向从机发送数据时,从机也可向主机发送数据。

  • 串口通信时序图

串口通信时序图如下图所示。

       0作为起始位,发送开始时第一个bit为低电平,然后发送8bit数据,8bit数据发送完成后检测到1,即停止位时,进入空闲状态,等待下一次数据发送。需要注意的是,UART是串行发送数据,并且先发送8bit数据的低位。

       串口通信的传输速率由波特率来确定,常用的波特率有9600、19200、38400、57600以及115200等。波特率的单位为bit/s,即一秒可以传输多少bit的数据,因此在代码设计时需要考虑波特率选择。例如时钟频率为100MHz,波特率选择为9600,则传输一bit数据需要的时钟周期为1000000000/9600/10=10416。

  • 串口发送模块

        串口发送模块设计框图如下所示。Clk为系统时钟,Rst_n为系统复位信号,Send_en为发送使能信号,当Send_en拉高时发送模块开始检测起始位,Data为待发送的8位数据,

Baud_set为波特率设置信号,Send_end为发送结束信号,Tx为输入发送数据线的数据。

       每次数据发生包含10bit数据(1位起始位,8位数据,1位终止位),可以设计两个计数器,其中一个计数器在计数到预设时钟数时清零,此时另一个计数器加一,知道计数为9时表示一次数据发生完成进入空闲状态,同时还需要设置波特率,以确定所需时钟数。利用case语句将8位数据进行发送。

        具体代码如下所示:

`timescale 1ns / 1ps

module uart_tx(
    input                           Clk,                            //系统时钟
    input                           Rst_n,                          //系统复位
    input       [7:0]               Data,                           //发送8位数据
    input                           Send_start,                     //发送使能信号
    input       [2:0]               Baud_set,                       //波特率设置

    output      reg                 Tx,                             //输入发送数据线数据
    output      reg                 Send_end                        //8位数据发生完成信号

    );

    reg         [9:0]               Baud_num;                       //一位数据发送所需时钟数
    reg         [9:0]               Baud_cnt;                       //对一位数据发送所需时钟数进行计数
    reg         [3:0]               bit_cnt;                        //Baud_cnt计满一次则加一,发送一位数据,计数到九
    reg         [7:0]               Data_r;                         //用于存放待发送的8位数据       
    reg                             Send_en;                        //发送使能信号


    always@(posedge Clk)begin
        case (Baud_set)
           0 : Baud_num <= 1_000_000_000/9600/10;                   //计算所需时钟数
           1 : Baud_num <= 1_000_000_000/19200/10;
           2 : Baud_num <= 1_000_000_000/38400/10;
           3 : Baud_num <= 1_000_000_000/57600/10;
           4 : Baud_num <= 1_000_000_000/115200/10;
        default: Baud_num <= 1_000_000_000/115200/10;
        endcase
    end

    always @(posedge Clk or negedge Rst_n)begin
        if(!Rst_n)
            Baud_cnt <= 10'b0;
        else if(Send_en)                                            //使能信号拉高才开始计数
            if(Baud_cnt == Baud_num - 1'b1)                         //计满清零,进行下一次计数
                Baud_cnt <= 10'b0;
            else
                Baud_cnt <= Baud_cnt + 1'b1;
        else
            Baud_cnt <= 10'b0;
    end


    always @(posedge Clk or negedge Rst_n)begin                     //对发送数据位数进行计数
        if(!Rst_n)
            bit_cnt <= 4'd0;
        else if(Baud_cnt == Baud_num - 1'b1)
            if(bit_cnt == 4'd9)
                bit_cnt <= 4'd0;                                     //10位数据发送完成,计数器清零
            else
                bit_cnt <= bit_cnt + 1'b1;
        else
            bit_cnt <= bit_cnt;
    end

    always @(posedge Clk or negedge Rst_n)begin                     //使能信号拉高将待发送数据写入寄存器
        if(!Rst_n)
            Data_r <= 'd0;
        else if(Send_en)
            Data_r <= Data;
    end

    always @(posedge Clk or negedge Rst_n)begin
        if(!Rst_n)
            Tx <= 1'b1;
        else if(Send_en)begin
            case (bit_cnt)
               0 : Tx <= 1'b0;                                      //起始位
               1 : Tx <= Data_r[0];                                 //低位先开始发送
               2 : Tx <= Data_r[1];
               3 : Tx <= Data_r[2];
               4 : Tx <= Data_r[3];
               5 : Tx <= Data_r[4];
               6 : Tx <= Data_r[5];
               7 : Tx <= Data_r[6];
               8 : Tx <= Data_r[7];
               9 : Tx <= 1'b1;                                      //终止位
                default: Tx <= 1'b1;
            endcase
        end
        else
            Tx <= 1'b1;
    end

    always @(posedge Clk or negedge Rst_n)begin
        if(!Rst_n)
            Send_end <= 1'b0;
        else if(bit_cnt == 'd9 && Tx == 1'b1 && Baud_cnt == Baud_num - 1'b1)                   //一次发送完成Send_end信号拉高
            Send_end <= 1'b1;
        else
            Send_end <= 1'b0;
    end

    always @(posedge Clk or negedge Rst_n)begin
        if(!Rst_n)
            Send_en <= 1'b0;
        else if(Send_start)                                                                 //发送开始信号到达,拉高发送使能信号
            Send_en <= 1'b1;
        else if(bit_cnt == 'd9 && Tx == 1'b1 && Baud_cnt == Baud_num - 1'b1)                //停止位发送完成,拉低发送使能信号
            Send_en <= 1'b0;
        else
            Send_en <= Send_en;

    end

endmodule
  • `timescale 1ns / 1ps
    
    module uart_tx_tb();
        reg                Clk;
        reg                Rst_n;
        reg     [2:0]      Baud_set;
        reg                Send_start;
        reg     [7:0]      Data;
    
        wire               Send_end;
        wire               Tx;
    
         uart_tx    inst1(
            .Clk            (Clk        ),                              //系统时钟
            .Rst_n          (Rst_n      ),                              //系统复位
            .Data           (Data       ),                              //发送8位数据
            .Send_start     (Send_start ),                              //发送使能信号
            .Baud_set       (Baud_set   ),                              //波特率设置
    
            .Tx             (Tx         ),                              //输入发送数据线数据
            .Send_end       (Send_end   )                               //8位数据发生完成信号
    
        );
    
        initial Clk = 1'b1;
        always#5 Clk = ~Clk;
     
        initial begin
            Rst_n = 1'b0;
            Baud_set = 'd0;
            Data = 'd0;
            Send_start = 1'b0;
            #201;
            Rst_n = 1'b1;
            #100;
            Baud_set = 'd4;
            Send_start = 1'b1;
            #10;
            Send_start = 1'b0;
            Data = ({$random} % 1024);
            #200000;
            Baud_set = 'd3;
            Send_start = 1'b1;
            #10;
            Send_start = 1'b0;
            Data = ({$random} % 1024);
            #200000;
            $stop;
        end
    
    endmodule
    
  • 串口接收模块@

串口接收模块如图所示,

        Clk为系统时钟,Rst_n为系统复位信号,Rx为接收数据线输入信号,Data为接收到的8位数据,Rx_done为接收终止信号。

        串口接收模块与串口发送模块的设计思路类似,主要是计数器的设计。具体代码如下:

`timescale 1ns / 1ps

module uart_rx(
    input                               Clk,
    input                               Rst_n,
    input                               Rx,
    input           [2:0]               Baud_set,

    output          reg[7:0]            Data,
    output          reg                 Rx_done
    );

    reg         [9:0]               Baud_num;                       //一位数据发送所需时钟数
    reg         [9:0]               Baud_cnt;                       //对一位数据发送所需时钟数进行计数
    reg         [3:0]               bit_cnt;                        //Baud_cnt计满一次则加一,发送一位数据,计数到九
    reg                             Rx_en;                          //接收使能信号,检测到下降沿时拉高
    reg         [1:0]               Rx_r;                           //两位寄存器存放Rx的数据,防止产生亚稳态,同时方便检测下降沿
    wire                            n_edge;                         //下降沿
    reg         [7:0]               Data_r;                         //

     always@(posedge Clk)begin
        case (Baud_set)
           0 : Baud_num <= 1_000_000_000/9600/10;                   //计算所需时钟数
           1 : Baud_num <= 1_000_000_000/19200/10;
           2 : Baud_num <= 1_000_000_000/38400/10;
           3 : Baud_num <= 1_000_000_000/57600/10;
           4 : Baud_num <= 1_000_000_000/115200/10;
        default: Baud_num <= 1_000_000_000/115200/10;
        endcase
    end

    always @(posedge Clk or negedge Rst_n)begin
        if(!Rst_n)
            Baud_cnt <= 10'b0;
        else if(Rx_en)                                              //使能信号拉高才开始计数
            if(Baud_cnt == Baud_num - 1'b1)                         //计满清零,进行下一次计数
                Baud_cnt <= 10'b0;
            else
                Baud_cnt <= Baud_cnt + 1'b1;
        else
            Baud_cnt <= 10'b0;
    end


    always @(posedge Clk or negedge Rst_n)begin                     //对接收数据位数进行计数
        if(!Rst_n)
            bit_cnt <= 4'd0;
        else if(Rx_en)                                              //使能信号拉高时才开始计数
            if(Baud_cnt == Baud_num - 1'b1)
                if(bit_cnt == 4'd9)
                    bit_cnt <= 4'd0;                                //10位数据接收完成,计数器清零
                else
                    bit_cnt <= bit_cnt + 1'b1;
            else
                bit_cnt <= bit_cnt;
        else
            bit_cnt <= 4'd0;                                        //使能信号没有拉高时计数器保持清零
    end

    always @(posedge Clk)begin                                      //将数据存入二维寄存器
        Rx_r[0] <= Rx;
        Rx_r[1] <= Rx_r[0];
    end

    assign n_edge = (Rx_r == 2'b10);                                //检测是否产生下降沿

    always @(posedge Clk or negedge Rst_n)begin                    
        if(!Rst_n)
            Rx_en <= 1'b0;
        else if(n_edge)                                             //检测到下降沿时拉高使能信号,进入接收状态
            Rx_en <= 1'b1;
        else if((bit_cnt == 'd9) && (Rx_r[1] == 1'b1) && (Baud_cnt == Baud_num >> 1'b1))        //拉低使能信号
            Rx_en <= 1'b0;
        else
            Rx_en <= Rx_en;
    end

    always @(posedge Clk or negedge Rst_n)begin                     //
        if(!Rst_n)begin
            Data <= 8'b0;                                           //复位清零
        end
        else if(Rx_en)begin                                         //进入接收状态
            case(bit_cnt)
                1 : Data_r[0] <= Rx_r[1];                           
                2 : Data_r[1] <= Rx_r[1];
                3 : Data_r[2] <= Rx_r[1];
                4 : Data_r[3] <= Rx_r[1];
                5 : Data_r[4] <= Rx_r[1];
                6 : Data_r[5] <= Rx_r[1];
                7 : Data_r[6] <= Rx_r[1];
                8 : Data_r[7] <= Rx_r[1];
                default:;                                           //起始位和终止位不用接收
            endcase
        end
        else begin
            Data <= 8'b0;                                           //使能信号没有拉高时保持清零
        end
    end

    always @(posedge Clk or negedge Rst_n) begin
        if(!Rst_n)begin
            Rx_done <= 1'b0;                                        //复位清零
            Data <= 8'd0;
        end
        else if((bit_cnt == 'd9) && (Rx_r[1] == 1'b1) && (Baud_cnt == Baud_num >> 1'b1))begin
            Rx_done <= 1'b1;                                        //一次发送结束
            Data <= Data_r;          
        end
        else begin
            Rx_done <= 1'b0;
            Data <= Data;
        end
    end

endmodule
`timescale 1ns / 1ps

module uart_rx_tb();
    reg                     Clk;
    reg                     Rst_n;
    reg          [2:0]      Baud_set;
    reg                     Rx;
    wire         [7:0]      Data;
    wire                    Rx_done;

    parameter counter = 1_000_000_000/115200;

   uart_rx    inst1(
        .Clk            (Clk        ),                              //系统时钟
        .Rst_n          (Rst_n      ),                              //系统复位
        .Data           (Data       ),                              //接收8位数据
        .Baud_set       (Baud_set   ),                              //波特率设置

        .Rx             (Rx         ),                              //输入接收数据线数据
        .Rx_done        (Rx_done    )                               //8位数据接收完成信号
    );    

    initial Clk = 1'b1;                                             //产生时钟
    always#5 Clk = ~Clk;

    task uart_rx_byte(                                              //对数据进行发送
        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
                #counter;
            end
    endtask

    initial begin
        Rst_n = 1'b0;
        Rx = 1'b1;
        Baud_set = 3'd4;
        #201;
        Rst_n = 1'b1;
        #20;
        uart_rx_byte({$random} % 256);                          //随机发送8位数据
        uart_rx_byte({$random} % 256);                          
        uart_rx_byte({$random} % 256);
        uart_rx_byte({$random} % 256);
        uart_rx_byte({$random} % 256);
        #100000;
        $stop;
    end

endmodule

        本文主要是记录之前的学过的东西,如有侵权,请联系博主。

  • 24
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值