VHDL实现UART串口通信:从原理到FPGA回环测试

张开发
2026/4/14 20:21:47 15 分钟阅读

分享文章

VHDL实现UART串口通信:从原理到FPGA回环测试
1. UART串口通信基础与FPGA实现价值第一次接触UART串口通信时我盯着示波器上那些高低电平的变化波形看了整整一个下午。这种看似简单的通信方式实际上蕴含着数字系统设计的精髓。UARTUniversal Asynchronous Receiver/Transmitter作为一种异步串行通信协议在嵌入式系统和FPGA开发中扮演着重要角色。它只需要两根信号线TX和RX就能实现全双工通信这种简洁性使其成为硬件开发者最亲密的伙伴。在实际项目中我经常用FPGA实现UART通信模块。相比现成的UART芯片用VHDL自主实现具有三大优势首先是资源利用率高一个精简的UART核心只需消耗FPGA的几百个LE逻辑单元其次是灵活性可以自由调整波特率、数据位宽等参数最重要的是学习价值通过实现UART能深入理解时序控制和状态机设计。记得我最早实现的版本在115200波特率下总是丢数据后来通过优化采样点才解决这个调试过程让我对时钟域 crossing 有了深刻认识。2. UART通信协议深度解析2.1 帧结构与时序控制UART的一帧数据包含起始位低电平、5-9位数据位、可选的校验位和1-2位停止位高电平。在我的开发板上最常用的配置是1位起始位、8位数据位、无校验位、1位停止位简写为8N1。这里有个容易忽略的细节起始位的低电平必须保持完整的一个波特周期我在早期实现时就因为起始位检测窗口设置不当导致频繁误触发。波特率的选择直接影响通信质量。常见的9600、115200等波特率对应着不同的时钟分频系数。以50MHz系统时钟和9600波特率为例每个位周期需要50_000_000/9_600≈5208个时钟周期。这里有个实用技巧采样点最好设置在位周期的中间位置我在代码中通过计数到BAUD_CNT_MAX/2-1时采样这样能最大限度避开信号跳变边缘。2.2 抗干扰与错误处理在实际环境中串口通信常受到电磁干扰。我总结出三个加固措施三级寄存器消抖代码中的uart_rxd_d0/1/2、多数表决采样对每个数据位采样多次取多数值、帧间隔保护两帧之间强制间隔2-3个位周期。有次在工业现场调试就是靠增加多数表决机制解决了电缆感应噪声导致的数据错误问题。3. VHDL实现接收机模块3.1 边缘检测与起始位识别接收机的核心是一个精密的状态机。首先是起始位检测我采用三级寄存器级联的方式进行边沿检测process(clk) begin if rising_edge(clk) then uart_rxd_d0 uart_rxd; -- 第一级采样 uart_rxd_d1 uart_rxd_d0; -- 第二级延迟 uart_rxd_d2 uart_rxd_d1; -- 第三级延迟 end if; end process;当检测到uart_rxd_d2为高而uart_rxd_d1为低时即下降沿且当前不在接收过程中rx_flag0就触发接收流程。这里有个坑我踩过必须确保起始位低电平持续满一个波特周期否则会被噪声干扰误触发。我的解决方案是启动接收后等待1.5个位周期再采样第一个数据位。3.2 数据位采样与状态转换接收状态机的工作流程如下检测到起始位后rx_flag置位波特率计数器启动每计数满5208对应9600波特率接收位计数器加1在第1-8个位周期中点采样数据位第9个位周期检测停止位完整帧接收后输出完成信号并复位状态关键代码片段process(clk) begin if rising_edge(clk) then if baud_cnt BAUD_CNT_MAX/2 -1 then case rx_cnt is when 1 rx_data_t(0) uart_rxd_d2; when 2 rx_data_t(1) uart_rxd_d2; ... when 8 rx_data_t(7) uart_rxd_d2; when others null; end case; end if; end if; end process;4. VHDL实现发射机模块4.1 并串转换与时序控制发射机的核心是将8位并行数据转换为串行比特流。与接收机不同发射机的时序控制更为严格因为每个位的输出必须保持精确的波特率周期。我的实现采用了一个典型的状态机检测发送使能信号uart_tx_en装载待发送数据到寄存器依次输出起始位、8个数据位、停止位每个位保持准确的5208个时钟周期process(clk) begin if rising_edge(clk) then case tx_cnt is when 0 uart_txd 0; -- 起始位 when 1 uart_txd tx_data_t(0); ... when 8 uart_txd tx_data_t(7); when 9 uart_txd 1; -- 停止位 end case; end if; end process;4.2 忙信号与流量控制实际项目中经常需要知道发射机状态。我设计了uart_tx_busy信号在发送过程中保持高电平。这个信号特别有用当需要连续发送多帧数据时上位机可以通过检测busy信号避免数据覆盖。有次做Modbus RTU协议实现时就是靠这个信号解决了从机响应覆盖的问题。5. 系统集成与回环测试5.1 顶层模块设计将接收机和发射机集成到顶层模块时需要注意两个关键点时钟域同步和数据通路设计。我的方案是直接用接收完成信号uart_rx_done触发发射使能uart_tx_en同时将接收数据总线直连到发射数据输入端RXD: uart_rx port map( clk clk, rst_n rst_n, uart_rxd uart_rxd, uart_rx_done uart_tx_en, -- 接收完成直接触发发送 uart_rx_data uart_data -- 共享数据总线 ); TXD: uart_tx port map( clk clk, rst_n rst_n, uart_tx_en uart_tx_en, uart_tx_data uart_data, -- 共享数据总线 uart_txd uart_txd );5.2 调试技巧与常见问题在回环测试中我推荐以下调试步骤先用示波器检查TX信号确认基本的波特率和帧格式正确发送固定字符如0x55二进制01010101用示波器观察波形逐步提高波特率测试系统稳定性最后通过串口助手进行实际数据收发测试常见问题排查数据错位检查波特率分频系数计算是否正确随机错误增加信号滤波检查PCB布线是否引入噪声丢帧确认状态机转换条件是否严格特别是停止位检测6. 性能优化与扩展应用6.1 波特率自适应技术固定波特率在某些场景下不够灵活。我后来实现了自动波特率检测功能通过测量起始位低电平持续时间来动态计算波特率。核心代码如下-- 测量起始位低电平时钟周期数 process(clk) begin if uart_rxd 0 and baud_measure 0 then baud_cnt baud_cnt 1; elsif uart_rxd 1 and baud_measure 0 then baud_measure 1; detected_baud CLK_FREQ / baud_cnt; end if; end process;6.2 FIFO缓冲与流量控制当处理高速数据流时建议添加FIFO缓冲。我常用的方案是使用FPGA内部的Block RAM实现16字节深的FIFO配合硬件流控信号RTS/CTS。这能有效解决数据吞吐量不匹配的问题特别是在图像传感器数据采集等场景中效果显著。

更多文章