FPGA之UART简单通信篇(1)


写在前面,希望记录一下自己动手做的事情,拿来主义永远是别人的

1、UART串口简介

串口顾名思义,是将字节数据以bit位进行串行传输,特点是线路简单,速度较慢。所以常用在工业控制,嵌入式等数据传输速度要求不高的场景中。

扩展:串行通信分两种:同步串行和异步串行通信

同步串行:通信双方在同一时钟控制下,同步传输数据

异步串行:通信双方使用各自时钟控制数据发送与接收过程

2、UART串口接线与数据与数据包格式

 

接线:UART是一种采用异步串行通信方式的通用异步收发传输器(universal asynchronous receiver-transmitter),它在发送数据时将并行数据转换成串行数据来传输,在接收数据时将接收到的串行数据转换成并行数据。

UART串口通信需要两根信号线来实现,一根用于串口发送,另外一根负责串口接收。

 串口接线

 

数据包格式: 在进行具体的串口设计之前,先了解串口通信协议。通常串口的一次发送或接收由四个部分组成:起始位S(“一般为逻辑‘0’)、数据位D0~D7(一般为6位~8位之间可变,数据低位在前)、校验位(奇校验、偶检验或不需要校验位)、停止位(通常为1位、1.5位、2位)。停止位必须为逻辑1。在一次串口通信过程中,数据接收与发送双方没有共享时钟,因此,双方必须协商好数据传输波特率。波特率即数据传输速率。根据双方协议好的传输速率,接收端即可对发送端的数据进行采样。常用的波特率有9600、19200、38400、57600以及115200等。当然更块的速度意味着对采样的要求更高,有可能误码率会逐渐提高。

      通常对串口进行数据采样,采用更高频的时钟。这样做的目的是采用高频时钟来锁存低频时钟,减少数据的误码率,增加接收模块的自纠错能力。

具体的工作流程为:

      发送端按照预先设定好的波特率,发送起始位(Start)+数据位(data)+奇偶校验位+结束位。其中,起始位为逻辑0,结束位为逻辑1,发送端在空闲状态为1。发送数据包格式如下图所示。

 

数据包格式

 

  数据的奇偶校验位是可以选择的,如果不使用奇偶,那么就没有这个数据位,本设计中没有用到奇偶校验位。

 3、设计 

接收端通过检测电平‘1’到‘0’的跳变来确定一个数据包的开始。确定开始位接收完成之后,依次接收数据,使用更高的采样时钟,完成数据采集。接收完数据位后,继续接收奇偶校验位和停止位。

串口的接收与发送,其主要时序设计包括两个部分:1、波特率的产生时序;2、数据传输时序,包括接收与发送。

       波特率产生时序设计:假设FPGA输入时钟100Mhz,为得到常用的波特率,仍然采用计数分频来得到。BAUD_DIV=100_000000/波特率。其中采样中心点为发送或接收时钟的中心点,即BAUD_DIV_CAP=100_000000/(2*波特率)。该部分在数据接收和发送部分均单独完成。如下图所示

 

 

 

 方案图

       数据接收模块:在设置好传输波特率的情况下,根据串口传输时序,进行解串。空闲状态时,接收 数据为逻辑高电平,等待起始位逻辑低电平的到来。当起始位到达后,由低位到高位,依次采集8位数据,并进行相应的解串,存入临时寄存器。接收有效数据完成后,判断结束位,接收完毕。

       数据发送模块:设置发送使能信号和待发送的数据。通过计数器,表示10个数据发送的周期。这10个数据,依次为起始位+8位数据位+1位结束位,实现数据位的逐个发送。

RX模块:

`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Company: 
// Engineer: 
// 
// Create Date: 2020/09/21 13:51:32
// Design Name: 
// Module Name: UART_RX
// Project Name: 
// Target Devices: 
// Tool Versions: 
// Description: 
// 
// Dependencies: 
// 
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
// 
//////////////////////////////////////////////////////////////////////////////////


module uart_rx_path(
    input            clk_100m,
    input            uart_rx_i,
    
    output    [7:0]    uart_rx_data_o,
    output            uart_rx_done,
    output            baud_bps_sim


);

    parameter    [13:0]    BAUD_DIV    =    14'd13020;    //9600bps    ,50Mhz
    parameter    [13:0]    BAUD_DIV_MID=    14'd6510;    //collection mid 
    
(*mark_debug="true"*)    reg    [13:0]    baud_div        =0;
(*mark_debug="true"*)    reg            baud_bps        =0;
(*mark_debug="true"*)    reg            bps_start        =0;
    
    always    @(posedge    clk_100m)
    begin
        if(baud_div==BAUD_DIV_MID)begin
            baud_bps<= 1'b1;
            baud_div<= baud_div +1'b1;
            end
        else if(baud_div<BAUD_DIV && bps_start)begin//当波特率启动时,计数器累加
            baud_div <= baud_div +1'b1;
            baud_bps <= 0;
            end
        else begin
            baud_bps<=0;
            baud_div<=0;
            end
    end

(*mark_debug="true"*)    reg    [4:0]    uart_rx_i_r    =    5'b11111;    
    always @(posedge clk_100m)
    begin
        uart_rx_i_r <= {uart_rx_i_r[3:0],uart_rx_i};
    end

    wire    uart_rx_int    = uart_rx_i_r[4]|uart_rx_i_r[3]|uart_rx_i_r[2]|uart_rx_i_r[1]|uart_rx_i_r[0];
    
(*mark_debug="true"*)    reg[3:0]    bit_num    =    0;
(*mark_debug="true"*)    reg            uart_rx_done_r    =0;
(*mark_debug="true"*)    reg            state    =1'b0;
    
(*mark_debug="true"*)    reg    [7:0]    uart_rx_data_0_r0    =    0; //process
    reg    [7:0]    uart_rx_data_0_r1    =    0;    //done
    
    always    @(posedge clk_100m)
    begin
        uart_rx_done_r <= 1'b0;
    case (state)
    1'b0:
        if(!uart_rx_int)begin
            bps_start<=1'b1;
            state<=1'b1;
        end
    1'b1:
        if(baud_bps)
        begin
            bit_num<=bit_num+1'b1;    
            if(bit_num<4'd9)        
                uart_rx_data_0_r0[bit_num-1]<= uart_rx_i;
            //else
               //uart_rx_data_0_r0 <= uart_rx_data_0_r0;     
        end
        else if (bit_num==4'd10)begin
            bit_num <= 0;
            uart_rx_done_r <= 1'b1;
            uart_rx_data_0_r1 <= uart_rx_data_0_r0;
            state<=1'b0;//进入状态0,再次循环检测
            bps_start<=0;
        end
    default:;

    endcase

    end
    
    assign baud_bps_tb=baud_bps;//for simulation

    assign uart_rx_data_o=uart_rx_data_0_r1;

    assign uart_rx_done=uart_rx_done_r;

endmodule

TX模块

module uart_tx_path(

input clk_i,



input [7:0] uart_tx_data_i, //待发送数据

input uart_tx_en_i, //发送发送使能信号



output uart_tx_o

);



parameter BAUD_DIV     = 14'd13020;//波特率时钟,9600bps,100Mhz/9600=10416,波特率可调

parameter BAUD_DIV_CAP = 14'd6510;//波特率时钟中间采样点,100Mhz/9600/2=d208,波特率可调



(*mark_debug="true"*)reg [13:0] baud_div=0; //波特率设置计数器

(*mark_debug="true"*)reg baud_bps=0; //数据发送点信号,高有效

reg [9:0] send_data=10'b1111111111;//待发送数据寄存器,1bit起始信号+8bit有效信号+1bit结束信号

(*mark_debug="true"*)reg [3:0] bit_num=0; //发送数据个数计数器

(*mark_debug="true"*)reg uart_send_flag=0; //数据发送标志位

(*mark_debug="true"*)reg uart_tx_o_r=1; //发送数据寄存器,初始状态位高



always@(posedge clk_i)

begin

if(baud_div==BAUD_DIV_CAP) //当波特率计数器计数到数据发送中点时,产生采样信号baud_bps,用来发送数据

begin

baud_bps<=1'b1;

baud_div<=baud_div+1'b1;

end

else if(baud_div<BAUD_DIV && uart_send_flag)//数据发送标志位有效期间,波特率计数器累加,以产生波特率时钟

begin

baud_div<=baud_div+1'b1;

baud_bps<=0;

end

else

begin

baud_bps<=0;

baud_div<=0;

end

end



always@(posedge clk_i)

begin

if(uart_tx_en_i) //接收数据发送使能信号时,产生数据发送标志信号

begin

uart_send_flag<=1'b1;

send_data<={1'b1,uart_tx_data_i,1'b0};//待发送数据寄存器装填,1bit起始信号0+8bit有效信号+1bit结束信号

end

else if(bit_num==4'd10) //发送结束时候,清楚发送标志信号,并清楚待发送数据寄存器内部信号

begin

uart_send_flag<=1'b0;

send_data<=10'b1111_1111_11;

end

end



always@(posedge clk_i)

begin

if(uart_send_flag) //发送有效时候

begin

if(baud_bps)//检测发送点信号

begin

if(bit_num<=4'd9)

begin

uart_tx_o_r<=send_data[bit_num]; //发送待发送寄存器内数据,从低位到高位

bit_num<=bit_num+1'b1;

end

end

else if(bit_num==4'd10)

bit_num<=4'd0;

end

else

begin

uart_tx_o_r<=1'b1; //空闲状态时,保持发送端位高电平,以备发送时候产生低电平信号

bit_num<=0;

end

end



assign uart_tx_o=uart_tx_o_r;



endmodule

TOP模块

module uart_top(

input clk_i_p,
input clk_i_n,

//input   clk_i,


input uart_rx_i,



output uart_tx_o

    );



wire [7:0] uart_rx_data_o;

wire uart_rx_done;
wire   clk_i;
   IBUFDS IBUFDS_inst (
      .O(clk_i),   // 1-bit output: Buffer output
      .I(clk_i_p),   // 1-bit input: Diff_p buffer input (connect directly to top-level port)
      .IB(clk_i_n)  // 1-bit input: Diff_n buffer input (connect directly to top-level port)
   );



    

uart_rx_path uart_rx_path_u (

    .clk_100m(clk_i),

    .uart_rx_i(uart_rx_i),



    .uart_rx_data_o(uart_rx_data_o),

    .uart_rx_done(uart_rx_done)

    );

    

uart_tx_path uart_tx_path_u (

    .clk_i(clk_i),

    .uart_tx_data_i(uart_rx_data_o),

    .uart_tx_en_i(uart_rx_done),

    .uart_tx_o(uart_tx_o)

    );

    

endmodule

Test_bench(本文开发板是KCU105,用的125差分,在仿真时,将顶层差分时钟屏蔽,复原clk_i单端即可,波特率使用125Mhz/9600,改为100Mhz/9600即可)

 
 

`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Company:
// Engineer:
//
// Create Date: 2020/09/21 13:55:20
// Design Name:
// Module Name: uart_top_tb
// Project Name:
// Target Devices:
// Tool Versions:
// Description:
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
//////////////////////////////////////////////////////////////////////////////////

 
 


module uart_top_TB;

 
 

 

 
 

reg clk_i_p;

 
 

reg clk_i_n;

 
 

reg rst_n_i;

 
 

reg uart_rx_i;

 
 

wire [7:0] uart_tx_o;

 
 

 

 
 

uart_top u_uart_top

 
 

(

 
 

.clk_i_p (clk_i_p),
.clk_i_n (clk_i_n),
//.rst_n_i (rst_n_i),

 
 

.uart_rx_i (uart_rx_i),

 
 

.uart_tx_o (uart_tx_o)

 
 

);

 
 

 

 
 

initial

 
 

begin

 
 

clk_i_p = 0;
clk_i_n = 1;
rst_n_i = 0;

 
 

uart_rx_i = 1'b1;

 
 

 

 
 

// Wait 100 ns for global reset to finish

 
 

#96;

 
 

rst_n_i=1;

 
 

#104170

 
 

uart_rx_i = 1'b1;

 
 

#104170

 
 

uart_rx_i = 1'b0;//start

 
 

//1001_0101

 
 

#104170

 
 

uart_rx_i = 1'b1;

 
 

#104170

 
 

uart_rx_i = 1'b0;

 
 

#104170

 
 

uart_rx_i = 1'b1;

 
 

#104170

 
 

uart_rx_i = 1'b0;

 
 

#104170

 
 

uart_rx_i = 1'b1;

 
 

#104170

 
 

uart_rx_i = 1'b0;

 
 

#104170

 
 

uart_rx_i = 1'b0;

 
 

#104170

 
 

uart_rx_i = 1'b1;

 
 

#104170

 
 

uart_rx_i = 1'b1;//stop

 
 

#808320

 
 

//00000101

 
 

uart_rx_i = 1'b0;//start

 
 

#104170

 
 

uart_rx_i = 1'b1;

 
 

#104170

 
 

uart_rx_i = 1'b0;

 
 

#104170

 
 

uart_rx_i = 1'b1;

 
 

#104170

 
 

uart_rx_i = 1'b0;

 
 

#104170

 
 

uart_rx_i = 1'b0;

 
 

#104170

 
 

uart_rx_i = 1'b0;

 
 

#104170

 
 

uart_rx_i = 1'b0;

 
 

#104170

 
 

uart_rx_i = 1'b0;

 
 

#104170

 
 

uart_rx_i = 1'b1;//stop

 
 

 

 
 

#808320

 
 

//10000100

 
 

uart_rx_i = 1'b0;//start

 
 

#104170

 
 

uart_rx_i = 1'b0;

 
 

#104170

 
 

uart_rx_i = 1'b0;

 
 

#104170

 
 

uart_rx_i = 1'b1;

 
 

#104170

 
 

uart_rx_i = 1'b0;

 
 

#104170

 
 

uart_rx_i = 1'b0;

 
 

#104170

 
 

uart_rx_i = 1'b0;

 
 

#104170

 
 

uart_rx_i = 1'b0;

 
 

#104170

 
 

uart_rx_i = 1'b1;

 
 

#104170

 
 

uart_rx_i = 1'b1;//stop

 
 

end

 
 

 

 
 

always

 
 

begin

 
 

#2 clk_i_p = ~clk_i_p;
#2 clk_i_n = ~clk_i_n;
end

 
 

 

 
 

endmodule



仿真发送的第一个数据是,10010101,,对应的Hex为95,第二个是00000101,hex为05。

 

4、综合布线前仿真如下:

 

 

 

 

 接收模块正确

 

 

 发送仿真ok

 

 

5、上板子调试

结果

 

测试结果

 有误码情况:

波特率提高时,会有误码出现,后面有时间再来改善一下。

 

误码图:

 

 

原先的Kcu105自带USB转串口,但是实际过程中效果不理想(出现出厂自带的测试东西),所以我用的以前的PL2303这个USB转串口,外挂了两个Pin管脚,效果还行。

 


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM