前面几节内容介绍了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程序如下:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 |
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
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 |
`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接收器。要求奇偶校验、停止位可以通过参数配置
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 |
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 |
仿真程序:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 |
`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]