Menu Close

异步串行收发(UART)协议详解(2)–波特率

异步串行收发(UART)协议详解(2)–波特率

上节内容介绍了UART串行通信协议中的数据帧格式,以及举例说明了数据帧在发送时如何控制。本节内容继续介绍协议中的其它内容。

  1. 波特率(Baudrate):

波特率是指每秒钟发送或接收的位( Bits)的总量,包含起始位、数据、校验位(如果采用校验),停止位的总和。异步串行通信常用波特率有120bps, 2400bps, 480bps, 960bps, 1920bps, 3840bps, 7680bps, 9600bps, 19200bps, 38400bps,57600bps, 115200bps等,其中比较常用有9600bps,129200bps,38400bps, 57600bps, 115200bps等。

例1:波特率为115200,8位数据, 一个停止位,没有校验,计算每秒钟发送数据字节数。

由于停止位为1位,数据为8位,没有数据位,一次每个数据帧的构成为:

1 start bit+8 data bits+1 stop bit=10bits, 而每发送10 bits的帧包含一个数据字节,因此每秒钟发送的字节数为 115200/10= 11520 Bytes/s。

2. 波特率设计

在系统设计中一般提供的时钟频率都比较高如50MHz,100Mhz等,如何做到系统时钟与波特率匹配呢?一般采用的方法如下:

a)发送端:

      • 时钟直接分频,如时钟为100Mhz,波特率为115200,则分频率设置为100000000/115200=868.05,取整为868。 虽然不能准确的整数化分频,但误差在万分之一以内。因此在发送中如果不是特别长的数据包,在时间上的误差积累一般也是可以承受的(数据包是指每次发送的数据是连续不间歇的传送,如传送一个文件,一副图片等)。一般解决办法是发送端可以设2个停止位,接收端设为1个接收停止位。这样可以接收端利用过采样的方式解决起始位相移的问题。
      • 采用使能的方式,即分频的输出不是频率的直接输出,而是采用使能的方式。

接收端一般采用过采样的方式进行数据接收,在异步串行通信中一般采用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倍过采样依然不能得到准确的整数倍分频,但误差却减小很多。
  • 采用使能脉冲的方式,就是分频后的触发器输出结果不是作为时钟直接使用,而是作为后级电路的使能或选择端使用。
    1. 波特率使用建议,在进行FPGA异步串行通信开发时,建议采用使能的方式进行,这样可以保证正常时序收敛。
    2. 如果需要波特率从时钟频率获得整数分频,一般可以采用11.0592Mhz(或22.1184Mhz)的晶振(或震荡器),该时钟频率配合PLL经过分频后可以准确无误的得到所需常用波特率对应的整数倍分频系数。

例1:设时钟频率为100M,波特率为115200,用Verilog设计发送与接收波特率的时钟和使能信号。

  1. 时钟分频的方法

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


  	

%title插图%num

 

图1

从图1可以看出,rx_clk的频率接近tx_clk的8倍关系,但由于采用整数倍分频,因此分频系数由于误差带来频率和相位偏移,这样对于收发器正确采样是不利的。因此在接收端要能及时调整偏差才能消除误差积累。

  1. 产生波特率使能信号


module baud_rate_en
#(
    parameter BAUD_RATE = 115200,
    parameter FREQUENCY = 100000000
)
(
    input      clk,
    input      rst,
    output reg tx_bd_en,
    output reg tx_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 <= 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
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下仿真波形

%title插图%num

 

图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?

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

2 Comments

  1. 张洪泉

    这里说推荐使用波特率的使能信号,不推荐用时钟分频,是不是因为时钟分频把误差累计容易导致相位错位,而使能方式只造成一个时钟周期的偏位,这样子就比较可靠,是不是这个道理

    • tzhuang

      分频的时钟,一般用作时钟信号,此时是经过逻辑电路生成,如在FPGA的开发中,再把该逻辑生成的时钟加载到时钟线上会有困难,而且性能也有损失,因此一般用作使能而不是时钟,当然如果像CPLD,ASIC等设计是可以直接做时钟使用的。

发表评论

相关链接