首先再次回顾一下通信协议UART
本质就是并行数据转串行数据
关键参数:起始位(0)、数据位(非任意位)、停止位(1)
假设发送8位数据,那实际上要发送10位数据,上升沿有效的话,需经历11个上升沿。
波特率计数计算:假设时钟周期为20ns,波特率为115200: 1000000000/115200/20=bps
接口规范: RS232、RS449、RS423、RS422、RS485
工程描述:构建串口接收模块,并进行仿真测试(此部分已在上述链接验证过),仿真通过后,对其上板测试。
具体工程如下:
主要模块 : 一个设计模块:serial_recv 一个tb模块:serial__tb
工程分析:
接收与发送不同,由于某些场合信号干扰或波特率不匹配或其他原因,很可能会导致数据接收的不准确性,因此在数据接收模块必须采用一些数据检测手段,本文所述的设计模块采用两个触发器构建边沿检测电路**,为保证接收到数据的准确性,将每一比特平均分成16段,去掉前5段和后四段,只保留中间7段,采样周期为bps/16。对于一个8位数据,加上起始位和停止位共10位,也就是需要160次采样。
代码如下:
serial_recv模块:
module serial_recv(
clk,
reset_n,
uart_recv,
data,
recv_done,
baud_set
);
input clk;
input reset_n;
input [2:0]baud_set;
input uart_recv;
output reg [7:0] data;
output reg recv_done;
//采样周期计数
reg[7:0] bps_cnt;
//只持续一个时钟周期,每一位数据的16段分之一的中点时刻
wire bps_clk;
assign bps_clk=(div_cnt==bps_dir/2);
//两个寄存器依次保存数据,弄清前后顺序
reg [1:0] bytedect;
always@(posedge clk)begin
bytedect[0]<=uart_recv; //0:每次传来最新的数据值
bytedect[1]<=bytedect[0]; //1:前一个数据的值
end
//边沿检测
//前一个时刻为低电平,后一个时刻为高电平:01
wire pedge;
assign pedge=(bytedect==1);
//前一个时刻为高电平 后一个时刻为低电平:10
wire nedge;
assign nedge=(bytedect==2);
reg[9:0] bps_dir;
always@(*)begin
case(baud_set)
0:bps_dir=1000000000/9600/16/20-1;
1:bps_dir=1000000000/19200/16/20-1;
2:bps_dir=1000000000/38400/16/20-1;
3:bps_dir=1000000000/57600/16/20-1;
4:bps_dir=1000000000/115200/16/20-1;
default:bps_dir=1000000000/9600/16/20-1;
endcase
end
//一个字节8位数据,所以需要8个寄存器,每个数据有8位
reg[2:0] r_data[7:0];
reg[2:0] start_bit;
reg[2:0] stop_bit;
always@(posedge clk,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) 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_recv;
21,22,23,24,25,26,27:r_data[0]<=r_data[0]+uart_recv;
37,38,39,40,41,42,43:r_data[1]<=r_data[1]+uart_recv;
53,54,55,56,57,58,59:r_data[2]<=r_data[2]+uart_recv;
69,70,71,72,73,74,75:r_data[3]<=r_data[3]+uart_recv;
85,86,87,88,89,90,91:r_data[4]<=r_data[4]+uart_recv;
101,102,103,104,105,106,107:r_data[5]<=r_data[5]+uart_recv;
117,118,119,120,121,122,123:r_data[6]<=r_data[6]+uart_recv;
133,134,135,136,137,138,139:r_data[7]<=r_data[7]+uart_recv;
149,150,151,152,153,154,155:stop_bit<=stop_bit+uart_recv;
default:;
endcase
end
end
reg recv_en;
always@(posedge clk,negedge reset_n)begin
if(!reset_n)
recv_en<=0;
else if(nedge)
recv_en<=1;
else if(recv_done||(start_bit>=4))begin
recv_en<=0;
end
end
//切分之后的每小段时钟计数
reg [8:0]div_cnt;
always@(posedge clk,negedge reset_n)begin
if(!reset_n)
div_cnt<=0;
else if(recv_en)begin
if(div_cnt==bps_dir)
div_cnt<=0;
else
div_cnt<=div_cnt+1;
end
else
div_cnt<=0;
end
always@(posedge clk,negedge reset_n)begin
if(!reset_n)
bps_cnt<=0;
else if(recv_en) begin
if(bps_clk)begin
if(bps_cnt==160)
bps_cnt<=0;
else
bps_cnt<=bps_cnt+1;
end
else
bps_cnt<=bps_cnt;
end
else
bps_cnt<=0;
end
always@(posedge clk,negedge reset_n)begin
if(!reset_n)begin
data<=0;
end
else if(bps_clk&&(bps_cnt==160))begin
data[0]<=(r_data[0]>=4)?1:0;
data[1]<=(r_data[1]>=4)?1:0;
data[2]<=(r_data[2]>=4)?1:0;
data[3]<=(r_data[3]>=4)?1:0;
data[4]<=(r_data[4]>=4)?1:0;
data[5]<=(r_data[5]>=4)?1:0;
data[6]<=(r_data[6]>=4)?1:0;
data[7]<=(r_data[7]>=4)?1:0;
end
end
always@(posedge clk,negedge reset_n)begin
if(!reset_n)
recv_done<=0;
else if((div_cnt==bps_dir/2)&&(bps_cnt==160))
recv_done<=1;
else
recv_done<=0;
end
endmodule
serial_recv_tb模块:
`timescale 1ns / 1ns
module serial_recv_tb ();
reg clk;
reg reset_n;
reg recv;
wire [7:0] data;
wire recv_done;
serial_recv r1(
.clk(clk),
.reset_n(reset_n),
.uart_recv(recv),
.data(data),
.recv_done(recv_done),
.baud_set(4)
);
initial clk=1;
always#10 clk=~clk;
initial begin
reset_n=0;
#201;
reset_n=1;
#200;
serial_send(8'h73);
#100000;
serial_send(8'h91);
#100000;
$stop;
end
task serial_send;
input[7:0] data;
begin
recv=1;
#200;
//起始位
recv=0;
#9000;
recv=data[0];
#9000;
recv=data[1];
#9000;
recv=data[2];
#9000;
recv=data[3];
#9000;
recv=data[4];
#9000;
recv=data[5];
#9000;
recv=data[6];
#9000;
recv=data[7];
#9000;
//停止位
recv=1;
#9000;
end
endtask
endmodule
仿真结果图如下:
图1:
本次工程由于采样周期太长,因此我主要对数据接收截止位做说明,如图1所示,
1、当采样计数到达160次时,对应的tb模块接受的第一个数据73也随之完整采样,此时recv_done拉高,随着recv_done拉高,recv_en也置低电平,的第一个数据这轮采样对应的起始计数周期div_cnt是从上轮计数的14开始,到这轮计数的13,加起来正好为1000000000/115200/16/20=27 次,
本次工程较为复杂,不好一一道来,具体分为以下几点进行阐述:
1、对一位数据进行多次采用,采样虽为16段,但为了采取更有效的数据,我们再次将16段掐头去尾采取其中间7段,并对这7段数据进行计数处理,每次对采样进来的数据进行自加,并进行判断是否大于等于4,若满足条件则采样进来的为高电平,否则为低电平。
2、起始位置采用两个寄存器设计边沿检测电路(后期应该会专门针对此电路设计来一篇文章做总结)
3、如图1所示,当采样计数到达160次时,对应的tb模块接受的第一个数据73也随之完整采样,此时recv_done拉高,随着recv_done拉高,recv_en也置低电平。
4、第10位数据的最后一段数据采样(也就是总共十位数据的第160段采样),在这轮采样对应的起始计数周期div_cnt是从第159段计数的14开始,到这轮计数的13,加起来正好是1000000000/115200/16/20=27 次,一切都很nice。
5、至此8位数据接收模块设计结束。
附RTL仿真电路图: