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

图1
- 起始位检测
从图1的帧格式可以看出,在空闲阶段,RX接收端的输入信号都是高电平。而起始位恰好是低电平,因此想要成功解码一个数据帧,起始位的检测是关键。如何保证正确的检测到起始位正是本节讨论的话题。在接收端对输入信号的检测实际上是对输入信号在本地时钟下采样。由于异步串行通信在传输线路中只有数据线而没有时钟线,因此发送端的时钟信号不能被接收端直接使用,因此在接收端只能利用本地的时钟信号采样接收信号,但本地的时钟信号无论时钟频率还是相位都无法与发送端的时钟完全匹配(有频率和相位误差)。从上几节的内容介绍可知,发送端最终是以波特率使能定位发送数据的,在接收端将以同样的方式定位接收数据并采样。
-
一倍率的采样方式,
一倍率的采样,是指接收端采用的采样速率与发送的波特率相同,也就是每一个数据只有一次采样机会,但是由于本地时钟生成的采样波特率的相位(相比接收数据)是随机的,这与初始PLL的锁定时刻,上电时间,以及收发两端由于时钟频率不完全相等从而引起的误差积累有关,因此采样的相位点可能完全不同。图1给出了不同的相位点,其中对应数据低电平的中间部分的两个采样点是比较理想的,采样后可以正确保证原始数据的含义。而靠近低电平两边边沿的部分是不可靠的。主要有如下两个原因可能引起数据出错:
- 在边沿处数据从一种状态到另一种状态过度时,不仅有过度时间,还有震荡现象发生,真实的数据变化如图2
- 在边沿处采样易受各种干扰的影响,
- 在边沿处采样亚稳态效果,也影响采样数据质量,
- 鉴于上面三点,如果不能够很好的动态控制采样点的相位,一倍采样基本上是不可能正确恢复原始数据。

图1

图2
-
多倍采样
从单倍采样的原理可以看出,如果想可靠的采样必须定位采样点的相位,这在简单的时序逻辑电路设计中几乎不可能实现,如果采用高品质PLL追随(时钟恢复),成本又过高,因此简单的解决方案是在低速率的接收中采用过采样的方式,即对每个数据位采样多次的方式。目前过采样用的比较多的是4倍采样和8倍采样的方式。下面介绍8倍采样的方式进行数据位的恢复。
-
-
边沿检测
-
在前面的章节中介绍了边沿检测的原理及实现方法。多倍采样的前提是先检测信号的变化,如起始位的检测,起始位之前是空闲状态或停止位,因此必然有从1到0的变化。因此可以采样下降沿作为起始位的标志,如图3。 在图3中,如果正确得到下降沿,再将采样的点延迟合适的相位使采样点在数据的中心位置,这样就可以正确的恢复该位的数据。

图3
-
- 起始位采样
起始位的下降沿检测到以后,为了消除起始位边沿可能由于某种干扰引起的,因此还需要真正采样该位进行判断是否为低电平。可以有两种方法判断该数据是否为低电平。
- 从边沿处通过计数延迟将采样点定位到到数据的中心位置,如图3
- 将过采样的各个采样值中为1的值求和,判断是否满足设定的阈值,例如8倍过采样,从边沿处的第一个采样点开始,如果合计1的个数大于等于5,则该位数据为1,否则判断为0。
例1: 采用8倍过采样检测UART的起始位。假设RX的波特率为115200.时钟为72M,设计RX的start_bit采样以及采样相位;并设计仿真激励程序,通过仿真波形显示采样过程。
- 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 [6: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; //get dual 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)
begin
if(rx_r[1])
sample_count <= sample_count + 1;
else sample_count <= 0;
end
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]; // get start bit
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 = 100_000_000;
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,

图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; //use xor to get edge
end
用rx_dedge作为触发信号进行计数延迟,目的是将采样点移到接收信号对应数据的中间位置。这里对接收端采样没有直接使用rx本身,而是对rx_r[1]进行采样。在sample_count计数为4时采样,正好对应rx_r[1]的中间位置。
- 其它数据位的采样
上面介绍了起始位的采样,那么数据位及停止位如何采样呢?由于收发双方的时钟信号误差不大,一般情况下起始位之后的采样可以直接采用等间隔采样即可。如图5,

图5
图5中从边沿之后延迟4个过采样时钟(波特率),可以采样到起始位的中间,而数据间隔是过采样时钟的8倍,因此从起始位采样开始,每过8个过采样时钟正好对应下一个数据位的中间位置,因此其它位的采样可以直接采样延迟计数即可。
- 设计8倍过采样的参数化RXD接收器。要求奇偶校验、停止位可以通过参数配置
module rx
#(
parameter PARITY = "ODD", //"ODD"--odd parity, "EVEN"--even parity, others-->; no parity
parameter STOP_BIT = 1
)
(
input sys_clk,
input rx,
input bd8_rate,
output reg [7:0] rx_data,
output reg rx_rdy,
input rst
);
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 [6: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 at least 10bits 1
rx_rdy_tmp[0] <= 0;
if(sample_count == 80)
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 == 3)
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];
rx_rdy <= 0;
if(^rx_rdy_tmp)
begin
rx_data <= rx_data_tmp;
rx_rdy <= rx_rdy_tmp;
end
end
endmodule
仿真程序:
`timescale 1 ns / 1 ps
module tb
(
);
parameter BAUD_RATE = 115200;
parameter FREQUENCY = 100_000_000;
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;
initial
begin
clk = 0;
#(PERIOD/2);
forever
#(PERIOD/2) clk = ~clk;
end
initial
begin
rst = 1;
#50
rst = 0;
#300000;
// $stop;
end
wire tx;
reg [7:0] tx_count;
reg [7:0] tx_data = 0;
reg [9:0] tx_send=10'b11_1111_1111;
always@(posedge clk or posedge rst)
if(rst)
begin
tx_count = 0;
end
else
begin
if(tx_bd_en)
begin
if(tx_count == 50)
begin
tx_data <= tx_data + 1;
tx_send<= {1'b1, tx_data, 1'b0};
tx_count = 0;
end
else
begin
tx_send<= {1'b1, tx_send[8:1]};
tx_count = tx_count + 1;
end
end
end
assign tx = tx_send[0];
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
wire [7:0] rx_data;
wire rx_rdy;
rx #(.PARITY ("NONE"))
rx_inst
(
.sys_clk (clk),
.rst (rst),
.rx (tx),
.bd8_rate (rx_bd_en),
.rx_data (rx_data),
.rx_rdy (rx_rdy)
);
endmodule
仿真波形如图6-1, 6-2, 6-3 所示:

图6-1

图6-2

图6-3
- 相关问题技术处理
(a) 为什么在IDLE0要等待10个rx为1的占位符?
由于在数据中也有为0的数据位,如果在状态机启动期间,接收端恰好在接收一个大的数据包,此时采样起始位可能会把数据位误判为起始位,为了准确可靠采样到起始位 希望起始阶段采样从IDLE状态开始。极端的情况start之后数据为8’hFF,同时又是偶校验,因此如果不做处理极有可能将偶检验位判为起始位,之后数据帧会处在失步状态,因此最低应等待9bits的高电平在采样起始位,上面程序等待10bits的高电平处理该问题。
(b)rx_data, rx_rdy最后输出转化位于系统时钟同步。
Quartus 工程下载:
[content_control]
[/content_control]
sample_count == 80 是不是写错了
老师,有疑惑的地方, 最后一个rx 模块里面的IDEL0状态,为什么只需要等待10个bd8_rate呢,不是8个bd8_rate发送端才发完一个bit么,如果需要等待10bits,那么应该需要80个db8_rate呀,是这样理解的么?
你理解的是正确的,已经改正。
好的,感谢老师
老师,如果设置了10个占位符,是否会导致少收到数据?
不会,数据的开始是由START位指示的,与占位符无关。无论设置多少个占位符,接收方收数据只关心START位。设置10个占位符的作用是人为分隔数据包。
老师,rx_rdy可以直接在接收第8bit的时候输出为1么,不通过移位寄存器处理?数据可以直接使用rx_data_tmp么?
可以在 EIGTH_BIT这步直接输出rx_data_tmp[7:0]和rx_rdy,不需要再移位锁存输出,但是这样做有缺点:
rx_data_tmp在接收的过程中一直发生变化,不锁存的话,就需要上层应用及时取走数据;
没有等待奇偶校验位是否正确(如果需要奇偶校验的情况下)。