一、UART串口接收原理
1.起始位检测
串口处于空闲状态时,tx为高电平,故接收模块rx端口接收到的也是高电平。起始位为低电平,所以检测起始位只需要检测rx的下降沿即可,检测电路如下,采用二级D触发器解决亚稳态问题。
2.采样
在实验室条件下,串口发送的数据通常是稳定的,在接收时取数据中点作为接收数据。
在工业环境下,干扰较多,信号中通常会产生冲激,用取数据中点的方法容易受到脉冲信号的影响,故用采样的方法保证接收数据的准确性。
上图为单bit数据接收的示意图,将每位数据等分成16份,两边标红的部分在数据跳变的边沿处,容易出现干扰,因此对中间绿色部分的稳定数据进行采样,采样第5-11份,共采样7次。
二、UART串口接收模块设计
1.模块端口说明
端口名称 | 方向 | 说明 |
---|---|---|
Clk | input | 系统时钟 |
Rst_n | input | 系统复位 |
rx | input | 串口接收端口 |
baud_set[2:0] | input | 波特率设置 |
data[7:0] | output | 8位并行接收数据 |
rx_done | output | 串口接收完成标志 |
uart_state | output | 串口接收状态标志 |
模块检测到起始位,即rx下降沿时,开始接收数据,uart_state置1。baud_set是数值为0-7的3位二进制数,分别对应波特率2400、4800、9600、19200、38400、57600、115200、256000;当串口接收完成时,rx_done置1,uart_state置0。
2.波特率设置表格
波特率是指每秒通信数据的比特个数,则每1/bps秒发送1bit数据,则波特率分频计数时间为
1
∗
1
0
9
/
b
p
s
1∗10^9/ bps
1∗109/bps ns,将数据等分为16份,对中间7份进行采样,系统时钟为50Mhz,得出分频计数值计算公式:
分频计数值
=
1
∗
1
0
9
/
b
p
s
/
16
/
20
−
1
分频计数值=1*10^9/bps/16/20-1
分频计数值=1∗109/bps/16/20−1
根据公式计算得出如下表格:
baud_set | 波特率 | 波特率分频计数值 |
---|---|---|
0 | 2400 | 1301 |
1 | 4800 | 650 |
2 | 9600 | 324 |
3 | 19200 | 161 |
4 | 38400 | 80 |
5 | 57600 | 53 |
6 | 115200 | 26 |
7 | 384000 | 11 |
3.代码设计
和串口发送模块的设计类似,接收模块可包括以下7个部分:起始位检测、串口接收状态标志uart_state赋值、波特率分频计数最大值cnt_bpsmax赋值、波特率时钟分频计数器、波特率时钟计数器、数据采样接收、串口接收完成标志rx_done赋值。
(1)起始位检测
s0_Rx为二级同步寄存器,用于解决亚稳态问题,s1_Rx为数据寄存器,存储前一时钟周期rx值,用于判断起始位。
reg [1:0] s0_Rx; //同步寄存器,用于解决亚稳态问题
reg s1_Rx; //数据寄存器,用于判断起始位下降沿
wire nedge; //判断起始位下降沿
//二级同步寄存器,消除亚稳态
always@(posedge Clk or negedge Rst_n)
if(!Rst_n)
s0_Rx <= 2'b0;
else
s0_Rx <= {s0_Rx[0],rx};
//数据寄存器,储存上一周期的rx
always@(posedge Clk or negedge Rst_n)
if(!Rst_n)
s1_Rx <= 1'b0;
else
s1_Rx <= s0_Rx[1];
assign nedge = s1_Rx && !s0_Rx[1];
起始位接收错误标志:
将每一位数据分为16份,取第5-11份进行采样,故波特率时钟计数器bps_cnt=12时,起始位已接收完成,此时判断起始位是否错误,若起始位错误,则START_BIT[2]=1。
wire START_ERR; //起始位接收错误标志
assign START_ERR = (bps_cnt == 8'd12) && START_BIT[2];
(2)串口接收状态标志uart_state赋值
当检测到起始位时,uart_state赋值为1;当停止位接收完毕时或起始位接收错误时,串口接收完成或停止,uart_state赋值为0。
always@(posedge Clk or negedge Rst_n)
if(!Rst_n)
uart_state <= 1'b0;
else if(nedge)
uart_state <= 1'b1;
else if(bps_cnt == 8'd159 || START_ERR)
uart_state <= 1'b0;
(3)波特率分频计数最大值cnt_bpsmax赋值
根据上面计算出的波特率设置表格对cnt_bpsmax进行赋值。复位时,cnt_bpsmax为324,表示默认波特率为9600。
always@(posedge Clk or negedge Rst_n)//波特率选择
if(!Rst_n)
cnt_bpsmax <= 16'd324;//默认波特率9600
else
case(baud_set)
0:cnt_bpsmax <= 16'd1301; //波特率2400
1:cnt_bpsmax <= 16'd650; //波特率4800
2:cnt_bpsmax <= 16'd324; //波特率9600
3:cnt_bpsmax <= 16'd161; //波特率19200
4:cnt_bpsmax <= 16'd80; //波特率38400
5:cnt_bpsmax <= 16'd53; //波特率57600
6:cnt_bpsmax <= 16'd26; //波特率115200
7:cnt_bpsmax <= 16'd11; //波特率256000
default:cnt_bpsmax <= 16'd324;
endcase
(4)波特率时钟分频计数器
得到波特率分频计数最大值后,编写波特率时钟分频计数器。
always@(posedge Clk or negedge Rst_n)//分频计数
if(!Rst_n)
cnt <= 16'd0;
else if(uart_state) begin
if(cnt == cnt_bpsmax)
cnt <= 16'd0;
else
cnt <= cnt + 1'b1;
end
else
cnt <= 16'b0;
(5)波特率时钟计数器
共接收10位:起始位、8位data、停止位,每位分为16份,bps_cnt计数160次。起始位接收错误时,波特率时钟停止计数。
always@(posedge Clk or negedge Rst_n)//波特率时钟计数
if(!Rst_n)
bps_cnt <= 8'd0;
else if(uart_state) begin
if(bps_cnt == 8'd159 || START_ERR)
bps_cnt <= 8'd0;
else if(cnt == 16'b1)
bps_cnt <= bps_cnt + 1'b1;
else
bps_cnt <= bps_cnt;
end
else
bps_cnt <= 8'd0;
(6)数据采样接收
每位数据采样7次,对7次采样结果累加,若结果为3、4、5、6,则认为该数据为1,若结果为0、1、2,则认为该数据为0。将每位数据的采样结果存储在位宽为3的寄存器中:
reg [2:0] rx_data [7:0];//8个3位寄存器
reg [2:0] START_BIT;
reg [2:0] STOP_BIT;
起始位在bps_cnt为5-11时采样,其它位数据在此基础上加16的倍数,每次采样将当前rx值s0_Rx[1]累加到起始位寄存器START_BIT中。
always@(posedge Clk or negedge Rst_n)//数据采样接收
if(!Rst_n) begin
START_BIT <= 3'b0;
for(i=0;i<8;i=i+1)
rx_data[i] <= 3'b0;
STOP_BIT <= 3'b0;
end
else if(cnt == 16'b1) begin
case(bps_cnt)
0:begin
START_BIT <= 3'b0;
for(i=0;i<8;i=i+1)
rx_data[i] <= 3'b0;
STOP_BIT <= 3'b0;
end
5, 6, 7, 8, 9, 10, 11: START_BIT <= START_BIT + s0_Rx[1];
21, 22, 23, 24, 25, 26, 27: rx_data[0] <= rx_data[0] + s0_Rx[1];
37, 38, 39, 40, 41, 42, 43: rx_data[1] <= rx_data[1] + s0_Rx[1];
53, 54, 55, 56, 57, 58, 59: rx_data[2] <= rx_data[2] + s0_Rx[1];
69, 70, 71, 72, 73, 74, 75: rx_data[3] <= rx_data[3] + s0_Rx[1];
85, 86, 87, 88, 89, 90, 91: rx_data[4] <= rx_data[4] + s0_Rx[1];
101,102,103,104,105,106,107: rx_data[5] <= rx_data[5] + s0_Rx[1];
117,118,119,120,121,122,123: rx_data[6] <= rx_data[6] + s0_Rx[1];
133,134,135,136,137,138,139: rx_data[7] <= rx_data[7] + s0_Rx[1];
149,150,151,152,153,154,155: STOP_BIT <= STOP_BIT + s0_Rx[1];
default;
endcase
end
else begin
START_BIT <= START_BIT;
for(i=0;i<8;i=i+1)
rx_data[i] <= rx_data[i];
STOP_BIT <= STOP_BIT;
end
数据全部采样完成时,对采样数据解码,计算每位数据的具体数值,当7次采样值大于等于3时,寄存器第2位为1,该位数据值也为1,因此rx_data[i][2]可作为每位数据的值,将其存储到data中。
always@(posedge Clk or negedge Rst_n)//采样数据解码
if(!Rst_n)
data <= 8'b0;
else if(bps_cnt == 8'd159)
for(i=0;i<8;i=i+1)
data[i] <= rx_data[i][2];
else
data <= data;
(7)串口接收完成标志rx_done赋值
停止位接收完成时,串口接收结束,rx_done置1。
always@(posedge Clk or negedge Rst_n)
if(!Rst_n)
rx_done <= 1'b0;
else if(bps_cnt == 8'd159)
rx_done <= 1'b1;
else
rx_done <= 1'b0;
串口接收源代码
//本模块用于实现串口数据接收,系统频率为50Mhz,时钟周期为20ns
module uart_data_rx(
input Clk, //系统时钟
input Rst_n, //系统复位
input rx, //串口接收端口
input [2:0] baud_set, //波特率设置
output reg [7:0] data, //8位并行接收数据
output reg rx_done, //串口接收完成标志
output reg uart_state //串口接收状态标志
);
reg [1:0] s0_Rx; //同步寄存器,用于解决亚稳态问题
reg s1_Rx; //数据寄存器,用于判断起始位下降沿
wire nedge; //判断起始位下降沿
wire START_ERR; //起始位接收错误标志
reg [15:0] cnt; //波特率分频计数器
reg [15:0] cnt_bpsmax; //波特率分频计数最大值
reg [7:0] bps_cnt; //波特率时钟计数器
reg [2:0] rx_data [7:0];//8个3位寄存器
reg [2:0] START_BIT;
reg [2:0] STOP_BIT;
integer i;//for循环使用
//二级同步寄存器,消除亚稳态
always@(posedge Clk or negedge Rst_n)
if(!Rst_n)
s0_Rx <= 2'b0;
else
s0_Rx <= {s0_Rx[0],rx};
//数据寄存器,储存上一周期的rx
always@(posedge Clk or negedge Rst_n)
if(!Rst_n)
s1_Rx <= 1'b0;
else
s1_Rx <= s0_Rx[1];
assign nedge = s1_Rx && !s0_Rx[1];
assign START_ERR = (bps_cnt == 8'd12) && START_BIT[2];
always@(posedge Clk or negedge Rst_n)//波特率选择
if(!Rst_n)
cnt_bpsmax <= 16'd324;//默认波特率9600
else
case(baud_set)
0:cnt_bpsmax <= 16'd1301; //波特率2400
1:cnt_bpsmax <= 16'd650; //波特率4800
2:cnt_bpsmax <= 16'd324; //波特率9600
3:cnt_bpsmax <= 16'd161; //波特率19200
4:cnt_bpsmax <= 16'd80; //波特率38400
5:cnt_bpsmax <= 16'd53; //波特率57600
6:cnt_bpsmax <= 16'd26; //波特率115200
7:cnt_bpsmax <= 16'd11; //波特率256000
default:cnt_bpsmax <= 16'd324;
endcase
always@(posedge Clk or negedge Rst_n)//分频计数
if(!Rst_n)
cnt <= 16'd0;
else if(uart_state) begin
if(cnt == cnt_bpsmax)
cnt <= 16'd0;
else
cnt <= cnt + 1'b1;
end
else
cnt <= 16'b0;
always@(posedge Clk or negedge Rst_n)//波特率时钟计数
if(!Rst_n)
bps_cnt <= 8'd0;
else if(uart_state) begin
if(bps_cnt == 8'd159 || START_ERR)
bps_cnt <= 8'd0;
else if(cnt == 16'b1)
bps_cnt <= bps_cnt + 1'b1;
else
bps_cnt <= bps_cnt;
end
else
bps_cnt <= 8'd0;
always@(posedge Clk or negedge Rst_n)//数据采样接收
if(!Rst_n) begin
START_BIT <= 3'b0;
for(i=0;i<8;i=i+1)
rx_data[i] <= 3'b0;
STOP_BIT <= 3'b0;
end
else if(cnt == 16'b1) begin
case(bps_cnt)
0:begin
START_BIT <= 3'b0;
for(i=0;i<8;i=i+1)
rx_data[i] <= 3'b0;
STOP_BIT <= 3'b0;
end
5, 6, 7, 8, 9, 10, 11: START_BIT <= START_BIT + s0_Rx[1];
21, 22, 23, 24, 25, 26, 27: rx_data[0] <= rx_data[0] + s0_Rx[1];
37, 38, 39, 40, 41, 42, 43: rx_data[1] <= rx_data[1] + s0_Rx[1];
53, 54, 55, 56, 57, 58, 59: rx_data[2] <= rx_data[2] + s0_Rx[1];
69, 70, 71, 72, 73, 74, 75: rx_data[3] <= rx_data[3] + s0_Rx[1];
85, 86, 87, 88, 89, 90, 91: rx_data[4] <= rx_data[4] + s0_Rx[1];
101,102,103,104,105,106,107: rx_data[5] <= rx_data[5] + s0_Rx[1];
117,118,119,120,121,122,123: rx_data[6] <= rx_data[6] + s0_Rx[1];
133,134,135,136,137,138,139: rx_data[7] <= rx_data[7] + s0_Rx[1];
149,150,151,152,153,154,155: STOP_BIT <= STOP_BIT + s0_Rx[1];
default;
endcase
end
else begin
START_BIT <= START_BIT;
for(i=0;i<8;i=i+1)
rx_data[i] <= rx_data[i];
STOP_BIT <= STOP_BIT;
end
always@(posedge Clk or negedge Rst_n)//采样数据解码
if(!Rst_n)
data <= 8'b0;
else if(bps_cnt == 8'd159)
for(i=0;i<8;i=i+1)
data[i] <= rx_data[i][2];
else
data <= data;
always@(posedge Clk or negedge Rst_n)
if(!Rst_n)
rx_done <= 1'b0;
else if(bps_cnt == 8'd159)
rx_done <= 1'b1;
else
rx_done <= 1'b0;
always@(posedge Clk or negedge Rst_n)
if(!Rst_n)
uart_state <= 1'b0;
else if(nedge)
uart_state <= 1'b1;
else if(bps_cnt == 8'd159 || START_ERR)
uart_state <= 1'b0;
endmodule
三、仿真
1.仿真文件
仿真时,需将之前写好的串口发送模块例化进来,模拟PC端串口发送数据8’hab和8’h23,由接收模块接收。
`timescale 1ns/1ns
module uart_data_rx_tb();
//共用参数
reg Clk,Rst_n;
reg [2:0] baud_set;
wire rx_tx;//将tx连接rx
//uart发送模块参数
reg send_en;
reg [7:0] data_tx;
wire uart_state_tx,tx_done;
//uart接收模块参数
wire rx_done;
wire uart_state_rx;
wire [7:0] data_rx;
uart_data_rx uart_data_rx(
.Clk(Clk),
.Rst_n(Rst_n),
.rx(rx_tx),
.baud_set(baud_set),
.data(data_rx),
.rx_done(rx_done),
.uart_state(uart_state_rx)
);
uart_data_tx PC_tx(
.Clk(Clk),
.Rst_n(Rst_n),
.send_en(send_en),
.data(data_tx),
.baud_set(baud_set),
.tx(rx_tx),
.tx_done(tx_done),
.uart_state(uart_state_tx)
);
initial Clk = 1;
always #10 Clk = ~Clk;
initial begin
Rst_n = 1'b0;
send_en = 1'b0;
data_tx = 8'h0;
baud_set = 3'd2;
#201;//20+1为了和其他时钟错开,便于观察结果
Rst_n = 1'b1;
#200;
data_tx = 8'hab;
send_en = 1'b1;
#20;
send_en = 1'b0;
@(posedge tx_done);//等待发送完成
#5000000;
data_tx = 8'h23;
send_en = 1'b1;
#20;
send_en = 1'b0;
@(posedge tx_done);
#5000000;
$stop;
end
endmodule