侧边栏壁纸
    • 累计撰写 296 篇文章
    • 累计收到 520 条评论
    【FPGA】深度理解串口接收模块设计与验证
    我的学记|刘航宇的博客

    【FPGA】深度理解串口接收模块设计与验证

    刘航宇
    2022-12-20 / 0 评论 / 250 阅读 / 正在检测是否收录...

    本章导读

    本章将学习 UART 的数据接收设计部分,针对实际使用中强电磁干扰可能会对数据的影响,本节提出一种改进型的串口接收模块设计方式。除了进行常规的功能仿真以外依旧使用 ISSP 工具进行板级测试。

    串口接收原理分析

    上一节中学习了串口发送模块的设计与实现,其 UART 发送端发送一个字节数据时序图如图 17.1 所示。

    这一讲介绍串口接收模块的设计与实现。当对于数据线 Rs232_Rx 上的每一位进行采样时,一般情况下认为每一位数据的中间点是最稳定的。因此一般应用中,采集中间时刻时的电平即认为是此位数据的电平,如图 17.2 所示。

    但是在实际工业应用中,现场往往有非常强的电磁干扰,只采样一次就作为该数据的电平状态是不可靠的。很有可能恰好采集到被干扰的信号而导致结果出错,因此这里提出以下改进型的单 bit 数据接收方式示意图,使用多次采样求概率的方式进行状态判定,如图 17.3所示。

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

    在图 17.3 中,将每一位数据再平均分成了 16 小段。对于 Bit_x 这一位数据,考虑到数据在刚刚发生变化和即将发生变化的这一时期,数据极有可能不稳定的(用深灰色标出的两段),在这两个时间段采集数据,很有可能得到错误的结果,因此判定这两段时间的电平无效,采集时直接忽略。而中间这一时间段(用浅灰色标出),数据本身是比较稳定的,一般都代表了正确的结果。也就是前面提到的中间测量方式,但是也不排除该段数据受强电磁干扰而出现错误的电平脉冲。因此对这一段电平,进行多次采样,并求高低电平发生的概率,6 次采集结果中,取出现次数多的电平作为采样结果。例如,采样 6 次的结果分别为1/1/1/1/0/1/,则取电平结果为 1,若为 0/0/1/0/0/0,,则取电平结果为 0,当 6 次采样结果中 1和 0 各占一半(各 3 次),则可判断当前通信线路环境非常恶劣,数据不具有可靠性,不进
    行处理。同理7次采集可以多数决定少数。

    UART 异步串行通信接收模块设计与实现

    串口接收模块接口设计

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

    RS232 串行输入信号同步设计

    这里输入数据相对于系统时钟是个异步信号,因此也需要对其进行同步,这里的处理方式与按键的输入部分一样,不再赘述。

    reg s0_Rs232_Rx,s1_Rs232_Rx; //同步寄存器
    reg tmp0_Rs232_Rx,tmp1_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
     s0_Rs232_Rx <= Rs232_Rx;
      s1_Rs232_Rx <= s0_Rs232_Rx;
    end
    //数据寄存器
    always@(posedge Clk or negedge Rst_n)
     if(!Rst_n)begin
     tmp0_Rs232_Rx <= 1'b0;
     tmp1_Rs232_Rx <= 1'b0; 
     end
     else begin
     tmp0_Rs232_Rx <= s1_Rs232_Rx;
     tmp1_Rs232_Rx <= tmp0_Rs232_Rx;
     end
     
     assign nedege = !tmp0_Rs232_Rx && tmp1_Rs232_Rx;

    采样时钟生成模块设计

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

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

     reg [15:0]bps_DR;
     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 倍。

     reg [15:0]div_cnt;
     reg bps_clk; 
     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;
     
     always@(posedge Clk or negedge Rst_n)
     if(!Rst_n)
     bps_clk <= 1'b0;
     else if(div_cnt == 16'd1)
     bps_clk <= 1'b1;
     else
     bps_clk <= 1'b0;

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

     reg [7:0]bps_cnt;
     always@(posedge Clk or negedge Rst_n)
     if(!Rst_n) 
     bps_cnt <= 8'd0;
     else if(bps_cnt == 8'd159 || (bps_cnt == 8'd12 && (START_BIT >
    2)))
     bps_cnt <= 8'd0;
     else if(bps_clk)
     bps_cnt <= bps_cnt + 1'b1;
     else
     bps_cnt <= bps_cnt;
     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;

    采样数据接收模块设计

    以图 17.3 起始位为例,位于中间的采样时间段对应的 bps_cnt 值分别为 6、7、8、9、10、11,在这些时刻直接累加本位数据。然后,后一位数据的采样时间段的第一个 bps_cnt值为前一位数据采样时间段的第一个 bps_cnt 值加 16。所以,起始位后面紧跟的第一个数据位 Bit0 的采样时间段的 bps_cnt 值分别是 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
     case(bps_cnt)
     0: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
     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:
     begin
     START_BIT <= START_BIT;
     r_data_byte[0] <= r_data_byte[0];
     r_data_byte[1] <= r_data_byte[1];
     r_data_byte[2] <= r_data_byte[2];
     r_data_byte[3] <= r_data_byte[3];
     r_data_byte[4] <= r_data_byte[4];
     r_data_byte[5] <= r_data_byte[5];
     r_data_byte[6] <= r_data_byte[6];
     r_data_byte[7] <= r_data_byte[7];
     STOP_BIT <= STOP_BIT; 
     end
     endcase
     end

    数据状态判定模块设计

    在原理部分介绍过,对一位数据需进行 6 次采样,然后取出现次数较多的数据作为采样结果,也就是说,6 次采样中出现次数多于 3 次的数据才能作为最终的有效数据。对此,可以用接收到数据 r_data_byte[n]结合数值比较器来判断,也可以直接令其等于当前位的最高位数据。以下面例子说明:当 r_data_byte[n]分别为二进制的 011B/010B/100B/101B时,这几个数据十进制格式分别为 3d/2d/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];
     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

    仿真及板级验证

    完成设计之后,对其进行功能仿真。在下面的 testbench 文件中,这里产生数据的激励输入使用上一章的发送数据模块的输出来实现,因此激励文件只需在上一章的激励文件中,修改端口信息、例化本模块以及将发送模块输出的 Rs232_Tx 连接到接收模块上的 Rs232_Rx即可。修改后的部分激励文件如下:

     wire Rs232_Tx;
     
     uart_byte_rx uart_byte_rx(
     .Clk(Clk),
     .Rst_n(Rst_n),
     .baud_set(baud_set),
     .Rs232_Rx(Rs232_Tx),
     
     .data_byte(data_byte_r),
     .Rx_Done(Rx_Done)
     );
     
     uart_byte_tx uart_byte_tx(
     .Clk(Clk),
     .Rst_n(Rst_n),
     .data_byte(data_byte_t),
     .send_en(send_en),
     .baud_set(baud_set),
     
     .Rs232_Tx(Rs232_Tx),
     .Tx_Done(Tx_Done),
     .uart_state(uart_state)
     );

    设置好仿真脚本后进行功能仿真,可以看到如图 17.5 所示的波形文件,每当一个字节发送结束后,数据输出 data_byte_r 均会更新输出一次。下图中由于 Rs232_Rx 仅声明了但并未调用,因此无数据显示,可以直接删除。

    与上一章不同,这里使用 ISSP 的探针功能,对本次数据接收模块进行板级调试与验证。其主要配置如图 17.6 所示,并加入到工程中。

    新建一个顶层文件 uart_rx_top.v,这里例化数据接收模块以及 ISSP 工具。只有接收成功后才采集下一次数据,符合实际使用情况。

     module uart_rx_top(Clk,Rst_n,Rs232_Rx);
     input Clk;
     input Rst_n;
     input Rs232_Rx;
     
     reg [7:0]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),
     .Rs232_Rx(Rs232_Rx),
     
     .data_byte(data_rx),
     .Rx_Done(Rx_Done)
     );
     
     issp issp(
     .probe(data_rx_r)
     ); 
     
     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

    分配引脚并全编译无误后下载工程到开发板中。在 Quartus Prime 中点击 Tools→InSystem Source and Probes Editor 启动 ISSP,手动选择下载器后,并将数据格式改为设计中的 hex 格式,持续触发模式。打开电脑上的串口助手,将主要参数设置为:波特率为 9600、无校验位、8 位数据位以及 1bit 停止位。

          图 17.8 ISSP 工具设置界面

    在串口助手上先后输入 aa、38 后在 ISSP 使用界面可以看到 Data 会随之对应变化。可知设计无误。

    本章学习了串口接收的相关原理,在设计过程中针对工业现场的强电磁干扰等问题,提出了一种基于权重的改进型数据接收方式。并在板级调试中使用了 ISSP 中的探针功能(probe)。
    在本章实验的基础上,可以将接收到的数据在 4 位 LED 或者数码管上进行更直观的显示。

    代码工程

    0
    【FPGA】【SPI】线性序列机与串行接口 ADC 驱动设计与验证
    « 上一篇 2022-12-25
    【FPGA】深度理解串口发送模块设计与验证
    下一篇 » 2022-12-18

    评论 (0)

    取消