UART 通信 协议 (二)
文章目录
前言
上一篇博客介绍了UART通信协议的内容以及UART 发送的实现。本文接着上篇博客:UART 通信 协议 (一),主要介绍UART 接收的FPGA实现。
设计平台:Vivado IDE 2018.3
硬件平台:武汉芯路恒科技ACX720开发板
(建议PC端查看)
一、接收时序图
由上图,对于接收到的电平信号,我们一般选择在中间时刻进行采样(上升沿采样)。因为中间时刻的电平相对稳定,对应的误码几率也很小,当然还是会存在误码情况的。实际应用中,为了应对强电磁干扰。会对同一个电平信号的不同时间段多次采样,然后对采样结果进行判决。最终确定电平值。
本次设计将对同一电平的不同时刻作 21 次采样。对中间的 7 次采样结果作判决,1 的个数多于 0 的个数则判为 1,否则为 0。
二、模块设计
1.总体方案
输入输出说明:
信号 | 位宽 | 方向 | 功能 |
I_CLK | 1 | I | 时钟信号 |
I_Rst_n | 1 | I | 模块复位信号(低电平有效) |
I_UART_RX | 1 | I | 接受的UART 信号 |
I_BAUD_SELECT | 3 | I | 波特率选择 |
O_DATA | 8 | O | 输出的并行数据 |
O_FINISH | 1 | O | 完成一次传输则有效(置1) |
2.接受数据信号同步化
串口接收的UART信号是一个异步信号,和FPGA内部的逻辑不在同一个时钟域。如果不做跨时钟域处理,很容易出现亚稳态,影响数据接收。
这里用一种较为简单且常用的二级同步器来实现对数据信号 的同步化处理。
一些高速的设计中为了提高平均故障时间,会采取三级同步器。
3.边沿检测设计
在没有数据接收时I_UART_RX保持高电平,一旦出现数据信号,起始信号拉低,那么此时会出现一个下降沿。下降沿的出现就要开始检测数据了,因此检测是否出现下降沿是能够正确接收数据的前提。
下降沿检测属于边沿检测的范畴。主要是利用信号的一级寄存、二级寄存的某种逻辑运算进行边沿检测。
下降沿检测的时序图:
4.生成采样时钟
系统的时钟频率为50MHz。不同波特率对应的采样时钟计数值为:
波特率 | 计数值 |
300 | 7936-1 |
1200 | 1984-1 |
2400 | 992-1 |
9600 | 248-1 |
19200 | 124-1 |
38400 | 62-1 |
57600 | 41-1 |
115200 | 21-1 |
5.采样数据求和判决
通过对每个BIT信号的七个采样点求和,进行判决,最终确定电平值。如果结果大于等于4,那么判为1,小于等于三则判为0。由于结果大于等于4的最高位为1,小于等于3的最高位为0,所以可以直接将求和结果的最高位输出为判决结果。
三、编程实现
总体的模块编程:
module UART_RX (
//--------------------输入端口列表-----------------------//
input I_CLK,
input I_Rst_n,
input I_UART_RX,
input [2:0] I_BAUD_SELECT,
//--------------------输出端口列表-----------------------//
output reg [7:0] O_DATA,
output reg O_FINISH
);
//------------------内部参数、变量定义-------------------//
//波特率 采样时钟 相关参数
localparam BAUD_300_SAMPLE = 13'd7936-1;
localparam BAUD_1200_SAMPLE = 13'd1984-1;
localparam BAUD_2400_SAMPLE = 13'd992-1;
localparam BAUD_9600_SAMPLE = 13'd248-1;
localparam BAUD_19200_SAMPLE = 13'd124-1;
localparam BAUD_38400_SAMPLE = 13'd62-1;
localparam BAUD_57600_SAMPLE = 13'd41-1;
localparam BAUD_115200_SAMPLE = 13'd21-1;
//模块状态信号
reg R_STATE;// 模块状态 , 1表示处于busy,0表示处于idle
//二级寄存器 同步输入信号
reg R1_UART_RX;
reg R2_UART_RX;
//下降沿检测
reg R_1_NEGEDGE_DETECT;
reg R_2_NEGEDGE_DETECT;
wire W_NEGEDGE_RES;
//波特率采样时钟 计数相关
reg [12:0] R_BAUD_SAMPLE_COUNT_MAX;
reg [12:0] R_BAUD_SAMPLE_COUNT;
reg [7:0] R_SAMPLE_CLK_COUNT;
//采样时钟 产生
reg R_SAMPLE_CLK;
//中间7位采样数据 求和 结果存放
reg [2:0] R_SUM_START;
reg [2:0] R_SUM_BIT0;
reg [2:0] R_SUM_BIT1;
reg [2:0] R_SUM_BIT2;
reg [2:0] R_SUM_BIT3;
reg [2:0] R_SUM_BIT4;
reg [2:0] R_SUM_BIT5;
reg [2:0] R_SUM_BIT6;
reg [2:0] R_SUM_BIT7;
reg [2:0] R_SUM_END;
//--------------------模块的程序设计---------------------//
//*** 接收数据数据同步 跨时钟域处理
always @ (posedge I_CLK)
begin
if(!I_Rst_n)
begin
R1_UART_RX <= 0;
R2_UART_RX <= 0;
end
else
begin
R1_UART_RX <= I_UART_RX;
R2_UART_RX <= R1_UART_RX;
end
end
//*** 下降沿检测
always @ (posedge I_CLK)
begin
if(!I_Rst_n)
begin
R_1_NEGEDGE_DETECT <= 0;
R_2_NEGEDGE_DETECT <= 0;
end
else
begin
R_1_NEGEDGE_DETECT <= R2_UART_RX;
R_2_NEGEDGE_DETECT <= R_1_NEGEDGE_DETECT;
end
end
assign W_NEGEDGE_RES = (!R_1_NEGEDGE_DETECT) & (R_2_NEGEDGE_DETECT);
//*** 生成采样时钟
always @ (posedge I_CLK)
begin
if(!I_Rst_n)
begin
R_BAUD_SAMPLE_COUNT_MAX <= 0;
end
else
begin
case (I_BAUD_SELECT)
0:
begin
R_BAUD_SAMPLE_COUNT_MAX <= BAUD_300_SAMPLE;
end
1:
begin
R_BAUD_SAMPLE_COUNT_MAX <= BAUD_1200_SAMPLE;
end
2:
begin
R_BAUD_SAMPLE_COUNT_MAX <= BAUD_2400_SAMPLE;
end
3:
begin
R_BAUD_SAMPLE_COUNT_MAX <= BAUD_9600_SAMPLE;
end
4:
begin
R_BAUD_SAMPLE_COUNT_MAX <= BAUD_19200_SAMPLE;
end
5:
begin
R_BAUD_SAMPLE_COUNT_MAX <= BAUD_38400_SAMPLE;
end
6:
begin
R_BAUD_SAMPLE_COUNT_MAX <= BAUD_57600_SAMPLE;
end
7:
begin
R_BAUD_SAMPLE_COUNT_MAX <= BAUD_115200_SAMPLE;
end
default:
begin
R_BAUD_SAMPLE_COUNT_MAX <= BAUD_9600_SAMPLE;
end
endcase
end
end
always @ (posedge I_CLK)
begin
if(!I_Rst_n)
begin
R_BAUD_SAMPLE_COUNT <= 0;
end
else if(R_STATE)
begin
if (R_BAUD_SAMPLE_COUNT == R_BAUD_SAMPLE_COUNT_MAX)
begin
R_BAUD_SAMPLE_COUNT <= 0;
end
else
begin
R_BAUD_SAMPLE_COUNT <= R_BAUD_SAMPLE_COUNT + 1;
end
end
else
begin
R_BAUD_SAMPLE_COUNT <= 0;
end
end
// 采样时钟 脉宽为一个时钟周期的窄脉冲信号
always @ (posedge I_CLK)
begin
if(!I_Rst_n)
begin
R_SAMPLE_CLK <= 0;
end
else if(R_BAUD_SAMPLE_COUNT == 13'd2)
begin
R_SAMPLE_CLK <= 1;
end
else
begin
R_SAMPLE_CLK <= 0;
end
end
//*** 采样时钟 计数
always @ (negedge I_CLK)//下降沿
begin
if(!I_Rst_n)
begin
R_SAMPLE_CLK_COUNT <= 0;
end
else if((R_SAMPLE_CLK_COUNT == 8'd209) || (R_SAMPLE_CLK_COUNT == 8'd15 && (R_SUM_START >= 3)))
begin
R_SAMPLE_CLK_COUNT <= 0;
end
else if(R_SAMPLE_CLK)
begin
R_SAMPLE_CLK_COUNT <= R_SAMPLE_CLK_COUNT + 1;
end
else
begin
R_SAMPLE_CLK_COUNT <= R_SAMPLE_CLK_COUNT;
end
end
//*** 采样数据求和
always @ (negedge I_CLK)//下降沿
begin
if(!I_Rst_n)
begin
R_SUM_START <=3'd0;
R_SUM_BIT0 <=3'd0;
R_SUM_BIT1 <=3'd0;
R_SUM_BIT2 <=3'd0;
R_SUM_BIT3 <=3'd0;
R_SUM_BIT4 <=3'd0;
R_SUM_BIT5 <=3'd0;
R_SUM_BIT6 <=3'd0;
R_SUM_BIT7 <=3'd0;
R_SUM_END <=3'd0;
end
else if(R_SAMPLE_CLK)
begin
case (R_SAMPLE_CLK_COUNT)
0:
begin
R_SUM_START <=3'd0;
R_SUM_BIT0 <=3'd0;
R_SUM_BIT1 <=3'd0;
R_SUM_BIT2 <=3'd0;
R_SUM_BIT3 <=3'd0;
R_SUM_BIT4 <=3'd0;
R_SUM_BIT5 <=3'd0;
R_SUM_BIT6 <=3'd0;
R_SUM_BIT7 <=3'd0;
R_SUM_END <=3'd0;
end
8,9,10,11,12,13,14:
begin
R_SUM_START <= R_SUM_START + R2_UART_RX;
end
29,30,31,32,33,34,35:
begin
R_SUM_BIT0 <= R_SUM_BIT0 + R2_UART_RX;
end
50,51,52,53,54,55,56:
begin
R_SUM_BIT1 <= R_SUM_BIT1 + R2_UART_RX;
end
71,72,73,74,75,76,77:
begin
R_SUM_BIT2 <= R_SUM_BIT2 + R2_UART_RX;
end
92,93,94,95,96,97,98:
begin
R_SUM_BIT3 <= R_SUM_BIT3 + R2_UART_RX;
end
113,114,115,116,117,118,119:
begin
R_SUM_BIT4 <= R_SUM_BIT4 + R2_UART_RX;
end
134,135,136,137,138,139,140:
begin
R_SUM_BIT5 <= R_SUM_BIT5 + R2_UART_RX;
end
155,156,157,158,159,160,161:
begin
R_SUM_BIT6 <= R_SUM_BIT6 + R2_UART_RX;
end
176,177,178,179,180,181,182:
begin
R_SUM_BIT7 <= R_SUM_BIT7 + R2_UART_RX;
end
197,198,199,200,201,202,203:
begin
R_SUM_END <= R_SUM_END + R2_UART_RX;
end
default:
begin
R_SUM_START <= R_SUM_START;
R_SUM_BIT0 <= R_SUM_BIT0 ;
R_SUM_BIT1 <= R_SUM_BIT1 ;
R_SUM_BIT2 <= R_SUM_BIT2 ;
R_SUM_BIT3 <= R_SUM_BIT3 ;
R_SUM_BIT4 <= R_SUM_BIT4 ;
R_SUM_BIT5 <= R_SUM_BIT5 ;
R_SUM_BIT6 <= R_SUM_BIT6 ;
R_SUM_BIT7 <= R_SUM_BIT7 ;
R_SUM_END <= R_SUM_END ;
end
endcase
end
end
//判决
always @ (posedge I_CLK)
begin
if(!I_Rst_n)
begin
O_DATA <= 0;
end
else if(R_SAMPLE_CLK_COUNT == 8'd209)
begin
O_DATA[0] <= R_SUM_BIT0[2];
O_DATA[1] <= R_SUM_BIT1[2];
O_DATA[2] <= R_SUM_BIT2[2];
O_DATA[3] <= R_SUM_BIT3[2];
O_DATA[4] <= R_SUM_BIT4[2];
O_DATA[5] <= R_SUM_BIT5[2];
O_DATA[6] <= R_SUM_BIT6[2];
O_DATA[7] <= R_SUM_BIT7[2];
end
end
//*** 传输完成信号
always @ (posedge I_CLK)
begin
if(!I_Rst_n)
begin
O_FINISH <= 0;
end
else if(R_SAMPLE_CLK_COUNT == 8'd209)
begin
O_FINISH <= 1;
end
else
begin
O_FINISH <= 0;
end
end
//*** 模块状态信号
always @ (posedge I_CLK)
begin
if(!I_Rst_n)
begin
R_STATE <= 0;
end
else if(W_NEGEDGE_RES)
begin
R_STATE <= 1;
end
else if(O_FINISH || (R_SAMPLE_CLK_COUNT == 8'd15 && (R_SUM_START >= 3)) || (R_SAMPLE_CLK_COUNT == 8'd204 && (R_SUM_END <= 3)))
begin
R_STATE <= 0;
end
else
begin
R_STATE <= R_STATE;
end
end
endmodule
四、仿真验证
利用了上一篇博客,UART发送模块与UART接收模块进行对接。编写testbench:
`timescale 1ns/1ps
module TB_UART_RX ();
//-----------------被测试模块输入接口声明--------------------//
// TX
reg I_CLK;
reg I_Rst_n;
reg I_VALID;
reg [7:0] I_DATA;
// RX
wire I_UART_RX;
//----------------被测试模块的输出接口声明-------------------//
// RX
wire [7:0] O_DATA;
wire O_FINISH_RX;
// TX
wire O_STATE;
wire O_FINISH_TX;
//-----------------------测试程序---------------------------//
//产生激励时钟
`define CLK_PERIOD 20
initial I_CLK = 0 ;
always #(`CLK_PERIOD/2) I_CLK = ~ I_CLK;
//初始化控制信号
initial
begin
I_Rst_n = 0;
I_DATA = 8'b0;
I_VALID = 0;
// I_BAUD_SELECT = 3'd3;
#(`CLK_PERIOD * 10 +2);
I_Rst_n = 1;
#(`CLK_PERIOD * 10);
//发送数据 1
I_DATA = 8'b0110_1001;
I_VALID = 1;
#(`CLK_PERIOD * 1);
I_VALID = 0;
@(posedge O_FINISH_TX);
#(`CLK_PERIOD * 1000);
//发送数据 2
I_DATA = 8'b1110_1011;
I_VALID = 1;
#(`CLK_PERIOD * 1);
I_VALID = 0;
@(posedge O_FINISH_TX);
#(`CLK_PERIOD * 50);
$stop;
end
//被测试模块例化 TX
UART_TX inst_UART_TX
(
.I_CLK (I_CLK),
.I_Rst_n (I_Rst_n),
.I_VALID (I_VALID),
.I_DATA (I_DATA),
.I_BAUD_SELECT (3'd3),
.O_STATE (O_STATE),
.O_DATA (I_UART_RX),
.O_FINISH (O_FINISH_TX)
);
//被测试模块例化 RX
UART_RX inst_UART_RX
(
.I_CLK (I_CLK),
.I_Rst_n (I_Rst_n),
.I_UART_RX (I_UART_RX),
.I_BAUD_SELECT (3'd3),
.O_DATA (O_DATA),
.O_FINISH (O_FINISH_RX)
);
endmodule
仿真波形:
五、板级调试验证
验证思路:利用VIO IP核进行数据接收。配置如下:
编写顶层模块:
module TOP_UART_RX_TEST (
//--------------------输入端口列表-----------------------//
input I_CLK,
input I_Rst_n,
input I_UART_RX
);
//------------------内部参数、变量定义-------------------//
wire [7:0] O_DATA;
wire O_FINISH;
reg [7:0] R_O_DATA;
//--------------------模块的程序设计---------------------//
UART_RX inst_UART_RX
(
.I_CLK (I_CLK),
.I_Rst_n (I_Rst_n),
.I_UART_RX (I_UART_RX),
.I_BAUD_SELECT (3'd3),
.O_DATA (O_DATA),
.O_FINISH (O_FINISH)
);
always @ (posedge I_CLK)
begin
if(!I_Rst_n)
begin
R_O_DATA <= 0;
end
else if(O_FINISH)
begin
R_O_DATA <= O_DATA;
end
else
begin
R_O_DATA <= R_O_DATA;
end
end
UART_VIO_RX your_instance_name (
.clk(I_CLK), // input wire clk
.probe_in0(R_O_DATA) // input wire [7 : 0] probe_in0
);
endmodule
然后对设计进行综合、布局布线、约束、实现,下载比特流文件。结合串口助手进行相关调试。
参考说明
[1].小梅哥 Xilinx FPGA 自学教程 v2.0