FPGA开发。串口通信(串口接收,其中也包括边沿检测部分)。
串口接受原理与思路
当对于数据线上的每一位进行采样时,一般情况下认为每一位数据的中间点是最稳定的。因此一般应用中,采集中间时刻时的电平即认为是此位数据的电平,如下图所示。
但是在实际工业应用中,现场往往有非常强的电磁干扰,只采样一次就作为该数据的电平状态是不可靠的。很有可能恰好采集到被干扰的信号而导致结果出错,因此这里提出以下改进型的单 bit 数据接收方式示意图,使用多次采样求概率的方式进行状态判定,如下图所示.
在上图中,将每一位数据再平均分成了 16 小段。对于 Bit_x 这一位数据,考虑到数据在刚刚发生变化和即将发生变化的这一时期,数据极有可能不稳定的(用深灰色标出的两段),在这两个时间段采集数据,很有可能得到错误的结果,因此判定这两段时间的电平无效,采集时直接忽略。而中间这一时间段(用浅灰色标出),数据本身是比较稳定的,一般都代表了正确的结果。也就是前面提到的中间测量方式,但是也不排除该段数据受强电磁干扰而出现错误的电平脉冲。因此对这一段电平,进行多次采样,并求高低电平发生的概率,6 次采集结果中,取出现次数多的电平作为采样结果。例如,采样 6 次的结果分别为1/1/1/1/0/1/,则取电平结果为 1,若为 0/0/1/0/0/0,,则取电平结果为 0,当 6 次采样结果中 1和 0 各占一半(各 3 次),则可判断当前通信线路环境非常恶劣,数据不具有可靠性,不进行处理。
基本原理:采用
技巧:一位数据采多次,统计得到高电平出现的次数,次数多的就是该位的电平值。 采样8次,0、1、2、3低电平,4、5、6、7为高电平。
起始位检测:通过边沿检测电路
边沿检测
什么是边沿检测
边沿检测用于检测信号的上升沿或下降沿,通常用于使能信号的捕捉等场景。(如串口通信判断何时接受传输的数据)
采用1级触发器的边沿检测电路设计(以下降沿为例)
波形:
信号说明:
- sys_clk:基准时钟信号(这里设定为50MHz,周期20ns)
- sys_rst_n:低电平有效的复位信号
- in:输入信号,需要对其进行下降沿检测
- ~in:输入信号的反相信号
- in_d1:对输入信号寄存一拍
- in_neg:得到的下降沿指示信号,该信号为 ind1 && ~in
对上图进行分析:
- 信号in是我们需要对其进行下降沿检测的信号
- 信号~in是将信号in反向
- 信号in_d1是使用寄存器寄存in信号,即对其进行打拍,或者说是同步到系统时钟域下
- 输入信号开始为高电平,在L2处变为低电平,产生第1个下降沿,在L5出产生第2个下降沿
- A处为产生的第1个下降沿指示信号,B处为产生的第2个下降沿指示信号
由此我们可以推导出边沿检测信号产生的一般方法:
- 将需要检测的信号寄存一拍,同步到系统时钟域下,得到信号 in_d1
- 将需要检测的信号反向,得到信号 ~in
- 将信号 in _ d1 反向,得到信号 ~in _ d1
- 通过组合逻辑电路可以得到下降沿信号 in _ neg:assign in _ neg = ~in && in _ d1
- 同样通过组合逻辑电路可以得到上升沿信号 in_pos:assign in_pos = in && ~in _ d1
- 双边沿检测就是将上两条加(或运算)起来就可以了,化简后有:双边沿信号 in_both = in ^ ind1
Veriog:
//使用1级寄存器的下降沿检测电路
module detect_1
(
input sys_clk, //时钟(设定为 50MHz)
input sys_rst_n, //复位信号(n 表示低电平有效)
input in, //需要进行下降沿检测的输入信号
output in_neg //输出的下降沿指示信号
);
//reg 定义
reg in_d1; //寄存一拍的信号
assign in_neg = ~in && in_d1; //组合逻辑得到下降沿
//上升沿: assign in_pos = in && ~in_d1;
//双边沿: assign in_pos = in ^ in_d1;
//寄存模块,将输入信号打一拍
always@(posedge sys_clk or negedge sys_rst_n)begin
if(!sys_rst_n)
in_d1 <= 1'b0; //复位清零
else
in_d1 <= in; //寄存一拍
end
endmodule
结合代码理解为何能够检测:输入为in,in _ d1为非阻塞赋值,所以要落后in一个时钟周期;那么取in _d1的反向信号然后与in_d1相与即可获得一个落后in一个时钟周期的边沿检测信号。
采用2级触发器的边沿检测电路(以下降沿为例)
波形:
各信号说明如下:
- sys_clk:基准时钟信号(这里设定为50MHz,周期20ns)
- sys_rst_n:低电平有效的复位信号
- in:输入信号,需要对其进行下降沿检测
- in_d1:对输入信号寄存1拍
- in_d2:对输入信号寄存2拍
- ~in_d1:in_d1信号的反相信号
- in_neg:得到的下降沿指示信号,该信号为 ~ind1 && ind2
对上图进行分析:
- 信号in是我们需要对其进行下降沿检测的信号
- 信号in_d1是使用寄存器寄存in信号,即对其打1拍
- 信号in_d2是使用寄存器寄存in_d1信号,即对其打1拍
- 信号~in_d1是将信号in_d1反向
- 输入信号开始为高电平,在L2处变为低电平,产生第1个下降沿,在L5出产生第2个下降沿
- A处为产生的第1个下降沿指示信号,B处为产生的第2个下降沿指示信号
- 输出的下降沿指示信号落后下降沿一个时钟周期,这是因为对输入信号进行了寄存以消除亚稳态
由此我们可以推导出边沿检测信号产生的一般方法:
- 将需要检测的信号分别寄存1拍、2拍,同步到系统时钟域下,得到信号 in _ d1、in _ d2
- 将in_d1信号反向,得到信号 ~in _ d1
- 将in_d2信号反向,得到信号 ~in _ d2
- 通过组合逻辑电路可以得到下降沿信号 in _ neg:assign in _ neg = ~in _ d1 && in _ d2
- 同样通过组合逻辑电路可以得到上升沿信号 in _ pos:assign in _ pos = in _ d1 && ~in _ d2
- 双边沿检测就是将上两条加(或运算)起来就可以了,化简后有:双边沿信号 in _ both = in _ d1 ^ in _ d2
Verilog:
//使用2级寄存器的下降沿检测电路
module detect_2
(
input sys_clk, //时钟(设定为 50MHz)
input sys_rst_n, //复位信号(n 表示低电平有效)
input in, //需要进行下降沿检测的输入信号
output in_neg //输出的下降沿指示信号
);
//reg 定义
reg in_d1; //寄存1拍的信号
reg in_d2; //寄存2拍的信号
assign in_neg = ~in_d1 && in_d2;//组合逻辑得到下降沿
//上升沿: assign in_pos = in && ~in_d1;
//双边沿: assign in_pos = in ^ in_d1;
//寄存模块,将输入信号打1拍、打2拍
always@(posedge sys_clk or negedge sys_rst_n)begin
if(!sys_rst_n)begin
in_d1 <= 1'b0; //复位清零
in_d2 <= 1'b0;
end
else begin
in_d1 <= in; //寄存1拍
in_d2 <= in_d1; //寄存2拍
end
end
endmodule
使用两个寄存器,两者都是非阻塞赋值,则第一个寄存器落后一个时钟周期,另一个寄存器落后两个时钟周期。对第一个寄存器取反,则得到落后于输入信号一个时钟周期的相反信号。将该信号与寄存器2相与则得到一个落后一个时钟周期的相反信号与一个落后两个时钟周期的信号的与信号,即处于落后一个时钟周期与第二个时钟周期之间的信号。
串口接收程序
module uart_rx( //定义串口接收模块。
input Clk, //定义系统时钟信号。
input Reset_n, //定义复位。
input uart_RXD, //定义串口接收信号。
output reg [7:0]uart_Data, //定义接收数据输出。
output reg Rx_done //定义接收完成使能信号。
);
reg [1:0]state_RXD; //定义一个两位寄存器来储存此时刻和上一时刻的接收信号。
wire nedge_RXD; //定义接收信号下降沿寄存器。
reg uart_EN; //定义接收使能。
reg [8:0]cnt_Scan; //定义9位寄存器来计数每一位的16分之1的时间。
parameter Bps_DR = 1_000_000_000 / 115200 / 16 / 20 - 1; //定义每16/1所需时钟周期。
reg [7:0]cnt_state; //定义8位寄存器来记录一共160个状态。
localparam MAX = 159; //定义总共多少状态。localparam代表不能在上层模块中修改。
reg [2:0]state_Start; //定义起始信号寄存器。
reg [2:0]state_Data[7:0]; //定义数据信号寄存器,代表7个3位的寄存器。
reg [2:0]state_Stop; //定义结束信号。
reg state_done; //定义16/1位接收完成使能信号。
always @(posedge Clk or negedge Reset_n) begin //定义数据信号此时刻和上一时刻接收模块。
if (!Reset_n) //默认状态为0.
state_RXD <= 2'd0;
else //记录状态到寄存器里。
state_RXD <= {state_RXD[0], uart_RXD};
end
assign nedge_RXD = (state_RXD == 2'b10); //判断是否为下降沿信号。
always @(posedge Clk or negedge Reset_n) begin //定义接收使能模块。
if (!Reset_n) //复位使能无效。
uart_EN <= 1'b0;
else if (nedge_RXD) //只有当下降沿,也就是起始位被检测到使能接收。
uart_EN <= 1'b1;
else if (cnt_state == MAX || state_Start[2]) //当160个状态计满或者起始信号为假时,后面会讲到,起始信号为真时,state_Start[2]为0。
uart_EN <= 0;
else //其他状态使能信号不变。
uart_EN <= uart_EN;
end
always @(posedge Clk or negedge Reset_n) begin //定义每16/1为计数器模块。
if (!Reset_n) //复位清零。
cnt_Scan <= 9'd0;
else if (uart_EN) begin //使能为有效时。
if (Bps_DR <= cnt_Scan) //计满归零。
cnt_Scan <= 9'd0;
else //否则加一。
cnt_Scan <= cnt_Scan + 1'b1;
end
else //使能失效清零。
cnt_Scan <= 9'd0;
end
always @(posedge Clk or negedge Reset_n) begin //160为状态计数器模块。
if (!Reset_n) //复位清零。
cnt_state <= 8'd0;
else if (uart_EN) begin //使能有效。
if (state_done) begin //当16/1为接收使能有效。
if (MAX <= cnt_state) //计满清零。
cnt_state <= 8'd0;
else //否则加一。
cnt_state <= cnt_state + 1'b1;
end
else //其他状态不变。
cnt_state <= cnt_state;
end
else //使能失效清零。
cnt_state <= 8'd0;
end
always @(posedge Clk or negedge Reset_n) begin //数据接收模块。
if (!Reset_n) begin //复位清零。
state_Start <= 3'd0;
state_Data[0] <= 3'd0; //定义成[x:0]a[y:0]这样的数据只能一个一个赋值,不能一起赋值。
state_Data[1] <= 3'd0;
state_Data[2] <= 3'd0;
state_Data[3] <= 3'd0;
state_Data[4] <= 3'd0;
state_Data[5] <= 3'd0;
state_Data[6] <= 3'd0;
state_Data[7] <= 3'd0;
state_Stop <= 3'd0;
end
else if (uart_EN) begin //接收使能有效。
case (cnt_state) //更据状态赋值。
5,6,7,8,9,10,11:state_Start <= state_Start + uart_RXD; //因为接收信号只有0或1,所以只需要每次累加,取中间七次,当数据大于4时,便为高电平,否则为低电平,下同。
21,22,23,24,25,26,27:state_Data[0] <= state_Data[0] + uart_RXD;
37,38,39,40,41,42,43:state_Data[1] <= state_Data[1] + uart_RXD;
53,54,55,56,57,58,59:state_Data[2] <= state_Data[2] + uart_RXD;
69,70,71,72,73,74,75:state_Data[3] <= state_Data[3] + uart_RXD;
85,86,87,88,89,90,81:state_Data[4] <= state_Data[4] + uart_RXD;
101,102,103,104,105,106,107:state_Data[5] <= state_Data[5] + uart_RXD;
117,118,119,120,121,122,123:state_Data[6] <= state_Data[6] + uart_RXD;
133,134,135,136,137,138,139:state_Data[7] <= state_Data[7] + uart_RXD;
149,150,151,152,153,154,155:state_Stop <= state_Stop + uart_RXD;
endcase
end
else begin //使能失效清零。
state_Start <= 3'd0;
state_Data[0] <= 3'd0;
state_Data[1] <= 3'd0;
state_Data[2] <= 3'd0;
state_Data[3] <= 3'd0;
state_Data[4] <= 3'd0;
state_Data[5] <= 3'd0;
state_Data[6] <= 3'd0;
state_Data[7] <= 3'd0;
state_Stop <= 3'd0;
end
end
always @(posedge Clk or negedge Reset_n) begin //数据读取转换模块。
if (!Reset_n) //复位清零。
uart_Data <= 8'd0;
else if (cnt_state == MAX) begin //将每一位一位数据读取出来,当数据大于4时,数据第三位为1,当数据小于4时,第三位为0,所以我们可以直接用第三位的值来判断读取的值。
uart_Data[0] <= state_Data[0][2];
uart_Data[1] <= state_Data[1][2];
uart_Data[2] <= state_Data[2][2];
uart_Data[3] <= state_Data[3][2];
uart_Data[4] <= state_Data[4][2];
uart_Data[5] <= state_Data[5][2];
uart_Data[6] <= state_Data[6][2];
uart_Data[7] <= state_Data[7][2];
end
else //其他状态数据不变。
uart_Data <= uart_Data;
end
always @(posedge Clk or negedge Reset_n) begin //16/1位完成使能信号。
if (!Reset_n) //复位清零。
state_done <= 1'b0;
else if (cnt_Scan == Bps_DR) //当每16/1计数器计满时使能信号。
state_done <= 1'b1;
else //否则使能失效。
state_done <= 1'b0;
end
always @(posedge Clk or negedge Reset_n) begin //接收完成使能信号模块。
if (!Reset_n) //复位清零。
Rx_done <= 1'b0;
else if (cnt_state == MAX) //当160个状态计满时使能接收完成信号。
Rx_done <= 1'b1;
else //其他时刻使能信号失效。
Rx_done <= 1'b0;
end
endmodule