前言
UART,Universal Asynchronous Receiver/Transmitter,通用异步收发器。是一种最为简单的通信协议。在发送数据时,将并行的数据转为串行;数据接收时,将串行数据转为并行数据。UART 可以实现全双工通信。本文将以FPGA作为硬件平台,通过编程实现,UART 数据发送。包括功能仿真、板级验证。
设计平台:Vivado IDE 2018.3
硬件平台:武汉芯路恒科技ACX720开发板
(建议PC端查看)
一、UART 协议
UART 通信协议包括有很多,RS232、RS449、RS423、RS422、RS485等,他们各自规定了通信口的电气特性、传输速率、连接特性、接口机械特性等。本文主要介绍RS232串行数据通信的接口标准,广泛应用在计算机串行接口外设。针脚定义和功能如下:

功能说明:
| 针脚序号 | 功能名称 | 英文全称 | 意义 |
| Pin1 | DCD | Data Carrier Detect | 数据载波检测 |
| Pin2 | RXD | Receive Data | 数据接收线 |
| Pin3 | TXD | Transmit Data | 数据发送线 |
| Pin4 | DTR | Data Terminal Ready | 电脑准备好 |
| Pin5 | GND | Ground | 地线 |
| Pin6 | DSR | Data Set Ready | 调制解调器准备好 |
| Pin7 | RTS | Request To Send | 电脑要求调制解调器提交数据 |
| Pin8 | CTS | Clear To Send | 调制解调器通知电脑传数据 |
| Pin9 | RI | Ring Indicator | 调制解调器通知电脑有振铃 |
UART 协议要点:
- 数据位:UART 通信时,传输的数据位数;一般可选择:5、6、7、8位;
- 波特率:通信的收发设备双方传输数据的速率,单位:bps;常用的波特率:300、1200、2400、9600、19200、115200bps;收发双方的通信波特率要彼此匹配(相等)。
- 奇偶校验位:在数据后跟随一位奇偶校验位,以在接收端判断传输差错情况。奇校验,即增加一位校验位,使得1的个数为奇数个;偶校验,即增加一位校验位,使得1的个数为偶数个。数据位数等于8时,无校验位。
- 停止位:数据位发送完后,发送停止位。
RS232标准中,常用的配置是8位数据加1位停止位。时序图为:

二、编程实现
模块总体设计,根据设计的功能需求,设计一个RS232标准的 UART 发送模块。

输入输出说明:
| 名称 | 方向 | 位宽 | 含义 |
| I_CLK | I | 1 | 时钟信号(50MHz) |
| I_Rst_n | I | 1 | 复位信号,低电平有效(异步复位) |
| I_VALID | I | 1 | 输入有效信号,有效时表示输入的数据可进行传输 |
| I_BAUD_SELECT | I | 3 | 波特率选择 |
| I_DATA | I | 8 | 待传输数据 |
| O_FINISH | O | 1 | 高电平有效,有效时表示,一次发送完成 |
| O_DATA | O | 1 | 输出的串行数据 |
| O_STATE | O | 1 | 发送模块的状态,空闲时为0 |
波特率选择:
| I_BAUD_SELECT | 波特率(bps) |
| 0 | 300 |
| 1 | 1200 |
| 2 | 2400 |
| 3 | 9600 |
| 4 | 19200 |
| 5 | 38400 |
| 6 | 57600 |
| 7 | 115200 |
设计程序:
module UART_TX (
//--------------------输入端口列表-----------------------//
input I_CLK,
input I_Rst_n,
input I_VALID,//注意 有效信号要伴随 有效的数据信号 一同送入 ,是 *窄脉冲* 信号
input [7:0] I_DATA,
input [2:0] I_BAUD_SELECT,
//--------------------输出端口列表-----------------------//
output reg O_STATE,
output reg O_DATA,
output reg O_FINISH
);
//------------------内部参数、变量定义-------------------//
//波特率定义
localparam BAUD_300 = 18'd166667-1;
localparam BAUD_1200 = 18'd41667-1;
localparam BAUD_2400 = 18'd20833-1;
localparam BAUD_9600 = 18'd5208-1;
localparam BAUD_19200 = 18'd2604-1;
localparam BAUD_38400 = 18'd1302-1;
localparam BAUD_57600 = 18'd868-1;
localparam BAUD_115200 = 18'd434-1;
//计数器相关
reg [17:0] R_COUNTER_MAX;
reg [17:0] R_COUNTER;//为产生 波特率时钟所用的计数器
reg [3:0] R_BIT_CONUTER;//记录 传输的 BIT数
//波特率时钟
reg R_BAUD_CLK;
//输入寄存
reg [7:0] R_I_DATA;
//--------------------模块的程序设计---------------------//
//保证数据稳定性,对输入数据寄存(打一拍)
always @ (posedge I_CLK or negedge I_Rst_n)
begin
if(!I_Rst_n)
begin
R_I_DATA <= 0;
end
else
begin
if (I_VALID)
begin
R_I_DATA <= I_DATA;
end
else
begin
R_I_DATA <= R_I_DATA;
end
end
end
//波特率选择
always @ (posedge I_CLK or negedge I_Rst_n)
begin
if(!I_Rst_n)
begin
R_COUNTER_MAX <= 0;
end
else
begin
case (I_BAUD_SELECT)
0:
begin
R_COUNTER_MAX <= BAUD_300;
end
1:
begin
R_COUNTER_MAX <= BAUD_1200;
end
2:
begin
R_COUNTER_MAX <= BAUD_2400;
end
3:
begin
R_COUNTER_MAX <= BAUD_9600;
end
4:
begin
R_COUNTER_MAX <= BAUD_19200;
end
5:
begin
R_COUNTER_MAX <= BAUD_38400;
end
6:
begin
R_COUNTER_MAX <= BAUD_57600;
end
7:
begin
R_COUNTER_MAX <= BAUD_115200;
end
default:
begin
R_COUNTER_MAX <= BAUD_9600;
end
endcase
end
end
//生成波特率时钟
always @ (posedge I_CLK or negedge I_Rst_n)
begin
if(!I_Rst_n)
begin
R_COUNTER <= 0;
end
else if(O_STATE)
begin
if (R_COUNTER == R_COUNTER_MAX)
begin
R_COUNTER <= 0;
end
else
begin
R_COUNTER <= R_COUNTER + 1;
end
end
else
begin
R_COUNTER <= 0;
end
end
//脉冲宽度为 1个时钟周期 的窄脉冲
always @ (posedge I_CLK or negedge I_Rst_n)
begin
if(!I_Rst_n)
begin
R_BAUD_CLK <= 0;
end
else
begin
if (R_COUNTER == 1)
begin
R_BAUD_CLK = 1;
end
else
begin
R_BAUD_CLK = 0;
end
end
end
//波特率时钟的脉冲计数器
always @ (negedge I_CLK or negedge I_Rst_n)
begin
if(!I_Rst_n)
begin
R_BIT_CONUTER <= 0;
end
else
begin
if (R_BIT_CONUTER == 4'd11)
begin
R_BIT_CONUTER <= 0;
end
else if(R_BAUD_CLK)
begin
R_BIT_CONUTER <= R_BIT_CONUTER + 1;
end
else
begin
R_BIT_CONUTER <= R_BIT_CONUTER;
end
end
end
//发送完成 标志信号
always @ (posedge I_CLK or negedge I_Rst_n)
begin
if(!I_Rst_n)
begin
O_FINISH <= 0;
end
else
begin
if (R_BIT_CONUTER == 4'd11)
begin
O_FINISH <= 1;
end
else
begin
O_FINISH <= 0;
end
end
end
//模块工作的 状态信号
always @ (posedge I_CLK or negedge I_Rst_n)
begin
if(!I_Rst_n)
begin
O_STATE <= 0;
end
else if(I_VALID)
begin
O_STATE <= 1;
end
else if(R_BIT_CONUTER == 4'd11)
begin
O_STATE <= 0;
end
else
begin
O_STATE <= O_STATE;
end
end
//UART 串行输出 先发低位
always @ (posedge I_CLK or negedge I_Rst_n)
begin
if(!I_Rst_n)
begin
O_DATA <= 1;
end
else
begin
case (R_BIT_CONUTER)
1:
begin
O_DATA <= 0;//起始位
end
2:
begin
O_DATA <= R_I_DATA[0];
end
3:
begin
O_DATA <= R_I_DATA[1];
end
4:
begin
O_DATA <= R_I_DATA[2];
end
5:
begin
O_DATA <= R_I_DATA[3];
end
6:
begin
O_DATA <= R_I_DATA[4];
end
7:
begin
O_DATA <= R_I_DATA[5];
end
8:
begin
O_DATA <= R_I_DATA[6];
end
9:
begin
O_DATA <= R_I_DATA[7];
end
10:
begin
O_DATA <= 1;//停止位
end
default:
begin
O_DATA <= 1;
end
endcase
end
end
endmodule三、仿真验证
编写 testbench 进行行为级仿真:
`timescale 1ns/1ps
module TB_UART_TX ();
//-----------------被测试模块输入接口声明--------------------//
reg I_CLK;
reg I_Rst_n;
reg I_VALID;
reg [7:0] I_DATA;
reg [2:0] I_BAUD_SELECT;
//----------------被测试模块的输出接口声明-------------------//
wire O_STATE;
wire O_DATA;
wire O_FINISH;
//-----------------------测试程序---------------------------//
//产生激励时钟
`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);
#(`CLK_PERIOD * 10);
//发送数据 2
I_DATA = 8'b1110_1011;
I_VALID = 1;
#(`CLK_PERIOD * 1);
I_VALID = 0;
@(posedge O_FINISH);
#(`CLK_PERIOD * 10);
$stop;
end
//被测试模块例化
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 (I_BAUD_SELECT),
.O_STATE (O_STATE),
.O_DATA (O_DATA),
.O_FINISH (O_FINISH)
);
endmodule仿真波形:

四、板级验证
板级验证的基本思路:用VIO IP 核控制 UART_TX 模块。首先生成 VIO IP核。
1、产生、配置 VIO IP 核

2、例化 VIO IP ,编写顶层文件

顶层文件:
module TOP_UART_TX_TEST (
//--------------------输入端口列表-----------------------//
input I_CLK,
input I_Rst_n,
//--------------------输出端口列表-----------------------//
output O_DATA,
output O_STATE //用板子上的 LED 指示
);
//------------------内部参数、变量定义-------------------//
wire I_VALID;
wire [7:0] I_DATA;
wire W_VALID;
reg R_1_VALID;
reg R_2_VALID;
//--------------------模块的程序设计---------------------//
//上升沿检测
always @ (posedge I_CLK)
begin
R_1_VALID <= W_VALID;
R_2_VALID <= R_1_VALID;
end
assign I_VALID = R_1_VALID & !R_2_VALID;
//例化 VIO IP
VIO_CTRL_UART_TX your_instance_name (
.clk(I_CLK), // input wire clk
.probe_out0(W_VALID), // output wire [0 : 0] probe_out0
.probe_out1(I_DATA) // output wire [7 : 0] probe_out1
);
//例化 UART_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'b011),//波特率 9600
.O_STATE (O_STATE),
.O_DATA (O_DATA),
.O_FINISH ()
);
endmodule3、综合

打开综合后的网表
设置IO规划

分配引脚、电平

实现

生成比特流
打开硬件管理器
连接仿真器、设备上电
打开串口接收:

参考说明
[1].小梅哥 Xilinx FPGA 自学教程 v2.0
[2].博客-1
[3].博客-2
