文章目录
任务前言
1.要点1
对于串口接收来说,我们应该怎么样从串行数据中准确的获取到每一位数据?
自己:按之前的思路,设置一个提醒信号。
我们注意到,开始的时候有一个上升的阶段。信号不可能立即的从0变到1,这总是要经历一个过程。
提问:这个存在的过程意味着什么?
我们信号在变化的这个过程,其值是0还是1是不可靠的,还在变化的过程中,还没有变到稳定的1上面去。
所以在中间点判断,是比较稳定的。
用一个计数器在下降沿计数,到一位数据发送完,清零,就和波特率分频计数器一样。
这样计算,在计数的中点接收数据。
2.要点2
如何检测串口接收数据的下降沿。
用一个非时钟信号作为D触发器的时钟,那么这个就是不合理的。
这样会使得D触发器的时钟质量很差,进而导致输出数据的错误和不可靠。也会使系统变的不稳定,所以仅推荐有时钟信号的作为D触发器的时钟。
提示:
在verilog中关于always 块的描述。
组合逻辑通常使用无时钟信号的always @ (*)块来描述,而顺序逻辑则使用带有时钟信号的always @(posedge clk)块。
就是前一个时刻为高电平,后一个时刻为低电平。就是经历了一个下降沿。
前一个时刻为低电平,后一个时刻为高电平。就是经历了一个上升沿。
下降沿,上升沿的检测:
这个在FPGA中有专门的电路可以实现。
上升沿下降沿检测电路
上升沿下降沿检测
3.要点3
如何设计波特率分频计数器使能逻辑。
使能逻辑,不因为前面的毛刺信号导致误判,发现不对后也能够停止计数,清零,等待下一个下降沿。
4.要点4
如何知道当前所读取的信号位于UART信号的哪一位?
答:还是可以使用位计数器的逻辑,计数到了哪一位,就是发送到了哪一位。
5.要点5
如何实现位接收的逻辑?
还是可以使用我们的case逻辑。
else begin
case(bit_cnt)
0: uart_tx <= 1’d0; //起始位是低电平。
1: uart_tx <= r_Data[0]; //1的时候发送数据的Bit0位。发送的是Data位。
2: uart_tx <= r_Data[1];
3: uart_tx <= r_Data[2];
4: uart_tx <= r_Data[3];
5: uart_tx <= r_Data[4];
6: uart_tx <= r_Data[5];
7: uart_tx <= r_Data[6];
8: uart_tx <= r_Data[7];
9: uart_tx <= 1’d1; //停止位为什么是1?根据信号传输的波形来的。
default:uart_tx <= uart_tx;
endcase
end
6.要点6
接受完成之后,产生一个接受完成的信号来表示接收完成。
一、串口接收是什么?
提问:假如说在在采样停止位的时候,采样到了低电平怎么办?
做法4,比较合理。但是实际中一般是做法3.
经过测试,CH340对于使用的是做法3.
二、具体实现
1.模块构建
代码如下(示例):
//位接收逻辑
always@( posedge Clk or negedge Reset_n)
if(!Reset_n)
Rx_Data <= 8'd0;
else if(baud_div_cnt ==MCNT_BAUD/2 )begin //波特率计数器计数到最大值的一半的时候
case(bit_cnt) //根据bit_cnt的值,做一个存储。
1:Rx_Data[0] <= uart_rx; //1的时候发送数据的Bit0位。发送的是Data位。
2:Rx_Data[1] <= uart_rx;
3:Rx_Data[2] <= uart_rx;
4:Rx_Data[3] <= uart_rx;
5:Rx_Data[4] <= uart_rx;
6:Rx_Data[5] <= uart_rx;
7:Rx_Data[6] <= uart_rx;
8:Rx_Data[1] <= uart_rx;
default:Rx_Data <= Rx_Data;
endcase
end
判断条件:波特率计数器计数到最大值的一半的时候
问:这里为什么没有起始位和停止位?
答:else if(baud_div_cnt == MCNT_BAUD/2) && (bit_cnt == 0)&& (uart_rx == 1)) 前面的部分判断过。
else if(baud_div_cnt ==MCNT_BAUD/2 )前置条件一定不能少。
通过这个条件,才能限制后面的每一位的稳定。每一个赋值逻辑都是在中点进行。不加这个逻辑,每个时钟周期都会执行一次。
最后存进RX_Date的,是最后一个时钟周期的时候。存进去的是我们
存进去的是某个值,最后一个时钟周期的时候,uart_rx的状态。只有下一个时刻变了,这个才不会再更新。
有时候会出现搞不清楚上升沿和下降沿的时候,r_uart_rx上次的信号,uart_rx这次的信号。
uart_rx的同步操作
亚稳态。
其他信号都是由Clk驱动的,只有uart_rx信号是由外部输入的,发生变化
其有可能与Clk上升沿很近的位置发生变化,就会产生D端口无法正确的存储数据,无法满足D触发器的必要条件。导致D触发器无法有效的来判断存储这个时刻的uart存储状态。有可能引起D触发器的一个输出震荡。从而让逻辑电路的工作不稳定,这样的情况就称之为亚稳态。
根据经验,在D触发器的信号前面在加两级D触发器,可以有效的避免这样的情况。对输入信号进行同步,
就能将该信号同步到原本工作的时钟域来。
再去使用D触发器的输出就可以避免亚稳态对我们的影响。
对dff1进行打拍存储,用dff1代替了原本没有同步过的uart_rx。
书写:
在写一些信号的时候,可以假设他已经存在了,先用,然后后面再补全这些信号。
完整代码
module uart_byte_rx(
Clk,
Reset_n,
Rx_Data,
uart_rx,
Rx_Done
);
input Clk;
input Reset_n;
input uart_rx; //串口接收信号
output reg Rx_Done;
output reg [7:0]Rx_Data;
parameter CLOCK_FREQ = 50_000_000;
parameter BAUD = 9600;
parameter MCNT_BAUD = CLOCK_FREQ / BAUD - 1;
reg [7:0]r_Rx_Data; //8位的D触发器用来存储Rx_Data。
reg en_baud_cnt;
reg [29:0]baud_div_cnt; //波特率分频计数器
reg [3:00]bit_cnt;
wire w_Rx_Done;
reg r_uart_rx; //下降沿标志信号。
reg dff0_uart_rx,dff1_uart_rx;
//波特率分频计数器逻辑
always@( posedge Clk or negedge Reset_n)
if(!Reset_n)
baud_div_cnt <= 0;
else if(en_baud_cnt)begin
if(baud_div_cnt == MCNT_BAUD)
baud_div_cnt <= 0;
else
baud_div_cnt <= baud_div_cnt + 1'd1;
end
else
baud_div_cnt <= 0; //不使能的时候保持计数器为0
//UART 信号边沿检测逻辑
always@( posedge Clk) //这个部分就是通过赋值语句多了两级D触发器。
dff0_uart_rx <= uart_rx;
always@( posedge Clk)
dff1_uart_rx <= dff0_uart_rx;
always@( posedge Clk)
r_uart_rx <= dff1_uart_rx; //r_uart_rx上次的信号,uart_rx这次的信号。
assign nedge_uart_rx = ((dff1_uart_rx) && (r_uart_rx == 1)); //当下降沿标志信号为1和串口接收信号为0的时候。对dff1进行打拍存储,用dff1代替了原本没有同步过的uart_rx。
//波特率计数器使能逻辑
always@( posedge Clk or negedge Reset_n)
if(!Reset_n)
en_baud_cnt <= 0;
else if(nedge_uart_rx) //下降沿标志信号。
en_baud_cnt <= 1; //
else