跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客我的书AI学习GitHub 精选镜像AI工具UI配色美学关于
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
汇编算法

FPGA 实现 UART 串口通信:原理与 Verilog 代码实例

FPGA 实现 UART 串口通信的原理与 Verilog 代码。内容涵盖 UART 核心参数(波特率、数据位、停止位等)、波特率计算与分频实现、发送与接收模块的状态机设计、顶层模块例化。提供了完整的 Verilog 代码示例,包括 uart_tx.v、uart_rx.v 和 uart_top.v。此外,还介绍了仿真验证步骤(Testbench)及硬件验证流程(引脚约束、接线、串口助手配置)。最后总结了关键设计要点(如引脚同步、采样位置)及常见问题的解决方法,并给出了扩展建议(如修改波特率、添加校验位等)。

雾岛听风发布于 2026/4/5更新于 2026/10/24121 浏览

FPGA 实现 UART 串口通信

UART 串口通信是 FPGA 入门阶段最经典、最实用的异步通信案例,无论是 FPGA 与单片机、电脑的交互,还是项目中的简单数据传输,UART 都能发挥作用。本文将从核心原理、关键参数(重点讲波特率)、Verilog 完整代码,到仿真/硬件验证,一步步拆解。

一、UART 串口通信核心原理

UART(通用异步收发传输器),核心关键词是'异步'——无需专门的时钟同步线,收发双方只需约定好相同的通信参数,就能实现数据的可靠传输。不同于 SPI、I2C 的同步通信,UART 的优势是接线简单(仅需 TX 发送线、RX 接收线,共地即可),成本低、实用性强。

1. 核心通信参数(新手必记,最常用配置)

UART 的通信稳定性,完全依赖于收发双方参数一致,其中最关键的 5 个参数如下:

  • 波特率:下文重点详解,核心是'每秒传输的二进制位数',决定通信速度;
  • 数据位:通常设为 8 位,即每次传输 1 个字节(0~255 的数值);
  • 停止位:通常设为 1 位,用于标识一个字节的数据传输结束;
  • 校验位:建议设为'无校验',简化设计(校验位用于检测传输错误,实际项目可根据需求添加);
  • 帧结构:一个完整的 UART 数据帧 = 1 位起始位(低电平 0) + 8 位数据位(低位先行) + 1 位停止位(高电平 1),共 10 位/字节。
2. 波特率详解
(1)波特率的核心定义

波特率(Baud Rate)的本质是:串口通信中每秒传输的二进制位数(bit/s),单位是 bps。简单来说,它就是串口传输'0'和'1'这些电信号的'速度'。

(2)直观实例

结合我们常用的波特率和 UART 帧结构(10 位/字节),举两个最常见的例子:

  • 波特率=9600 → 每秒传输 9600 个二进制位 → 每秒可传输 9600÷10=960 个字节;
  • 波特率=115200 → 每秒传输 115200 个二进制位 → 每秒可传输 115200÷10=11520 个字节;

很明显,波特率越高,通信速度越快,但同时对 FPGA 的时钟精度、硬件接线的抗干扰能力要求也越高。新手入门建议先从 9600 波特率开始,稳定性最好,不易出错。

(3)波特率的核心作用:保证收发同步

前面说过,UART 是'异步通信',没有专门的时钟线来同步收发双方的节奏,那怎么保证接收端能正确识别发送端的数据呢?答案就是'约定波特率'。

  • 发送端:按照约定的波特率,每发送 1 个 bit,就等待固定的时间(比如 9600 波特率下,每个 bit 占用 1÷9600≈104μs);
  • 接收端:同样按照约定的波特率,每隔固定时间去采样一次接收引脚的电平(高电平=1,低电平=0),从而解析出正确的数据。

重点提醒:收发两端的波特率必须完全一致,否则会出现'采样错位',最终解析出错误的数据。

(4)FPGA 中波特率的具体实现

FPGA 的系统时钟通常是固定的(比如 50MHz、100MHz),而波特率是我们约定的(比如 9600),如何用 FPGA 的高频时钟,模拟出波特率对应的'bit 时间'?核心就是'时钟分频'。

分频系数计算公式:分频系数 = 系统时钟频率 ÷ 波特率

举例(后续代码用的就是这个配置):系统时钟=50MHz,波特率=9600,那么分频系数=50_000_000 ÷ 9600≈5208。也就是说,FPGA 每计数 5208 个系统时钟脉冲,就对应 UART 的 1 个 bit 位,这样就能精准匹配波特率的速度。

(5)工程常用波特率

波特率是标准化的参数,实际项目中不要随意自定义,常用的标准化波特率如下:

常用波特率每个 bit 的时间(近似值)每秒传输字节数(10 位/字节)
9600104μs960
1920052μs1920
3840026μs3840
1152008.68μs11520
3. FPGA 实现 UART 的核心思路

FPGA 实现 UART 通信,不需要复杂的 IP 核(新手建议手写代码,理解更透彻),核心只需要两个模块,再加上一个顶层模块整合,就能完成完整的收发功能:

  • 发送模块(UART_TX):负责将 FPGA 内部的并行数据转换成 UART 串行数据,通过 TX 引脚发送出去;
  • 接收模块(UART_RX):负责通过 RX 引脚接收外部的 UART 串行数据,转换成 FPGA 内部能直接使用的并行数据,同时给出'接收完成'的标志;
  • 顶层模块(UART_TOP):将发送模块和接收模块例化,统一接口,方便下载到 FPGA 开发板验证。

核心逻辑总结:用时钟分频生成波特率节拍,用状态机控制 UART 帧结构的收发,用寄存器缓存数据和状态,实现并行与串行数据的转换。

二、FPGA UART 完整代码实例(Verilog 实现)

以下代码基于「50MHz 系统时钟、9600 波特率、8 位数据位、1 位停止位、无校验」的默认配置,包含发送模块、接收模块、顶层模块,注释详细。

1. 发送模块(uart_tx.v)

功能:接收 FPGA 内部的 8 位并行数据,当检测到'发送使能'信号后,按照 UART 帧结构,将并行数据转换成串行数据,通过 tx_pin 引脚发送出去,发送完成后给出'发送完成'标志。

module uart_tx #( parameter CLK_FREQ = 50_000_000, // 系统时钟频率(50MHz) parameter BAUD_RATE = 9600 // 约定波特率(9600) ) ( input clk, // 系统时钟(输入) input rst_n, // 低电平复位(输入,复位时停止发送) input [7:0] tx_data, // 待发送的 8 位并行数据(输入) input tx_en, // 发送使能(输入,高电平有效,触发发送) output reg tx_done, // 发送完成标志(输出,高电平表示发送结束) output reg tx_pin // UART 发送引脚(输出,串行数据) ); // 计算波特率分频系数 localparam BAUD_DIV = CLK_FREQ / BAUD_RATE; // 50_000_000 / 9600 ≈ 5208 localparam BAUD_DIV_BIT = $clog2(BAUD_DIV); // 内部寄存器定义 reg [BAUD_DIV_BIT-1:0] baud_cnt; // 波特率计数器 reg [3:0] bit_cnt; // 数据位计数器 reg [7:0] tx_data_reg; // 数据缓存寄存器 reg tx_state; // 发送状态寄存器 always @(posedge clk or negedge rst_n) begin if(!rst_n) begin tx_state <= 1'b0; baud_cnt <= 0; bit_cnt <= 0; tx_pin <= 1'b1; tx_done <= 1'b0; tx_data_reg <= 8'd0; end else begin tx_done <= 1'b0; case(tx_state) 1'b0: begin tx_pin<= 1'b1; if(tx_en) begin tx_state <= 1'b1; tx_data_reg <= tx_data; baud_cnt <= 0; bit_cnt <= 0; end end 1'b1: begin baud_cnt <= baud_cnt + 1'b1; if(baud_cnt == BAUD_DIV - 1) begin baud_cnt <= 0; bit_cnt <= bit_cnt + 1'b1; case(bit_cnt) 4'd0: tx_pin <= 1'b0; 4'd1: tx_pin <= tx_data_reg[0]; 4'd2: tx_pin <= tx_data_reg[1]; 4'd3: tx_pin <= tx_data_reg[2]; 4'd4: tx_pin <= tx_data_reg[3]; 4'd5: tx_pin <= tx_data_reg[4]; 4'd6: tx_pin <= tx_data_reg[5]; 4'd7: tx_pin <= tx_data_reg[6]; 4'd8: tx_pin <= tx_data_reg[7]; 4'd9: tx_pin <= 1'b1; default: begin tx_state <= 1'b0; tx_done <= 1'b1; end endcase end end endcase end end endmodule
2. 接收模块(uart_rx.v)

功能:通过 rx_pin 引脚接收外部的 UART 串行数据,按照约定的波特率和帧结构,将串行数据转换成 8 位并行数据,接收完成后给出'接收完成'标志。

关键优化:对 rx_pin 引脚打两拍(消抖 + 同步),解决外部异步信号的亚稳态问题;在每个 bit 的中间位置采样,提高采样准确性。

module uart_rx #( parameter CLK_FREQ = 50_000_000, parameter BAUD_RATE = 9600 ) ( input clk, input rst_n, input rx_pin, output reg [7:0] rx_data, output reg rx_done ); localparam BAUD_DIV = CLK_FREQ / BAUD_RATE; localparam BAUD_DIV_BIT = $clog2(BAUD_DIV); localparam BAUD_MID = BAUD_DIV / 2; reg [BAUD_DIV_BIT-1:0] baud_cnt; reg [3:0] bit_cnt; reg rx_state; reg rx_pin_reg1, rx_pin_reg2; wire rx_pin_sync; always @(posedge clk or negedge rst_n) begin if(!rst_n) begin rx_pin_reg1 <= 1'b1; rx_pin_reg2 <= 1'b1; end else begin rx_pin_reg1 <= rx_pin; rx_pin_reg2 <= rx_pin_reg1; end end assign rx_pin_sync = rx_pin_reg2; always @(posedge clk or negedge rst_n) begin if(!rst_n) begin rx_state <= 1'b0; baud_cnt <= 0; bit_cnt <= 0; rx_data <= 8'd0; rx_done <= 1'b0; end else begin rx_done <= 1'b0; case(rx_state) 1'b0: begin baud_cnt <= 0; bit_cnt <= 0; if(rx_pin_sync == 1'b0) begin rx_state <= 1'b1; end end 1'b1: begin baud_cnt <= baud_cnt + 1'b1; if(baud_cnt == BAUD_DIV - 1) begin baud_cnt<= 0; bit_cnt <= bit_cnt + 1'b1; end if(baud_cnt == BAUD_MID - 1) begin case(bit_cnt) 4'd0: ; 4'd1: rx_data[0] <= rx_pin_sync; 4'd2: rx_data[1] <= rx_pin_sync; 4'd3: rx_data[2] <= rx_pin_sync; 4'd4: rx_data[3] <= rx_pin_sync; 4'd5: rx_data[4] <= rx_pin_sync; 4'd6: rx_data[5] <= rx_pin_sync; 4'd7: rx_data[6] <= rx_pin_sync; 4'd8: rx_data[7] <= rx_pin_sync; 4'd9: begin rx_state <= 1'b0; if(rx_pin_sync == 1'b1) begin rx_done <= 1'b1; end end endcase end end endcase end end endmodule
3. 顶层模块(uart_top.v)

功能:将发送模块(uart_tx)和接收模块(uart_rx)例化,统一所有接口。

module uart_top #( parameter CLK_FREQ = 50_000_000, parameter BAUD_RATE = 9600 ) ( input clk, input rst_n, input rx_pin, output tx_pin, input [7:0] tx_data_in, input tx_en_in, output tx_done_out, output [7:0] rx_data_out, output rx_done_out ); uart_tx #( .CLK_FREQ(CLK_FREQ), .BAUD_RATE(BAUD_RATE) ) uart_tx_inst ( .clk(clk), .rst_n(rst_n), .tx_data(tx_data_in), .tx_en(tx_en_in), .tx_done(tx_done_out), .tx_pin(tx_pin) ); uart_rx #( .CLK_FREQ(CLK_FREQ), .BAUD_RATE(BAUD_RATE) ) uart_rx_inst ( .clk(clk), .rst_n(rst_n), .rx_pin(rx_pin), .rx_data(rx_data_out), .rx_done(rx_done_out) ); endmodule

三、代码验证方法

1. 仿真验证

仿真的目的是验证:发送模块能正确输出 UART 帧结构,接收模块能正确解析串行数据。

  • 新建仿真工程,添加 uart_tx.v、uart_rx.v、uart_top.v 三个文件;
  • 编写测试激励(testbench):给顶层模块输入 50MHz 时钟、低电平复位;
  • 驱动 tx_en_in 为高电平,tx_data_in 赋值,观察 tx_pin 输出是否符合帧结构;
  • 向 rx_pin 输入模拟的 UART 串行数据,观察 rx_data_out 是否能正确解析。

小技巧:仿真时重点观察'波特率计数器'和'数据位计数器',确保计数节奏正确。

2. 硬件验证

硬件验证需要准备:FPGA 开发板、CH340 USB 转 TTL 模块、杜邦线、串口助手。

  1. 引脚约束:根据 FPGA 开发板,编写引脚约束文件,将顶层模块的 clk、rst_n、rx_pin、tx_pin 等接口,绑定到 FPGA 的实际引脚上;
  2. 接线:用杜邦线连接 FPGA 和 CH340 模块——FPGA 的 tx_pin 接 CH340 的 RX 引脚,FPGA 的 rx_pin 接 CH340 的 TX 引脚,FPGA 和 CH340 共地;
  3. 下载程序:将编译好的程序下载到 FPGA 开发板;
  4. 串口助手配置:打开串口助手,选择 CH340 对应的 COM 口,波特率设为 9600,数据位 8 位,停止位 1 位,无校验;
  5. 测试通信:
    1. FPGA 发送数据:驱动 tx_en_in 为高电平,tx_data_in 赋值,串口助手会接收到对应的数据;
    2. FPGA 接收数据:用串口助手发送数据,FPGA 的 rx_data_out 会解析出对应的数据,rx_done_out 置位。

四、关键设计要点与常见问题

1. 关键设计要点
  • 引脚同步:接收模块中对 rx_pin 打两拍,是 FPGA 设计中处理外部异步信号的标准操作;
  • 采样位置:接收时在每个 bit 的中间位置采样,是提高采样准确性的关键;
  • 状态机设计:收发模块均采用简单的两段式状态机,逻辑清晰;
  • 复位处理:所有寄存器都必须有复位逻辑,保证 FPGA 上电后处于正确的初始状态。
2. 常见问题与解决方法
  • 问题 1:串口助手接收不到数据,或接收的数据乱码? 解决:检查收发两端波特率是否一致;检查接线是否正确;检查 FPGA 时钟是否为 50MHz。
  • 问题 2:接收完成标志(rx_done)不置位? 解决:检查 rx_pin 引脚约束是否正确;检查采样点是否设置正确;检查停止位是否为高电平。
  • 问题 3:发送数据不稳定,偶尔出错? 解决:增加数据缓存寄存器,避免发送过程中待发送数据被修改;检查复位信号是否稳定。

五、总结与扩展

本文完整讲解了 FPGA 实现 UART 串口通信的核心原理、完整 Verilog 代码实例,以及仿真/硬件验证方法。

核心总结:FPGA 实现 UART 的关键,是'时钟分频'(匹配波特率)和'状态机'(控制帧结构),再加上简单的同步、采样优化,就能保证通信稳定。

扩展建议(进阶学习):

  • 修改波特率:只需修改顶层模块的 BAUD_RATE 参数,代码会自动计算分频系数;
  • 添加校验位:在发送模块中添加奇偶校验逻辑,接收模块中添加校验判断;
  • 实现中断机制:在接收完成、发送完成后,添加中断信号,配合 FPGA 的中断控制器;
  • 多字节收发:扩展代码,实现连续多字节的发送和接收。

目录

  1. FPGA 实现 UART 串口通信
  2. 一、UART 串口通信核心原理
  3. 1. 核心通信参数(新手必记,最常用配置)
  4. 2. 波特率详解
  5. (1)波特率的核心定义
  6. (2)直观实例
  7. (3)波特率的核心作用:保证收发同步
  8. (4)FPGA 中波特率的具体实现
  9. (5)工程常用波特率
  10. 3. FPGA 实现 UART 的核心思路
  11. 二、FPGA UART 完整代码实例(Verilog 实现)
  12. 1. 发送模块(uart_tx.v)
  13. 2. 接收模块(uart_rx.v)
  14. 3. 顶层模块(uart_top.v)
  15. 三、代码验证方法
  16. 1. 仿真验证
  17. 2. 硬件验证
  18. 四、关键设计要点与常见问题
  19. 1. 关键设计要点
  20. 2. 常见问题与解决方法
  21. 五、总结与扩展

更多推荐文章

查看全部
  • 基于 STM32 的智慧农业大棚系统设计
  • ChatGPT 学术版驱动的科研写作全流程辅助方案
  • MySQL 与 Navicat 在 Windows 下的安装及配置教程
  • SpringBoot 集成 RabbitMQ 常见消息模式示例
  • C++ 模板初阶:泛型编程基础
  • F5 刷新后浏览器前端发生了什么?
  • ResNet18 镜像:内置权重、WebUI 与极速推理方案
  • Python 金融数据获取:问财 API 全解析与实战
  • ASP.NET 页面输出二进制图像方法
  • ToDesk/顺网/海马云部署 DeepSeek 实测对比
  • 基于Vivado的RISC-V五级流水线CPU FPGA实现详解
  • 双指针算法初阶:移动零与复写零
  • Spring IoC 与 DI 详解:@Bean 注解、扫描路径及依赖注入方式
  • Minecraft 开源注入式客户端 LiquidBounce 使用指南
  • Copilot助力AI原生应用:提升开发效率的5种方法
  • Qwen3.5 开源发布:国产大模型性能与技术架构深度解读
  • DeepSeek-R1-Distill-Llama-8B 部署教程:Docker Compose 多模型推理服务
  • Ubuntu 20.04 网络配置指南
  • Qwen3-ASR-1.7B 实战:新闻发布会实时语音转写与发言人提取
  • Gradle 增量式构建原理与实战

相关免费在线工具

  • 加密/解密文本

    使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online

  • Gemini 图片去水印

    基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online

  • Markdown转HTML

    将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online

  • HTML转Markdown

    将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online