【FPGA】深度理解串口接收模块设计与验证
我的学记|刘航宇的博客

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

刘航宇
3年前发布 /正在检测是否收录...
温馨提示:
本文最后更新于2022年12月20日,已超过794天没有更新,若内容或图片失效,请留言反馈。

本章导读

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

串口接收原理分析

上一节中学习了串口发送模块的设计与实现,其 UART 发送端发送一个字节数据时序图如图 17.1 所示。
图片[1] - 【FPGA】深度理解串口接收模块设计与验证 - 我的学记|刘航宇的博客
这一讲介绍串口接收模块的设计与实现。当对于数据线 Rs232_Rx 上的每一位进行采样时,一般情况下认为每一位数据的中间点是最稳定的。因此一般应用中,采集中间时刻时的电平即认为是此位数据的电平,如图 17.2 所示。
图片[2] - 【FPGA】深度理解串口接收模块设计与验证 - 我的学记|刘航宇的博客
但是在实际工业应用中,现场往往有非常强的电磁干扰,只采样一次就作为该数据的电平状态是不可靠的。很有可能恰好采集到被干扰的信号而导致结果出错,因此这里提出以下改进型的单 bit 数据接收方式示意图,使用多次采样求概率的方式进行状态判定,如图 17.3所示。
图片[3] - 【FPGA】深度理解串口接收模块设计与验证 - 我的学记|刘航宇的博客

    图 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 所示。
图片[4] - 【FPGA】深度理解串口接收模块设计与验证 - 我的学记|刘航宇的博客
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。
图片[5] - 【FPGA】深度理解串口接收模块设计与验证 - 我的学记|刘航宇的博客
这里依旧使用一个选择器,来实现不同波特率与采样时钟分频计数值之间的对应关系。设计代码如下所示。

 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 仅声明了但并未调用,因此无数据显示,可以直接删除。
图片[6] - 【FPGA】深度理解串口接收模块设计与验证 - 我的学记|刘航宇的博客
与上一章不同,这里使用 ISSP 的探针功能,对本次数据接收模块进行板级调试与验证。其主要配置如图 17.6 所示,并加入到工程中。
图片[7] - 【FPGA】深度理解串口接收模块设计与验证 - 我的学记|刘航宇的博客
新建一个顶层文件 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 停止位。
图片[8] - 【FPGA】深度理解串口接收模块设计与验证 - 我的学记|刘航宇的博客

      图 17.8 ISSP 工具设置界面

在串口助手上先后输入 aa、38 后在 ISSP 使用界面可以看到 Data 会随之对应变化。可知设计无误。
图片[9] - 【FPGA】深度理解串口接收模块设计与验证 - 我的学记|刘航宇的博客
本章学习了串口接收的相关原理,在设计过程中针对工业现场的强电磁干扰等问题,提出了一种基于权重的改进型数据接收方式。并在板级调试中使用了 ISSP 中的探针功能(probe)。
在本章实验的基础上,可以将接收到的数据在 4 位 LED 或者数码管上进行更直观的显示。

代码工程

© 版权声明
THE END
喜欢就支持一下吧
点赞 0 分享 赞赏
评论 抢沙发
取消