从零开始的FPGA学习13-串口RS232详解

原理

RS232 通信协议

  1. RS232 是 UART 的一种,没有时钟线,只有两根数据线,分别是 rx 和 tx,这两根线都是 1bit 位宽的。其中 rx 是接收数据的线,tx 是发送数据的线。
  2. 串口数据的发送与接收是基于帧结构的,即一帧一帧的发送与接收数据。每一帧除了中间包含 8bit 有效数据外,还在每一帧的开头都必须有一个起始位,且固定为 0;在每一帧的结束时也必须有一个停止位,且固定为 1,即最基本的帧结构(不包括校验等)有10bit。在不发送或者不接收数据的情况下,rx 和 tx 处于空闲状态,此时 rx 和 tx 线都保持高电平,如果有数据帧传输时,首先会有一个起始位,然后是 8bit 的数据位,接着有 1bit的停止位,然后 rx 和 tx 继续进入空闲状态,然后等待下一次的数据传输。 请添加图片描述

工程整体框图

请添加图片描述

串口数据接收模块

请添加图片描述

波形图:

第一部分:
首先画出三个输入信号,必不可少的两个输入信号是时钟和复位,另一个是串行输入数据 rx,如图 24-12 所示,我们发现 rx 串行数据一开始直接打了两拍,就是经过了两级寄存器,理论上我们应该按照串口接收数据的时序要求找到 rx 的下降沿,然后开始接收起始位的数据,但为什么先将数据打了两拍呢?那就要先从跨时钟域会导致亚稳态的问题上说起。
请添加图片描述
如果 FPGA 的系统时钟刚好采集到 rx 信号上升沿或下降沿的中间位置附近(按照概率来讲,如果数据传输量足够大或传输速度足够快时一定会产生这种情况,即 FPGA 在接收 rx 数据时不满足内部寄存器的建立时间 Tsu(指触发器的时钟信号上升沿到来以前,数据稳定不变的最小时间)和保持时间 Th(指触发器的时钟信号上升沿到来以后,数据稳定不变的最小时间),此时 FPGA 的第一级寄存器的输出端在时钟沿到来之后比较长的一段时间内都处于不确定的状态,在 0 和 1 之间处于振荡状态,而不是等于串口输入的确定的 rx 值。

如图 24-13 所示为产生亚稳态的波形示意图,rx 信号经过 FPGA 中的第一级寄存器后输出的 rx_reg1 信号在时钟上升沿 Tco 时间后会有 Tmet(决断时间)的振荡时段,当第一个寄存器发生亚稳态后,经过 Tmet 的振荡稳定后,第二级寄存器就能采集到一个相对稳定的值。但由于振荡时间 Tmet 是受到很多因素影响的,所以 Tmet 时间有长有短。
请添加图片描述

如图24-14 所示,当 Tmet1 时间长到大于一个采样周期后,那第二级寄存器就会采集到亚稳态,但是从第二级寄存器输出的信号就是相对稳定的了。当然会人会问到第二级寄存器的Tmet2 的持续时间会不会继续延长到大于一个采样周期?这种情况虽然会存在,但是其概率是极小的,寄存器本身就有减小 Tmet 时间让数据快速稳定的作用。
请添加图片描述

代码

module  uart_rx
#(
//    parameter UART_BPS = 'd9600, //串口波特率
//    parameter CLK_FREQ = 'd50_000_000 //时钟频率
    parameter   UART_BPS    =   'd9600,         //串口波特率
    parameter   CLK_FREQ    =   'd50_000_000    //时钟频率
)
(
    input   wire    sys_clk     ,   //系统时钟50MHz
    input   wire    sys_rst_n   ,   //全局复位
    input   wire    rx          ,   //串口接收数据
    output  reg     [7:0] po_data ,  //串转并后的 8bit 数据
    output  reg     po_flag          //串转并后的数据有效标志信号
);

    reg rx_reg1=0;
    reg rx_reg2=0;
    reg rx_reg3=0;
    //rx下降信号
    reg start_nedge=0;
    //工作使能
    reg work_en=0;
    //取数标志信号
    reg [13:0]baud_cnt=0;
    reg bit_flag=0;
    reg [3:0]bit_cnt=0;
    
    reg [7:0]rx_data=0;
    reg rx_flag=0;
    
    //rx缓存 打两拍
    always @ (posedge sys_clk or negedge sys_rst_n)
    begin
        if(sys_rst_n==0)
            begin
                rx_reg1<=1;
                rx_reg2<=1;
                rx_reg3<=1;
            end
        else
            begin
                rx_reg1<=rx;
                rx_reg2<=rx_reg1;
                rx_reg3<=rx_reg2;
            end
    end

    //rx下降沿检测
    always @ (posedge sys_clk or negedge sys_rst_n) begin
        if(sys_rst_n==0)
            start_nedge<=0;
        else if(work_en==1)
            start_nedge<=0;
        else if(rx_reg3==1 && rx_reg2==0) begin
            start_nedge<=1;
        end
        else
            start_nedge<=0;
    end
    
    //work_en信号
    always @ (posedge sys_clk or negedge sys_rst_n) begin
        if(sys_rst_n==0)
            work_en<=0;
        else if(bit_flag==1 && bit_cnt=='d8)
            work_en<=0;
        else if(start_nedge==1) begin
            work_en<=1;
        end
        else
            work_en<=work_en;
    end
    
    //baud_cnt计数
    always @ (posedge sys_clk or negedge sys_rst_n) begin
        if(sys_rst_n==0)
            baud_cnt<=0;
        else if(baud_cnt=='d5207)
            baud_cnt<=0;
        else if(work_en==1) begin
            baud_cnt<=baud_cnt+1;
        end
        else
            baud_cnt<=0;
    end
    
     //bit_flag
    always @ (posedge sys_clk or negedge sys_rst_n) begin
        if(sys_rst_n==0)
            bit_flag<=0;
        else if(baud_cnt=='d2603) begin
            bit_flag<=1;
        end
        else
            bit_flag<=0;
    end   
    
    //bit_cnt
    always @ (posedge sys_clk or negedge sys_rst_n) begin
        if(sys_rst_n==0)
            bit_cnt<=0;
        else if(bit_cnt=='d9)
            bit_cnt<=0;
        else if(bit_flag==1) begin
            bit_cnt<=bit_cnt+1;
        end
        else
            bit_cnt<=bit_cnt;
    end  
    
    //rx_data
    always @ (posedge sys_clk or negedge sys_rst_n) begin
        if(sys_rst_n==0)
            rx_data<=0;
        else if(bit_cnt==0)
            rx_data<=rx_data;
        else if(bit_flag=='d1)
            rx_data<={rx,rx_data[7:1]};
        else
            rx_data<=rx_data;
    end 
    //rx_flag
    always @ (posedge sys_clk or negedge sys_rst_n) begin
        if(sys_rst_n==0)
            rx_flag<=0;
        else if(bit_cnt=='d8 && bit_flag==1)
            rx_flag<=1;
        else
            rx_flag<=0;
    end 
    
    //输出
    always @ (posedge sys_clk or negedge sys_rst_n) begin
        if(sys_rst_n==0) begin
            po_data<=0;
        end else if(rx_flag==1)
            po_data<=rx_data;
        else
            po_data<=0;
    end 
    
    always @ (posedge sys_clk or negedge sys_rst_n) begin
        if(sys_rst_n==0) begin
            po_flag<=0;
        end else if(rx_flag==1)
            po_flag<=1;
        else
            po_flag<=0;
    end 
    
    
endmodule

仿真

`timescale  1ns/1ns

module  tb_uart_rx();

//********************************************************************//
//****************** Parameter and Internal Signal *******************//
//********************************************************************//
//reg   define
reg             sys_clk;
reg             sys_rst_n;
reg             rx;

//wire  define
wire    [7:0]   po_data;
wire            po_flag;

//********************************************************************//
//***************************** Main Code ****************************//
//********************************************************************//
//初始化系统时钟、全局复位和输入信号
initial begin
        sys_clk    = 1'b1;
        sys_rst_n <= 1'b0;
        rx        <= 1'b1;
        #20;
        sys_rst_n <= 1'b1;
end

//模拟发送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

//sys_clk:每10ns电平翻转一次,产生一个50MHz的时钟信号
always #10 sys_clk = ~sys_clk;

//定义一个名为rx_bit的任务,每次发送的数据有10位
//data的值分别为0~7由j的值传递进来
//任务以task开头,后面紧跟着的是任务名,调用时使用
task rx_bit(
    //传递到任务中的参数,调用任务的时候从外部传进来一个8位的值
        input   [7:0]   data
);
        integer i;      //定义一个常量
//用for循环产生一帧数据,for括号中最后执行的内容只能写i=i+1
//不可以写成C语言i=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
            #(5208*20); //每发送1位数据延时5208个时钟周期
        end
endtask         //任务以endtask结束

//********************************************************************//
//*************************** Instantiation **************************//
//********************************************************************//
//------------------------uart_rx_inst------------------------
uart_rx uart_rx_inst(
        .sys_clk    (sys_clk    ),  //input           sys_clk
        .sys_rst_n  (sys_rst_n  ),  //input           sys_rst_n
        .rx         (rx         ),  //input           rx
                
        .po_data    (po_data    ),  //output  [7:0]   po_data
        .po_flag    (po_flag    )   //output          po_flag
);

endmodule


  • 4
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值