首页 > 其他分享 >FPGA入门笔记010——UART串口接收模块设计

FPGA入门笔记010——UART串口接收模块设计

时间:2024-03-28 16:12:13浏览次数:21  
标签:采样 接收 FPGA UART Rx 模块 串口 Rst Rs232

1、串口接收模块原理

​ 当对于数据线 Rs232_Rx 上的每一位进行采样时,一般情况下认为每一位数据的中间点是最稳定的。因此一般应用中,采集中间时刻时的电平即认为是此位数据的电平,如图 1 所示。

image-20240326093443039

图1——串口接收时序图(图中 BPS_CLK 为采样时钟)

​ 但是在实际工业应用中,现场往往有非常强的电磁干扰,只采样一次就作为该数据的电平状态是不可靠的。如果在实际工业应用中对每位数据只采样一次,则结果很有可能恰好采集到被干扰的信号而导致结果出错,因此这里针对性提出以下改进型的单 bit 数据接收方式设计方法,以此来提高数据传输的正确性。

改进型的单 bit 数据接收方式的设计思想为:使用多次采样统计概率的方式进行该数据位高低电平的判定,如图 2 所示。

image-20240326093940909

图2——改进型串口接收方式示意图

​ 在上图中,将每一位数据平均分成了 16 小段。对于 Bit_x 这一位数据,考虑到数据在刚刚发生变化和即将发生变化的这一时期,数据极有可能不稳定(用深灰色标出的两段),在这两个时间段采集数据,很有可能得到错误的结果,因此判定这两段时间的电平无效,采 集时直接忽略。而中间这一时间段(用浅灰色标出),数据本身是比较稳定的,一般都代表了正确的结果。

​ 因此改进型单 bit 数据接收方式对中间段进行多次采样,并统计高低电平发生的概率,6 次采集结果中,取出现次数多的电平作为单 bit 最终采样结果。 例如,采样 6 次的结果分别为 1/1/1/1/0/1/,则取电平结果为 1,若为 0/0/1/0/0/0,,则取电平结果为 0,当 6 次采样结果中 1 和 0 各占一半(各 3 次),则可判断当前通信线路环境非常恶劣,数据不具有可靠性,不进行处理。

2、串口接收模块整体设计

​ 串口接收模块包含三个主要组件:

1、起始位检测进程;

2、波特率时钟生成模块;

3、数据接收进程;

4、数据状态判定模块

​ 基于以上原理,串口接收模块整体框图如图 3 所示,其接口列表如表 1 所示。

image-20240326095301108

图3——串口接收模块整体框图

image-20240326095413252

image-20240326095443552

表1——串口接收模块接口列表

​ 串口接收模块主要构成之一即为波特率时钟生成模块。这里可根据本节提到的串口接收模块原理部分中提到的过采样方式,即实际的采样频率是波特率的 16 倍,所以得除以16,得出计数值与波特率之间的关系如表 2 所示,其中系统时钟周期为 System_clk_period,这里为 20ns。

3、接口设置

module uart_byte_rx(
    Clk,
    Rst_n,
    Baud_Set,
    Rs232_Rx,

    Data_Byte,
    Rx_Done
);

    input Clk;
    input Rst_n;
    input [2:0]Baud_Set;
    input Rs232_Rx;

    output reg[7:0]Data_Byte;
    output reg Rx_Done;

    //起始位检测进程
    reg s0_Rs232_Rx,s1_Rs232_Rx;    //两个同步寄存器(消除亚稳态)
    reg tmp0_Rs232_Rx,tmp1_Rs232_Rx;    //两个暂存寄存器(数据寄存)
    wire neg;

    //采样时钟生成模块
    reg [15:0]bps_DR;    //分频计数器最大值
    reg [15:0]div_cnt;  //分频计数器
    reg bps_clk;    //波特率时钟
    reg [7:0]bps_cnt;   //波特率时钟计数器

    //数据接收进程
    reg [2:0]r_data_byte [7:0]; //"[7:0]"表示一段数据总共有8位——位长 , "[2:0]"表示每位存储6个数据——位宽

    reg [2:0]START_BIT;
    reg [2:0]STOP_BIT;

    reg UART_state; //接收结束信号

endmodule

4、起始位检测进程

4.1、亚稳态的概念与消除方法

​ 首先,对异步信号Rs232_Rx消除亚稳态。

1、亚稳态的概念:

​ 异步信号:该信号的产生与时钟上升沿无关(例如按键按下)。

​ 同步信号:每次时钟上升沿到来都会产生该信号。

​ 异步信号在输入系统的时候存在两种情况,如图 4 所示:

image-20240326105348608

图4——异步信号在输入系统时存在的两种情况

​ 上图中,系统时钟上升沿采集到异步信号 2 ,此时异步信号 2 处于不定态,对应该信号的D触发器产生了亚稳态的情况,D触发器的输出q产生震荡,震荡一段时间后变为稳定,可能为 1 ,也可能为 0 ,如图 5 所示。

image-20240326111115208

图5——D触发器处于亚稳态时的输出结果

2、亚稳态的消除方法(同步处理、数据寄存、边沿检测):

(1)同步处理

​ 如果此时,该D触发器又加了一级D1,如图 6 所示,若第一级D触发器到第二个时钟上升沿到来时还没稳定,则第二级D触发器也会处于亚稳态,如图 7 所示,

image-20240326111449617

图6——两级D触发器

image-20240326112405439

图7——两级D触发器处于亚稳态时的输出结果

​ 虽然两级D触发器无法保证最终的输出结果是否正确,但至少不会让信号处于不定态,能保证第二级D触发器输出结果是稳定的,所以在处理异步信号时,经常对输入信号加入两级D触发器进行同步

​ 对于异步信号Rs232_Rx而言,加入两个同步寄存器 s0_Rs232_Rx,s1_Rs232_Rx,进行亚稳态的消除,同步后的信号为:s1_Rs232_Rx,如下列代码所示:

    //对异步信号进行同步处理(加入一个两级D触发器,对异步信号Rs232_Rx消除其亚稳态)
    always@(posedge Clk or negedge Rst_n)
    if(!Rst_n)begin
        s0_Rs232_Rx <= 1'b0;
        s1_Rs232_Rx <= 1'b0;
    end
    else begin  //两级D触发器
        s0_Rs232_Rx <= Rs232_Rx;
        s1_Rs232_Rx <= s0_Rs232_Rx;
    end

(2)数据寄存

​ 同时,使用两级D触发器存储两个相邻时钟上升沿时Rs232_Rx信号(已同步到系统时钟域中)的电平状态,如下列代码所示:

    //数据寄存(使用D触发器存储两个相邻时钟上升沿时外部输入信号(已同步到系统时钟域中)的电平状态)
    always@(posedge Clk or negedge Rst_n)
    if(!Rst_n)begin
        tmp0_Rs232_Rx <= 1'b0;
        tmp1_Rs232_Rx <= 1'b0;
    end
    else begin  //两级D触发器
        tmp0_Rs232_Rx <= s1_Rs232_Rx;
        tmp1_Rs232_Rx <= tmp0_Rs232_Rx;
    end

(3)边沿检测

​ 通过检测同步后的Rs232_Rx信号是否产生了下降沿,来检测起始位。

​ 注意 temp0_Rs232_Rx 信号、temp1_Rs232_Rx 信号的先后顺序:temp0_Rs232_Rx后、temp1_Rs232_Rx先

    //边沿检测(通过检测检测同步后的Rs232_Rx信号是否产生了下降沿,来检测起始位)
    //temp0_Rs232_Rx后、temp1_Rs232_Rx先
    assign neg = !tmp0_Rs232_Rx & tmp1_Rs232_Rx;

5、采样时钟生成模块

​ 串口接收模块主要构成之一即为波特率时钟生成模块。这里可根据本节提到的串口接收模块原理部分中提到的过采样方式,即实际的采样频率是波特率的 16 倍,所以得除以16,得出计数值与波特率之间的关系如表 2 所示,其中系统时钟周期为 System_clk_period,这里为 20ns。

image-20240326095810600

表2——串口接收模块采样时钟计算

​ 这里依旧使用一个选择器,来实现不同波特率与采样时钟分频计数值之间的对应关系。 设计代码如下所示。

    //查找表(DR_LUT)
    always@(posedge Clk or negedge Rst_n)
    if(!Rst_n)
        bps_DR <= 16'd324;
    else begin
        case(Baud_Set)
            0:bps_DR <= 16'd324;
            1:bps_DR <= 16'd162;
            2:bps_DR <= 16'd80;
            3:bps_DR <= 16'd53;
            4:bps_DR <= 16'd26;
            default:bps_DR <= 16'd324;
        endcase
    end

​ 现在产生采样时钟,即波特率时钟的 16 倍。

    //分频计数器
    always@(posedge Clk or negedge Rst_n)
    if(!Rst_n)
        div_cnt <= 16'd0;
    else if(UART_state)begin
        if(div_cnt == bps_DR)
            div_cnt <= 16'd0;
        else
            div_cnt <= div_cnt + 1'b1;
    end
    else
        div_cnt <= 16'd0;

    //产生波特率时钟(bps_clk)
    always@(posedge Clk or negedge Rst_n)
    if(!Rst_n)
        bps_clk <= 1'b0;
    else if(div_cnt == 16'd1)   //当计数器开始工作时,bps_clk就产生一个高脉冲,若计数器计数到bps_DR,则脉冲信号会延后一位。
        bps_clk <= 1'b1;
    else
        bps_clk <= 1'b0;

​ 采样时钟计数器:计数器清零条件之一: bps_cnt == 8'd159 代表一个字节接收完毕。 (bps_cnt == 8'd12 && (START_BIT > 2)) 是实现起始位检测是否出错,在后面会对此进行详细解释。

    //波特率时钟计数器(bps_cnt)
    always@(posedge Clk or negedge Rst_n)
    if(!Rst_n)
        bps_cnt <= 8'd0;
    else if(Rx_Done | (bps_cnt == 8'd12 && (START_BIT > 2)))    //"bps_cnt == 8'd12 && (START_BIT > 2"指开始信号START错误的情况。
        bps_cnt <= 8'd0;
    else if(bps_clk)
        bps_cnt <= bps_cnt + 1'b1;
    else
        bps_cnt <= bps_cnt;

    //接收结束信号生成(Rx_Done)
    always@(posedge Clk or negedge Rst_n)
    if(!Rst_n)
        Rx_Done <= 1'b0;
    else if(bps_cnt == 8'd159)
        Rx_Done <= 1'b1;
    else
        Rx_Done <= 1'b0;

6、采样数据接收模块设计

​ 以图 2 起始位为例,位于中间的采样时间段对应的 bps_cnt 值分别为 6、7、8、9、10、11,在这些时刻直接累加本位数据。后一位数据位于中间的采样时间段的第一个bps_cnt 值为前一位数据位于中间的采样时间段的第一个 bps_cnt 值加 16,分别为 22、23、24、25、26、27,同样,在这些时刻,直接累加本位数据。以此类推,可以得到其他位的采样时间段对应的 bps_cnt 值。

​ 现解释为何在上面清零条件之一为(bps_cnt == 8'd12 && (START_BIT > 2)),理想情况下(真正的起始位)也就是当 bps_cnt 计数值为 12 时,START_BIT 的计算值应该为0。而在实际中,有可能会出现一个干扰信号,而并非真正的起始信号,也导致下降沿的出现。此时,如果不加判断,直接视之为起始信号,进入接收状态,那么必然会接收到错误数据,也可能导致错过正确数据的接收。为了增加抗干扰能力,这里采用了这样的一种思路:当数据线上出现了下降沿时,先假设它是起始信号,然后对其进行中间段采样。如果这 6 次采样值累加结果大于 2,即 6 次采样中有至少一半的状态为高电平时,那么这显然不符合真正起始信号的持续低电平要求,此时就把刚才到来的下降沿视为干扰信号,而不视为起始信号。

//数据接收进程
    //多路器
    always@(posedge Clk or negedge Rst_n)   //用'*'号可能会出现毛刺,所有用时序逻辑来编写组合逻辑电路,较稳定
    if(!Rst_n)begin
        START_BIT <= 3'd0;
        r_data_byte[0] <= 3'd0;
        r_data_byte[1] <= 3'd0;
        r_data_byte[2] <= 3'd0;
        r_data_byte[3] <= 3'd0;
        r_data_byte[4] <= 3'd0;
        r_data_byte[5] <= 3'd0;
        r_data_byte[6] <= 3'd0;
        r_data_byte[7] <= 3'd0;
        STOP_BIT <= 3'd0;
    end
    else if(bps_clk)begin   //当bps_clk为1的时候才会累加,如果不加上这个判断条件,每当时钟上升沿到来时就会累加,会造成错误的结果
        case(bps_cnt)   //累加计算
            0:begin     //当bps_cnt为0时,对所有寄存器进行清零操作,防止数据一直累加,使得结果错误
                START_BIT <= 3'd0;
                r_data_byte[0] <= 3'd0;
                r_data_byte[1] <= 3'd0;
                r_data_byte[2] <= 3'd0;
                r_data_byte[3] <= 3'd0;
                r_data_byte[4] <= 3'd0;
                r_data_byte[5] <= 3'd0;
                r_data_byte[6] <= 3'd0;
                r_data_byte[7] <= 3'd0;
                STOP_BIT <= 3'd0;
            end
            6,7,8,9,10,11:START_BIT <= START_BIT + s1_Rs232_Rx;
            22,23,24,25,26,27:r_data_byte[0] <= r_data_byte[0] + s1_Rs232_Rx;
            38,39,40,41,42,43:r_data_byte[1] <= r_data_byte[1] + s1_Rs232_Rx;
            54,55,56,57,58,59:r_data_byte[2] <= r_data_byte[2] + s1_Rs232_Rx;
            70,71,72,73,74,75:r_data_byte[3] <= r_data_byte[3] + s1_Rs232_Rx;
            86,87,88,89,90,91:r_data_byte[4] <= r_data_byte[4] + s1_Rs232_Rx;
            102,103,104,105,106,107:r_data_byte[5] <= r_data_byte[5] + s1_Rs232_Rx;
            118,119,120,121,122,123:r_data_byte[6] <= r_data_byte[6] + s1_Rs232_Rx;
            134,135,136,137,138,139:r_data_byte[7] <= r_data_byte[7] + s1_Rs232_Rx;
            150,151,152,153,154,155:STOP_BIT <= STOP_BIT + s1_Rs232_Rx;
            default;    //bps_cnt为其他值时,保持所有寄存器的值不变
        endcase
    end

7、数据状态判定模块设计

​ 在原理部分介绍过,对一位数据需进行 6 次采样,然后取出现次数较多的数据作为采样 结果,也就是说,6 次采样中出现次数多于 3 次的数据才能作为最终的有效数据。

​ 对此,可以用接收到数据 r_data_byte[n]结合数值比较器来判断,也可以直接令其等于当前位的最高位数据。 以下面例子说明 :当 r_data_byte[n] 分别为二进制的010B/011B/100B/101B 时,这几个数据十进制格式分别为 2d/3d/4d/5d,可以发现大于等 4d 的为 100B/101B。当最高位是 1 即此时的数据累加值大于等于 4d,可以说明数据真实值为1;当最高位是 0 即此时的数据累加值小于等于 3d,可以说明数据真实值为 0,因此只需判 断最高位即可。

	//数据提取
    always@(posedge Clk or negedge Rst_n)
    if(!Rst_n)
        Data_Byte <= 8'd0;
    else if(bps_cnt == 8'd159)begin
        Data_Byte[0] <= r_data_byte[0][2];   //如果r_data_byte的最高位为1,则代表采样出1的数量大于等于4,则Data_Byte = 1。
        Data_Byte[1] <= r_data_byte[1][2];
        Data_Byte[2] <= r_data_byte[2][2];
        Data_Byte[3] <= r_data_byte[3][2];
        Data_Byte[4] <= r_data_byte[4][2];
        Data_Byte[5] <= r_data_byte[5][2];
        Data_Byte[6] <= r_data_byte[6][2];
        Data_Byte[7] <= r_data_byte[7][2];
    end

    //UART_state产生模块
    always@(posedge Clk or negedge Rst_n)
    if(!Rst_n)
        UART_state <= 1'b0;
    else if(neg)   //检测到下降沿"neg",开始接收数据
        UART_state <= 1'b1;
    else if(Rx_Done | (bps_cnt == 8'd12 && (START_BIT > 2)))   //接收完成or接收错误(START_BIT信号所采集到的值大于等于三,则START_BIT可能为1,接收错误),停止接收
        UART_state <= 1'b0;
	else
        UART_state <= UART_state;

8、激励创建及仿真测试

​ 完成设计之后,对其进行功能仿真。在下面的 testbench 文件中,使用上一节的发送数据模块的输出来实现产生待测模块数据的输入。因此本节需要的仿真文件,只需串口发送模块的仿真文件基础上,修改端口信息、例化本模块以及将发送模块输出的 Rs232_Tx 连接到接收模块上的 Rs232_Rx 即可。

​ 修改后的仿真文件如下:

`timescale 1ns/1ns
`define clk_period 20

module uart_byte_rx_tb;


    reg Clk;
    reg Rst_n;
    reg [2:0]Baud_Set;

    //接收端信号
    wire [7:0]Data_Byte_r;
    wire Rx_Done;

    //发送端信号
    reg Send_En;
    reg [7:0]Data_Byte_t;

    wire Rs232_Tx;
    wire Tx_Done;
    wire UART_state;

//接收模块rx
    uart_byte_rx uart_byte_rx(
        .Clk(Clk),
        .Rst_n(Rst_n),
        .Baud_Set(Baud_Set),
        .Rs232_Rx(Rs232_Tx),    //"uart_byte_tx"模块发送的数据"Rs232_Tx",通过"uart_byte_rx"模块的"Rs232_Rx"端口接收

        .Data_Byte(Data_Byte_r),    //"Data_Byte_r"表示接收的信号
        .Rx_Done(Rx_Done)
    );

//发送模块tx
    uart_byte_tx uart_byte_tx(
        .Clk(Clk),
        .Rst_n(Rst_n),
        .Send_En(Send_En),
        .Data_Byte(Data_Byte_t),    //"Data_Byte_t"表示发送的信号
        .Baud_Set(Baud_Set),

        .Rs232_Tx(Rs232_Tx),
        .Tx_Done(Tx_Done),
        .UART_state(UART_state)
    );

//时钟激励源
    initial Clk = 1'b1;
    always#(`clk_period/2) Clk = ~Clk;

    initial begin
        //发送数据
        Rst_n = 1'b0;
        Data_Byte_t = 8'b0;
        Send_En = 1'b0;
        Baud_Set = 3'd4;

        #(`clk_period*20 + 1)   //让复位信号不与时钟边沿对其,更好观察时序关系
        Rst_n = 1'b1;

        #(`clk_period*50)
        Data_Byte_t = 8'haa;
        Send_En = 1'b1;
        #`clk_period
        Send_En = 1'd0;

        @(posedge Tx_Done)  //当Tx_Done信号出现上升沿时,进行接下来的语句

        #(`clk_period*5000)
        Data_Byte_t = 8'h55;
        Send_En = 1'b1;
        #`clk_period
        Send_En = 1'd0;

        @(posedge Tx_Done)
        #(`clk_period*5000)
        $stop;
    end

endmodule

9、建立顶层文件进行板级验证

建立一个顶层文件"uart_byte_rx_top.v",将串口接收模块与issp模块添加进去,代码如下:

module uart_byte_rx_top(
    Clk,
    Rst_n,
    Rs232_Rx
);

    input Clk;
    input Rst_n;
    input Rs232_Rx;

    reg [7:0]Data_rx_r;  //只有接收成功时(Rx_Done == 1'b1)才会把"Data_rx"更新到"Data_rx_r"中

    wire [7:0]Data_rx;
    wire Rx_Done;

    uart_byte_rx uart_byte_rx(
        .Clk(Clk),
        .Rst_n(Rst_n),
        .Baud_Set(3'd0),    //波特率为9600
        .Rs232_Rx(Rs232_Rx),

        .Data_Byte(Data_rx),
        .Rx_Done(Rx_Done)
    );

    issp issp(
        .probe(Data_rx_r),
        .source()
    );

    always@(posedge Clk or negedge Rst_n)
    if(!Rst_n)
        Data_rx_r <= 8'd0;
    else if(Rx_Done)
        Data_rx_r <= Data_rx;
    else
        Data_rx_r <= Data_rx_r;

endmodule

​ 与发送模块不同,这里使用的是 ISSP 的探针功能对本次数据接收模块进行板级调试与验证。按如下方式配置好以后,将其加入到工程中。

image-20240328160119791

​ 代码中,RX_Done 信号为接收完成信号,只有接收成功后才采集下一次数据,符合实际使用情况。

标签:采样,接收,FPGA,UART,Rx,模块,串口,Rst,Rs232
From: https://www.cnblogs.com/little55/p/18101963

相关文章

  • FPGA时序约束实战
    改编自8FPGA时序约束实战篇之主时钟约束_checktimingnoclock 以Vivado自带的wave_gen工程为例,该工程的各个模块功能较为明确,如下图所示。为了引入异步时钟域,我们在此程序上由增加了另一个时钟–clkin2,该时钟产生脉冲信号pulse,samp_gen中在pulse为高时才产生信号。下面我......
  • 实测52.4MB/s!全国产ARM+FPGA的CSI通信案例分享!
    CSI总线介绍与优势CSI(CMOSsensorparallelinterfaces)总线是一种用于连接图像传感器和处理器的并行通信接口,应用于工业自动化、能源电力、智慧医疗等领域,CSI总线接口示意图如下所示(以全志科技T3处理器的CSI0为例)。  图1高带宽:CSI总线支持高速数据传输,可以满足多通道高速......
  • FPGA原语
    ODDR代表的是双数据速率输出寄存器(OutputDoubleDataRateRegister)。这种原语用于在一个时钟周期内产生两个数据输出,通常用于高速数据传输和时钟数据恢复等应用。在以下示例VHDL代码中,ODDR原语被用来生成一个双数据速率的输出信号ad9653_1clk。ad1_clk:ODDRgenericmap(......
  • Xilinx ZYNQ 7000+Vivado2015.2系列(八)ARM+FPGA的优势,PS控制PL产生需要的PWM波(基于AXI
    上一节我们观察了AXI总线的信号,了解了基于AXI总线读写的时序,这一节我们继续探索基于AXI总线的设计,来看一看ZYNQ系列开发板的独特优势,PS可以控制PL产生定制化的行为,而不需要去动硬件代码。这次实验是产生频率和占空比可调的PWM(PulseWidthModulation)信号,调用8次,产生8路PWM......
  • 串口通信
    串口通信 在对串口进行编程时候,我们要向串口发送指令,然后我们解析串口返回的指令。从.NETFramework2.0开始,C#提供了SerialPort类用于实现串口控制。命名空间:System.IO.Ports。1、常用的字段:PortName:获取或设置通信端口BaudRate:获取或设置串行波特率Parity:获取或设置奇偶......
  • 基于FPGA实现的自适应三速以太网
    一、三速以太网千兆以太网PHY芯片是适配百兆和十兆的,十兆就不管了,我们的设计只适应千兆和百兆。根据上图,我们是可以获取当前主机网口的速率信息的。always@(posedgew_rxc_bufr)beginif(w_rec_valid=='d0)beginro_speed<=w_rec_data[2:1];......
  • FPGA学习DDR篇—MIG IP核使用
    文章目录一、MIGIP核配置详解1、第一页2、第二页3、第三页4、第四页5、第五页6、第六页7、第七页8、第八页9、第九页10、第十页二、MIG仿真一、MIGIP核配置详解1、第一页2、第二页3、第三页类型选择DDR34、第四页ClockPeriod:DDR3芯片运行的时钟速率,该数......
  • FPGA与以太网:概念知识
    参考:以太网详解(一)-MAC/PHY/MII/RMII/GMII/RGMII基本介绍-CSDN博客OSI七层模型、TCP/IP四层模型(超详细!!!!!)-CSDN博客TCP/IPLWIPFPGA笔记_rltcpnet和lwip-CSDN博客达芬奇Pro的以太网PHY芯片型号是YT8531(底板);TCP/IP四层模型TCP/IP(TransmissionControlProtocol/InternetProt......
  • cubemx使用dma实现Uart不定长数据接收
    CubeMx版本: volatileuint8_trecv_end_flag=0;volatileuint32_trx_len=0;volatileuint32_trx_len2=0;uint8_trx_buffer[BUFFER_SIZE]={0};uint8_trx_buffer2[BUFFER_SIZE]={0};voidUSART1_IRQHandler(void){/*USERCODEBEGINUSART1_IRQn0*/......
  • stm32串口使用dma接收数据全为0发送正常
    cubemx版本:keil版本:当使用cubeMX生成代码时,需要调整dma初始化和串口初始化的顺序,在3处那里调整,不然串口接收的数据全是0,未知原因,只找到办法......