串口接收:串---->并
空闲状态,rx为高电平,发送起始位的时候会置为低电平。
假如采样起始位的时候,采到了低电平怎么办
做法1:该数据直接舍弃,也就是不通知外界完成了一个数据的接收,不产生标志信号
做法2:当成新的起始位来解
做法3:当成正确的数据通知外部
做法4:通知外界接收完成,但同时也输出一个错误标志信号,告知外界,此次接收的数据是错误的
1.设计部分
module uart_rx_1(input clk ,
input reset_n ,
input uart_rx ,
output reg [7:0]rx_data,
output reg rx_done );
//默认使用波特率BAUD 9600 时钟频率 CLK_FREQ 50MHz
parameter BAUD = 9600;
parameter CLK_FREQ = 50_000_000;
parameter bps_c = CLK_FREQ / BAUD ;
reg rx_en ;
reg[3:0] rx_flag;
// bps
reg [30:0] counter_bps ;
always@(posedge clk or negedge reset_n)
if(! reset_n)
counter_bps <= 0 ;
else if (rx_en)
if(counter_bps == bps_c - 1)
counter_bps <= 0 ;
else
counter_bps <= counter_bps + 1'b1 ;
else
counter_bps <= 0 ;
reg dff_rx_0 , dff_rx_1 ;
reg r_uart_rx;
wire neg_rx_go ;
always@(posedge clk )
dff_rx_0 <= uart_rx ;
always@(posedge clk )
dff_rx_1 <= dff_rx_0 ;
always@(posedge clk )
r_uart_rx <= dff_rx_1 ;
assign neg_rx_go = (dff_rx_1 == 0)&&(r_uart_rx == 1);
// rx_en
always@(posedge clk or negedge reset_n)
if(! reset_n)
rx_en <= 0 ;
else if(neg_rx_go)
rx_en <= 1 ;
else if((rx_flag==9)&&(counter_bps == bps_c - 1))
rx_en <= 0 ;
else if((rx_flag==0)&&(counter_bps == bps_c/2 )&&(dff_rx_1==1))
rx_en <= 0 ;
// rx_flag
// always@(posedge clk or negedge reset_n)
// if(!reset_n) rx_flag <= 4'b0000 ;
// else if(counter_bps == bps_c - 1)
// begin
// if(rx_flag == 9)
// rx_flag <= 0 ;
// else
// rx_flag <= rx_flag + 1'b1 ;
// end
// rx_flag
always@(posedge clk or negedge reset_n)
if(!reset_n)
rx_flag <= 4'b0000 ;
else if((rx_flag == 9)&&(counter_bps == bps_c /2))
rx_flag <= 4'b0000 ;
else if(counter_bps == bps_c - 1)
rx_flag <= rx_flag + 1'b1 ;
// [7:0]r_rx_data
reg [7:0] r_rx_data;
always@(posedge clk )
if(!rx_en) r_rx_data <= r_rx_data;
else if(counter_bps == bps_c / 2)
begin
case(rx_flag)
1 : r_rx_data[0] <= dff_rx_1;
2 : r_rx_data[1] <= dff_rx_1;
3 : r_rx_data[2] <= dff_rx_1;
4 : r_rx_data[3] <= dff_rx_1;
5 : r_rx_data[4] <= dff_rx_1;
6 : r_rx_data[5] <= dff_rx_1;
7 : r_rx_data[6] <= dff_rx_1;
8 : r_rx_data[7] <= dff_rx_1;
default : r_rx_data <= r_rx_data;
endcase
end
// rx_done
always@(posedge clk)
rx_done <= (rx_flag==9)&&(counter_bps == bps_c - 1);
// rx_data ;
always@(posedge clk)
if(rx_done) rx_data <= r_rx_data;
endmodule
2.模块分析
(1)两级触发器,用于消除亚稳态
reg dff_rx_0 , dff_rx_1 ;
reg r_uart_rx;
wire neg_rx_go ;
always@(posedge clk )
dff_rx_0 <= uart_rx ;
always@(posedge clk )
dff_rx_1 <= dff_rx_0 ;
always@(posedge clk )
r_uart_rx <= dff_rx_1 ;
assign neg_rx_go = (dff_rx_1 == 0)&&(r_uart_rx == 1);
neg_rx_go为1可能表示接收器处于一种“准备就绪”的状态,即它已经接收到了数据并且当前没有新的数据正在被接收。neg_rx_go 信号通过比较dff_rx_1(当前值)和r_uart_rx(前一个值)来检测是否有数据从高电平跳变到低电平,这通常表示接收到新的有效数据。
为了处理毛刺信号,即在比特周期的一半时,如果信号是高电平,可能是噪声等引起的毛刺,而不是有效的起始位。
(2)锁存器
// rx_done
always@(posedge clk)
rx_done <= (rx_flag==9)&&(counter_bps == bps_c - 1);
// rx_data ;
always@(posedge clk)
if(rx_done) rx_data <= r_rx_data;
rx_data会在传送过程中发生变化,和上篇串口发送模块一样,需要设置锁存器r_rx_data
3.测试
接收是从低位往高位接收
`timescale 1ns / 1ns
module uart_rx_tb();
reg clk , reset_n ;
reg uart_rx;
wire [7:0]rx_data;
wire rx_done;
uart_rx_1 uart_rx_tb_( .clk(clk) ,
.reset_n(reset_n) ,
.uart_rx(uart_rx) ,
.rx_data(rx_data),
.rx_done(rx_done) );
initial clk = 1 ;
always #10 clk = ~clk ;
initial
begin
reset_n = 0 ;
uart_rx = 1 ;
#201;
reset_n = 1 ; #2000;
// 0F 0000_1111
uart_rx = 0 ; #(5208*20);
uart_rx = 1 ; #(5208*20);
uart_rx = 1 ; #(5208*20);
uart_rx = 1 ; #(5208*20);
uart_rx = 1 ; #(5208*20);
uart_rx = 0 ; #(5208*20);
uart_rx = 0 ; #(5208*20);
uart_rx = 0 ; #(5208*20);
uart_rx = 0 ; #(5208*20);
uart_rx = 1 ; #(5208*20);
#200000;
// 55 0101_0101
uart_rx = 0 ; #(5208*20);
uart_rx = 1 ; #(5208*20);
uart_rx = 0 ; #(5208*20);
uart_rx = 1 ; #(5208*20);
uart_rx = 0 ; #(5208*20);
uart_rx = 1 ; #(5208*20);
uart_rx = 0 ; #(5208*20);
uart_rx = 1 ; #(5208*20);
uart_rx = 0 ; #(5208*20);
uart_rx = 1 ; #(5208*20);
#200000;
$stop;
end
endmodule
结果:
4.优化
正常传输 :波特率115200 传输一位bit的时间1/115200=8680.5ns
传输十位就需要86805ns
如果是误差传输:例如发送时间由于设备原因少了10ns,即传输位时间为8670.5,则传输十位就需要80705,发送器就少了100ns,而接收器比发送器多了100ns,会导致发送方发完第一个数据后,会接着立即发送第二个数据,而接收方还处于接收上一个数据的状态,忽略掉新一个数据的起始位,导致第二个数据接受失败!这个bug很难发现问题,最终通过示波器抓取波形分析才能找到问题。
问题解决:通过在停止位的位时间的中点,就停止数据接收,节省0.5s的时间,避免下一个信号到来错过起始位。
即将stop信号提前到蓝线处:
代码修改:
//(rx_flag==9)&&(counter_bps == bps_c - 1)
(rx_flag==9)&&(counter_bps == bps_c / 2)
//rx_done<=(rx_flag==9)&&(counter_bps==bps_c/2)