上节内容介绍了UART串行通信协议中的数据帧格式,以及举例说明了在发送端如何组织数据帧。本节内容继续介绍协议中的其它内容。
1.波特率(Baud rate):
波特率是指每秒钟发送或接收的位( Bits)的总量,包含起始位、数据、校验位(如果采用校验),停止位的总和。异步串行通信常用波特率有150bps,300bps,600bps,1200bps,2400bps,4800bps,9600bps,19200bps,38400bps,57600bps,115200bps等,其中比较常用有9600bps,19200bps,38400bps,57600bps,115200bps等。
例1:波特率为115200,8位数据, 一个停止位,没有校验位,计算每秒钟发送数据字节数。
由于开始位为1位,停止位为1位,数据为8位,没有校验位,因此每个数据帧的构成为:
1 start bit + 8 data bits + 1 stop bit = 10 bits,而每发送10 bits的帧包含一个数据字节,因此每秒钟发送的字节数为 115200/10 = 11520 Bytes/s。
2. 波特率设计
在系统设计中一般提供的时钟频率都比较高如50 MHz,100 MHz等,如何做到系统时钟与波特率匹配呢?一般采用的方法如下:
a)发送端:
-
- 时钟直接分频,如时钟为100MHz,波特率为115200,则分频率设置为100000000/115200=868.05,取整为868。 虽然不能准确的整数化分频,但误差在万分之一以内。因此在发送中如果不是特别长的数据包,在时间上的误差积累一般也是可以承受的(数据包是指每次发送的数据是连续不间歇的传送,如传送一个文件,一副图片等)。一般解决办法是发送端可以设2个停止位,接收端设为1个接收停止位。这样接收端可以利用过采样的方式解决起始位相移的问题。
- 采用使能的方式,即分频的输出不是频率的直接输出,而是采用使能的方式。
注:关于分频输出时钟或使能的设计优缺点见文章Verilog 分频器及对应视频。
b)接收端:
一般采用过采样的方式进行数据接收,在异步串行通信中一般采用8倍或16倍过采样方式。这里我们以8倍过采样进行介绍。首先计算波特率:
-
- 时钟直接分频,由于采用8倍过采样,因此同样时钟为100MHz,115200的波特率,分频系数采用100000000/(115200*8) = 108.507 取整,取108(注意一定要向下取108而不是109),当然这个误差比较大,约200分之1。解决误差大的方法,可以利用PLL生成最接近的时钟如72MHz,此时72000000/115200 = 625,72000000/(115200*8) = 78.125,取整数倍78。虽然在接收端采用8倍过采样依然不能得到准确的整数倍分频,但误差却减小很多。
- 采用使能脉冲的方式,就是分频后的触发器输出结果不是作为时钟直接使用,而是作为后级电路的使能或选择端使用。
- 波特率使用建议,在进行FPGA异步串行通信开发时,建议采用使能的方式进行,这样可以保证正常时序收敛。
- 如果需要波特率从时钟频率获得整数分频,一般可以采用11.0592 MHz(或22.1184 MHz)的晶振(或震荡器),该时钟频率配合PLL经过分频后可以准确无误的得到所需常用波特率对应的整数倍分频系数。
例1:设时钟频率为100M,波特率为115200,用Verilog设计发送与接收波特率的时钟和使能信号。
- 时钟分频的方法
module baud_rate_clk
#(
parameter BAUD_RATE = 115200,
parameter FREQUENCY = 100000000
)
(
input clk,
input rst,
output reg tx_clk,
output reg rx_clk
);
localparam TX_DIV_COE = FREQUENCY / (BAUD_RATE*2) - 1; //coe---coefficient,系数
localparam RX_DIV_COE = FREQUENCY / (BAUD_RATE*8*2) - 1;
reg [18:0] tx_clk_div;
reg [18:0] rx_clk_div;
always@(posedge clk or posedge rst)
if(rst)
begin
tx_clk_div <= 0;
rx_clk_div <= 0;
tx_clk <= 0;
rx_clk <= 0;
end
else
begin
if(tx_clk_div == TX_DIV_COE)
begin
tx_clk_div <= 0;
tx_clk <= !tx_clk;
end
else
tx_clk_div <= tx_clk_div + 1;
if(rx_clk_div == RX_DIV_COE)
begin
rx_clk_div <= 0;
rx_clk <= !rx_clk;
end
else
rx_clk_div <= rx_clk_div + 1;
end
endmodule
Testbench 程序
`timescale 1 ns / 1 ps
module tb
(
);
parameter PERIOD = 10 ; //100Mhz
reg clk;
reg rst;
wire tx_clk;
wire rx_clk;
initial
begin
clk = 0;
#(PERIOD/2);
forever
#(PERIOD/2) clk=~clk;
end
initial
begin
rst = 1;
#50
rst = 0;
end
baud_rate_clk #(
.BAUD_RATE (115200),
.FREQUENCY (100000000)
)
bau_rate_clk_inst
(
.clk (clk),
.rst (rst),
.tx_clk (tx_clk),
.rx_clk (rx_clk)
);
endmodule

图1
从图1可以看出,rx_clk的频率接近tx_clk的8倍关系,但由于采用整数倍分频,因此分频系数由于误差带来频率和相位偏移,这样对于收发器正确采样是不利的。因此在接收端要能及时调整偏差才能消除误差积累。
- 产生波特率使能信号
module baud_rate_en
#(
parameter BAUD_RATE = 115200,
parameter FREQUENCY = 100000000
)
(
input clk,
input rst,
output reg tx_bd_en,
output reg rx_bd_en
);
localparam TX_DIV_COE = FREQUENCY / (BAUD_RATE) - 1;
localparam RX_DIV_COE = FREQUENCY / (BAUD_RATE*8) - 1;
reg [18:0] tx_clk_div;
reg [18:0] rx_clk_div;
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;
rx_bd_en <= 1'b0;
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
endmodule
Testbench 文件tb.v
`timescale 1 ns / 1 ps
module tb
(
);
parameter PERIOD = 10 ; //100Mhz
reg clk;
reg rst;
wire tx_bd_en;
wire rx_bd_en;
initial
begin
clk = 0;
#(PERIOD/2);
forever
#(PERIOD/2) clk = ~clk;
end
initial
begin
rst = 1;
#50
rst = 0;
end
baud_rate_en #(
.BAUD_RATE (115200),
.FREQUENCY (100000000)
)
bau_rate_en_inst
(
.clk (clk),
.rst (rst),
.tx_bd_en (tx_bd_en),
.rx_bd_en (rx_bd_en)
);
endmodule
Modelsim下仿真波形

图2
从图2中可以看出rx_bd_en比tx_bd_en快8倍,它们都是100Mhz时钟的一个时钟周期的宽度。在FPGA应用设计时推荐使用如图2的使能方式。
因此上节内容中的TX发送器的代码可以修改如下:
module tx
#(
parameter PARITY = "ODD",
parameter STOP_BIT = 1
)
(
input rst,
input clk,
input tx_bd_en,
input [7:0] tx_data,
input tx_rdy,
output reg tx_ack,
output reg tx
);
localparam [3:0]
IDLE = 0,
START = 1,
FIRST_BIT = 2,
SEC_BIT = 3,
THIRD_BIT = 4,
FOUTH_BIT = 5,
FIF_BIT = 6,
SIX_BIT = 7,
SEVN_BIT = 8,
EIGTH_BIT = 9,
PAR_BIT = 10,
STOP1_BIT = 11,
STOP2_BIT = 12;
reg [7:0] tx_data_r;
wire odd = ^tx_data_r;
reg [3:0] tx_st;
always@(posedge clk or posedge rst)
if(rst)
begin
tx <= 1'b1;
tx_st <= 0;
tx_data_r <= 0;
tx_ack <= 1'b0;
end
else
begin
if(tx_bd_en)
case(tx_st)
IDLE:
begin
tx_ack <= 1'b0;
tx <= 1'b1;
if(tx_rdy)
begin
tx_data_r <= tx_data;
tx_ack <= 1'b1;
tx_st <= START;
end
end
START:
begin
tx <= 1'b0;
tx_ack <= 1'b0;
tx_st <= FIRST_BIT;
end
FIRST_BIT:
begin
tx <= tx_data_r[0];
tx_st <= SEC_BIT;
end
SEC_BIT:
begin
tx <= tx_data_r[1];
tx_st <= THIRD_BIT;
end
THIRD_BIT:
begin
tx <= tx_data_r[2];
tx_st <= FOUTH_BIT;
end
FOUTH_BIT:
begin
tx <= tx_data_r[3];
tx_st <= FIF_BIT;
end
FIF_BIT :
begin
tx <= tx_data_r[4];
tx_st <= SIX_BIT;
end
SIX_BIT:
begin
tx <= tx_data_r[5];
tx_st <= SEVN_BIT;
end
SEVN_BIT:
begin
tx <= tx_data_r[6];
tx_st <= EIGTH_BIT;
end
EIGTH_BIT:
begin
tx <= tx_data_r[7];
if( (PARITY=="ODD") || (PARITY=="EVEN") )
tx_st <= PAR_BIT;
else
tx_st <= STOP1_BIT;
end
PAR_BIT :
begin
tx_st <= STOP1_BIT;
if(PARITY=="ODD")
tx <= ~odd;
else
tx <= odd;
end
STOP1_BIT:
begin
tx <= 1'b1;
if(STOP_BIT == 1)
begin
if(tx_rdy)
begin
tx_data_r <= tx_data;
tx_ack <= 1'b1;
tx_st <= START;
end
else
tx_st <= IDLE;
end
else
tx_st <= STOP2_BIT;
end
STOP2_BIT :
begin
tx <= 1'b1;
if(tx_rdy)
begin
tx_data_r <= tx_data;
tx_ack <= 1'b1;
tx_st <= START;
end
else
tx_st <= IDLE;
end
default: tx_st <= IDLE;
endcase
end
endmodule
本节内容主要介绍了UART中的波特率的概念,以及波特率在收、发模块中的不同要求,以及波特率的精度探讨。最后以发送器tx.v展示了波特率的使用。
思考题:100M时钟,8倍采样波特率分频系数采用100000000/(115200*8) = 108.507 取整,为什么取108而不是109?
文章关于波特率方面可以说讲的非常详尽了。就是算的时候有个问题不是很明白: localparam TX_DIV_COE = FREQUENCY / (BAUD_RATE*2) – 1; localparam TX_DIV_COE = FREQUENCY / (BAUD_RATE*8*2) – 1; 这个公式为什么要减1。
因为tx_clk_div与TX_DIV_COE进行比较的时候,初始值是0,所以比如想要计算5个时钟周期,TX_DIV_COE = 5,tx_clk_div从0记到5,再回到0,已经是6个时钟周期了,所以比较的时候需要使用TX_DIV_COE = 4(文中就是TX_DIV_COE = FREQUENCY / (BAUD_RATE*2) – 1),这样才能确保比较正确。
RX_DIV_COE 也是一样的理由,推荐同学可以做下仿真,观察波形。
100M时钟,8倍采样波特率分频系数采用100_000_000/(11520*8) = 108.507 为啥取整的时候为啥不取109呢?我认为也是可以取109的。
对于UART来说,使用分频109也是没有问题的。但是使用108会使过采样频率略大于8倍,而使用109会使过采样频率略小于8倍,所以这里推荐使用108。过采样频率大小是否对之后采样数据的正确性有影响取决于采样的算法(判多或是偏移相位到数据中心点)。
老师,思考题没思考出来,打个比方,如果实际发送一个数据需要108.5个周期
1. 8倍采样按照108取整,第一次采样到数据中间是在第54个周期的地方,比实际数据中间少0.25个周期;第二个采样在53.5个周期的地方,比实际的数据中间前移0.75个周期,依次类推;
2.8倍采样按照109取整,第一次采样在54.5个周期的地方,比实际的数据中间后移0.25个周期;第二次采样往后移0.75个周期
2中采样方式只要每次发送的数据包在一定的数据内,都是可以正确采集到对应的数据的。
不知道自己的思路对不对,麻烦老师再解答一下呢?十分感谢!
你分析的是对的。对于UART来说,使用108会使过采样频率略大于8倍,而使用109会使过采样频率略小于8倍,所以这里推荐使用108。但是过采样频率大小是否对之后采样数据的正确性有影响取决于采样的算法(判多或是偏移相位到数据中心点),或者是你说的数据的多少(相位偏差有没有累积到错误)。