基于GTX 8B10B编码的自定义PHY发送模块(高速收发器十二)

发布于:2024-06-02 ⋅ 阅读:(182) ⋅ 点赞:(0)

  点击进入高速收发器系列文章导航界面


  为了熟悉该IP的使用,当然不是只用下官方提供的示例工程收发数据就算完成了,如果自己能够设计一个自定义的PHY协议,然后验证收发是否正确,才是掌握这个IP的最快方式。

  自定义收发数据的一帧数据格式如下所示,在每次发送数据之前,会发送两个逗号字符,之后发送一个字节的起始位8’hfb,然后发送数据,数据全部发完后,需要发送停止位8’hfd。注意逗号中的8’hBC和起始位、停止位的数据都是K码。

在这里插入图片描述

图1 自定义PHY格式

  在设置IP时,设置为在任何时候检测逗号,因此在发送数据时,每间隔一定的空闲时钟周期,就会发送一次逗号,便于逗号检测和接收端时钟纠正。

  为了减小线路中的EMI问题,在发送端处于空闲时,会发送伪随机序列,避免发送单频信号。

  为方便用户使用,开放给用户端口为axi_stream流端口,用户可以通过控制掩码信号发送任意字节的数据。

  注意一般axi_stream采用大端对齐,而GTX IP发送通道小端对齐,先发送低字节数据,比如发送32位数据tx_data,先发送tx_data[7:0],最后发送tx_data[31:24]。在接收、发送数据时,需要注意大小端的转换。

1、自定义PHY发送模块的设计思路

  首先需要考虑与用户接口对接的接口设计,思路大概有两种。

  一种是通过控制应答来达到组帧时间的控制,这种设计比较简单,但是用户数据端口需要等待从机应答信号拉高,才能发送下一个数据,可能需要存储模块对数据暂存。

  另一种是使用FIFO来存储用户需要发送的数据,用户则可以直接将数据存入FIFO中,发送完帧头数据后,从FIFO中取出数据发送即可。这种方式对上游数据发送比较友善,因此采用这种设计思路。

  设计思路比较简单,以状态机为主体架构,内嵌一个计数器辅助状态跳转。状态转换图如下所示,包含六个状态。因为GT收发器上电后需要初始化,因此有一个初始化状态,发送通道初始化完成后跳转到空闲状态。

  在空闲状态下,GT发送通道一直发送32位的LFSR码(伪随机序列)。没经过一段空闲时间,就需要发送一次逗号,便于接收端好做逗号对齐和时钟纠正。

  处于发送逗号状态下时,会发送2个32位的逗号数据,如果有数据发送,则跳转到发送起始位状态,否则回到空闲状态。

  由于起始位只有8位数据,因此需要从第一个数据中取32位数据拼接后一起发送,下一个时钟周期跳转到发送数据状态。

  在发送数据状态下,由于发送起始位时已经发送了第一个数据的高24位(用户端口大端对齐,先发高字节数据),因此每次发送的数据由前一个数据的低8位和后一个数据的高24位组成。

在这里插入图片描述

图2 状态转换图

  在发送数据状态下,需要注意最后一个数据可能并不是所有字节都是有效的,需要根据AXI的数据掩码信号判断有效字节。掩码位置不同会影响该状态下发送数据个数,进而影响计数器cnt的最大计数值,因此可以根据下图进行分析。

  如果需要发送数据个数为wr_len,keep用户需要发送最后一个数据的掩码信号状态,cnt_num表示计数器cnt在发送数据状态下的最大计数值,则有下表关系。

表1 掩码信号与计数器最大值的关系
keep cnt_num
4’b1000 wr_len - 3
4’b1100 wr_len - 3
4’b1110 wr_len - 2
4’b1111 wr_len - 2

  解释一下上表中计数器为什么是-2和减3,首先cnt_num表示状态机在发送数据状态需要发送的数据个数。那么就要去除在起始位和结束位发送的数据,当最后一个数据需要发送字节数小于3时,最后的数据将会和停止位一起发送(起始位的时候发送了24位数据,导致之后每次发送数据都要从后一个数据取24位拼接,如果最后一个数据需要发送的有效数据小于3字节,那么就会和前一个数据剩余的低8位数据、停止位组成最后一个数据发送个IP)。又由于计数器从0开始计数器,所以计数器最大值是发送数据个数减3。

  如果最后一个数据有效字节数大于等于3,那么最后一个数据发完之后,下一个数据才会是停止位和部分数据,因此需要多一个时钟周期。

  其余设计就比较简单了,需要用一个计数器计数用户发送数据的个数,并且将最后一个数据掩码信号保存,FIFO采用超前模式,尽量减小输出数据延时。

  为了加快数据发送速率,当检测到输入数据有效时,就开始产生逗号和起始位,但需要注意FIFO存入数据后,一般需要至少两个周期才能读取数据。

  上面的设计通过控制计数器最大值控制状态机跳转,这个思路可能在写计数器时需要思考的东西多一点,但是最后在发送数据时相应代码就会简单很多。在写代码之前还是仔细分析一下,找到一些规律后,很可能简化设计,节省后续仿真时间。

2、代码分析

  模块对应端口信号如下所示,用到的GT IP信号就只有三个,发送通道初始化完成信号,发送数据信号(小端对齐),K码指示信号。开放给用户的是axi_stream接口,这个接收的数据是大端对齐的,因此在数据发送时需要调整。

module phy_tx (
    input									clk		        ,//系统时钟信号;
    input									rst  	        ,//系统复位信号,高电平有效;

    input       [31 : 0]                    axi_s_data      ,//AXI的数据输入信号,先传输高字节数据;
    input       [3 : 0]                     axi_s_keep      ,//AXI的数据掩码信号,低电平有效;
    input                                   axi_s_last      ,//AXI输入最后一个数据指示信号;
    input                                   axi_s_valid     ,//AXI输入数据有效指示信号;
    output reg                              axi_s_ready     ,//AXI输入数据应答信号;

    input                                   gt_tx_done      ,//GTX发送部分初始化成功,高电平有效;
    output reg  [31 : 0]                    gt_tx_data      ,//GTX发送数据,先发送低字节数据;
    output reg  [3 : 0]                     gt_tx_char       //GTX发送数据K码指示信号,高电平有效;
);

  下面是状态机编码及发送逗号的空闲间隔时间,并且通过自定义函数计算该计数器位宽。

    localparam  ICOMMOA_CYCLE           =   500             ;//间隔500个时钟周期后发送一次同步码。
    localparam  ICOMMOA_CYCLE_W         =   clogb2(ICOMMOA_CYCLE-1) ;//.
    localparam  INIT                    =   6'b00_0001      ;//状态机的初始化状态编码;
    localparam  IDLE                    =   6'b00_0010      ;//状态机的空闲状态编码;
    localparam  COMMOA                  =   6'b00_0100      ;//状态机的发送同步码状态编码;
    localparam  SOF                     =   6'b00_1000      ;//状态机的发送起始位状态编码;
    localparam  DATA                    =   6'b01_0000      ;//状态机的发送数据状态编码;
    localparam  EOF                     =   6'b10_0000      ;//状态机的发送停止位状态编码;

    reg         [5 : 0]	                    state_n         ;//状态机的次态信号;
    reg         [5 : 0]	                    state_c         ;//状态机的现态信号;
    reg         [3 : 0]                     axi_s_keep_r    ;//
    reg         [2 : 0]                     axi_s_valid_r   ;
    reg         [2 : 0]                     axi_s_ready_r   ;
    reg         [9 : 0]                     cnt             ;//
    reg         [9 : 0]                     cnt_num         ;//
    reg         [9 : 0]                     wr_len          ;//
    reg         [ICOMMOA_CYCLE_W - 1 : 0]   commoa_cnt      ;//
    reg                                     fifo_rden       ;
    reg         [31 : 0]                    fifo_dout_r     ;
    
    wire       		                        add_cnt         ;
    wire       		                        end_cnt         ;
    wire       		                        add_commoa_cnt  ;
    wire       		                        end_commoa_cnt  ;
    wire                                    valid_pos       ;
    wire                                    fifo_full       ;
    wire                                    fifo_empty      ;
    wire        [31 : 0]                    lfsr_value      ;
    wire        [31 : 0]                    fifo_dout       ;
    wire        [31 : 0]                    fifo_dout_big   ;

    //自动计算位宽函数。
    function integer clogb2(input integer depth);begin
        if(depth == 0)
            clogb2 = 1;
        else if(depth != 0)
            for(clogb2=0 ; depth>0 ; clogb2=clogb2+1)
                depth=depth >> 1;
        end
    endfunction

  下面例化LFSR生成模块和FIFO IP,并且将FIFO输出信号调整为小端模式,将先发送的数据放在低字节,便于后续使用。

    //将FIFO读出的数据进行调整,与输出数据保持一致,先发送低字节数据;
    assign fifo_dout = {fifo_dout_big[7:0],fifo_dout_big[15:8],fifo_dout_big[23:16],fifo_dout_big[31:24]};

    //例化lfsr模块
    lfsr_gen #(
        .LFSR_INIT  (16'hA076       ) 
    )
    u_lfsr_gen (
        .clk        ( clk           ),
        .rst        ( rst           ),
        .lfsr_value ( lfsr_value    )
    );
    
    //例化同步FIFO存储数据
    fifo_32X1024 u_fifo_32X1024 (
        .clk    ( clk           ),//input wire clk;
        .din    ( axi_s_data    ),//input wire [31 : 0] din;
        .wr_en  ( axi_s_valid && axi_s_ready    ),//input wire wr_en;
        .rd_en  ( fifo_rden     ),//input wire rd_en;
        .dout   ( fifo_dout_big ),//output wire [31 : 0] dout;
        .full   ( fifo_full     ),//output wire full;
        .empty  ( fifo_empty    ) //output wire empty;
    );

  注意FIFO在设置时使用模式选择,如下所示,选择First Word Fall Through。

在这里插入图片描述

图3 FIFO模式选择

  将axi的数据有效指示信号、应答信号暂存、FIFO输出数据通过移位寄存器暂存,便于后文使用。

    //通过移位寄存器将数据暂存,便于后文使用。
    always@(posedge clk)begin
        fifo_dout_r <= fifo_dout;
        axi_s_valid_r <= {axi_s_valid_r[1:0],axi_s_valid};
        axi_s_ready_r <= {axi_s_ready_r[1:0],axi_s_ready};
    end

  检测输入数据的上升沿,作为用户发送数据个数计数器的置一条件,该计数器当主机和从机握手时加1,表示接收到一个用户数据。

    assign valid_pos = axi_s_valid && (~axi_s_valid_r[0]);//检测输入数据有效指示信号的上升沿。

    //对输入的数据进行计数,便于读FIFO数据时作为参考。
    always@(posedge clk)begin
        if(rst)begin//初始值为1;
            wr_len <= 10'd1;
        end
        else if(valid_pos)begin//从1开始计数,表示存储到FIFO中的数据个数。
            wr_len <= 10'd1;
        end
        else if(axi_s_valid && axi_s_ready)begin
            wr_len <= wr_len + 1;
        end
    end

  之后是数据应答信号,当用户发送完一次数据后拉低,状态机回到空闲状态,表示上一帧数据已经全部发送完成,可以接收下一帧数据,此时应答信号拉高。把用户需要发送最后一个数据的掩码信号暂存。

    //生成AXI接收数据应答信号;
    always@(posedge clk)begin
        if(rst)begin//初始值为0;
            axi_s_ready <= 1'b0;
        end
        else if(axi_s_last)begin//当接收完最后一个数据时拉低;
            axi_s_ready <= 1'b0;
        end
        else if(state_c == IDLE)begin//当状态机处于空闲状态时拉高。
            axi_s_ready <= 1'b1;
        end
    end

    //将发送最后一字节数据的掩码信号暂存。
    always@(posedge clk)begin
        if(rst)begin//初始值为0;
            axi_s_keep_r <= 4'd0;
        end
        else if(axi_s_last && axi_s_valid && axi_s_ready)begin//将最后一字节数据的掩码信号暂存。
            axi_s_keep_r <= axi_s_keep;
        end
    end

  下面是状态的现态和次态的跳转,与前文状态转换图描述一致,不再赘述。

    //状态机次态到现态的跳转。
    always@(posedge clk)begin
        if(rst)begin//初始位于初始化状态。
            state_c <= INIT;
        end
        else begin
            state_c <= state_n;
        end
    end
    
    //状态机次态的跳转。
    always@(*)begin
        case(state_c)
            INIT : begin
                if(gt_tx_done)begin//初始化完成后跳转到空闲状态。
                    state_n = IDLE;
                end
                else begin
                    state_n = state_c;
                end
            end
            IDLE : begin
                if((axi_s_valid_r[0] && axi_s_ready_r[0]) || end_commoa_cnt)begin//如果写入数据有效或者间隔固定空闲时钟时,跳转到发送同步码状态。
                    state_n = COMMOA;
                end
                else begin
                    state_n = state_c;
                end
            end
            COMMOA : begin
                if(end_cnt)begin//同步码发送完毕后,如果有数据需要发送,则跳转到起始位,否则跳转到空闲状态。
                    if(axi_s_valid_r[2] && axi_s_ready_r[2])
                        state_n = SOF;
                    else
                        state_n = IDLE;
                end
                else begin
                    state_n = state_c;
                end
            end
            SOF : state_n = DATA;//直接跳转到发送数据状态;
            DATA : begin
                if(end_cnt)begin//数据发送完毕,跳转到发送停止位状态。
                    state_n = EOF;
                end
                else begin
                    state_n = state_c;
                end
            end
            EOF : state_n = IDLE;//回到空闲状态;
            default : begin
                state_n = INIT;
            end
        endcase
    end

接着是逗号间隔计数器,如下所示,对状态机处于空闲状态的时钟进行计数,当达到最大值时清零。

    //记录空闲状态对应的时钟个数,初始值为0。
    always@(posedge clk)begin
        if(rst)begin//
            commoa_cnt <= 0;
        end
        else if(add_commoa_cnt)begin
            if(end_commoa_cnt)
                commoa_cnt <= 0;
            else
                commoa_cnt <= commoa_cnt + 1;
        end
    end
    
    assign add_commoa_cnt = (state_c == IDLE);
    assign end_commoa_cnt = add_commoa_cnt && commoa_cnt == ICOMMOA_CYCLE - 1;

  下面是辅助状态机跳转的计数器,该状态机只有在发送逗号和数据两个状态需要计数,因此计数器在这两个状态下对时钟计数,技术到最大值或者状态机跳转时清零。

    //计数器cnt,对状态机处于发送同步码和发送数据的状态进行计数。
    always@(posedge clk)begin
        if(rst)begin//
            cnt <= 0;
        end
        else if(state_c != state_n)begin//状态机跳转时清零。
            cnt <= 0;
        end
        else if(add_cnt)begin
            if(end_cnt)
                cnt <= 0;
            else
                cnt <= cnt + 1;
        end
    end
    
    //当状态机处于发送同步码或者发送数据时对时钟计数。
    assign add_cnt = (state_c == COMMOA) || (state_c == DATA);
    assign end_cnt = add_cnt && cnt == cnt_num;//当计数到对应数值时清零。

  下面是计数器最大值的设置,在逗号状态下只需要发送两个32位数据,而在发送数据状态下,根据最后一个数据的有效字节数,得到计数器对应的最大值。

    //用于记录状态机在不同状态下计数器的最大值。
    always@(posedge clk)begin
        if(rst)begin//初始值最好不为0;
            cnt_num <= 10'd20;
        end
        else if(state_c == COMMOA)begin
            cnt_num <= 2 - 1;//每次需要发送4字节同步码16'HBC50;
        end
        else if(state_c == DATA)begin
            case (axi_s_keep_r)
                4'b1000 : cnt_num <= wr_len - 3;//发送数据状态需要发送数据的个数;
                4'b1100 : cnt_num <= wr_len - 3;//发送数据状态需要发送数据的个数;
                4'b1110 : cnt_num <= wr_len - 2;//发送数据状态需要发送数据的个数;
                4'b1111 : cnt_num <= wr_len - 2;//发送数据状态需要发送数据的个数;
                default : cnt_num <= wr_len - 2;//;
            endcase
        end
    end

  然后是FIFO读使能信号,在逗号发送完且需要发送数据时拉高读使能,根据最后一个数据有效字节数不同,拉低的条件会有区别。

    //生成FIFO的读使能信号,初始值为零。
    always@(posedge clk)begin
        if(rst)begin//初始值为0;
            fifo_rden <= 1'b0;
        end//如果最后一个数据需要发送多余2字节,则状态机在读数据结束时拉低FIFO读使能。
        else if(&axi_s_keep_r[3:1] && (state_c == DATA && end_cnt))begin
            fifo_rden <= 1'b0;
        end
        else if(state_c == EOF)begin//当状态机跳转到发送停止位时拉低,其余时间保持不变。
            fifo_rden <= 1'b0;
        end
        else if((state_c == COMMOA) && end_cnt && axi_s_valid_r[2] && axi_s_ready_r[2])begin//当输出最后一个同步码时,将读使能拉高,下个时钟输出数据。
            fifo_rden <= 1'b1;
        end
    end

  最后是发送数据和K码指示信号生成,在发送停止位时,需要根据掩码信号的值确定具体数据和K码指示信号状态,代码的注释都比较详细,不再赘述。在停止发送完毕和空闲状态下,发送LFSR随机序列。

    //生成发送数据和K码指示信号,低字节数据先发送。
    always@(posedge clk)begin
        if(rst)begin//初始值为0;
            gt_tx_data <= 32'd0;
            gt_tx_char <= 4'd0;
        end
        else begin
            case (state_c)
                COMMOA : begin//发送同步码32'hBC50BC50.
                    gt_tx_data <= 32'h50bc50bc;
                    gt_tx_char <= 4'b0101;
                end
                SOF    : begin//发送起始码且需要加入3字节数据。
                    gt_tx_data <= {fifo_dout[23:0],8'hfb};
                    gt_tx_char <= 4'b0001;
                end
                DATA   : begin//发送数据,由于起始位发送了三字节数据,下次发送时需要进行拼接。
                    gt_tx_data <= {fifo_dout[23:0],fifo_dout_r[31:24]};
                    gt_tx_char <= 4'b0000;
                end
                EOF    : begin//在发送结束码状态时,根据需要发送的字节数不同,需要对数据拼接。
                    case (axi_s_keep_r)
                        4'b1000 : begin//最后一个数据只有1字节有效时,将上次剩余数据进行拼接后发送。
                            gt_tx_data <= {lfsr_value[7:0],8'hfd,fifo_dout[7:0],fifo_dout_r[31:24]};
                            gt_tx_char <= 4'b0100;
                        end
                        4'b1100 : begin//最后一个数据只有2字节有效时,将上次剩余数据和停止吗进行拼接后发送。
                            gt_tx_data <= {8'hfd,fifo_dout[15:0],fifo_dout_r[31:24]};
                            gt_tx_char <= 4'b1000;
                        end
                        4'b1110 : begin//最后一个数据有3字节有效时,状态机跳转时数据就已经发送完成,最需要将停止码发出即可。
                            gt_tx_data <= {lfsr_value[23:0],8'hfd};
                            gt_tx_char <= 4'b0001;
                        end
                        4'b1111 : begin//最后一个数据均有效时,状态机跳转时会剩余一字节数据没有发送,将剩余数据和停止码拼接发出。
                            gt_tx_data <= {lfsr_value[15:0],8'hfd,fifo_dout_r[31:24]};
                            gt_tx_char <= 4'b0010;
                        end
                        default : begin//其余时间发送随机数。
                            gt_tx_data <= lfsr_value;
                            gt_tx_char <= 4'b0000;
                        end
                    endcase
                end
                default : begin//其余时间发送随机数。
                    gt_tx_data <= lfsr_value;
                    gt_tx_char <= 4'b0000;
                end
            endcase
        end
    end

  上述代码理解难度稍微大一点的就是FIFO读使能和发送数据状态计数器的最大值,其余部分都比较简单,也正是由于这两部分思路绕一点,其他代码也才能够如此简单。有兴趣可以试试其余方法,可能会将难度装一道状态机上面去,导致跳转变得很复杂。

3、模块仿真

  对应的TestBench如下所示,比较简单,通过设置TX_KEEP参数,即可确定最后一个数据的有效字节,用来进行不同字节数的仿真测试。

//--###############################################################################################
//--#
//--# File Name		: tb_phy_tx
//--# Designer		: 数字站
//--# Tool			: Vivado 2021.1
//--# Design Date	: 2024.3.17
//--# Description	: TestBench
//--# Version		: 0.0
//--# Coding scheme	: GBK(If the Chinese comment of the file is garbled, please do not save it and check whether the file is opened in GBK encoding mode)
//--#
//--###############################################################################################
`timescale 1 ns/1 ns
module tb_phy_tx();
    localparam	CYCLE		= 	10		        ;//系统时钟周期,单位ns,默认10ns;
    localparam	RST_TIME	= 	10		        ;//系统复位持续时间,默认10个系统时钟周期;
    localparam  TX_KEEP     =   4'b1111         ;//发送最后一个数据的有效位数,大端对齐;

    reg			                clk             ;//系统时钟,默认100MHz;
    reg			                rst_n           ;//系统复位,默认低电平有效;
    reg         [7 : 0]         send_value      ;

    reg         [31 : 0]        axi_s_data      ;
    reg         [3 : 0]         axi_s_keep      ;
    reg                         axi_s_last      ;
    reg                         axi_s_valid     ;
    wire                        axi_s_ready     ;
    wire        [31 : 0]        gt_tx_data      ;
    wire        [3 : 0]         gt_tx_char      ;

    phy_tx u_phy_tx(
        .clk		    ( clk		    ),//系统时钟信号;
        .rst_n	        ( rst_n	        ),//系统复位信号,低电平有效;
        .axi_s_data     ( axi_s_data    ),//AXI的数据输入信号,先传输高字节数据;
        .axi_s_keep     ( axi_s_keep    ),//AXI的数据掩码信号,低电平有效;
        .axi_s_last     ( axi_s_last    ),//AXI输入最后一个数据指示信号;
        .axi_s_valid    ( axi_s_valid   ),//AXI输入数据有效指示信号;
        .axi_s_ready    ( axi_s_ready   ),//AXI输入数据应答信号;
        .gt_tx_done     ( 1'b1          ),//GTX发送部分初始化成功,高电平有效;
        .gt_tx_data     ( gt_tx_data    ),//GTX发送数据,先发送低字节数据;
        .gt_tx_char     ( gt_tx_char    ) //GTX发送数据K码指示信号,高电平有效;
    );

    //生成周期为CYCLE数值的系统时钟;
    initial begin
        clk = 0;
        forever #(CYCLE/2) clk = ~clk;
    end

    //生成复位信号;
    initial begin
        rst_n = 1;
        #2;
        rst_n = 0;//开始时复位10个时钟;
        #(RST_TIME*CYCLE);
        rst_n = 1;
    end

    //生成输入信号din;
    initial begin
        axi_s_data  = 32'd0;
        axi_s_keep  = 4'd0;
        axi_s_last  = 1'd0;
        axi_s_valid = 1'd0;
        wait(rst_n);//等待复位完成;
        repeat(10) @(posedge clk);
        forever begin
            phy_tx_task(5);
        end
    end

    //发送数据的任务;
    task phy_tx_task(
        input	[7 : 0]		len
    );
        begin : phy_tx_task_0
            integer i;
            axi_s_data  <= 32'd0;
            axi_s_keep  <= 4'd0;
            axi_s_last  <= 1'd0;
            axi_s_valid <= 1'd0;
            send_value <= 8'd1;
            @(posedge clk);
            wait(axi_s_ready);
            for(i=0 ; i<len ; i=i+1)begin
                send_value <= send_value + 1;
                axi_s_data <= {send_value,send_value,send_value,send_value};
                if(i == len - 1)begin//最后一个数据时控制掩码信号;
                    axi_s_last <= 1'b1;
                    axi_s_keep <= TX_KEEP;
                end
                else begin
                    axi_s_last <= 1'b0;
                    axi_s_keep <= 4'hf;
                end
                axi_s_valid <= 1'b1;
                @(posedge clk);
            end
            axi_s_data  <= 32'd0;
            axi_s_keep  <= 4'd0;
            axi_s_last  <= 1'd0;
            axi_s_valid <= 1'd0;
            @(posedge clk);
        end
    endtask

endmodule

  如下图所示,用户一帧发送5个32位数据,最后一个数据只有最高字节有效。该模块首先输出2个32位的逗号数据,之后发送起始位8’hfb,起始位中包含24位数据,之后每个数据都由前后两个用户数据拼接。最后停止位中会包含2字节的数据,8’hfd是停止位。注意axi_s_data是大端对齐,gt_tx_data是小端对齐。

在这里插入图片描述

图4 最后数据单字节有效仿真

  下图是用户发送最后一个数据高两字节均有效的仿真结果,此时停止位会包含3字节数据,只发送了2字节的8’h05,仿真正确。

在这里插入图片描述

图5 最后数据两字节有效仿真

  下图是用户发送最后一个数据高三字节均有效的仿真结果,停止位不包含数据,最后发送了三字节8’h55,仿真正确。

在这里插入图片描述

图6 最后数据三字节有效仿真

  下图是用户发送最后一个数据均节均有效的仿真结果,停止位包含1字节数据,一帧数据发送了四字节8’h55,仿真正确。

在这里插入图片描述

图7 最后数据均有效仿真

  经过上述仿真,初步判断该模块功能正常,在空闲状态也插入了LFSR编码,而这个编码与m序列的原理基本类似,也比较简单。

  在接收模块设计完成后,整个工程一起上板进行测试,本工程暂时还不能上板测试。


  如果对文章内容理解有疑惑或者对代码不理解,可以在评论区或者后台留言,看到后均会回复!

  如果本文对您有帮助,还请多多点赞👍、评论💬和收藏⭐!您的支持是我更新的最大动力!将持续更新工程!


网站公告

今日签到

点亮在社区的每一天
去签到