从零开始:手把手教你用FPGA实现UART通信(Verilog代码解析)

张开发
2026/4/17 17:42:30 15 分钟阅读

分享文章

从零开始:手把手教你用FPGA实现UART通信(Verilog代码解析)
从零构建FPGA-UART通信系统Verilog实战与深度优化指南第一次接触FPGA上的UART实现时我被一个简单的问题困扰了整整三天——为什么接收端总是漏掉第一个字节直到在示波器上捕捉到信号时序才发现波特率计数器的边界条件处理存在微妙错误。这种魔鬼在细节中的体验让我意识到UART这种看似简单的协议在硬件实现时需要精确到时钟周期的控制。本文将分享从基础实现到性能优化的完整路径特别适合已经掌握Verilog基础但希望提升实战能力的开发者。1. UART协议核心机制解析UART通信的本质是时间维度上的精确舞蹈。没有时钟线的约束下发送端和接收端依靠预先约定的波特率完成比特流的同步解析。这种异步特性带来了硬件设计的独特挑战起始位检测的容错性理想情况下起始位是从高到低的跳变。但实际电路中可能存在毛刺需要至少3次采样确认才能真正判定起始位中点采样原则每个数据位的采样点应该位于该位时间窗的中间位置这要求波特率计数器具有精确的相位控制多数表决机制典型的实现会对每个数据位进行3次采样前、中、后通过多数表决确定最终值以下是一个标准的UART数据帧结构参数表组成部分长度(位)电平作用说明起始位1低电平标志传输开始数据位5-8-有效载荷LSB优先奇偶校验位0/1-可选错误检测停止位1-2高电平标志帧结束提供时钟恢复缓冲关键提示FPGA实现时最常见的错误来源是波特率生成误差。当系统时钟不是目标波特率的整数倍时需要特别注意累计误差的处理。2. 可配置UART接收机设计接收模块的状态机设计直接决定了系统的鲁棒性。我们采用五状态模型实现带错误检测的接收器parameter IDLE 3d0; // 等待起始位 parameter START 3d1; // 验证起始位 parameter RECEIVE 3d2; // 数据位采样 PARAMETER STOP 3d3; // 停止位检查 PARAMETER OUTPUT 3d4; // 数据输出波特率生成采用动态重装载技术避免传统计数器溢出方式的累计误差// 假设系统时钟50MHz目标波特率115200 localparam BAUD_DIV (CLK_FREQ/(16*BAUD_RATE)) - 1; always (posedge clk) begin if(baud_cnt 0) begin baud_cnt BAUD_DIV; sample_en 1b1; end else begin baud_cnt baud_cnt - 1; sample_en 1b0; end end数据采样窗口采用三次采样表决机制显著提高抗干扰能力always (posedge clk) begin case(sample_point) 0: samples[0] rxd; 1: samples[1] rxd; 2: begin samples[2] rxd; data_bit (samples[0] samples[1]) | (samples[1] samples[2]) | (samples[0] samples[2]); end endcase end3. 高性能发送模块实现技巧发送时序控制的关键在于精确的位定时和干净的信号切换。我们采用预分频技术实现波特率同步// 波特率时钟生成 always (posedge clk or posedge reset) begin if(reset) begin baud_clk 0; prescaler 0; end else begin if(prescaler DIVIDER-1) begin prescaler 0; baud_clk ~baud_clk; end else begin prescaler prescaler 1; end end end为适应不同设备需求发送模块支持可配置的数据帧格式// 可配置参数示例 parameter DATA_BITS 8; // 5-8位数据 parameter STOP_BITS 1; // 1或2位停止 parameter PARITY_EN 1; // 奇偶校验使能 parameter PARITY_ODD 0; // 0偶校验 1奇校验高级应用中可以添加FIFO缓冲提升吞吐量module uart_tx_fifo ( input wire clk, input wire [7:0] data_in, input wire wr_en, output wire tx_busy, output wire txd ); // FIFO实例化 fifo_generator_0 tx_fifo ( .clk(clk), .din(data_in), .wr_en(wr_en), .full(full), .dout(tx_data), .rd_en(tx_rd_en) ); // 发送状态机 always (posedge clk) begin case(state) IDLE: if(!empty) begin tx_rd_en 1b1; state START; end // 其他状态... endcase end endmodule4. 系统集成与调试实战4.1 测试平台构建构建自检测试环境是验证UART功能的关键。我们设计闭环测试系统[Test Pattern Generator] -- [UART TX] --FPGA引脚-- [UART RX] -- [Data Checker] ^ | |_____________________________________|典型测试用例包括边界值测试0x00, 0xFF等特殊数据连续传输测试验证FIFO和流控制错误注入测试人为制造奇偶错误4.2 信号完整性优化当波特率超过1Mbps时PCB布局和终端匹配变得至关重要保持TX/RX走线等长差分对控制在5mil以内添加33Ω串联电阻匹配传输线阻抗在高速应用中考虑使用LVDS电平标准4.3 高级功能扩展对于工业级应用可以扩展以下功能自动波特率检测通过测量起始位宽度硬件流控制RTS/CTS信号实现多节点通信添加地址识别层// 自动波特率检测核心逻辑 always (negedge rxd) begin // 捕捉起始沿 if(state IDLE) begin baud_counter 0; state MEASURE; end end always (posedge clk) begin if(state MEASURE) begin baud_counter baud_counter 1; if(rxd) begin // 检测起始位结束 detected_baud SYSTEM_CLK / (baud_counter * 16); state IDLE; end end end在完成基础实现后我习惯用逻辑分析仪捕获实际通信波形。有次发现停止位偶尔被误判最终定位到是时钟域交叉问题——这提醒我们在高速设计中跨时钟域信号必须经过适当的同步处理。

更多文章