UART接收单字节时序图:
(实验室环境下):对每一位的中点进行采样最稳定;
(工业环境下):有强电磁干扰,每位只采样一次作为电平判定依据是不保险的,需要多次采中间部分,求哪一种电平的概率高,来确定该位电平;
如上图。对于每一位数据,考虑到开头和尾部不稳定,所以忽略掉。只取中间6~11这6次采样结果进行电平概率的判定依据。如6次结果为1/1/1/1/0/1,电平取1;0/1/0/0/0/1,电平取0;1/0/0/1/0/1,电平取0或1都可以;
设计思路:
rs232_rx为外部异步信号,进入系统先用两个寄存器将信号同步到系统时钟以避免亚稳态,再用两个寄存器边沿检测(只检测下降沿,作为开始接收工作状态的判定依据)。
bps_set选择各波特率对应的分频最大计数值,利用计数器生成分频器。一共10位,每一位采16次,接收一个字节的过程就要bps_cnt计数160次。(与发送相比,bps_clk要*16,bps_DR要/16)。
设定每一位的中间6~11采的6次作为电平高低的有效判定依据。每一位的六次累加,大于3就是高电平,否则为低电平。
判断出外部输入的一个字节(10位)后,在bps_cnt<=159时,一起寄存给data_byte输出,同时接收停止产生rx_down。
RTL程序:
module uart_byte_rx(clk50M,rst_n,rs232_rx,bps_set,uart_state,data_byte,rx_down);
input clk50M;
input rst_n;
input rs232_rx;
input [2:0] bps_set;
output reg [7:0] data_byte;
output reg rx_down; //接受完成单脉冲标志信号
output reg uart_state;
reg s0_reg,s1_reg; //两个同步寄存器,目的是消除外部异步输入信号(rs232_rx)亚稳态
reg t0_reg,t1_reg; //两个数据寄存器,目的是边沿检测
wire nedge; //定义下降沿为nedge
reg [15:0] counter; //波特率分频器计数器
reg [15:0] bps_DR; //波特率分频器计数最大计数值
reg bps_clk; //波特率时钟
reg [15:0] bps_cnt; //bps_clk计数
reg [2:0] r_data_byte [7:0];//8位数据位的电平概率,最大为6最小为0,三位足够
reg [2:0] start_bit;//起始位的电平概率,最大为6最小为0,三位足够
reg [2:0] stop_bit;//停止位的电平概率,最大为6最小为0,三位足够
/******************同步外部信号,消除亚稳态***************/
always @ (posedge clk50M or negedge rst_n) begin
if(!rst_n) begin
s0_reg<=1'b0;
s1_reg<=1'b0;
end
else begin
s0_reg<=rs232_rx;
s1_reg<=s0_reg;
end
end
/*******************************************************/
/*****************数据寄存器,边沿检测********************/
always @ (posedge clk50M or negedge rst_n) begin
if(!rst_n) begin
t0_reg<=1'b0;
t1_reg<=1'b0;
end
else begin
t0_reg<=s1_reg;
t1_reg<=t0_reg;
end
end
/*******************************************************/
/**********************下降沿检测************************/
assign nedge=t1_reg&(!t0_reg);
/*******************************************************/
/*************波特率分频器计数最大计数值查找表*************/
always @ (posedge clk50M or negedge rst_n) begin
if(!rst_n)
bps_DR<=16'd324;
else begin
case(bps_set)
0:bps_DR<=16'd324;//9600
1:bps_DR<=16'd162;//19200
2:bps_DR<=16'd80;//38400
3:bps_DR<=16'd53;//57600
4:bps_DR<=16'd26;//115200
default:bps_DR<=16'd324;
endcase
end
end
/*******************************************************/
/*********************计数器****************************/
always @ (posedge clk50M or negedge rst_n) begin
if(!rst_n)
counter<=16'd0;
else if(uart_state) begin
if(counter==bps_DR)
counter<=16'd0;
else
counter<=counter+1'd1;
end
else
counter<=16'd0;
end
/*******************************************************/
/********************bps_clk时钟生成*********************/
always @ (posedge clk50M or negedge rst_n) begin
if(!rst_n)
bps_clk<=1'b0;
else if (counter==16'd1)
bps_clk<=1'b1;
else
bps_clk<=1'b0;
end
/*******************************************************/
/*******************bps_clk时钟脉冲计数******************/
always @ (posedge clk50M or negedge rst_n) begin
if(!rst_n)
bps_cnt<=16'd0;
else if(bps_clk)
bps_cnt<=bps_cnt+1'd1;
else if(rx_down)
bps_cnt<=16'd0;
else
bps_cnt<=bps_cnt;
end
/*******************************************************/
/**************rx_down发送完成单脉冲标志信号生成***********/
always @ (posedge clk50M or negedge rst_n) begin
if(!rst_n)
rx_down<=1'b0;
else if(bps_cnt==16'd159)
rx_down<=1'b1;
else
rx_down<=1'b0;
end
/*******************************************************/
/**********采样,并且将采样电平寄存到r_data_byte***********/
always @ (posedge clk50M or negedge rst_n) begin
if(!rst_n) begin
start_bit<=3'd0;
r_data_byte[0]<=3'd0;
r_data_byte[1]<=3'd0;
r_data_byte[2]<=3'd0;
r_data_byte[3]<=3'd0;
r_data_byte[4]<=3'd0;
r_data_byte[5]<=3'd0;
r_data_byte[6]<=3'd0;
r_data_byte[7]<=3'd0;
stop_bit<=3'd0;
end
else if(bps_clk) begin
case(bps_cnt)
0 : begin
start_bit<=3'd0;
r_data_byte[0]<=3'd0;
r_data_byte[1]<=3'd0;
r_data_byte[2]<=3'd0;
r_data_byte[3]<=3'd0;
r_data_byte[4]<=3'd0;
r_data_byte[5]<=3'd0;
r_data_byte[6]<=3'd0;
r_data_byte[7]<=3'd0;
stop_bit<=3'd0;
end
6,7,8,9,10,11 : start_bit<=start_bit+s1_reg;
22,23,24,25,26,27 : r_data_byte[0]<=r_data_byte[0]+s1_reg;
38,39,40,41,42,43 : r_data_byte[1]<=r_data_byte[1]+s1_reg;
54,55,56,57,58,59 : r_data_byte[2]<=r_data_byte[2]+s1_reg;
70,71,72,73,74,75 : r_data_byte[3]<=r_data_byte[3]+s1_reg;
86,87,88,89,90,91 : r_data_byte[4]<=r_data_byte[4]+s1_reg;
102,103,104,105,106,107 : r_data_byte[5]<=r_data_byte[5]+s1_reg;
118,119,120,121,122,123 : r_data_byte[6]<=r_data_byte[6]+s1_reg;
134,135,136,137,138,139 : r_data_byte[7]<=r_data_byte[7]+s1_reg;
150.151,152,153,154,155 : stop_bit<=stop_bit+s1_reg;
default : begin
r_data_byte[0]<=r_data_byte[0];
r_data_byte[1]<=r_data_byte[1];
r_data_byte[2]<=r_data_byte[2];
r_data_byte[3]<=r_data_byte[3];
r_data_byte[4]<=r_data_byte[4];
r_data_byte[5]<=r_data_byte[5];
r_data_byte[6]<=r_data_byte[6];
r_data_byte[7]<=r_data_byte[7];
end
endcase
end
end
/*******************************************************/
/*****************把接收到数据字节输出********************/
always @ (posedge clk50M or negedge rst_n) begin
if(!rst_n)
data_byte<=8'd0;
else if(bps_cnt==16'd159) begin
data_byte[0]<=r_data_byte[0][2];
data_byte[1]<=r_data_byte[1][2];
data_byte[2]<=r_data_byte[2][2];
data_byte[3]<=r_data_byte[3][2];
data_byte[4]<=r_data_byte[4][2];
data_byte[5]<=r_data_byte[5][2];
data_byte[6]<=r_data_byte[6][2];
data_byte[7]<=r_data_byte[7][2];
end
end
/*******************************************************/
/********************UART工作状态信号********************/
always @ (posedge clk50M or negedge rst_n) begin
if(!rst_n)
uart_state<=1'b0;
else if(nedge)
uart_state<=1'b1;
else if(rx_down)
uart_state<=1'b0;
else
uart_state<=uart_state;
end
/*******************************************************/
endmodule
注意 :reg [2:0] r_data_byte [7:0];这句话的意思是r_data_byte有8位,每一位不只是0/1表示,这里最大用到6,所以[2:0]三位足够。 模板:reg [数据位宽:0] r_data_byte [地址位宽:0];
testbench测试文件:
`timescale 1ns/1ns
`define clock_period 20
module uart_byte_rx_tb;
reg clk;
reg rst_n;
reg rs232_rx;
reg [2:0] bps_set;
wire uart_state_r;
wire [7:0] data_byte_r;
wire rx_down;
reg [7:0]data_byte_t;
reg tx_en;
wire rs232_tx;
wire tx_down;
wire uart_state_t;
uart_byte_rx uart_byte_rx(
.clk50M(clk),
.rst_n(rst_n),
.rs232_rx(rs232_tx), //把发送输出数据和接收输入数据连接起来
.bps_set(bps_set),
.uart_state(uart_state_r), //接收状态与发送状态区分开
.data_byte(data_byte_r),
.rx_down(rx_down)
);
uart_byte_tx uart_byte_tx(
.clk50M(clk),
.rst_n(rst_n),
.data_byte(data_byte_t),
.tx_en(tx_en),
.bps_set(bps_set),
.rs232_tx(rs232_tx),
.tx_down(tx_down),
.uart_state(uart_state_t) //接收状态与发送状态区分开
);
initial clk=1;
always #(`clock_period/2) clk=~clk;
initial begin
//首先激励初始化,波特率查找表默认选4,也就是115200.然后复位结束,系统工作
rst_n=0;
data_byte_t=0;
tx_en=0;
bps_set=4'd4;
#(`clock_period*20);
rst_n=1;
#(`clock_period*50);
//发送0xaa,等待tx_down到来时延时五千个系统周期
tx_en=1;
data_byte_t=8'haa;
#(`clock_period);
tx_en=0;
@(posedge tx_down)
#(`clock_period*5000);
//发送0x37,等待tx_down到来时延时五千个系统周期。然后停止仿真
tx_en=1;
data_byte_t=8'h37;
#(`clock_period);
tx_en=0;
@(posedge tx_down)
#(`clock_period*5000);
$stop;
end
endmodule
上一个发送字节例程的uart_byte_tx.v程序:
module uart_byte_tx(clk50M,rst_n,data_byte,tx_en,bps_set,rs232_tx,tx_down,uart_state);
input clk50M;
input rst_n;
input [7:0] data_byte; //输入想要发送的8位数据
input [2:0] bps_set; //波特率选择查找表的输入
input tx_en; //发送开始的标志单脉冲信号
output reg rs232_tx; //输出发送数据端
output reg tx_down; //发送停止的标志单脉冲信号
output reg uart_state; //UART工作状态信号,1为发送,0为空闲
reg [15:0] counter; //分频器计数寄存器
reg [15:0] bps_DR; //选定的波特率的最大计数值
reg [3:0] bps_cnt; //1~11的位计数
reg [7:0] r_data_byte; //data_byte进来 先寄存一次,以便后续稳定发送
reg bps_clk; //波特率时钟
localparam start_bit = 1'b0; //定义起始位为0低电平
localparam stop_bit = 1'b1; 定义停止位为1高电平
/*************波特率对应的分频计数值查找表**************/
always @ (posedge clk50M or negedge rst_n)begin
if(!rst_n)
bps_DR<=16'd5207;
else begin
case(bps_set)
3'd0:bps_DR<=16'd5207;//9600
3'd1:bps_DR<=16'd2603;//19200
3'd2:bps_DR<=16'd1301;//38400
3'd3:bps_DR<=16'd867;//57600
3'd4:bps_DR<=16'd433;//115200
default:bps_DR<=16'd5207;
endcase
end
end
/****************************************************/
/********************分频计数器生成********************/
always @ (posedge clk50M or negedge rst_n)begin
if(!rst_n)
counter<=16'd0;
else if(uart_state) begin
if(counter==bps_DR)
counter<=16'd0;
else
counter<=counter+1'd1;
end
else
counter<=16'd0;
end
/****************************************************/
/********************分频时钟生成*********************/
always @ (posedge clk50M or negedge rst_n)begin
if(!rst_n)
bps_clk<=1'b0;
else if(counter==16'd1)
bps_clk<=1'b1;
else
bps_clk<=1'b0;
end
/****************************************************/
/****************bps_clk计数(最大11)****************/
always @ (posedge clk50M or negedge rst_n)begin
if(!rst_n)
bps_cnt<=4'd0;
else if(bps_cnt==4'd11)
bps_cnt<=4'd0;
else if(bps_clk)
bps_cnt<=bps_cnt+1'd1;
else
bps_cnt<=bps_cnt;
end
/****************************************************/
/************tx_down发送停止单脉冲标志信号*************/
always @ (posedge clk50M or negedge rst_n)begin
if(!rst_n)
tx_down<=1'b0;
else if(bps_cnt==4'd11)
tx_down<=1'b1;
else
tx_down<=1'b0;
end
/****************************************************/
/******************寄存一下data_byte******************/
always @ (posedge clk50M or negedge rst_n)begin
if(!rst_n)
r_data_byte<=8'd0;
else if(tx_en)
r_data_byte<=data_byte;
else
r_data_byte<=r_data_byte;
end
/****************************************************/
/**********************发送模块***********************/
always @ (posedge clk50M or negedge rst_n)begin
if(!rst_n)
rs232_tx<=1'b1;
else begin
case(bps_cnt)
0:rs232_tx<=1'b1;
1:rs232_tx<=start_bit;
2:rs232_tx<=r_data_byte[0];
3:rs232_tx<=r_data_byte[1];
4:rs232_tx<=r_data_byte[2];
5:rs232_tx<=r_data_byte[3];
6:rs232_tx<=r_data_byte[4];
7:rs232_tx<=r_data_byte[5];
8:rs232_tx<=r_data_byte[6];
9:rs232_tx<=r_data_byte[7];
10:rs232_tx<=stop_bit;
default:rs232_tx<=1'b1;
endcase
end
end
/****************************************************/
/********************状态标志信号*********************/
always @ (posedge clk50M or negedge rst_n)begin
if(!rst_n)
uart_state<=1'b0;
else if(tx_en)
uart_state<=1'b1;
else if(bps_cnt==4'd11)
uart_state<=1'b0;
else
uart_state<=uart_state;
end
/****************************************************/
endmodule
这里用到了上一个发送字节例程的uart_byte_tx.v程序。
首先将上一个例程的uart_byte_tx.v代码复制到本例程中,然后需要让uart_byte_rx.v设置为Top文件。
在testbench文件中将uart_byte_tx模块例化。并且对uart_byte_rx、uart_byte_tx进行连线。
激励初始化就是用上一发送例程的testbench文件中的激励产生方式,使发送模块发送数据,连接到接收模块。当发送结束时,接收也结束,产生rx_down的同时,输出波形可以看到接收到的数据。
波形: