FPGA-串口通信

串口通信概念

UART通信原理

UART (universal asynchronous receiver-transmitter)是一种采用异步串行通信方式的通用异步收发传输器;它在发送数据时将并行数据转换成串行数据来传输,在接收数据时将接收到的串行数据转换成并行数据。UART串口通信需要两根信号线来实现,一根用于发送,另外一根接收(表明是异步全双工通信)。

①协议层:通信协议(包括数据格式、传输速率等)。

②物理层:接口类型、电平标准等。

协议层:数据格式,一帧数据由4部分组成(用代码设计串口用到的就是协议层):

·起始位( 1bit)

·数据位(6/7/8bit)

·奇偶校验位(1bit)

·停止位(1bit/1.5bit/2bit)

起始位,数据位,停止位是必要的。

数据位的数据高位在后,低位在前,从左只有从低到高,即bit[0]至bit[n].

协议层:传输速率

串口通信的速率用波特率表示,它表示每秒传输二进制数据的位数,

单位是bit/s (位/秒),简称bps;

常用的波特率有9600、19200、 38400、 57606以 及115200等。

物理层:接口类型、电平标准串口电平标准:

.TTL电平的串口(3.3V)

·RS232电平的串口(+5~+12V为低电平,-12~-5V为高电平)

串口按电气标准分包括:

·RS-232-C:TXD/RXD/GND、15米/9600bps

·RS-422:TX+/TX-/RX+/RX-/GND

.RS485:A/B/G 、1200米/9600bps

数据传输(串口回环)整体框架:

数据传输时,串行数据经过接收端进行串转并的处理变为并行数据,再经过发送端并转串处理传输串行数据。

起始位检测

首先找到起始位才能开始数据接收(检测下降沿,进行同步打拍;检测下降沿时为r0低电平,r1高电平)

ps:为什么要做同步?因为传输过程中是没有时钟信号的,首先需要将其与时钟边沿同步,然后打拍检测边沿

起始位传输占据1bit,求波特率为9600传输1bit需要的时钟周期,即起始位所占用的时间,计算方法为:链接(https://blog.csdn.net/weixin_45388202/article/details/116465712),需要计数大约5208次。

数据位传输

传输一帧数据的数据位如图所示:

从低位到高位传输,所以数据位数据为11111001.

接收端RX

rx_data接收数据进行中间采样(中间信号稳定),如果接收的时候检测到了下降沿但采样的起始位为1(接收数据包含了起始位),说明rxd可能产生了抖动过后又回到高电平,因此需要进行判断其是否为起始位,采样到1说明产生了抖动,此时将bit计数器清零,rx_data不是有效数据不能作为输出。

输入串行数据rxd,进入FPGA后,一帧数据里包含停止位和起始位,但只有数据位这8位是需要的有效数据。

接收端设计文件:

  1. 输入串行数据rxd,输出8位数据rx_data,加入标志信号rx_data_vld判断输出的rx_data是否为8位数据位,是则有效。

  1. 对rxd进行同步打拍,边沿检测下降沿(起始位为一帧数据的开始)

  1. 定义参数及信号,flag为接收一帧数据的标志信号,作为开启bps计数器的标志,即在一帧数据到来时,起始位检测下降沿bps计数器开启计数flag拉高,记完1bit后end_cnt_bps拉高结束这一比特计时,此处flag则拉低。除开end_cnt_bps的其他时刻均保持flag拉高,知道最后1bit数据传输完毕。

  1. 对于end_cnt_bit = add_cnt_bit && (cnt_bit == 9 || data_in[0]),它必须满足计数完第十个bit或者起始位data_in[0]为1,此时才是bit结束计数标志,两钟情况满足任一即可。

  1. 进行数据采样时最好是选择之间的稳定信号,为什么加入data_in信号?我的理解是此信号是经过串行信号通过FPGA进行并行处理,但我们只需要数据位的8位有效信号,因此需要对有效信号进行提取做准备

  1. 将有效数据位数据赋予rx_data,并进行是否是有效数据的判断,判断条件结束bit计数和存在起始位必须同时满足,这样才能有一帧完整的10bit数据,然后去除的data_in[8:1]中间的8位数据才是数据位信息。

module uart_rx (
    input                  clk        ,
    input                  rst_n      ,
    input                  rxd        ,//输入串行数据

    output  reg [7:0]      rx_data    ,
    output  reg            rx_data_vld//判断标志信号,判断rx_data的数据是否有效
);
    //参数定义
    parameter          BPS_9600 = 5208 ;

    //信号定义
    reg                rx_r0       ;
    reg                rx_r1       ;

    wire               nedge       ;//检测起始位

    reg    [12:0]      cnt_bps     ;//计数1bit时钟周期
    wire               add_cnt_bps ;
    wire               end_cnt_bps ;
    reg                flag        ;//接收标志

    reg    [3:0]       cnt_bit     ;//计数bit数
    wire               add_cnt_bit ;
    wire               end_cnt_bit ;

    reg    [9:0]       data_in     ;//一帧数据,起始位(1bit),数据位(8bit),停止位(1bit)
                                    //进行串并转换

    //同步,打拍,边沿检测
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            rx_r0 <= 1'b1 ;
            rx_r1 <= 1'b1 ;
        end
        else begin
            rx_r0 <= rxd   ;//同步数据
            rx_r1 <= rx_r0 ;//打拍
        end
    end
    
    assign nedge = rx_r1 & ~rx_r0 ;//下降沿检测

    //flag
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            flag <= 1'b0 ;
        end
        else if(nedge) begin
            flag <= 1'b1 ;
        end
        else if(end_cnt_bit) begin
            flag <= 1'b0;
        end
    end
    
    //cnt_bps
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            cnt_bps <= 'd0;
        end
        else if (add_cnt_bps) begin
            if (end_cnt_bps) begin
                cnt_bps <= 'd0 ;
            end
            else begin
                cnt_bps <= cnt_bps + 1'b1 ;
            end
        end
    end

    assign add_cnt_bps = flag ;
    assign end_cnt_bps = add_cnt_bps && (cnt_bps == BPS_9600 - 1) ;

    

    //cnt_bit
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            cnt_bit <= 'd0;
        end
        else if (add_cnt_bit) begin
            if (end_cnt_bit) begin
                cnt_bit <= 'd0 ;
            end
            else begin
                cnt_bit <= cnt_bit + 1'b1 ;
            end
        end
    end

    assign add_cnt_bit = end_cnt_bps ;
    assign end_cnt_bit = add_cnt_bit && (cnt_bit == 9 || data_in[0]) ;//data_in[0]是存的起始位

    //data_in采样数据(包含起始位和停止位)
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            data_in <= 0;
        end
        else if (cnt_bps == BPS_9600 >> 1) begin//为了稳定采样中间的值
            data_in[cnt_bit] <= rx_r1 ;
        end
    end
    
    //取数据位rx_data
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            rx_data <= 0 ;
        end
        else if (end_cnt_bit) begin
            rx_data <= data_in[8:1] ;//只需要给数据位
        end
        else begin
            rx_data <= 0 ;
        end
    end

    //判断rx_data是否有效
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            rx_data_vld <= 0 ;
        end
        else begin 
            rx_data_vld <= end_cnt_bit && (data_in[0] == 0);
        end
    end


endmodule

接收端测试文件:

定义系统时钟周期,例化模块,产生时钟,上电复位并赋予初值,然后分配起始位,数据位,停止位并进行延时处理。

`timescale 1ns/1ps
module tb_uart_rx ();
    reg             clk           ;
    reg             rst_n         ;
    reg             rxd           ;

    wire   [7:0]    rx_data       ;
    wire            rx_data_vld   ;

    //参数定义
    parameter             CYCLE = 20 ;

    //例化
    uart_rx u_uart_rx(
    .clk                (clk        ),
    .rst_n              (rst_n      ),
    .rxd                (rxd        ),
    .rx_data            (rx_data    ),
    .rx_data_vld        (rx_data_vld)
    );
    
    //时钟
    initial begin
        clk = 1'b1 ;
        forever begin
        #(CYCLE/2);
        clk = ~clk ;
        end
    end
    
    integer i;//数据个数
    
    initial begin
        rst_n = 1'b1 ;
        #(CYCLE);
        rst_n = 1'b0 ;
        rxd   = 1'b1 ;
        #22;
        rst_n = 1'b1 ;
        
        #(CYCLE*200);

//模拟一帧数据的格式
        rxd   = 1'b0 ;//起始位
        #(CYCLE*5208);

        //数据位
        for (i=0;i<8;i=i+1) begin
            case (i)//赋值rxd = 1111_1001
                1: rxd = 1'b0 ;
                2: rxd = 1'b0 ;
                default: rxd = 1'b1 ;//只有第一位和第二位为0
            endcase
            #(CYCLE*5208);
        end

        rxd = 1'b1 ;//停止位
        #(CYCLE*5208);

        #(CYCLE*200);
        $stop;
     end
endmodule

仿真

test.do文件:

vlib work
vmap work work

#编译testbench文件
vlog     tb_uart_rx.v
#编译   设计文件
vlog     ../rtl/uart_rx.v


#vlog     altera_mf.v


#指定仿真顶层
vsim -novopt work.tb_uart_rx
#添加信号到波形窗
add wave -position insertpoint sim:/tb_uart_rx//*

run -all

观测到最后8位数据位为1111_1001符合要求。

疑问

此处没有理解透彻5802为啥右移一位就变成中间部分的数据了?将打拍的数据赋予data_in内部为什么应bit计数器表示?

答:右移一一位相当于除以2,符合在中间值采样原则

发送端TX

发送端设计文件:

和发送端类似,由上设计框图

  1. 输入并行信号tx_data,将rx_data的数据传输给它,tx_data_vld为传输8位数据位有效信号标志,输出串行数据txd

  1. 必须保持波特率一致,定义参数

  1. flag在这里作为发送数据标志,同时也是计时器bps的开启条件,其在数据有效时拉高进行发送,所有bit的数据发送完后拉低

  1. 同样用data_out寄存一帧10bit的数据(并行数据),通过发送端转化为串行数据,在数据发送有效时,添加起始位和停止位赋值于它进行寄存

  1. 输出串行数据txd,仅在发送数据标志flag拉高时发送数据,数据来自于寄存于data_out的10bit并行数据。

module uart_tx (
    input                 clk      ,
    input                 rst_n    ,
    input      [7:0]      tx_data  ,
    input                 tx_data_vld ,//发送有效信号标志

    output  reg           txd     
);
    //参数定义
    parameter          BPS_9600 = 5208 ;

    //信号定义

    reg    [12:0]      cnt_bps     ;//计数1bit时钟周期
    wire               add_cnt_bps ;
    wire               end_cnt_bps ;
    reg                flag        ;//发送数据标志,同时也是bps计数的开启条件

    reg    [3:0]       cnt_bit     ;//计数bit数
    wire               add_cnt_bit ;
    wire               end_cnt_bit ;

    reg    [9:0]       data_out    ;

    //flag
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            flag <= 1'b0 ;
        end
        else if(tx_data_vld) begin //在发送有效信号时,flag作为标志拉高 
            flag <= 1'b1 ;
        end
        else if(end_cnt_bit) begin//bit计数结束时,flag拉低
            flag <= 1'b0;
        end
    end
    
    //cnt_bps
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            cnt_bps <= 'd0;
        end
        else if (add_cnt_bps) begin
            if (end_cnt_bps) begin
                cnt_bps <= 'd0 ;
            end
            else begin
                cnt_bps <= cnt_bps + 1'b1 ;
            end
        end
    end

    assign add_cnt_bps = flag ;
    assign end_cnt_bps = add_cnt_bps && (cnt_bps == BPS_9600 - 1) ;

    

    //cnt_bit
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            cnt_bit <= 'd0;
        end
        else if (add_cnt_bit) begin
            if (end_cnt_bit) begin
                cnt_bit <= 'd0 ;
            end
            else begin
                cnt_bit <= cnt_bit + 1'b1 ;
            end
        end
    end

    assign add_cnt_bit = end_cnt_bps ;
    assign end_cnt_bit = add_cnt_bit && (cnt_bit == 9) ;

    //寄存数据至data_out(包含起始位和停止位)
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            data_out <= 0;
        end
        else if (tx_data_vld) begin
            data_out <= {1'b1,tx_data,1'b0} ;
        end
    end

    //输出txd
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            txd <= 1'b1 ;
        end
        else if (flag) begin
            txd <= data_out[cnt_bit] ;
        end
    end
    
endmodule

发送端测试文件:

只需要加上接收端的例化模块即可,中间信号端口互通,在最后再增加10bit的周期延迟。

`timescale 1ns/1ps
module tb_uart_rx ();
    reg             clk           ;
    reg             rst_n         ;
    reg             rxd           ;

    wire   [7:0]    rx_data       ;
    wire            rx_data_vld   ;

    //参数定义
    parameter             CYCLE = 20 ;

    //例化
    uart_rx u_uart_rx(
    .clk                (clk        ),
    .rst_n              (rst_n      ),
    .rxd                (rxd        ),
    .rx_data            (rx_data    ),
    .rx_data_vld        (rx_data_vld)
    );

    uart_tx u_uart_tx(
        .clk           (clk     )     ,
        .rst_n         (rst_n   )     ,
        .tx_data       (rx_data    )     ,
        .tx_data_vld   (rx_data_vld)     ,

        .txd           (txd     )
    );
    
    //时钟
    initial begin
        clk = 1'b1 ;
        forever begin
        #(CYCLE/2);
        clk = ~clk ;
        end
    end
    
    integer i;//数据个数
    
    initial begin
        rst_n = 1'b1 ;
        #(CYCLE);
        rst_n = 1'b0 ;
        rxd   = 1'b1 ;
        #22;
        rst_n = 1'b1 ;

        #(CYCLE*200);//增加间隔

//模拟一帧数据的格式
        rxd   = 1'b0 ;//起始位
        #(CYCLE*5208);

        //数据位
        for (i=0;i<8;i=i+1) begin
            case (i)//赋值rxd = 1111_1001
                1: rxd = 1'b0 ;
                2: rxd = 1'b0 ;
                default: rxd = 1'b1 ;//只有第一位和第二位为0
            endcase
            #(CYCLE*5208);
        end

        rxd = 1'b1 ;//停止位
        #(CYCLE*5208);

        #(CYCLE*200);//增加间隔

        #(CYCLE*10*5208);//10bit时钟周期延迟

 
        $stop;
     end
endmodule

仿真

test.do文件

vlib work
vmap work work

#编译testbench文件
vlog     tb_uart_rx.v
#编译   设计文件
vlog     ../rtl/uart_rx.v
vlog     ../rtl/uart_tx.v


#vlog     altera_mf.v


#指定仿真顶层
vsim -novopt work.tb_uart_rx
#添加信号到波形窗
add wave -position insertpoint sim:/tb_uart_rx//*

run -all

顶层仿真

只需要改动例化模块即可

`timescale 1ns/1ps
module tb_uart ();
    reg             clk           ;
    reg             rst_n         ;
    reg             rxd           ;

    wire            txd           ;

    //参数定义
    parameter             CYCLE = 20 ;

    //例化
    uart u_uart(
         .clk         (clk  ),
         .rst_n       (rst_n),
         .rxd         (rxd  ),

         .txd         (txd  )
    );
    
    //时钟
    initial begin
        clk = 1'b1 ;
        forever begin
        #(CYCLE/2);
        clk = ~clk ;
        end
    end
    
    integer i;//数据个数
    
    initial begin
        rst_n = 1'b1 ;
        #(CYCLE);
        rst_n = 1'b0 ;
        rxd   = 1'b1 ;
        #22;
        rst_n = 1'b1 ;

        #(CYCLE*200);//增加间隔

//模拟一帧数据的格式
        rxd   = 1'b0 ;//起始位
        #(CYCLE*5208);

        //数据位
        for (i=0;i<8;i=i+1) begin
            case (i)//赋值rxd = 1111_1001
                1: rxd = 1'b0 ;
                2: rxd = 1'b0 ;
                default: rxd = 1'b1 ;//只有第一位和第二位为0
            endcase
            #(CYCLE*5208);
        end

        rxd = 1'b1 ;//停止位
        #(CYCLE*5208);

        #(CYCLE*200);//增加间隔

        #(CYCLE*10*5208);//10bit时钟周期延迟

 
        $stop;
     end
endmodule

仿真图

另外一种测试方法:

用FPGA的tx端去模拟PC端的tx端发送数据,然后FPGA接收端rx进行接收

代码:

直接建立task发送三个数据

`timescale 1ns/1ps
module tb_uart1 ();
    reg             clk           ;
    reg             rst_n         ;
    reg     [7:0]   data          ;
    reg             data_vld      ;

    wire             dout         ; //中间连线
    wire             txd           ;//tx输出

    //参数定义
    parameter             CYCLE = 20 ;

    //例化
     uart_tx u_uart_tx(
        .clk           (clk     )     ,
        .rst_n         (rst_n   )     ,
        .tx_data       (data    )     ,
        .tx_data_vld   (data_vld)     ,

        .txd           (dout     )   
    );

     uart u_uart(
      .clk                (clk        ),
      .rst_n              (rst_n      ),
      .rxd                (dout        ),

      .txd                (txd         )
     );
   
    //时钟
    initial begin
        clk = 1'b1 ;
        forever begin
        #(CYCLE/2);
        clk = ~clk ;
        end
    end
    
    
    initial begin
        rst_n = 1'b1 ;
        #(CYCLE);
        rst_n = 1'b0 ;
        data  = 0 ;
        data_vld = 1'b0 ;
        #22;
        rst_n = 1'b1 ;
        #(CYCLE*200);//增加间隔

        //发送几个数据
        Send(8'hf9);
        Send(8'ha0);
        Send(8'hff);

        #(CYCLE*10*5208);//10bit时钟周期延迟       
        #(CYCLE*200); 
        $stop;
     end

     task Send;
          input     [7:0] send_data ;
          begin
            data = send_data ;
            data_vld = 1'b1 ;
            #(CYCLE);
            data_vld = 1'b0 ;
            #(10*5208*CYCLE);
          end
     endtask



endmodule

上板验证

能够在波特率为9600时进行发送和接收

  • 5
    点赞
  • 41
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值