Menu Close

UART 接收器设计(RXD)–中心对齐

前面几节内容介绍了UART的协议部分内容,并针对接收信号的边沿进行检测做了介绍,为本章的UART接收器的设计做了铺垫。异步信号接收,目前在信号接收方面比较常用的有两种方法,一种是时钟追踪的方法,另一种是过采样的方法。时钟追踪的方法将在光纤通信,PCIE等高级部分课程中讲解。本节内容涉及到UART通信由于通信速率低,过采样设计成本低,控制实现容易,因此采用过采样的方式进行处理。

  1. 过采样RXD接收原理

%title插图%num

图1

  1. 起始位检测

从图1的帧格式可以看出,在空闲阶段,RX接收端的输入信号都是高电平。而起始位恰好是低电平,因此想要成功解码一个数据帧,起始位的检测是关键。如何保证正确的检测到起始位正是本节讨论的话题。在接收端对输入信号的检测实际上是对输入信号在本地时钟下采样。由于异步串行通信在传输线路中只有数据线而没有时钟线,因此发送端的时钟信号不能被接收端直接使用,因此在接收端只能利用本地的时钟信号采样接收信号,但本地的时钟信号无论时钟频率还是相位都无法与发送端的时钟完全匹配(有频率和相位误差)。从上几节的内容介绍可知,发送端最终是以波特率使能定位发送数据的,在接收端将以同样的方式定位接收数据并采样。

  • 一倍率的采样方式,

一倍率的采样,是指接收端采用的采样速率与发送的波特率相同,也就是每一个数据只有一次采样机会,但是由于本地时钟生成的采样波特率的相位(相比接收数据)是随机的,这初始PLL的锁定时刻,上电时间,以及收发两端由于时钟频率不完全相等从而引起的误差积累有关,因此采样的相位点可能完全不同。图1给出了不同的相位点,其中对应数据低电平的中间部分的两个采样点是比较理想的,采样后可以正确保证原始数据的含义。而靠近低电平两边靠近边沿的部分是不可靠的。主要有如下两个原因可能引起数据出错:

  1. 在边沿处数据从一种状态到另一种状态过度时,不仅有过度时间,还有震荡现象发生,真实的数据变化如图2
  2. 在边沿处采样易受各种干扰的影响,
  3. 在边沿处采样亚稳态效果,也影响采样数据质量
  4. 鉴于上面三点,如果不能够很好的动态控制采样点的相位,一倍采样基本上是不可能正确恢复原始数据。

%title插图%num

图1

%title插图%num

图2

  • 多倍采样

从单倍采样的原理可以看出,如果想可靠的采样必须定位采样点的相位,这在简单的时序逻辑电路设计中几乎不可能实现,如果采用高品质PLL追随(时钟恢复),成本又过高,因此简单的解决方案是在低速率的接收中采用过采样的方式,即对每个数据位采样多次的方式。目前过采样用的比较多的是4倍采样和8倍采样的方式。下面介绍8倍采样的方式进行数据位的恢复。

    • 边沿检测

在前面的章节中介绍了边沿检测的原理及实现方法。多倍采样的前提是先检测信号的变化,如起始位的检测,起始位之前是空闲状态或停止位,因此必然有从1到0的变化。因此可以采样下降沿作为起始位的标志,如图3。 在图3中,如果正确得到下降沿,再将采样的点延迟合适的相位使采样点在数据的中心位置,这样就可以正确的恢复该位的数据。

%title插图%num

图3

    • 起始位采样

起始位的下降沿检测到以后,为了消除起始位边沿可能由于某种干扰引起的,因此还需要真正采样该位进行判断是否为低电平。可以有两种方法判断该数据是否为低电平。

  1. 从边沿处通过计数延迟将采样点定位到到数据的中心位置,如图3
  2. 将过采样的各个采样值中为1的值求和,判断是否满足设定的阈值,例如8倍过采样,从边沿处的第一个采样点开始,如果合计1的个数大于等于5,则该位数据为1,否则判断为0。

例1: 采用8倍过采样检测UART的起始位。假设RX的波特率为115200.时钟为72M,设计RX的start_bit采样以及采样相位;并设计仿真激励程序,通过仿真波形显示采样过程。

  1. rx.v程序如下:
module rx
(
    input       sys_clk,
    input       rst,
    input       rx,
    input       bd8_rate,
 
    output reg  rx_bit,
    output reg  rx_bit_rdy
);
 
reg [1:0] rx_r;
reg       rx_dedge; //
reg [2:0] sample_st;
reg [3:0] sample_count;
 
 
always@(posedge sys_clk or posedge rst)
if(rst) 
begin
    rx_r <= 0;
    rx_dedge <= 0;
end
else 
begin
    if(bd8_rate)  
    begin
        rx_r <= {rx_r[0], rx};          //rx shift left
        rx_dedge <= rx_r[1] ^ rx_r[0];  //use xor to get edge
    end
end
 
always@(posedge sys_clk or posedge rst)
if(rst) 
begin
    sample_st <= 0;
    sample_count <= 0;
    rx_bit <= 1'b1;
    rx_bit_rdy <= 1'b0;
end
else 
begin
    case(sample_st)
    0:
    begin     //idle state, waiting for ta least 10bits 1
        if(sample_count == 80)    //sample_count ==80 equal to 10bits width 
            sample_st <= 1;
        else if(bd8_rate)
            sample_count <= sample_count + 1;
    end
    1:
    begin //waiting for start_bit
        sample_count <= 0;
 
        if(rx_dedge)
            sample_st <= 2;
    end
    2:
    begin
        if(sample_count == 4)
        begin
            rx_bit <= rx_r[1];
            rx_bit_rdy <= 1'b1;
            sample_st <= 3;
        end     
        else if(bd8_rate)
            sample_count <= sample_count + 1;
    end
    3:
    begin
        sample_st <= 3;
    end
    endcase
end
 
endmodule

 

仿真程序tb.v

`timescale 1 ns / 1 ps
 
module tb
(
);
 
 
parameter BAUD_RATE = 115200;
parameter FREQUENCY = 72000000;
 
localparam  TX_DIV_COE = FREQUENCY / (BAUD_RATE);
localparam  RX_DIV_COE = FREQUENCY / (BAUD_RATE*8);
 
reg [18:0]  tx_clk_div;
reg [18:0]  rx_clk_div;
 
parameter PERIOD = 10   ; //100Mhz
 
reg   clk;
reg   rst;
reg   rx_bd_en;
reg   tx_bd_en;
 
wire  rx_bit;
wire  rx_bit_rdy;
 
reg   tx;
reg   [3:0] tx_count;
 
initial 
begin
    clk = 0;
    #(PERIOD/2);
 
    forever 
        #(PERIOD/2) clk = ~clk;
end
 
initial 
begin
    rst = 1;
    #50
 
    rst = 0;
end
 
 
always@(posedge clk or posedge rst)
if(rst) 
begin
    tx = 1'b1;
    tx_count = 0;
end
else 
begin
    if(tx_bd_en)
    begin
        if(tx_count == 80)
        begin
            tx = 1'b0;
            tx_count = 0;
        end
        else 
        begin
            tx_count = tx_count + 1;
            tx = 1'b1;
        end     
    end
end
 
 
always@(posedge clk or posedge rst)
if(rst) 
begin
    tx_clk_div <= 0;
    rx_clk_div <= 0;
    tx_bd_en <= 0;
    rx_bd_en <= 0;
end
else 
begin
    tx_bd_en <= 1'b0;
 
    if(tx_clk_div == TX_DIV_COE)
    begin
        tx_clk_div <= 0;
        tx_bd_en <= 1'b1;
    end
    else 
        tx_clk_div <= tx_clk_div + 1'b1;
    rx_bd_en <= 0;
 
    if(rx_clk_div == RX_DIV_COE)
    begin
        rx_clk_div <= 0;
        rx_bd_en <= 1'b1;
    end
    else 
        rx_clk_div <= rx_clk_div + 1;    
end
 
 
rx   rx_inst
(
    .sys_clk    (clk),
    .rst        (rst),
    .rx         (tx),
    .bd8_rate   (rx_bd_en),
    .rx_bit     (rx_bit),
    .rx_bit_rdy (rx_bit_rdy)
);
 
endmodule

 

仿真波形如图4,

%title插图%num

 

图4

为了避免一次性介绍太多的概念,上面的程序仅对起始位的采样进行了介绍,从图4中可以看出首先根据rx_r[1:0]判断rx的下降沿,并锁存为宽度为波特率的单脉冲(rx_dedge),

if(bd8_rate) begin

rx_r<={rx_r[0],rx}; //rx shift left

rx_dedge<=rx_r[1]^rx_r[0]; //use xor to get edge

end

用rx_dedge作为触发信号进行计数延迟,目的是将采样点移到接收信号对应数据的中间位置。这里对接收端采样没有直接使用rx本身,而是对rx_r[1]进行采样。在sample_count计数为4时采样,正好对应rx_r[1]的中间位置。

  1. 其它数据位的采样

上面介绍了起始位的采样,那么数据位及停止位如何采样呢?由于收发双方的时钟信号误差不大,一般情况下起始位之后的采样可以直接采用等间隔采样即可。如图5,

%title插图%num

图5

图5中从边沿之后延迟4个过采样时钟(波特率),可以采样到起始位的中间,而数据间隔是过采样时钟的8倍,因此从起始位采样开始,每过8个过采样时钟正好对应下一个数据位的中间位置,因此其它位的采样可以直接采样延迟计数即可。

  1. 设计8倍过采样的参数化RXD接收器。要求奇偶校验、停止位可以通过参数配置
module rx
#(
    parameter PARITY   = "ODD",   //"ODD"--odd parity, "EVEN"--even parity, others-->;  no parity
    parameter STOP_BIT = 1
)
(
    input            sys_clk,
    input            rst,
    input            rx,
    input            bd8_rate,
 
    output reg [7:0] rx_data,
    output reg       rx_rdy
);
 
 
localparam [3:0] 
    IDLE0     = 0,
    IDLE1     = 1,
    START     = 2,
    FIRST_BIT = 3,
    SEC_BIT   = 4,
    THIRD_BIT = 5,
    FOUTH_BIT = 6,
    FIF_BIT   = 7,
    SIX_BIT   = 8,
    SEVN_BIT  = 9,
    EIGTH_BIT = 10,
    PAR_BIT   = 11,
    STOP1_BIT = 12,
    STOP2_BIT = 13;
 
reg [1:0] rx_r;
reg       rx_dedge; //
reg [7:0] rx_data_tmp;
reg [1:0] rx_rdy_tmp;
reg [3:0] sample_st;
reg [3:0] sample_count;
 
always@(posedge sys_clk or posedge rst)
if(rst) 
begin
    rx_r <= 0;
    rx_dedge <= 0;
end
else 
begin
    if(bd8_rate)  
    begin
        rx_r <= {rx_r[0], rx};          //rx shift left
        rx_dedge <= rx_r[1] ^ rx_r[0];  //use xor to get edge
    end
end
 
always@(posedge sys_clk or posedge rst)
if(rst) 
begin
    sample_st <= IDLE0;
    sample_count <= 0;
    rx_data_tmp <= 0;
    rx_rdy_tmp[0] <= 1'b0;
end
else 
begin
    if(bd8_rate) 
    begin
        case(sample_st)
        IDLE0:
        begin     //idle state, waiting for ta least 10bits 1
            rx_rdy_tmp[0] <= 0;
 
            if(sample_count == 10)
                sample_st <= IDLE1;
            else if(rx_r[1])
                sample_count <= sample_count + 1;
            else
                sample_count <= 0;
        end
        IDLE1:
        begin //waiting for start_bit
            sample_count <= 0;
            rx_rdy_tmp[0] <= 0;
 
            if(rx_dedge)
                sample_st <= START;
        end
        START: 
        begin
            sample_count <= sample_count + 1;
 
            if(sample_count == 4)
            begin
                sample_count <= 0;
                sample_st <= FIRST_BIT;
            end     
        end
        FIRST_BIT: 
        begin
            sample_count <= sample_count + 1;
 
            if(sample_count == 7)
            begin
                sample_count <= 0;
                rx_data_tmp[0] <= rx_r[1]; 
                sample_st <= SEC_BIT;
            end
        end
        SEC_BIT: 
        begin
            sample_count <= sample_count + 1;
 
            if(sample_count == 7)
            begin
                sample_count <= 0;
                rx_data_tmp[1] <= rx_r[1]; 
                sample_st <= THIRD_BIT;
            end
        end
        THIRD_BIT: 
        begin
            sample_count <= sample_count + 1;
 
            if(sample_count == 7)
            begin
                sample_count <= 0;
                rx_data_tmp[2] <= rx_r[1];
                sample_st <= FOUTH_BIT;
            end
        end
        FOUTH_BIT: 
        begin
            sample_count <= sample_count + 1;
 
            if(sample_count == 7)
            begin
                sample_count <= 0;
                rx_data_tmp[3] <= rx_r[1];
                sample_st <= FIF_BIT ;
            end
        end
        FIF_BIT: 
        begin
            sample_count <= sample_count + 1;
 
            if(sample_count == 7)
            begin
                sample_count <= 0;
                rx_data_tmp[4] <= rx_r[1];
                sample_st <= SIX_BIT;
            end
        end
        SIX_BIT: 
        begin
            sample_count <= sample_count + 1;
 
            if(sample_count == 7)
            begin
                sample_count <= 0;
                rx_data_tmp[5] <= rx_r[1];
                sample_st <= SEVN_BIT;
            end
        end
        SEVN_BIT: 
        begin
            sample_count <= sample_count + 1;
 
            if(sample_count == 7)
            begin
                sample_count <= 0;
                rx_data_tmp[6] <= rx_r[1]; //  
                sample_st<=EIGTH_BIT;
            end
        end
        EIGTH_BIT: 
        begin
            sample_count <= sample_count + 1;
 
            if(sample_count == 7)
            begin
                sample_count <= 0;
                rx_data_tmp[7] <= rx_r[1];
 
                if( (PARITY=="ODD") || (PARITY=="EVEN") )
                    sample_st <= PAR_BIT;
                else
                    sample_st <= STOP1_BIT;
            end
        end
        PAR_BIT: 
        begin
            sample_count <= sample_count + 1;
 
            if(sample_count == 7)
            begin
                sample_count <= 0;
                sample_st <= STOP1_BIT;
            end
        end
        STOP1_BIT:
        begin
            sample_count <= sample_count + 1;
            rx_rdy_tmp[0] <= 1'b1;
 
            if(sample_count == 7)
            begin
                sample_count <= 0;
 
                if(STOP_BIT == 2)
                    sample_st <= STOP2_BIT;
                else 
                    sample_st <= IDLE1; 
            end  
        end
        STOP2_BIT:
        begin
            sample_count <= sample_count + 1;
 
            if(sample_count == 7)
            begin
                sample_count <= 0;
                sample_st <= IDLE1; 
            end
        end
        endcase
    end
end
 
 
always@(posedge sys_clk or posedge rst)
if(rst) 
begin
    rx_rdy <= 0;
    rx_data <= 0;
end
else  
begin
    rx_rdy_tmp[1] <= rx_rdy_tmp[0];
 
    if(^rx_rdy_tmp)
    begin
        rx_data <= rx_data_tmp;
        rx_rdy <= rx_rdy_tmp;
    end
end
 
endmodule

 

  1. 相关问题技术处理

   (a) 为什么在IDLE0要等待10个rx为1的占位符?

由于在数据中也有为0的数据位,如果在状态机启动期间,接收端恰好在接收一个大的数据包,此时采样起始位可能会把数据位误判为起始位,为了准确可靠采样到起始位 希望起始阶段采样从IDLE状态开始。极端的情况start之后数据为8’hFF,同时又是偶校验,因此如果不做处理极有可能将偶检验位判为起始位,之后数据帧会处在失步状态,因此最低应等待9bits的高电平在采样起始位,上面程序等待10bits的高电平处理该问题。

(b)rx_data,  rx_rdy最后输出转化位于系统时钟同步。

 

 

Posted in FPGA, FPGA, Quartus II, Verilog, Verilog, 教材与教案, 文章

1 Comment

发表评论

相关链接