串口接收(E:/FPGACode/uart_rx)
一、原理与思路
1、 基本原理:采样
2、起始位检测:通过边沿检测电路(下降沿)
使用两个寄存器,当第一个clk上升沿时得到q1传到reg2,第二个clk上升沿时reg2获得q1
传过来的q2,并且reg1获得新的q1.若q2=1,q1=0,则为下降沿,反之为上升沿。
//边沿检测
reg [1:0] uart_tx_r;//两个D触发器
always@(posedge Clk)begin
uart_tx_r[0]<=uart_rx;
uart_tx_r[1]<=uart_tx_r[0];
end
wire posedge_uart_tx,nedge_uart_tx;
assign posedge_uart_tx=(uart_tx_r==2'b01)?1:0;
assign nedge_uart_tx=(uart_tx_r==2'b10)?1:0;
二、技巧
一位数据采样多次,统计得到高电平出现的次数,次数多的就是该位的电平值。(采样7次,0、1、2、3为低电平,4、5、6、7为高电平)
- 将1位数据分成16段,舍弃前5段和后4段,取中间7段进行采样。
- 设波特率位9600
每一位时间:1000,000,000/9600
每一小段时间:1000,000,000/9600/16
每一小段的计数周期:1000,000,000/9600/16/20=325(次) - 设波特率位115200——27次
- Bps_DR为上图每一个小块的clk计数值
- 一开始写的是if(nedge_uart_tx),是因为下降沿开始接收,但是nedge_uart_tx=1只维持一个时钟周期,如果要保持一个状态,必须要加一个RX_EN,所以写了一个RX_EN的always块
wire bps_clk_16x;//每一个bps_clk分成16份,每一份取中间,为一个脉冲
assign bps_clk_16x=(div_cnt==Bps_DR/2)?1:0;//脉冲在中间
//分频计数器
reg [8:0]div_cnt;
always@(posedge Clk or negedge Reset_n)begin
if(!Reset_n)
div_cnt<=0;
else if(RX_EN)begin//一开始写的是if(nedge_uart_tx),但是nedge_uart_tx=1只维持一个时钟周期,如果要保持一个状态,必须要加一个RX_EN
if(div_cnt==Bps_DR)
div_cnt<=0;
else
div_cnt<=div_cnt+1'b1;
end
else
div_cnt<=0;
end
always@(posedge Clk or negedge Reset_n)begin
if(!Reset_n)
RX_EN<=0;
else if(nedge_uart_tx)
RX_EN<=1;
else if(Rx_Done==1 || (start_bit>=4))//传输结束或者起始位采样之后发现并不是低电平0
RX_EN<=0;
end
//分段采样——1位分为16段,一共10位(开始+data+结束)
//16段舍弃前5段和后4段,取中间7段进行采样
reg [7:0] bps_cnt;//160段
always@(posedge Clk or negedge Reset_n)begin
if(!Reset_n)
bps_cnt<=0;
else if(RX_EN) begin
if(bps_clk_16x)begin
if(bps_cnt==159)
bps_cnt<=0;
else
bps_cnt<=bps_cnt+1'b1;
end
else
bps_cnt<=bps_cnt;
end
else
bps_cnt<=0;
end
always@(posedge Clk or negedge Reset_n)begin
if(!Reset_n)
Rx_Done<=0;
else if(bps_clk_16x && (bps_cnt==159))
Rx_Done<=1;
else begin
Rx_Done<=0;
end
end
关于采样:
一开始我以为是当bps_clk_16x=1&&bps_cnt=1时,是16x的第一段,因为当bps_clk_16x=1时,bps_cnt++。
但是根据老师写的代码发现:bps_cnt=5,6,7,8,9,10,11:start_bit<=start_bit+uart_rx
那这是为什么呢?后来查看波形图发现:mark1(第一条蓝色线)到mark2为1位分成16段中的一段。由于时序逻辑+非阻塞赋值,此时代码中posedge Clk&&bps_clk_16x=1&&bps_cnt=0时(图中黄色线),根据波形图检测到的就是第1段,并不是所认为bps_cnt==1时才是第一段。
也可以这样去理解:在bps_clk_16x=、=1也就是黄色线那里,bps_cnt++,即从0变成1,而且根据非阻塞赋值,此时取到的bps_cnt的值为0,处于16段中的第一段。所以当bps_cnt能取到1时,说明它是由1变成了2,就是bps_clk_16x第二次为1,也就是第2段。
由上述分析可知:当bps_cnt=n-1时,也就是第n段。
//采样
reg [2:0] r_data[7:0];//3位r_data8个-数据位
reg [2:0] start_bit;//起始位
reg [2:0] stop_bit;//结束位
always@(posedge Clk or negedge Reset_n)begin
if(!Reset_n)begin
start_bit<=0;
stop_bit<=0;
r_data[0]<=0;r_data[1]<=0;r_data[2]<=0;r_data[3]<=0;
r_data[4]<=0;r_data[5]<=0;r_data[6]<=0;r_data[7]<=0;
end else if(bps_clk_16x) begin
case(bps_cnt)
0:begin
start_bit<=0;
stop_bit<=0;
r_data[0]<=0;r_data[1]<=0;r_data[2]<=0;r_data[3]<=0;
r_data[4]<=0;r_data[5]<=0;r_data[6]<=0;r_data[7]<=0;
end
5,6,7,8,9,10,11:start_bit<=start_bit+uart_rx;//一位分成16份,前5份不要
21,22,23,24,25,26,27:r_data[0]<=r_data[0]+uart_rx;
37,38,39,40,41,42,43:r_data[1]<=r_data[1]+uart_rx;
53,54,55,56,57,58,59:r_data[2]<=r_data[2]+uart_rx;
69,70,71,72,73,74,75:r_data[3]<=r_data[3]+uart_rx;
85,86,87,88,89,90,91:r_data[4]<=r_data[4]+uart_rx;
101,102,103,104,105,106,107:r_data[5]<=r_data[5]+uart_rx;
117,118,119,120,121,122,123:r_data[6]<=r_data[6]+uart_rx;
133,134,135,136,137,138,139:r_data[7]<=r_data[7]+uart_rx;
149,150,151,152,153,154,155:stop_bit<= stop_bit + uart_rx;
default:;
endcase
end
end
//根据采样的中间7位判断,此位是为高电平还是低电平
always@(posedge Clk or negedge Reset_n)begin
if(!Reset_n)
Data<=0;
else if(bps_clk_16x &&(bps_cnt==159))begin
Data[0]<=(r_data[0]>=4)?1'b1:1'b0;
Data[1]<=(r_data[1]>=4)?1'b1:1'b0;
Data[2]<=(r_data[2]>=4)?1'b1:1'b0;
Data[3]<=(r_data[3]>=4)?1'b1:1'b0;
Data[4]<=(r_data[4]>=4)?1'b1:1'b0;
Data[5]<=(r_data[5]>=4)?1'b1:1'b0;
Data[6]<=(r_data[6]>=4)?1'b1:1'b0;
Data[7]<=(r_data[7]>=4)?1'b1:1'b0;
end
end
三、仿真测试
`timescale 1ns / 1ps
module uart_byte_rx_tb();
reg uart_rx,Clk,Reset_n;
wire [7:0] Data;
wire Rx_Done;
uart_byte_rx uart_byte_rx(
uart_rx,
Clk,
Reset_n,
4,
Data,
Rx_Done
);
initial Clk=1;
always #10 Clk=~Clk;
initial begin
Reset_n=0;
#201;
Reset_n=1;
#200;
uart_tx_byte(8'h5a);
@(posedge Rx_Done);
#5000;
uart_tx_byte(8'ha5);
@(posedge Rx_Done);
#5000;
uart_tx_byte(8'h86);
@(posedge Rx_Done);
#5000;
$stop;
end
task uart_tx_byte;
input [7:0] tx_data;
begin
uart_rx=1'b1;
#20;
uart_rx=1'b0;
#8680;
uart_rx=tx_data[0];
#8680;
uart_rx=tx_data[1];
#8680;
uart_rx=tx_data[2];
#8680;
uart_rx=tx_data[3];
#8680;
uart_rx=tx_data[4];
#8680;
uart_rx=tx_data[5];
#8680;
uart_rx=tx_data[6];
#8680;
uart_rx=tx_data[7];
#8680;
uart_rx=1'b1;
#8680;
end
endtask
endmodule
四、问题
1、只接收了第一个数据,后面就一直检测不到posedge Rx_Done
原因:由波形图可得,当uart_rx传输了停止位到结束只有8.09,但是代码中是经过#8680ns停止(uart_tx_byte任务执行完成)。所以经过8680ns后检测到的Rx_Done已经为0了,所以检测不到posedge
一开始的代码是else if(bps_clk_16x && (bps_cnt==159))
就相当关于最后一个停止位的最后一块只计数了一半,让它计满即可。
always@(posedge Clk or negedge Reset_n)begin
if(!Reset_n)
Rx_Done<=0;
else if((div_cnt==Bps_DR) && (bps_cnt==159))
Rx_Done<=1;
else begin
Rx_Done<=0;
end
end
2、bps_cnt计数的160比159小一半
分析:div_cnt应该是从计数14->26->0->13结束。所以修改
always@(posedge Clk or negedge Reset_n)begin
if(!Reset_n)
Rx_Done<=0;
else if(bps_clk_16x && (bps_cnt==160))
Rx_Done<=1;
else begin
Rx_Done<=0;
end
end
3、还是提前到来
由于Verilog的误差,后面有小数,仿真时间走不到实际值。所以Rx_Done会提前。
initial begin
Reset_n=0;
#201;
Reset_n=1;
#200;
uart_tx_byte(8'h5a);
#90000;
uart_tx_byte(8'ha5);
#90000;
uart_tx_byte(8'h86);
#90000;
$stop;
end