UART协议:
UART和RS232(单端全双工)RS422(差分全双工)RS485(差分半双工)
UART的全称叫做通用异步收发传输器。将数据在串行通信和并行通信间的传输转换。通俗的讲就是把多比特的数据转化为单比特的数据,或者把单比特的数据转化为多比特的数据。工作原理是将数据的每个bit一位一位传输
UART是一种通用串行数据总线,用于异步通信。该总线双向通信,可以实现全双工传输和接收。
RS232是UART的一种,是目前最常用的一种串行通讯接口,用于pc机跟外部板级通信。有两根线,分别是rx和tx,这两根线都是1比特位宽的。其中rx是接收线,tx是发送线。
rx,位宽为1比特,pc机通过串口往FPGA发8比特数据时,FPGA通过串口线rx一位一位地接收,从最低位到最高位依次接收,最后在FPGA里面位拼接成8比特数据。
tx,位宽为1比特,FPGA通过串口往pc机发8比特数据时,FPGA把8比特数据通过tx线一位一位的传给pc机,从最低位到最高位依次发送,最后上位机通过串口助手把这一位一位的数据位拼接成8比特数据。、
在不发送或者不接收数据的情况下,rx和tx处于空闲状态,此时rx和tx线都保持高电平(1),如果有数据传递,首先会有一个起始位(O),然后是8比特的数据位,接着有1比特的停止位(1),如果停止位以后不再发数据,将进入空闲状态,否则又将数据线拉低(进入起始位状态)。

波特率:在串口通信时的速率,单位时间内载波变化的次数,这里选用的是9600Bd,即发送一比特数据需要的时间为1/9600秒。
用串口发送或者接收数据(起始位、数据位、停止位)时,每发送或者接收一位数据的时间都需要1个波特,即1/9600秒。
串口发送或者接收一比特数据的时间为一个波特(1/9600),因此如果用50M的系统时钟来计数﹐就需要记数cnt=(1/9600s)/20ns~5208个系统时钟,才再次发送或者接收下一个数据。
上位机通过串口发8比特数据时,会自动在发8位有效数据前发一个波特时间的起始位,也会自动在发完8位有效数据后发一个停止位。同理,串口助手接收fpga发送的数据前,必须检测到一波特的起始位才会接收数据,接收完数据后,再接收一个停止位,所以FPGA通过串口除了发数据以外,还要发起始位和停止位。
RS232测试回环


// -----------------------------------------------------------------------------
// Copyright (c) 2014-2025 All rights reserved
// -----------------------------------------------------------------------------
// Author : lvjitao lvjitao_o@163.com
// File : top_uart.v
// Create : 2025-10-09 10:15:12
// Revise : 2025-10-09 10:42:54
// Editor : sublime text3, tab size (4)
// -----------------------------------------------------------------------------
`timescale 1ns/1ps
module top_uart(
input wire rx,
output wire tx
);
assign tx = rx;
endmodule
set_property PACKAGE_PIN T11 [get_ports rx]
set_property IOSTANDARD LVCMOS18 [get_ports rx]
set_property PACKAGE_PIN T10 [get_ports tx]
set_property IOSTANDARD LVCMOS18 [get_ports tx]



首先画出三个输入信号,必不可少的两个输入信号是时钟和复位,另一个是串行输入数据rx,如图 34‑14所示,我们发现rx串行数据一开始直接打了两拍,就是经过了两级寄存器,理论上我们应该按照串口接收数据的时序要求找到rx的下降沿,然后开始接收起始位的数据,但为什么先将数据打了两拍呢?那就要先从跨时钟域会导致"亚稳态"的问题上说起。

由于在PC机中波特率和rx信号是同步的,而rx信号和FPGA的系统时钟sys_clk是异步的关系,我们此时要做的是将慢速时钟域(PC机中的波特率)系统中的rx信号同步到快速时钟域(FPGA中的sys_clk)系统中,所使用的方法叫电平同步,俗称"打两拍法"。
rx信号进入FPGA后会首先经过一级寄 存器,出现如图 所示的亚稳态现象,导致rx_reg1信号的状态不确定是0还是1,就会受其影响使其他相关信号做出不同的判断,有的判断到"0"有的判断到"1",有的也进入了亚稳态并产生连锁反应,导致后级相关逻辑电路混乱。为了避免这种情况,rx信号进来后首先进行打一拍的处理,打一拍后产生rx_r eg1信号。但rx_reg1可能还存在低概率的亚稳态现象,为了进一步降低出现亚稳态的概率,我们将从rx_reg1信号再打一拍后产生rx_reg2信号,使之能够较大概率保证rx_reg2信号是0或者1中的一种确定情况,这样rx_reg2所影响的后级电路就都是相对稳定的了。
但是大家一定要注意:打两拍后虽 然能让信号稳定到0或者1中确定的值,但究竟是0还是1却是随机的,与打拍之前输入信号的值没有必然的关系。

综合rx时序图:

当bit_cnt计数器的 计数值为1时说明第一个有用数据已经接收到了,刚好剔除了起始位,就可以进行移位了。注意移位的条件,要在bit_cnt计数器的计数值为1到8区间内且bit_flag取数标志信号同时为高时才能移位,也就是移动7次即可,接收最后1bit有用数据时就不需要再进行移位了。当移位7次后1bit的串行数据已经变为8 bit的并行数据了,此时产生一个移位完成标志信号rx_flag。


x_data信号是参与移位的数据,在移位的过程中数据是变动的,不可以被后级模块所使用,而可以肯定的是在移位完成标志信号rx_flag为高时,rx_data信号一定是移位完成的稳定的8bit有用数据。如图 34 ‑25所示,此时我们当移位完成标志信号rx_flag为高时让rx_data信号赋值给专门用于输出稳定8bit有用数据的po_data信号就可以了,但rx_flag信号又不能作为po_data信号有效的标志信号,所以需要将rx_flag信号再打一拍。最后输出的有用8bit数据为po_data信号和伴随 po_data信号有效的标志信号po_flag信号。

// -----------------------------------------------------------------------------
// Copyright (c) 2014-2025 All rights reserved
// -----------------------------------------------------------------------------
// Author : lvjitao lvjitao_o@163.com
// File : top_uart.v
// Create : 2025-10-09 10:15:12
// Revise : 2025-10-09 18:42:11
// Editor : sublime text3, tab size (4)
// -----------------------------------------------------------------------------
`timescale 1ns/1ps
module top_uart(
input wire rx,
input wire sclk,
input wire s_rst_n,
output wire tx
);
wire po_flag;
wire [7:0] po_data;
assign tx = 1;
uart_rx #(
.MAX_BAUD_CNT(9600)
) inst_uart_rx (
.sclk (sclk),
.s_rst_n (s_rst_n),
.rx (rx),
.po_flag (po_flag),
.po_data (po_data)
);
ila_0 ila_inst (
.clk(sclk), // input wire clk
.probe0({po_flag, po_data}) // input wire [15:0] probe0
);
endmodule
set_property PACKAGE_PIN T11 [get_ports rx]
set_property IOSTANDARD LVCMOS18 [get_ports rx]
set_property PACKAGE_PIN T10 [get_ports tx]
set_property IOSTANDARD LVCMOS18 [get_ports tx]
set_property PACKAGE_PIN W19 [get_ports s_rst_n]
set_property IOSTANDARD LVCMOS18 [get_ports s_rst_n]
set_property PACKAGE_PIN U14 [get_ports sclk]
set_property IOSTANDARD LVCMOS18 [get_ports sclk]
// -----------------------------------------------------------------------------
// Copyright (c) 2014-2025 All rights reserved
// -----------------------------------------------------------------------------
// Author : lvjitao lvjitao_o@163.com
// File : tb_uart_rx.v
// Create : 2025-10-09 15:05:53
// Revise : 2025-10-09 15:41:07
// Editor : sublime text3, tab size (4)
// -----------------------------------------------------------------------------
`timescale 1ns/1ps
module tb_uart_rx(
);
reg sclk, s_rst_n;
wire po_flag;
wire [7:0] po_data;
reg [8:0] men[15:0];
reg rx;
initial begin
sclk = 0;
s_rst_n = 0;
repeat(10) @(posedge sclk);
s_rst_n = 1;
end
initial begin
$readmemh("./data.mif", men);
end
initial begin
repeat(100) @(posedge sclk);
gen_rx_byte();
end
task gen_rx_byte;
integer i;
begin
for (i=0; i<16; i=i+1)
begin
gen_rx_bit(men[i]);
end
end
endtask
task gen_rx_bit(input [7:0] data);
integer i;
begin
for(i=0; i<10; i=i+1) begin
case(i)
0: rx = 0;
1: rx = data[1];
2: rx = data[2];
3: rx = data[3];
4: rx = data[4];
5: rx = data[5];
6: rx = data[6];
7: rx = data[7];
8: rx = data[8];
9: rx = 1;
endcase
#104160;
end
end
endtask
uart_rx #(
.MAX_BAUD_CNT(MAX_BAUD_CNT)
) inst_uart_rx (
.sclk (sclk),
.s_rst_n (s_rst_n),
.rx (rx),
.po_flag (po_flag),
.po_data (po_data)
);
endmodule
逻辑分析仪



异步复位进入同步后同步复位退出电路设计:




(*ASYNC_REG="true"*)是XilinxFPGA设计中用于明确标识异步同步寄存器的关键属性,通过硬件布局优化和仿真控制提升跨时钟域信号的可靠性。实际使用时需结合同步电路设计规范
使其部署在一个最小单元。




rx:

// -----------------------------------------------------------------------------
// Copyright (c) 2014-2025 All rights reserved
// -----------------------------------------------------------------------------
// Author : lvjitao lvjitao_o@163.com
// File : uart_tx.v
// Create : 2025-10-09 19:55:12
// Revise : 2025-10-09 20:15:35
// Editor : sublime text3, tab size (4)
// -----------------------------------------------------------------------------
`timescale 1ns/1ps
module uart_tx #(
parameter MAX_BAUD_CNT = 5200 - 1
)(
input wire sclk,
input wire s_rst_n,
input wire[7:0] pi_data,
input wire pi_flag,
output reg tx=1
);
reg[7:0] data_reg;
reg tx_flag;
reg[15:0] cnt_baud;
reg bit_flag;
reg[3:0] bit_cnt;
reg[8:0] shift_flag = 1;
always @(posedge sclk) begin
if (pi_flag == 1) begin
data_reg <= pi_data;
end
end
always @(posedge sclk ) begin
if (s_rst_n == 0) begin
// reset
tx_flag <= 0;
end
else if (bit_flag == 1 && bit_cnt == 'd0) begin
tx_flag <= 0;
end
else if (pi_flag == 1'b1) begin
tx_flag <= 1;
end
end
always @(posedge sclk ) begin
if (s_rst_n == 1'b0) begin
// reset
cnt_baud <= 0;
end
else if (tx_flag == 1'b1) begin
if (cnt_baud == MAX_BAUD_CNT) begin
cnt_baud <= 0;
end
else begin
cnt_baud <= cnt_baud + 1;
end
end
else begin
cnt_baud <= 'd0;
end
end
always @(posedge sclk ) begin
if (s_rst_n == 0) begin
// reset
bit_flag <= 0;
end
else if (cnt_baud == MAX_BAUD_CNT - 1) begin
bit_flag <= 1;
end
else begin
bit_flag <= 0;
end
end
always @(posedge sclk ) begin
if (s_rst_n == 0) begin
// reset
bit_cnt <= 0;
end
else if (bit_flag == 1'b1) begin
bit_cnt <= bit_cnt + 1'b1;
end
end
always @(posedge sclk ) begin
if (s_rst_n) begin
// reset
tx <= 1;
end
else if(tx_flag == 1) begin
case(bit_cnt)
4'd0:tx <= 1'b0;
4'd1:tx <= data_reg[0];
4'd2:tx <= data_reg[1];
4'd3:tx <= data_reg[2];
4'd4:tx <= data_reg[3];
4'd5:tx <= data_reg[4];
4'd6:tx <= data_reg[5];
4'd7:tx <= data_reg[6];
default: tx <= 1'b1;
endcase
end
else begin
tx <= 1;
end
end
//相同写法
// always @(posedge sclk ) begin
// if (s_rst_n == 0) begin
// // reset
// shift_flag <= 1;
// end
// else if (pi_flag == 1) begin
// shift_flag <= {pi_data, 1'b0};
// end
// else if (bit_flag == 0) begin
// shift_flag <= {1'b1, shift_flag[8:1]};
// end
// end
// always @(posedge sclk ) begin
// if (s_rst_n == 0) begin
// // reset
// tx<=1'b1;
// end
// else begin
// tx <= shift_flag[0];
// end
// end
endmodule