跳到主要内容FPGA 实现 UART 串口通信:原理与 Verilog 代码实例 | 极客日志汇编算法
FPGA 实现 UART 串口通信:原理与 Verilog 代码实例
FPGA 实现 UART 串口通信的原理与 Verilog 代码。内容涵盖 UART 核心参数(波特率、数据位、停止位等)、波特率计算与分频实现、发送与接收模块的状态机设计、顶层模块例化。提供了完整的 Verilog 代码示例,包括 uart_tx.v、uart_rx.v 和 uart_top.v。此外,还介绍了仿真验证步骤(Testbench)及硬件验证流程(引脚约束、接线、串口助手配置)。最后总结了关键设计要点(如引脚同步、采样位置)及常见问题的解决方法,并给出了扩展建议(如修改波特率、添加校验位等)。
雾岛听风7 浏览 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 位/字节) |
|---|
| 9600 | 104μs | 960 |
| 19200 | 52μs | 1920 |
| 38400 | 26μs | 3840 |
| 115200 | 8.68μs | 11520 |
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 模块、杜邦线、串口助手。
- 引脚约束:根据 FPGA 开发板,编写引脚约束文件,将顶层模块的 clk、rst_n、rx_pin、tx_pin 等接口,绑定到 FPGA 的实际引脚上;
- 接线:用杜邦线连接 FPGA 和 CH340 模块——FPGA 的 tx_pin 接 CH340 的 RX 引脚,FPGA 的 rx_pin 接 CH340 的 TX 引脚,FPGA 和 CH340 共地;
- 下载程序:将编译好的程序下载到 FPGA 开发板;
- 串口助手配置:打开串口助手,选择 CH340 对应的 COM 口,波特率设为 9600,数据位 8 位,停止位 1 位,无校验;
- 测试通信:
- FPGA 发送数据:驱动 tx_en_in 为高电平,tx_data_in 赋值,串口助手会接收到对应的数据;
- 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 的中断控制器;
- 多字节收发:扩展代码,实现连续多字节的发送和接收。
微信扫一扫,关注极客日志
微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
相关免费在线工具
- 加密/解密文本
使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,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
- JSON 压缩
通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online