一、UART简介与实验任务
1.1 UART简介
UART即通用异步收发器,串口是串行接口的简称,两者组合起来就是通用异步串行通信接口,它包括了 RS232、RS499、RS423、RS422 和 RS485等接口标准规范和总线标准规范,因此串口广泛应用于嵌入式、工业控制等领域。
1)数据通信的概念
通信方式在日常的应用中一般分为串行通信和并行通信。并行通信:并行通信是指多比特数据同时通过并行线进行传送,一般以字或字节为单位并行进行传输,这种传输方式用的通信线多、成本高,故不宜进行远距离通信,因此并行通信一般用于近距离的通信,通常传输距离小于 30 米;串行通信是指数据在一条数据线上,一比特接一比特地按顺序传送的方式,串行通信的的特点:一是节省传输线,大大降低了使用成本,二是数据传送速度慢,这一点在大位宽的数据传输上尤为明显。
串行通信一般有 2 种通信方式:同步串行通信和异步串行通信。同步串行通信需要通信双方在同一时钟的控制下同步传输数据;异步串行通信是指具有不规则数据段传送特性的串行数据传输。在常见的通信总线协议中,I2C,SPI 属于同步通信而 UART 属于异步通信。
1.2 UART通信协议介绍
UART 在发送或接收过程中的一帧数据由 4 部分组成,起始位、数据位、奇偶校验位和停止位。如下图所示:
**起始位:**当不传输数据时,UART 数据传输线通常保持高电压电平。若要开始数据传输,发送 UART会将传输线从高电平拉到低电平并保持 1 个波特率周期。
数据帧:数据帧包含所传输的实际数据。如果使用奇偶校验位,数据帧长度可以是 5 位到 8 位。如果不使用奇偶校验位,数据帧长度可以是 9 位。在大多数情况下,数据以最低有效位优先方式发送。
奇偶校验:奇偶性描述数字是偶数还是奇数。通过奇偶校验位,接收 UART 判断传输期间是否有数据发生改变。
停止位:为了表示数据包结束,发送 UART 将数据传输线从低电压驱动到高电压并保持 1 到 2 位时间。
数据位可选择为 5、6、7、8 位,其中 8 位数据位是最常用的,在实际应用中一般都选择 8 位数据位;校验位可选择奇校验、偶校验或者无校验位;停止位可选择 1 位(默认),1.5 或 2 位。串口通信的速率用波特率表示,它表示每秒传输二进制数据的位数,单位是 bps(位/秒),常用的波特率有 9600、19200、38400、57600 以及 115200 等。“波特率”,常用符号“Baud”表示,其单位为“波特每(Bps)。 故若115200 的波特率,串口发送或者接收 1bit 数据的时间为一个波特,即 1/115200s。
1.3 实验任务
本节实验任务是上位机通过串口调试助手发送数据给 FPGA,FPGA 通过 USB 串口接收数据并将接收到的数据发送给上位机,完成串口数据环回。
二、程序设计
2.1 串口接收模块设计
串口接收模块我们的输入信号主要有系统时钟信号、系统复位信号与串口接收端口。我们需要知道接受完成以及将接受到的数据存储起来,所以输出为接收完成标志和串口接收数据信号。模块接口框图如下所示:
本次实验波特率采用115200bps,在50MHz(周期为 20ns)的系统时钟来计数,需要计数的个数为 cnt = 50_000_000/115200bit ≈ 434 个系统时钟周期。为了消除亚稳态,将进来的串口接收数据打拍。
波形图如下:
RTL代码uart_rx如下:
module uart_loopback(
input sys_clk,
input sys_rst_n,
input uart_rxd,
output uart_txd
);
parameter CLK_FREQ = 50000000;
parameter UART_BPS = 115200;
wire uart_rx_done;
wire [7:0] uart_rx_data;
uart_rx u_uart_rx #(
.CLK_FREQ (CLK_FREQ),
.UART_BPS (UART_BPS)
)
(
.clk (sys_clk),
.rst_n (sys_rst_n),
.uart_rxd (uart_rxd),
.uart_rx_done(uart_rx_done),
.uart_rx_data(uart_rx_data)
);
uart_tx u_uart_tx#(
.CLK_FREQ (CLK_FREQ),
.UART_BPS (UART_BPS)
)
(
.clk (sys_clk),
.rst_n (sys_rst_n),
.uart_tx_en (uart_rx_done),
.uart_tx_data(uart_rx_data),
.uart_tx_busy(),
.uart_txd (uart_txd)
);
endmodule
2.2 串口发送模块设计
串口发送模块我们的输入信号主要有系统时钟信号、系统复位信号以及发送使能信号和待发送数据,输出信号主要有发送忙状态标志和串口发送端口。模块接口框图如下所示:
波形图如下:
RTL代码如下:
module uart_tx(
input clk,
input rst_n,
input uart_tx_en,
input [7:0] uart_tx_data,
output reg uart_tx_busy,
output reg uart_txd
);
parameter CLK_FREQ = 50000000;
parameter UART_BPS = 115200;
localparam BAUD_CNT_MAX = CLK_FREQ/UART_BPS;
reg [7:0] tx_data_t;
reg [15:0] baud_cnt;
reg [3:0] tx_cnt;
always @(posedge clk or negedge rst_n) begin
if(!rst_n)
tx_data_t <= 8'd0;
else if(uart_tx_en)
tx_data_t <= uart_tx_data;
else
tx_data_t <= tx_data_t;
end
always @(posedge clk or negedge rst_n) begin
if(!rst_n)
uart_tx_busy <= 1'b0;
else if(uart_tx_en)
uart_tx_busy <= 1'b1;
else if(tx_cnt == 4'd9 && baud_cnt == BAUD_CNT_MAX - 1'b1)
uart_tx_busy <= 1'b0;
else
uart_tx_busy <= uart_tx_busy;
end
always @(posedge clk or negedge rst_n) begin
if(!rst_n)
baud_cnt <= 16'd0;
else if(uart_tx_busy) begin
if(baud_cnt < BAUD_CNT_MAX - 1'b1)
baud_cnt <= baud_cnt + 1'b1;
else
baud_cnt <= 16'd0;
end
else
baud_cnt <= 16'd0;
end
always @(posedge clk or negedge rst_n) begin
if(!rst_n)
tx_cnt <= 4'd0;
else if(uart_tx_busy) begin
if(baud_cnt < BAUD_CNT_MAX - 1'b1)
tx_cnt <= tx_cnt + 1'b1;
else
tx_cnt <= tx_cnt;
end
else
tx_cnt <= 4'd0;
end
always @(posedge clk or negedge rst_n) begin
if(!rst_n)
uart_txd <= 1'b1;
else if(uart_tx_busy) begin
case(tx_cnt)
4'd0:uart_txd <= 1'b0;
4'd1:uart_txd <= tx_data_t[1];
4'd2:uart_txd <= tx_data_t[2];
4'd3:uart_txd <= tx_data_t[3];
4'd4:uart_txd <= tx_data_t[4];
4'd5:uart_txd <= tx_data_t[5];
4'd6:uart_txd <= tx_data_t[6];
4'd7:uart_txd <= tx_data_t[7];
4'd8:uart_txd <= tx_data_t[8];
4'd9:uart_txd <= 1'b1;
default:uart_txd <= 1'b1;
endcase
end
else
uart_txd <= 1'b1;
end
endmodule
2.3 顶层模块
RTL代码如下:
module uart_loopback(
input sys_clk,
input sys_rst_n,
input uart_rxd,
output uart_txd
);
parameter CLK_FREQ = 50000000;
parameter UART_BPS = 115200;
wire uart_rx_done;
wire [7:0] uart_rx_data;
uart_rx u_uart_rx #(
.CLK_FREQ (CLK_FREQ),
.UART_BPS (UART_BPS)
)
(
.clk (sys_clk),
.rst_n (sys_rst_n),
.uart_rxd (uart_rxd),
.uart_rx_done(uart_rx_done),
.uart_rx_data(uart_rx_data)
);
uart_tx u_uart_tx#(
.CLK_FREQ (CLK_FREQ),
.UART_BPS (UART_BPS)
)
(
.clk (sys_clk),
.rst_n (sys_rst_n),
.uart_tx_en (uart_rx_done),
.uart_tx_data(uart_rx_data),
.uart_tx_busy(),
.uart_txd (uart_txd)
);
endmodule
三、仿真
仿真代码:
`timescale 1ns/1ns
module tb_uart_loopback();
parameter CLK_PERIOD = 20;
reg sys_clk; //周期20ns
reg sys_rst_n;
reg uart_rxd;
wire uart_txd;
initial begin
sys_clk <= 1'b0;
sys_rst_n <= 1'b0;
uart_rxd <= 1'b1;
#200 sys_rst_n <= 1'b1; //空闲
#1000 uart_rxd <= 1'b0; //起始位
#8680 uart_rxd <= 1'b1; //D0
#8680 uart_rxd <= 1'b0; //D1
#8680 uart_rxd <= 1'b1; //D2
#8680 uart_rxd <= 1'b0; //D3
#8680 uart_rxd <= 1'b1; //D4
#8680 uart_rxd <= 1'b0; //D5
#8680 uart_rxd <= 1'b1; //D6
#8680 uart_rxd <= 1'b0; //D7
#8680 uart_rxd <= 1'b1; //停止位
#8680 uart_rxd <= 1'b1; //空闲位
end
always #(CLK_PERIOD/2) sys_clk = ~sys_clk;
uart_loopback u_uart_loopback(
.sys_clk (sys_clk),
.sys_rst_n (sys_rst_n),
.uart_rxd (uart_rxd),
.uart_txd (uart_txd)
);
endmodule
仿真结果:
四、下载验证
4.1 引脚约束
#时序约束
create_clock -period 20.000 -name sys_clk [get_ports sys_clk]
#IO 引脚约束
#----------------------系统时钟---------------------------
set_property -dict {PACKAGE_PIN R4 IOSTANDARD LVCMOS33} [get_ports sys_clk]
#----------------------系统复位---------------------------
set_property -dict {PACKAGE_PIN U2 IOSTANDARD LVCMOS33} [get_ports sys_rst_n]
#----------------------USB UART---------------------------
set_property -dict {PACKAGE_PIN U5 IOSTANDARD LVCMOS33} [get_ports uart_rxd]
set_property -dict {PACKAGE_PIN T6 IOSTANDARD LVCMOS33} [get_ports uart_txd]