UART协议及其Verilog实现

概述

Uart是个缩写,全称是通用异步收发传输器(Universal Asynchronous Receiver/Transmitter)。单向传输只需要单线。异步传输的意思是没有同步时钟来同步发送端和接受端的数据,所以在数据之前添加起始位,之后添加结束位,以此来判断传输过程的开始和结束。当接收端检测到开始位,即开始以特定的频率来接收输入的bit位,这个特定的频率称为波特率。发送端和接收端要在大致相同的波特率下工作,才可以保证传输的正确性(最多相差10%)。

数据包的构成

Uart协议的传输数据被整合成数据包,每个数据包包含1位起始位,5-9位的数据位(具体决定于需求等因素),1位可选的奇偶校验位和1-2位的停止位。如下图所示:

UART协议及其Verilog实现

起始位(start bit)

数据传输线空闲的时候保持低电平,当开始传输时,拉低一个时钟周期,这就是起始位。当接受端检测到数据线由高到低的变化时便开始以约定的波特率来接收上述的数据包。

数据帧(data frame)

这是实际需要传输的数据。如果使用奇偶校验功能的话,可以传输5-8位的数据;如果不使用奇偶校验功能,则可以传输9位。一般由最低位开始传输。

奇偶校验位(parity)

用于接收端判断接收到的数据的正误。当接受端接收到数据帧后,计算其中1的个数是奇数个还是偶数个。如果奇偶校验位是0(偶校验),那么数据帧中1的个数应该是一个偶数。如果奇偶校验位是1(奇校验),那么数据帧中1的个数应该是奇数。当奇偶校验位与数据匹配时,传输没有错误。但是如果奇偶校验位是0,但1的个数是奇数或者奇偶校验位是1,个数却是偶数,则数据传输过程中发生了变化。奇偶校验只有粗略判断正误的功能,没有改正的能力。

停止位(stop bits)

高电平保持1-2个时钟周期表示1-2位停止位,即停止位为高电平。

以上参考:BASICS OF UART COMMUNICATION

波特率

波特率和比特率

比特率:每秒钟传输的二进制位数(bit),表示有效数据的传输速率,单位是b/s 、bit/s、比特/秒,读作:比特每秒。

波特率:波特率可以被理解为单位时间内传输符号的个数(传符号率),通过不同的调制方法可以在一个符号上负载多个比特信息。

比特率和波特率在数值上有如下关系:

\[ I=S \cdot \log _{2} N \]

其中I 为传信率(比特率),S 为波特率,N 为每个符号负载的信息量,而\(\log _{2} N\)以比特为单位。

以RS232为例,假设目前“波特率”为 9600, 则此RS232的比特率计算为
\[ I=S \cdot \log _{2} N=9600 \cdot \log _{2} 2=9600 b i t / s \]
常有人把RS232之N 误以为是每个“符号”(symbol)所夹带的讯息量为\(2^8\),但实际上每一个“位元”(bit)即为一个“符号”(symbol)。

计算机通信中,波特率与比特率虽在数值上相等,但是它们的意义并不相同。

以上参考:波特率

常见波特率

9600、19200 、38400 、57600 、115200、230400、460800、921600

时钟与波特率的计算

FPGA 主频如果为50M,则时钟周期就是20ns。若数据发送速率为9600bps,则一位数据需要的时间为1000000000/9600=104167ns,则FPGA 传送一位需要翻转104167/20=5208个周期才可传送一位,所以程序中需计数5208才可满足9600bps。

简单一点就是时钟频率除以波特率就是需要的计数。

Verilog模块详解

全部rtl和tb

参考链接,建议固定位宽和不需要奇偶校验,使用此博文中的简洁描述

tx_clk_gen.v

发送波特率生成模块,在tx_starttx_done两信号有效的间隙生成选择的波特率时钟信号。思路如上一节所述!

支持的波特率:9600、19200 、38400 、57600 、115200、230400、460800、921600,可由参数配置。

相应Verilog描述:

`timescale 1ns / 1ps
module tx_clk_gen
#(
    parameter   CLK_FREQUENCE   = 50_000_000,       //hz
                BAUD_RATE       = 9600              //9600、19200 、38400 、57600 、115200、230400、460800、921600
)
(
    input                   clk         ,   //system_clk
    input                   rst_n       ,   //system_reset
    input                   tx_done     ,   //once_tx_done
    input                   tx_start    ,   //once_tx_start
    output  reg             bps_clk         //baud_rate_clk
);

localparam  BPS_CNT =   CLK_FREQUENCE/BAUD_RATE-1,
            BPS_WD  =   log2(BPS_CNT);

reg [BPS_WD-1:0] count;
reg c_state;
reg n_state;
//FSM-1         1'b0:IDLE   1'b1:send_data
always @(posedge clk or negedge rst_n) begin
    if (!rst_n)
        c_state <= 1'b0;
    else
        c_state <= n_state;
end
//FSM-2
always @(*) begin
    case (c_state)
        1'b0: n_state = tx_start ? 1'b1 : 1'b0;
        1'b1: n_state = tx_done ? 1'b0 : 1'b1;
        default: n_state = 1'b0;
    endcase
end
//FSM-3 FSM's output(count_en) is equal to c_state

//baud_rate_clk_counter
always @(posedge clk or negedge rst_n) begin
    if (!rst_n)
        count <= {BPS_WD{1'b0}};
    else if (!c_state)
        count <= {BPS_WD{1'b0}};
    else begin
        if (count == BPS_CNT) 
            count <= {BPS_WD{1'b0}};
        else
            count <= count + 1'b1;
    end
end
//baud_rate_clk_output
always @(posedge clk or negedge rst_n) begin
    if (!rst_n)
        bps_clk <= 1'b0;
    else if (count == 'd1)
        bps_clk <= 1'b1;
    else
        bps_clk <= 1'b0;
end
//get_the_width_of_
function integer log2(input integer v);
  begin
    log2=0;
    while(v>>log2) 
      log2=log2+1;
  end
endfunction

endmodule

uart_frame_tx.v

数据帧发送模块,支持通过参数设定波特率、奇偶检验位及数据位宽。采用状态机和移位寄存器实现。当有校验位时则发送检验位;若没有校验位则直接发送停止位(发送两次停止位),如下所示。

`timescale 1ns / 1ps
module uart_frame_tx
#(
    parameter   CLK_FREQUENCE   = 50_000_000,       //hz
                BAUD_RATE       = 9600      ,       //9600、19200 、38400 、57600 、115200、230400、460800、921600
                PARITY          = "NONE"    ,       //"NONE","EVEN","ODD"
                FRAME_WD        = 8                 //if PARITY="NONE",it can be 5~9;else 5~8
)
(
    input                       clk         ,   //system_clk
    input                       rst_n       ,   //system_reset
    input                       frame_en    ,   //once_tx_start
    input       [FRAME_WD-1:0]  data_frame  ,   //data_to_tx
    output  reg                 tx_done     ,   //once_tx_done
    output  reg                 uart_tx         //uart_tx_data
);

wire    bps_clk;

tx_clk_gen
#(
    .CLK_FREQUENCE  (CLK_FREQUENCE),        //hz
    .BAUD_RATE      (BAUD_RATE  )           //9600、19200 、38400 、57600 、115200、230400、460800、921600
)
tx_clk_gen_inst
(
    .clk            ( clk        ),     //system_clk
    .rst_n          ( rst_n      ),     //system_reset
    .tx_done        ( tx_done    ),     //once_tx_done
    .tx_start       ( frame_en   ),     //once_tx_start
    .bps_clk        ( bps_clk    )      //baud_rate_clk
);

localparam  IDLE        =   6'b00_0000  ,
            READY       =   6'b00_0001  ,
            START_BIT   =   6'b00_0010  ,
            SHIFT_PRO   =   6'b00_0100  ,
            PARITY_BIT  =   6'b00_1000  ,
            STOP_BIT    =   6'b01_0000  ,
            DONE        =   6'b10_0000  ;

wire    [1:0]   verify_mode;
generate
    if (PARITY == "ODD")
        assign verify_mode = 2'b01;
    else if (PARITY == "EVEN")
        assign verify_mode = 2'b10;
    else
        assign verify_mode = 2'b00;
endgenerate

reg     [FRAME_WD-1:0]  data_reg    ;
reg     [log2(FRAME_WD-1)-1:0] cnt  ;
reg                     parity_even ;
reg     [5:0]           cstate      ;
reg     [5:0]           nstate      ;

always @(posedge clk or negedge rst_n) begin
    if (!rst_n)
        cnt <= 'd0;
    else if (cstate == SHIFT_PRO & bps_clk == 1'b1) 
        if (cnt == FRAME_WD-1)
            cnt <= 'd0;
        else
            cnt <= cnt + 1'b1;
    else
        cnt <= cnt;
end
//FSM-1
always @(posedge clk or negedge rst_n) begin
    if (!rst_n)
        cstate <= IDLE;
    else
        cstate <= nstate;
end
//FSM-2
always @(*) begin
    case (cstate)
        IDLE        : nstate = frame_en ? READY : IDLE  ;
        READY       : nstate = (bps_clk == 1'b1) ? START_BIT : READY;
        START_BIT   : nstate = (bps_clk == 1'b1) ? SHIFT_PRO : START_BIT;
        SHIFT_PRO   : nstate = (cnt == FRAME_WD-1 & bps_clk == 1'b1) ? PARITY_BIT : SHIFT_PRO;
        PARITY_BIT  : nstate = (bps_clk == 1'b1) ? STOP_BIT : PARITY_BIT;
        STOP_BIT    : nstate = (bps_clk == 1'b1) ? DONE : STOP_BIT;
        DONE        : nstate = IDLE;
        default     : nstate = IDLE;
    endcase
end
//FSM-3
always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        data_reg <= 'd0;
        uart_tx <= 1'b1;
        tx_done <= 1'b0;
        parity_even <= 1'b0;
    end else begin
        case (nstate)
            IDLE        : begin
                            data_reg <= 'd0;
                            tx_done <= 1'b0;
                            uart_tx <= 1'b1;
                        end
            READY       : begin
                            data_reg <= 'd0;
                            tx_done <= 1'b0;
                            uart_tx <= 1'b1;
                        end
            START_BIT   : begin
                            data_reg <= data_frame;
                            parity_even <= ^data_frame;     //生成偶校验位
                            uart_tx <= 1'b0;
                            tx_done <= 1'b0;
                        end
            SHIFT_PRO   : begin
                            if(bps_clk == 1'b1) begin
                                data_reg <= {1'b0,data_reg[FRAME_WD-1:1]};
                                uart_tx <= data_reg[0];
                            end else begin
                                data_reg <= data_reg;
                                uart_tx <= uart_tx;
                            end
                            tx_done <= 1'b0;
                        end
            PARITY_BIT  : begin
                            data_reg <= data_reg;
                            tx_done <= 1'b0;
                            case (verify_mode)
                                2'b00: uart_tx <= 1'b1;     //若无校验多发一位STOP_BIT
                                2'b01: uart_tx <= ~parity_even;
                                2'b10: uart_tx <= parity_even;
                                default: uart_tx <= 1'b1;
                            endcase
                        end
            STOP_BIT    : uart_tx <= 1'b1;
            DONE        : tx_done <= 1'b1;
            default     :  begin
                            data_reg <= 'd0;
                            uart_tx <= 1'b1;
                            tx_done <= 1'b0;
                            parity_even <= 1'b0;
                        end
        endcase
    end
end

function integer log2(input integer v);
  begin
    log2=0;
    while(v>>log2) 
      log2=log2+1;
  end
endfunction

endmodule

uart_frame_rx.v

数据接收模块的主要描述如下:

module uart_frame_rx
#(
    parameter   CLK_FREQUENCE   = 50_000_000,       //hz
                BAUD_RATE       = 9600      ,       //9600、19200 、38400 、57600 、115200、230400、460800、921600
                PARITY          = "NONE"    ,       //"NONE","EVEN","ODD"
                FRAME_WD        = 8                 //if PARITY="NONE",it can be 5~9;else 5~8
)
(
    input                       clk         ,       //sys_clk
    input                       rst_n       ,       
    input                       uart_rx     ,       
    output  reg [FRAME_WD-1:0]  rx_frame    ,       //frame_received,when rx_done = 1 it's valid
    output  reg                 rx_done     ,       //once_rx_done
    output  reg                 frame_error         //when the PARITY is enable if frame_error = 1,the frame received is wrong
);

wire            sample_clk      ;
wire            frame_en        ;       //once_rx_start
reg             cnt_en          ;       //sample_clk_cnt enable
reg     [3:0]   sample_clk_cnt  ;       
reg     [log2(FRAME_WD+1)-1:0]      sample_bit_cnt  ;
wire            baud_rate_clk   ;

localparam  IDLE        =   5'b0_0000,
            START_BIT   =   5'b0_0001,
            DATA_FRAME  =   5'b0_0010,
            PARITY_BIT  =   5'b0_0100,
            STOP_BIT    =   5'b0_1000,
            DONE        =   5'b1_0000;

reg [4:0]   cstate;
reg [4:0]   nstate;
//
wire    [1:0]   verify_mode;
generate
    if (PARITY == "ODD")
        assign verify_mode = 2'b01;
    else if (PARITY == "EVEN")
        assign verify_mode = 2'b10;
    else
        assign verify_mode = 2'b00;
endgenerate
//detect the start condition--the negedge of uart_rx
reg     uart_rx0,uart_rx1,uart_rx2,uart_rx3;

always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        uart_rx0 <= 1'b0;
        uart_rx1 <= 1'b0;
        uart_rx2 <= 1'b0;
        uart_rx3 <= 1'b0;
    end else begin
        uart_rx0 <= uart_rx ;
        uart_rx1 <= uart_rx0;
        uart_rx2 <= uart_rx1;
        uart_rx3 <= uart_rx2;
    end
end
//negedge of uart_rx-----start_bit
assign frame_en = uart_rx3 & uart_rx2 & ~uart_rx1 & ~uart_rx0;

always @(posedge clk or negedge rst_n) begin
    if (!rst_n) 
        cnt_en <= 1'b0;
    else if (frame_en) 
        cnt_en <= 1'b1;
    else if (rx_done) 
        cnt_en <= 1'b0;
    else
        cnt_en <= cnt_en;
end

assign baud_rate_clk = sample_clk & sample_clk_cnt == 4'd8;

always @(posedge clk or negedge rst_n) begin
    if (!rst_n) 
        sample_clk_cnt <= 4'd0;
    else if (cnt_en) begin
        if (baud_rate_clk) 
            sample_clk_cnt <= 4'd0;
        else if (sample_clk)
            sample_clk_cnt <= sample_clk_cnt + 1'b1;
        else
            sample_clk_cnt <= sample_clk_cnt;
    end else 
        sample_clk_cnt <= 4'd0;
end
//the start_bit is the first one (0),then the LSB of the data_frame is the second(1) ......
always @(posedge clk or negedge rst_n) begin
    if (!rst_n) 
        sample_bit_cnt <= 'd0;
    else if (cstate == IDLE)
        sample_bit_cnt <= 'd0;
    else if (baud_rate_clk)
        sample_bit_cnt <= sample_bit_cnt + 1'b1;
    else
        sample_bit_cnt <= sample_bit_cnt;
end
//read the readme
reg     [1:0]   sample_result   ;
always @(posedge clk or negedge rst_n) begin
    if (!rst_n) 
        sample_result <= 1'b0;
    else if (sample_clk) begin
        case (sample_clk_cnt)
            4'd0:sample_result <= 2'd0;
            4'd3,4'd4,4'd5: sample_result <= sample_result + uart_rx;
            default: sample_result <= sample_result;
        endcase
    end
end
//FSM-1
always @(posedge clk or negedge rst_n) begin
    if (!rst_n) 
        cstate <= IDLE;
    else 
        cstate <= nstate;
end
//FSM-2
always @(*) begin
    case (cstate)
        IDLE        : nstate = frame_en ? START_BIT : IDLE ;
        START_BIT   : nstate = (baud_rate_clk & sample_result[1] == 1'b0) ? DATA_FRAME : START_BIT ;
        DATA_FRAME  : begin
                        case (verify_mode[1]^verify_mode[0])
                            1'b1: nstate = (sample_bit_cnt == FRAME_WD & baud_rate_clk) ? PARITY_BIT : DATA_FRAME ;     //parity is enable
                            1'b0: nstate = (sample_bit_cnt == FRAME_WD & baud_rate_clk) ? STOP_BIT : DATA_FRAME ;       //parity is disable
                            default: nstate = (sample_bit_cnt == FRAME_WD & baud_rate_clk) ? STOP_BIT : DATA_FRAME ;    //defasult is disable
                        endcase
                    end
        PARITY_BIT  : nstate = baud_rate_clk ? STOP_BIT : PARITY_BIT ;
        STOP_BIT    : nstate = (baud_rate_clk & sample_result[1] == 1'b1) ? DONE : STOP_BIT ;
        DONE        : nstate = IDLE;
        default: nstate = IDLE;
    endcase
end
//FSM-3
always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        rx_frame    <= 'd0;
        rx_done     <= 1'b0;
        frame_error <= 1'b0;
    end else begin
        case (nstate)
            IDLE        : begin
                            rx_frame    <= 'd0;
                            rx_done     <= 1'b0;
                            frame_error <= 1'b0;
                        end 
            START_BIT   : begin
                            rx_frame    <= 'd0;
                            rx_done     <= 1'b0;
                            frame_error <= 1'b0;
                        end 
            DATA_FRAME  : begin
                            if (sample_clk & sample_clk_cnt == 4'd6) 
                                rx_frame <= {sample_result[1],rx_frame[FRAME_WD-1:1]};
                            else
                                rx_frame    <= rx_frame;
                            rx_done     <= 1'b0;
                            frame_error <= 1'b0;
                        end 
            PARITY_BIT  : begin
                            rx_frame    <= rx_frame;
                            rx_done     <= 1'b0;
                            if (sample_clk_cnt == 4'd8)
                            frame_error <= ^rx_frame ^ sample_result[1];
                            else
                            frame_error <= frame_error;
                        end 
            STOP_BIT    : begin
                            rx_frame    <= rx_frame;
                            rx_done     <= 1'b0;
                            frame_error <= frame_error;
                        end 
            DONE        : begin
                            frame_error <= frame_error;
                            rx_done     <= 1'b1;
                            rx_frame    <= rx_frame;
                        end 
            default: begin
                            rx_frame    <= rx_frame;
                            rx_done     <= 1'b0;
                            frame_error <= frame_error;
                        end 
        endcase
    end
end

rx_clk_gen
#(
    .CLK_FREQUENCE  (CLK_FREQUENCE  ),  //hz
    .BAUD_RATE      (BAUD_RATE      )   //9600、19200 、38400 、57600 、115200、230400、460800、921600
)
rx_clk_gen_inst
(
    .clk            ( clk        )  ,
    .rst_n          ( rst_n      )  ,
    .rx_start       ( frame_en   )  ,
    .rx_done        ( rx_done    )  ,
    .sample_clk     ( sample_clk )  
);  

function integer log2(input integer v);
  begin
    log2=0;
    while(v>>log2) 
      log2=log2+1;
  end
endfunction
endmodule

根据uart协议,数据传输线空闲时位高电平,数据传输以一位低电平的起始位开始,因此准确检测起始位是数据成功传输的关键。由于接受端和发送端是异步的,需要专门的边沿检测电路来捕捉下降沿。这里采用4个移位寄存器,连续采集4个时钟上升沿时的数据,通过对比前两个时刻和后两个时刻的数据线的状态来得到数据线准确的下降沿,获得准确的开始接收条件。

在简单的串口接收中,我们通常选取一位数据的中间时刻进行采样,因为此时数据最稳定,但是在工业环境中,存在着各种干扰,在干扰存在的情况下,如果采用传统的中间时刻采样一次的方式,采样结果就有可能受到干扰而出错。为了滤除这种干扰,这里采用多次采样求概率的方式。如下图,将一位数据平均分成9个时间段,对位于中间的三个时间段进行采样。然后对三个采样结果进行统计判断,如果某种电平状态在三次采样结果中占到了两次及以上,则可以判定此电平状态即为正确的数据电平。例如4、5、6时刻采样结果分别为1、1、0,那么就取此位解码结果为1,否则,若三次采样结果为0、1、0,则解码结果就为0。即3次采样为a,b,c,则结果为a&b | b&c |a&c,显而易见此结果是全加器的进位,参考链接

UART协议及其Verilog实现

rx_clk_gen.v

所以采样时钟应该为波特率时钟的9倍,Verilog描述如下:

`timescale 1ns / 1ps

module rx_clk_gen
#(
    parameter   CLK_FREQUENCE   = 50_000_000,   //hz
                BAUD_RATE       = 9600          //9600、19200 、38400 、57600 、115200、230400、460800、921600
)
(
    input                   clk         ,
    input                   rst_n       ,
    input                   rx_start    ,
    input                   rx_done     ,
    output  reg             sample_clk   
);

localparam  SMP_CLK_CNT =   CLK_FREQUENCE/BAUD_RATE/9 - 1,
            CNT_WIDTH   =   log2(SMP_CLK_CNT)            ;

reg     [CNT_WIDTH-1:0] clk_count   ;
reg     cstate;
reg     nstate;
//FSM-1 1'b0:IDLE 1'b1:RECEIVE
always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        cstate <= 1'b0;
    end else begin
        cstate <= nstate;
    end
end
//FSM-2
always @(*) begin
    case (cstate)
        1'b0: nstate = rx_start ? 1'b1 : 1'b0;
        1'b1: nstate = rx_done ? 1'b0 : 1'b1 ;
        default: nstate = 1'b0;
    endcase
end
//FSM-3 FSM's output(clk_count_en) is equal to cstate

//sample_clk_counter
always @(posedge clk or negedge rst_n) begin
    if (!rst_n) 
        clk_count <= 'd0;
    else if (!cstate) 
        clk_count <= 'd0;
    else if (clk_count == SMP_CLK_CNT)
        clk_count <= 'd0;
    else
        clk_count <= clk_count + 1'b1;
end
//generate sample_clk = 9xBAUD_RATE
always @(posedge clk or negedge rst_n) begin
    if (!rst_n) 
        sample_clk <= 1'b0;
    else if (clk_count == 1'b1) 
        sample_clk <= 1'b1;
    else 
        sample_clk <= 1'b0;
end
//get the width of sample_clk_counter
function integer log2(input integer v);
  begin
    log2=0;
    while(v>>log2) 
      log2=log2+1;
  end
endfunction

endmodule

总结

顾及的功能太多,比如奇偶校验,位宽设定等,最终的描述不简洁。但是功能基本都实现了,把思路和代码沉淀在这里。Verilog和本文多处借鉴他人成果,都已给出参考链接,侵删。

上一篇:z = z*z + c的分型图如何画


下一篇:ST算法详解