基于FIFO使用UART实现FPGA板与PC通信
1. UART 简介
UART(通用异步收发传输器)是一种常用的串行通信协议,广泛用于FPGA与外部设备(如PC、传感器等)之间的通信。UART 通信的核心是将并行数据转换为串行数据传输,然后在接收端再将串行数据恢复为并行数据。
UART协议特点:
- 异步通信:无需时钟信号
- 双向数据传输
- 起始位、数据位、停止位、校验位可配置
UART 一般传输速率较慢,但对于非实时、高速数据通信应用中,具有成本低、实现简单的优势。
2. UART 软件设计
在本设计中,我们使用 FIFO(先进先出队列)缓存数据,通过 UART 模块实现 FPGA 和 PC 之间的数据收发。设计主要分为三部分:串口接收模块、串口发送模块和 FIFO 控制模块,串口模块我是直接用的正点原子的代码,源码中也有注明,fifo_control代码是基于正点原子再修改的。各个模块的功能如下:
-
uart_rx:串口接收模块,负责从 PC 接收串行数据并转换为并行数据,传入 FIFO。
-
uart_tx:串口发送模块,负责从 FIFO 中读取并行数据,转换为串行数据并发送到 PC。
-
fifo_control:控制 FIFO 的数据流,协调 uart_rx 和 uart_tx 模块之间的数据传输。
-
uart_fifo(顶层文件):连接所有子模块,控制整个系统的工作流程。
3. 模块介绍
3.1 uart_rx
模块
功能介绍
uart_rx
模块用于接收来自 PC 的串行 UART 数据,并将其转换为 8 位并行数据输出。模块通过波特率计数来确定数据接收的时序,并生成接收完成标志信号 uart_rx_done
,指示接收过程结束。
接口说明
module uart_rx(
input clk , // 系统时钟
input rst_n , // 系统复位,低电平有效
input uart_rxd , // UART 接收端口
output reg uart_rx_done, // UART 接收完成信号
output reg [7:0] uart_rx_data // UART 接收到的数据
);
- clk:系统时钟信号。
- rst_n:系统复位信号,低电平有效。
- uart_rxd:从 PC 端接收的串行 UART 数据。
- uart_rx_done:接收完成信号,高电平时表示接收到完整数据。
- uart_rx_data:接收到的 8 位并行数据。
参数定义
parameter CLK_FREQ = 50000000; // 系统时钟频率
parameter UART_BPS = 115200; // UART 波特率
localparam BAUD_CNT_MAX = CLK_FREQ / UART_BPS; // 每一位数据的计数周期
- CLK_FREQ:FPGA 系统时钟频率,这里设定为 50MHz。
- UART_BPS:UART 波特率,设定为 115200。
- BAUD_CNT_MAX:通过系统时钟频率和波特率计算出的每一位数据所需的时钟周期。
主要信号及逻辑说明
异步信号同步处理
模块首先对 uart_rxd
进行多级同步处理,消除亚稳态:
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
uart_rxd_d0 <= 1'b0;
uart_rxd_d1 <= 1'b0;
uart_rxd_d2 <= 1'b0;
end
else begin
uart_rxd_d0 <= uart_rxd;
uart_rxd_d1 <= uart_rxd_d0;
uart_rxd_d2 <= uart_rxd_d1;
end
end
起始位检测
通过检测 uart_rxd
的下降沿来判断起始位:
assign start_en = uart_rxd_d2 & (~uart_rxd_d1) & (~rx_flag);
数据接收逻辑
当检测到起始位后,进入接收状态,模块通过 baud_cnt
和 rx_cnt
控制数据接收的时序。数据接收采用位移寄存的方式,逐位存储接收到的数据:
always @(posedge clk or negedge rst_n) begin
if (rx_flag && baud_cnt == BAUD_CNT_MAX/2 - 1'b1) begin
case(rx_cnt)
4'd1: rx_data_t[0] <= uart_rxd_d2;
4'd2: rx_data_t[1] <= uart_rxd_d2;
//...
4'd8: rx_data_t[7] <= uart_rxd_d2;
endcase
end
end
接收完成信号
当 rx_cnt
达到 9 位(1 位起始位,8 位数据位)时,模块拉高 uart_rx_done
信号,表明数据接收完成:
always @(posedge clk or negedge rst_n) begin
if (rx_cnt == 4'd9 && baud_cnt == BAUD_CNT_MAX/2 - 1'b1) begin
uart_rx_done <= 1'b1;
uart_rx_data <= rx_data_t;
end
end
模块总结
uart_rx
模块采用标准的 UART 接收流程,通过波特率计数器控制时序,并同步接收数据。该模块的设计充分考虑了异步信号的同步处理,确保了数据接收的稳定性。
3.2 uart_tx
模块
功能介绍
uart_tx
模块用于将并行数据转换为串行 UART 数据并发送至 PC 端。通过波特率计数器控制数据的发送速率,模块根据输入的 8 位并行数据进行数据位、起始位和停止位的顺序发送,同时输出发送忙状态信号 uart_tx_busy
来指示数据发送状态。
接口说明
module uart_tx(
input clk , // 系统时钟
input rst_n , // 系统复位,低电平有效
input uart_tx_en , // UART 发送使能
input [7:0] uart_tx_data, // 要发送的并行数据
output reg uart_txd , // UART 发送端口
output reg uart_tx_busy // 发送忙状态信号
);
- clk:系统时钟信号。
- rst_n:系统复位信号,低电平有效。
- uart_tx_en:发送使能信号,高电平时开始发送数据。
- uart_tx_data:待发送的 8 位并行数据。
- uart_txd:UART 串行发送端口。
- uart_tx_busy:发送忙状态信号,高电平表示正在发送数据。
参数定义
parameter CLK_FREQ = 50000000; // 系统时钟频率
parameter UART_BPS = 115200; // UART 波特率
localparam BAUD_CNT_MAX = CLK_FREQ / UART_BPS; // 波特率周期计数
- CLK_FREQ:FPGA 系统时钟频率,设定为 50MHz。
- UART_BPS:UART 波特率,设定为 115200。
- BAUD_CNT_MAX:根据系统时钟频率和波特率计算得到的每一位数据的时钟周期。
主要信号及逻辑说明
发送数据寄存器和忙信号
当 uart_tx_en
使能信号有效时,输入的并行数据 uart_tx_data
会被存入发送寄存器 tx_data_t
,同时 uart_tx_busy
信号拉高,表示开始发送数据:
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
tx_data_t <= 8'b0;
uart_tx_busy <= 1'b0;
end
else if (uart_tx_en) begin
tx_data_t <= uart_tx_data;
uart_tx_busy <= 1'b1;
end
else if (tx_cnt == 4'd9 && baud_cnt == BAUD_CNT_MAX - 1) begin
uart_tx_busy <= 1'b0;
end
end
波特率计数器
波特率计数器 baud_cnt
控制数据发送的速率。当处于发送状态时,波特率计数器循环计数,每到一个波特率周期时发送下一位数据:
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)
baud_cnt <= baud_cnt + 16'b1;
else
baud_cnt <= 16'd0;
end
end
数据发送逻辑
tx_cnt
计数器用于跟踪数据发送的进度,每发送一位数据,计数器加 1。当 tx_cnt
达到 9 时,表示数据发送完成,包括 1 位起始位、8 位数据位和 1 位停止位:
always @(posedge clk or negedge rst_n) begin
if (!rst_n)
tx_cnt <= 4'd0;
else if (uart_tx_busy && baud_cnt == BAUD_CNT_MAX - 1)
tx_cnt <= tx_cnt + 1'b1;
end
根据 tx_cnt
的值,控制发送端口 uart_txd
的输出,发送 1 位起始位、8 位数据位和 1 位停止位:
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[0]; // 数据位最低位
4'd2 : uart_txd <= tx_data_t[1];
//...
4'd8 : uart_txd <= tx_data_t[7]; // 数据位最高位
4'd9 : uart_txd <= 1'b1; // 停止位
endcase
end
end
模块总结
uart_tx
模块根据设定的波特率,将并行数据逐位转换为串行数据进行发送,并提供忙信号 uart_tx_busy
来表明发送过程是否完成。该模块实现了从起始位到停止位的完整 UART 发送过程。
3.3 fifo_control
模块
功能介绍
fifo_control
模块用于在 UART 接收和发送之间引入 FIFO 缓存,实现数据的异步缓存和流控处理。该模块接收 UART 数据,并将其存入 FIFO,当 FIFO 中有足够的数据或达到一定条件时,通过 uart_tx_en
发送使能信号控制数据发送。模块还设计了一个超时机制,当超过指定时间没有收到新的数据时,自动触发数据发送。
接口说明
module fifo_control(
// 系统信号
input clk , // 时钟信号
input rst_n , // 复位信号
// UART 相关信号
input uart_rx_done, // UART 接收完成信号
input [7:0] uart_rx_data, // UART 接收到的数据
input uart_tx_busy, // UART 忙信号
output uart_tx_en , // UART 发送使能信号
output [7:0] uart_tx_data // 要发送给 UART 的数据
);
- clk:系统时钟信号。
- rst_n:复位信号,低电平有效。
- uart_rx_done:UART 接收完成信号,高电平时表示接收到完整数据。
- uart_rx_data:UART 接收到的 8 位数据。
- uart_tx_busy:UART 发送忙信号,高电平表示 UART 正在发送数据。
- uart_tx_en:UART 发送使能信号,高电平时启动 UART 发送。
- uart_tx_data:需要通过 UART 发送的数据。
参数和信号说明
参数定义
localparam TIMEOUT_THRESHOLD = 16'd50000; // 设置超时阈值(根据时钟频率调整)
- TIMEOUT_THRESHOLD:超时阈值,用于控制数据的发送时机。如果超过此阈值没有接收到新的数据,模块将主动触发数据发送。
信号定义
- wr_req:写请求信号,当 UART 接收完成且 FIFO 未满时,拉高该信号,将数据写入 FIFO。
- rd_req:读请求信号,当 FIFO 有数据且 UART 处于空闲状态时,拉高该信号,将数据从 FIFO 读出。
- uart_tx_en:UART 发送使能信号,与
rd_req
相同,当拉高时启动 UART 发送。 - wr_data:写入 FIFO 的数据,来自 UART 接收到的并行数据
uart_rx_data
。 - uart_tx_data:从 FIFO 读出的数据,发送给 UART。
主要逻辑说明
超时计数器逻辑
模块引入了超时机制,当 UART 接收到数据时,复位超时计数器;如果在指定时间内没有接收到新的数据,超时计数器达到阈值后,将触发发送信号。
always @(posedge clk or negedge rst_n) begin
if (!rst_n)
timeout_cnt <= 16'd0;
else if (uart_rx_done)
timeout_cnt <= 16'd0; // 接收到数据时,复位超时计数器
else if (timeout_cnt < TIMEOUT_THRESHOLD)
timeout_cnt <= timeout_cnt + 1'b1; // 如果没接收到数据,开始计时
end
读启动信号逻辑
当超时计数器达到阈值时,标志信号 flag
被拉高,触发 FIFO 的读请求信号 rd_req
。当 rd_req
被拉高且 UART 空闲时,UART 发送开始。
always @(posedge clk or negedge rst_n) begin
if (!rst_n)
flag <= 1'b0;
else if (timeout_cnt == TIMEOUT_THRESHOLD - 1'b1)
flag <= 1'b1; // 超时后,拉高读启动信号
end
FIFO 缓存管理
模块中例化了一个 FIFO IP核,采用的异步等宽数据FIFO,但在实际连接中写和读用的是同一个时钟,如果想要使用不同时钟,需要再调用PLL锁相环IP核,通过 wr_req
和 rd_req
信号控制数据的写入和读取。写入的数据来自 UART 接收到的并行数据,读取的数据通过 UART 发送出去。
fifo fifo_inst (
.aclr ( ~rst_n ),
.data ( wr_data ),
.rdclk ( clk ),
.rdreq ( rd_req ),
.wrclk ( clk ),
.wrreq ( wr_req ),
.q ( rd_data ),
.rdempty ( rd_empty ),
.rdfull ( rd_full ),
.rdusedw ( rd_usedw ),
.wrempty ( wr_empty ),
.wrfull ( wr_full ),
.wrusedw ( wr_usedw )
);
写请求信号逻辑
当 UART 接收到数据且 FIFO 未满时,写请求信号 wr_req
被拉高,数据被写入 FIFO:
assign wr_req = uart_rx_done && (~wr_full);
读请求信号逻辑
当 FIFO 中有数据且 UART 空闲时,读请求信号 rd_req
被拉高,数据从 FIFO 中读出并发送:
assign rd_req = flag && (~uart_tx_busy) && (~rd_empty);
assign uart_tx_en = rd_req;
模块总结
fifo_control
模块通过 FIFO 实现数据缓冲,避免了 UART 接收和发送速度不匹配的问题。同时,通过超时计数器实现了自动触发数据发送的机制,确保在长时间没有新数据到来时也能及时发送缓存中的数据。
4. ModelSim 仿真
在仿真阶段,我们使用 ModelSim 对整个系统进行仿真,验证设计的正确性。
注意仿真涉及到IP核时需要添加仿真库文件altera_mf.v
,如下图中所示:
仿真步骤:
- 使用 tb 文件编写测试用例,验证 UART 模块的发送和接收功能。
- 查看波形,验证 FIFO 的数据流和 UART 串行通信的正确性。
- 验证数据传输的完整性,检查接收到的数据是否与发送的数据一致。
仿真结果如下图所示:
仿真测试模块功能解读:
-
时钟与复位设置:
- 通过
sys_clk
模拟一个50MHz的系统时钟,每个周期为20ns。时钟信号以10ns的间隔取反,保证时钟的稳定。 sys_rst_n
作为复位信号,在仿真开始时为低电平,经过200ns后变为高电平,表示系统解除复位。
- 通过
-
UART信号发送过程:
uart_rxd
模拟UART接收端的信号输入,最开始为1(空闲状态),并通过逐个时钟周期发送数据位来模拟一个UART字节的传输过程。每个数据传输周期为8680ns,对应于9600波特率下的1个bit时间。- 发送的字节数据分别为A, B, C等对应的二进制ASCII码,通过每8位数据加1个起始位和1个停止位进行传输。
-
测试过程:
uart_tb
模块每发送完一个字节,保持一段空闲状态,随后开始下一个字节的发送过程。这样,可以测试UART接收模块是否能正确接收和解析数据位。- 最终,发送数据经过FIFO后,通过
uart_txd
接口再次发送出去,验证UART发送功能。
模块实例化:
- 顶层模块
uart_fifo
在仿真中被实例化,连接系统时钟、复位、以及UART的收发信号。
5. Quartus II 综合
在FPGA设计中,综合是将Verilog代码转化为FPGA硬件资源配置的过程,下面是基于Quartus II的综合步骤:
5.1 工程创建与设置
-
打开Quartus II,选择
File -> New Project Wizard
,创建一个新的工程。 -
在"New Project Wizard"中,指定项目路径、名称和顶层模块文件名。
-
选择合适的FPGA芯片型号,例如
Cyclone IV
或Cyclone V
,这里我用的是正点原子新起点V2开发板,因此我选择Cyclone IV E
-
点击
Finish
完成项目创建。
5.2 生成FIFO IP核
为了实现UART和FPGA之间的FIFO缓存,可以使用Quartus II中的MegaWizard Plug-In Manager
生成FIFO IP核,具体步骤如下:
1. 打开MegaWizard Plug-In Manager
- 在Quartus II的菜单中,选择
Tools -> MegaWizard Plug-In Manager
,打开IP核生成器。 - 选择
Create a new custom megafunction variation
,然后点击Next
。
2. 选择FIFO类型
- 在弹出的窗口中,选择
Memory Compiler -> FIFO
,然后点击Next
。 - 根据设计需求选择合适的FIFO模式,通常可以选择
FIFO (Single Clock)
或FIFO (Dual Clock)
,取决于数据写入和读取时钟是否一致。
3. 配置FIFO参数
-
FIFO宽度:设置数据位宽,比如对于UART的8位数据,FIFO宽度可以设置为8。
-
FIFO深度:根据设计需求选择FIFO的深度,比如设置为16、32或64,这将决定FIFO可以存储多少数据。
-
其他参数可以根据需求配置。
4. 生成FIFO
- 点击
Next
,按照向导完成剩余的配置,通常可以选择默认设置。 - 最后点击
Finish
,Quartus会生成相应的FIFO IP核文件,生成的文件通常包括一个.v
或.vhd
文件以及配套的.qip
文件。
5. 添加FIFO到工程中
在项目中,右键Files
,选择Add/Remove Files in Project
,将生成的FIFO文件(如fifo.v
)添加到工程中。
5.3 添加Verilog代码
-
在项目目录下,右键点击
Files
,选择Add/Remove Files in Project
,将你的Verilog代码(如uart_fifo.v
)和其他模块代码(如uart_rx.v
、uart_tx.v
、fifo_control.v
)添加到工程中。 -
确保所有代码文件都已正确添加并编译通过。
5.4 Pin Assignment (引脚分配)
-
打开
Assignments -> Pin Planner
,根据FPGA开发板的实际情况,分配UART的发送、接收引脚(如uart_txd
、uart_rxd
)。 -
通过查阅开发板手册确定引脚的编号,并在Pin Planner中对应分配。
5.5 综合与编译
-
在Quartus II中点击
Processing -> Start Compilation
,开始综合和编译过程。 -
编译完成后,可以在编译报告中查看资源使用情况,例如LE(逻辑单元)利用率、时钟频率、延迟等。
6. 上板验证
最后一步是将比特流文件烧录到 FPGA 开发板上,进行实际硬件验证。
6.1 硬件连接
- 硬件准备:确保有一个支持UART通信的FPGA开发板和用于数据传输的PC。使用FPGA板自带的UART接口(如USB-UART模块)与PC连接。
- 连接说明:
- 使用USB线将FPGA开发板与电脑连接,确保板载UART通信模块能够正确识别。
- 如果FPGA板上有多个串口或通信模块,请选择正确的UART接口。
6.2 Quartus II 下载配置
-
Bitstream 文件生成:首先在Quartus II中完成综合(Compile)步骤,确保工程没有任何错误。生成相应的.sof文件。
-
配置下载:通过Quartus II中的“Programmer”工具,将生成的.sof文件下载到FPGA开发板上。
-
确保复位:下载完成后,按下FPGA开发板上的复位按钮,使板卡进入复位状态。
6.3 终端软件调试
-
终端工具:在PC上运行串口调试工具,这里我使用的是SSCOM5.13.1版本,设置串口的波特率、数据位、停止位和校验位,确保与FPGA端的UART配置相同。
-
波特率:
115200
-
数据位:
8
-
停止位:
1
-
校验:无
-
数据流:无
-
-
连接串口:在串口工具中选择正确的COM口,并点击“打开串口”连接,确保能够与FPGA进行通信。
6.4 数据通信验证
-
数据发送测试:通过PC的终端工具发送预设的测试数据(如前面仿真中所使用的8'h55或者直接发送字符串),观察FPGA是否能够接收数据,并在FPGA板上进行处理。
- FPGA应该能够接收数据,并通过FIFO缓存数据。
-
数据回传测试:FPGA收到数据后,FIFO应该将数据回传给PC。终端工具中应显示FPGA返回的数据,并与发送的原始数据一致。
-
测试发送例如字符
LilMonsterOvO
,观察返回数据是否也是LilMonsterOvO
。
-
通过以上步骤,我们成功实现了基于 FIFO 使用 UART 进行 FPGA 板与 PC 之间的通信。该设计可以应用于多种串行通信场景,为数据收发提供稳定可靠的解决方案。
GitHub源码:LilMonsterOvO (github.com)
参考文章:
[1] 【FPGA】UART串口通信---基于FIFO_uart fifo-CSDN博客
[2] 使用UART实现FPGA板与PC通信 - kentle - 博客园 (cnblogs.com)
[3] 第22.1讲 FIFO IP核简介_哔哩哔哩_bilibili
这篇博客用于记录自己的学习生活,方便自己回顾学过的知识。同时,也希望能帮助大家理解 UART 和 FIFO 在 FPGA 中的应用。如有问题或建议,欢迎留言讨论!
标签:tx,FPGA,UART,uart,FIFO,发送,PC,模块 From: https://www.cnblogs.com/LilMonsterOvO/p/18460688