官方论坛
官方淘宝
官方博客
微信公众号
点击联系吴工 点击联系周老师
您的当前位置:主页-old > 新闻中心 > FPGA技术教程 > 边缘检测 >

边缘检测工程:串口接收模块代码解析(附参考代码)-明德扬科教(mdy-edu.com)

发布时间:2019-12-10   作者:admin 浏览量:



本文为明德扬原创文章,转载请注明出处!

串口接收模块的功能:接收上位机通过串口发送过来的数据,进行串并转换之后送给下游模块。

      注:串口波特率9600,无奇偶校验位。

一、设计架构

上图是与上位机通信的串口的时序图。我们从图中可以获取到如下关键信息。

1. 串口数据线位宽为1bit,默认状态下为高电平。

2. 每次上游模块发送数据,都是先发送1位的起始位0,然后发送8位的数据,最后是1位的停止位1。

3. 1位所占的时间,可以通过波特率来计算。计算方法如下:

      波特率是指1s内发送接受了多少比特数据,若波特率为9600 bit/s,则1s可以传输9600bit,那么传输1bit的时间为:1/9600(s)。以mp801开发板为例,时钟频率为50M,那么发送1bit就需要5208个时钟周期。

串口接收模块采用两个计数器的架构,这两个计数器分别表示接收1bit需要的时间和共需要接收多少bit。其结构图如下

      计数器cnt0:对接收1bit数据需要的时间进行计数接收1bit需要5208个时钟周期。该计数器的计数周期为5208。

      计数器cnt1:对接收多少bit的数据进行计数停止位不参与,起始位加上数据位共9bit。该计数器的计数周期为9


      本工程使用了检测信号下降沿的方法,信号下降沿的检测方法:

检查uart_rx的下降沿,就要用到FPGA里的边沿检测技术。所谓的边沿检测,就是检测输入信号,或者FPGA内部逻辑信号的跳变,即上升沿或者下降沿的检测。就比如前面uart_rx由1变0时,就出现了下降沿,接着一次指令结束,uart_rx由0变1时,就出现了上升沿,边沿检测技术这在FPGA电路设计泛。电路图如下:




对应的信号列表如下图所示:




      中间信号,trigger连到触发器的信号输入端D,触发器的输出器连的是tri_ff0。将trigger取反,与tri_ff0相与,就得到信号neg_edge,如果neg_edge=1就表示检测到trigger的下降沿。将tri_ff0取反,与trigger相与,就得到信号pos_edge,如果pos_edge=1,就表示检测到trigger的上升沿。
我们来讲解这个原理,信号的波形图如下:


Tri_ff0是触发器的输出,因此tri_ff0的信号与trigger信号相似,但是相差了一个时钟周期。我们也可以理解为:每个时钟上升沿看到的tri_ff0的值,其实就是triffer信号上一个时钟看到的值,也就是tri_ff0是trigger之前的值。
我们在看第3时钟上升沿,此时trigger值为0,而tri_ff0的值为1,即当前trigger的值为0,之前的值为1,这就是下降沿,此时neg_edge为1。当看到neg_edge为1,就表示检测到trigger的下降沿了。同理在第7个时钟上升沿,看到trigger值为1,而之前值为0,pos_edge为1,表示检测到trigger的上升沿。

      本模块输入信号uart_rx是异步信号,异步信号都需要经验同步化后,才能够使用。异步信号同步化如下:
在前面讨论边沿检测的波形中,我们把trigger当做理想的同步信号来考虑,也就是trigger满足D触发器的建立和保持时间,在同步系统中实现边沿检测不是问题。但如果trigger不是理想的同步信号,例如外部按键信号,以及本工程的uart_rx信号。这些信号什么时候产生变化,是外部传输指令给FPGA,对于FPGA来说完全是随机的。很有可能出现,信号在时钟上升沿产生变化的情况,从而无法满足触发器的建立时间和保持时间要求,出现亚稳态的情况,从而导致系统崩溃。如下图,我们先将信号用2个触发器进行了寄存,确定了信号的稳定性,然后再进行边沿检测,达成了同步系统中实现边沿检测的需求。


       那么边沿检测的代码设计就需要先进行触发器的设计,假设输入的信号trigger不是同步信号,要将该信号用2个触发器进行寄存,得到tri_ff0和tri_ff1。需要特别注意的是,在第一个触发器阶段,信号依旧有亚稳态的情况,因此tri_ff0绝对不可以拿来当条件使用,只能使用tri_ff1。接着进行检测边沿,根据前面所说,得出同步信号后再用寄存器寄存,得到tri_ff2。根据tri_ff1和tri_ff2,我们就可以得到边沿检测结果。当tri_ff1==1且tri_ff2==0时,上升沿的pos_edge有效;当tri_ff1==0且tri_ff2==1时,下降沿的neg_edge有效,代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
1

always  @(posedgeclk or negedgerst_n)begin
    if(rst_n==1'b0)begin
        tri_ff0 <= 0;
        tri_ff1 <= 0;
        tri_ff2 <= 0;
    end
    else begin
        tri_ff0 <= trigger ;
        tri_ff1 <= tri_ff0 ;
        tri_ff2 <= tri_ff1 ;
    end
end
[size=9.0000pt]
assign neg_edge = tri_ff1==0 && tri_ff2==1;
assign pos_edge = tri_ff1==1 && tri_ff2==0;

综上所述,如果进来的信号是异步信号,那么就需要先进行同步化,再做检测,即通过打两拍的方式,实现了信号的同步化;再通过打一拍的方式,实现边沿检测电路。反之,如果进来的信号本身就是同步信号,那就没有必要做同步化了,可以直接做边沿检测。


二、信号的意义

信号
类型
意义
clk
输入信号
时钟信号。
rst_n
输入信号
复位信号,低电平有效。
uart_rx
输入信号


串口接收数据线,位宽为1bit,空闲时为高电平,开始接收时,

会变为低电平,持续1/9600(s),然后是数据、停止位等。


rx_data
输出信号
从串口中接收到的1字节数据。
rx_vld
输出信号


串口接收数据有效指示信号。当其为高电平时,对应的输出rx_data有效,

表示接收到1个字节的数据。注意,1个时钟的高电平表示接收到1个字节数据。


uart_ff0
内部信号
输入进来的uart_rx信号寄存一拍后的信号。
该信号的目的是为了做时序同步。
uart_ff1
内部信号


对uart_ff0寄存一拍后的信号。

该信号的目的是为了做时序同步。该信号就是同步化后的,

可以使用的信号。


uart_ff2
内部信号
uart_ff1寄存一拍的信号。
该信号的目的,是与uart_ff1配合,检测uart_ff1的下降沿。
flag_add
内部信号


模块处于接收数据状态的指示信号,当其为1时,

表示正在接收数据;为0时,表示空闲状态。

产生逻辑是:检测到uart_ff1的下降沿时变高

当接收完整个字节数据时(计数器cnt1数完了就变低。


cnt0
内部信号


对每输入1bit数据的时间进行计数,接收1bit需要5208个时钟周期。

该计数器的计数周期为5208。


add_cnt0
内部信号
计数器cnt0计数有效信号。当处于处于接收状态时,该信号有效。
end_cnt0
内部信号


计数器cnt0的结束条件接收1bit需要5208个时钟周期,

所以数到5208个就结束。


cnt1
内部信号


对接收多少bit的数据进行计数,停止位不参与,

起始位加上数据位共9bit。该计数器的计数周期为9。


add_cnt1
内部信号
计数器cnt1的加一条件。接收完1位(end_cnt0),就有效。
end_cnt1
内部信号

计数器cnt1的结束条件,共接收9bit数据,

所以数到9个就结束。

add_en
内部信号


uart_ff1下降沿有效指示信号

当检测到接收的数据前一时刻uart_ff2为高电平,

当前时刻uart_ff1为低电平时,就有效




三、参考代码

下面展出本模块的设计,欢迎进一步交流,如果需要整个项目源代码,欢迎与明德扬联系。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
module uart_rx(
    clk     ,
    rst_n   ,
    uart_rx ,
    rx_vld  ,
    rx_data
    );
[size=9.0000pt]

    parameter      CNT_BTL =     20'd2604;
    parameter      CNT_MID =     20'd1302;
    parameter      CNT_RX =         4'd9;
    parameter      DATA_W =         8;
[size=9.0000pt]
    input               clk             ;
    input               rst_n           ;
    input               uart_rx         ;
[size=9.0000pt]
    wire                uart_rx         ;
    output[DATA_W-1:0]  rx_data         ;
    output               rx_vld         ;
[size=9.0000pt]
    reg   [DATA_W-1:0]  rx_data         ;
    reg                 rx_vld          ;
[size=9.0000pt]
    reg                 uart_rx_ff0     ;
    reg                 uart_rx_ff1     ;
    reg                 uart_rx_ff2     ;
    reg                 flag_add_add            ;
    reg   [19:0]        cnt0            ;
    wire                add_cnt0        ;
    wire                end_cnt0        ;
[size=9.0000pt]
    reg   [3:0]         cnt1            ;
    wire                add_cnt1        ;
    wire                end_cnt1        ;
    wire                add_en          ;
[size=9.0000pt]
    always @(posedge clk or negedge rst_n)begin
        if(!rst_n)begin
            cnt0 <= 0;
        end
        else if(add_cnt0)begin
            if(end_cnt0)
                cnt0 <= 0;
            else
                cnt0 <= cnt0 + 1;
        end
    end
[size=9.0000pt]
    assign add_cnt0 = flag_add[size=9.0000pt];
    assign end_cnt0 = add_cnt0 && cnt0== CNT_BTL-1;
[size=9.0000pt]
    always @(posedge clk or negedge rst_n)begin
        if(!rst_n)begin
            cnt1 <= 0;
        end
        else if(add_cnt1)begin
            if(end_cnt1)
                cnt1 <= 0;
            else
                cnt1 <= cnt1 + 1;
        end
    end
[size=9.0000pt]
    assign add_cnt1 = end_cnt0;
    assign end_cnt1 = add_cnt1 && cnt1== CNT_RX-1;
[size=9.0000pt]
    always  @(posedge clk or negedge rst_n)begin
        if(rst_n==1'b0)begin
            uart_rx_ff0  <= 1'b1;
            uart_rx_ff1 <= 1'b1;
            uart_rx_ff2 <= 1'b1;
        end
        else begin
            uart_rx_ff0  <= uart_rx;
            uart_rx_ff1 <= uart_rx_ff0;
            uart_rx_ff2 <= uart_rx_ff1;
        end
    end
    assign  add_en = uart_rx_ff2&&~uart_rx_ff1;

    always  @(posedge clk or negedge rst_n)begin
        if(rst_n==1'b0)begin
            flag_add <= 1'b0;
        end
        else if(add_en)begin
            flag_add <= 1'b1;
        end
        else if(end_cnt1)begin
            flag_add <= 1'b0;
        end
    end

    always  @(posedge clk or negedge rst_n)begin
        if(rst_n==1'b0)begin
            rx_vld <= 1'b0;
        end
        else if(end_cnt1)begin
            rx_vld <= 1'b1;
        end
        else begin
            rx_vld <= 1'b0;
        end
    end

    always  @(posedge clk or negedge rst_n)begin
        if(rst_n==1'b0)begin
            rx_data <= 8'b0;
        end
        else if(cnt1!=0&&add_cnt0&&cnt0==CNT_MID-1)begin
            rx_data[cnt1-1] <= uart_rx_r2;
        end
    end

endmodule


以上就是基于FPGA边缘检测工程串口接收模块的代码分享,关注明德扬获取边缘检测工程完整的源代码!


   拓展阅读