简介:本文深入剖析UART串口通信中FPGA接收端的识别机制,从物理层信号采样、起始位判定、波特率配置、数据位逐比特提取到停止位校验与错误检测,完整讲解接收状态机设计与常见工程陷阱。无论你是FPGA初学者还是嵌入式开发者,都能从中掌握构建可靠UART接收模块的核心方法,避免数据错位与丢帧问题。
UART(通用异步收发器)是一种广泛使用的串行通信协议,其核心特点在于“异步”——收发双方无需共享时钟信号,而是通过约定的波特率(Baud Rate)来实现位同步。在FPGA接收端,我们需要面对的第一个问题就是:如何在没有独立时钟线的条件下,从串行数据流中准确复原出发送端的数据内容?
UART通信通常采用TTL电平标准,空闲状态下信号线保持高电平(逻辑1),起始位是一个低电平脉冲(逻辑0),随后依次发送数据位、校验位(可选)和停止位。在FPGA内部,接收引脚会直接连接至I/O Bank的输入缓冲器,外部信号经过施密特触发器整形后进入FPGA的逻辑阵列。
值得注意的是,实际工程中可能通过电平转换芯片(如MAX3232)与RS-232标准对接,也可能直接与USB转UART桥接芯片通信。不同的电气标准会影响信号边沿的陡峭程度和噪声容限,但FPGA内部的处理逻辑是一致的——只需要关心引脚上呈现的数字电平状态。
一个完整的UART数据帧由以下部分构成:
发送端在空闲时输出高电平,当需要发送数据时,先拉低电平并保持一个位时间(即1/波特率秒),然后按位依次发送数据。这样的帧结构设计使得接收端能够通过检测下降沿来识别传输的起始点。
FPGA内部使用系统时钟(如50MHz、100MHz)进行逻辑运算,而UART数据速率通常在9600bps到921600bps之间。如何用快时钟去采样慢速的异步信号,是设计接收模块的首要挑战。
假设系统时钟频率为 clk_freq,目标波特率为 baud_rate,则每个数据位持续的系统时钟周期数为:
divisor = clk_freq / baud_rate
例如:系统时钟50MHz(50,000,000Hz),波特率115200,则divisor ≈ 434。FPGA通过一个计数器实现分频,在每个数据位的中点产生采样脉冲,从而获得稳定的位采样时机。
这里需要特别强调:UART通信双方允许存在一定误差,通常要求波特率误差小于±2%。对于标准整数分频,当clk_freq不是baud_rate的整数倍时,会引入累积误差。工程实践中,应优先选择能够整除或误差极小的时钟频率组合。
接收端必须对每个数据位进行多次采样,以保证可靠性。常见的做法是在一个位周期内采样3次或更多次,采用“多数表决”(Majority Vote)机制——即取多数采样点的电平值作为该位的最终判定值。
以16倍过采样为例:系统时钟频率为 16 × baud_rate,每个数据位被划分为16个时间片。接收端在边沿检测到起始位后,在第8个时间片(即位中点)附近连续采样3次,取多数投票结果。这一策略能有效抑制毛刺噪声和信号边沿抖动引起的误判。
起始位检测是UART接收的关键起点。理想情况下,接收端应当实时监测RX引脚的电平:
为区分真实起始位和噪声,成熟的设计通常在检测到下降沿后,延时半个位周期(即 divisor/2 个时钟周期)再次采样。若此时采样值仍为低电平,则确认起始位有效;若已恢复高电平,则判定为毛刺并忽略,重新回到空闲检测状态。
// 起始位检测伪代码always @(posedge clk or negedge rst_n) beginif (!rst_n) beginstate <= IDLE;sample_cnt <= 0;end else begincase (state)IDLE: beginif (rx_data == 1'b0) beginsample_cnt <= 0;// 延时divisor/2后再次确认state <= START_CHECK;endendSTART_CHECK: beginif (sample_cnt == divisor/2 - 1) beginif (rx_data == 1'b0)state <= DATA_SAMPLE;elsestate <= IDLE; // 毛刺,忽略end else beginsample_cnt <= sample_cnt + 1;endend// ...endcaseendend
一个健壮的UART接收模块通常采用有限状态机(FSM)实现。常见的状态包括:空闲(IDLE)、起始位确认(STRT)、数据接收(DATA)、校验位检测(PRTY)、停止位确认(STOP)和错误处理(ERROR)。
| 状态 | 功能描述 | 转移条件 |
|---|---|---|
| IDLE | 等待下降沿 | RX引脚出现低电平 |
| STRT | 确认起始位 | 半位周期后仍为低电平 |
| DATA | 逐位采样数据 | 采样完8个数据位 |
| PRTY | 校验位(可选) | 完成校验位采样 |
| STOP | 确认停止位 | 采样到高电平 |
| ERROR | 错误处理 | 停止位为低或校验失败 |
数据接收状态内部需要维护两个计数器:位计数器 bit_index 和数据位采样计数器 sample_cnt。当 sample_cnt 到达divisor时,表示一位采样完成,此时锁存采样结果,递增 bit_index,并重置 sample_cnt。
假设采用1倍波特率采样(即每个位只采样一次),理论上是可行的,但工程上更推荐过采样。我们以16倍过采样为例展开:
16 × baud_rate这种设计在FPGA中实现起来非常直接,且对时钟频率要求不高。以115200bps为例,16倍过采样需要1.8432MHz时钟,这在大多数FPGA开发板上都容易通过PLL或分频获得。
// 数据位采样核心逻辑reg [3:0] sample_cnt; // 16倍过采样计数器reg [2:0] bit_cnt; // 位计数器,0~7reg [7:0] shift_reg; // 移位寄存器always @(posedge clk or negedge rst_n) beginif (!rst_n) beginsample_cnt <= 0;bit_cnt <= 0;shift_reg <= 0;end else if (state == DATA) beginif (sample_cnt == 15) beginsample_cnt <= 0;shift_reg <= {rx_data, shift_reg[7:1]}; // 低位先收bit_cnt <= bit_cnt + 1;end else beginsample_cnt <= sample_cnt + 1;endendend
注意上述代码中移位操作的方向:UART协议规定LSB先发送,因此接收到的第一位应放置在移位寄存器的最低位,后续数据依次左移。
停止位的作用是确保发送端在下一帧起始位之前,将线路恢复为高电平至少一个位时间。接收端在完成最后一个数据位采样后,经过一个位周期采样停止位:
帧错误是UART通信中最常见的错误类型之一,它通常意味着发送端的时钟频率与接收端设定的波特率存在过大偏差。在调试阶段,如果发现FPGA接收频繁出错,首先应当检查波特率配置是否正确,尤其是分频计数器的计算是否存在截断误差。
假设发送端波特率为 B_TX,接收端设定的采样波特率为 B_RX,理想情况下二者严格相等。但在实际场景中,晶振存在初始误差(通常±30ppm),温度漂移也会引起频率偏移,USB转串口芯片的波特率发生器也可能存在舍入误差。
接收端要求每个数据位中点采样的偏移不超过半个位周期,否则可能采样到相邻位。因此,允许的最大误差为:
Error_max = ±0.5 / N_bit
其中 N_bit 是一帧中的总位数(起始位+数据位+校验位+停止位)。对于8位数据、无校验、1位停止位的帧,N_bit = 10,最大允许误差为±5%。但这是理论极限,实际设计中应预留至少2~3倍的裕量,即波特率误差应控制在±2%以内。
在实际硬件中,RX引脚可能受到电磁干扰、串扰、连接器接触不良等因素影响,产生毛刺脉冲。毛刺的典型宽度为几纳秒到几十纳秒,远小于一个位周期。利用16倍过采样,毛刺在多数表决机制下几乎不可能影响最终采样结果,因为只有连续3个采样点中至少2个点同时被干扰,才可能改变判定结果。
对于极慢波特率(如9600bps),位周期约为104μs,毛刺更容易被平均化。而对于高速波特率(如921600bps),位周期仅为1.085μs,此时必须依赖过采样和多数表决来保证可靠性。
当FPGA需要连续接收大量数据时,接收模块的数据输出端通常连接一个FIFO缓冲区。由于系统处理逻辑(如状态分析、协议解析)可能需要若干周期才能完成一次数据读取,若没有FIFO缓冲,下一个数据字节可能被覆盖。
工程上的典型做法是:接收模块产生8位并行数据后,将其写入FIFO,同时置位“数据有效”标志。上层逻辑在空闲时读取FIFO并清除标志。FIFO深度可根据应用场景设置,常见配置为16字节、32字节或64字节。
在Modbus、Ymodem等基于UART的通信协议中,通常要求接收端能够识别一帧消息的结束。常见的判断方式有两种:
FPGA实现超时检测时,需要一个定时器,在每个字节接收完成后重置计数器。当计数值超过预设阈值时,产生帧结束信号。
编写UART接收模块的测试平台时,需要模拟发送端的行为。最直接的方式是使用Verilog任务(Task)生成符合波特率时序的串行数据:
task uart_send_byte;input [7:0] data;integer i;begin// 起始位uart_tx = 0;#(BIT_PERIOD);// 数据位,LSB firstfor (i = 0; i < 8; i = i + 1) beginuart_tx = data[i];#(BIT_PERIOD);end// 停止位uart_tx = 1;#(BIT_PERIOD);endendtask
其中 BIT_PERIOD 需要根据仿真时钟频率和目标波特率计算,例如仿真时钟10MHz、波特率115200时,BIT_PERIOD = 8680ns。
位序颠倒:发送端发送0x53(二进制01010011),如果接收端恢复为0xCA,说明数据位顺序搞反了。应检查移位寄存器的移位方向和最终输出时的重组逻辑。
起始位判定过早:如果接收状态机在下降沿出现后立即开始采样数据位,而没有等待半位周期,那么第一次采样点可能落在起始位内部而非第一个数据位的中点,导致整帧数据全部偏移一位。
计数器边界错误:分频计数器从0计数到divisor-1,而不是divisor。若divisor=434,则计数范围应为0~433,共434个时钟周期,对应一个数据位。边界条件出错会导致采样频率偏移。
异步复位释放时刻:FPGA中使用异步复位时,复位释放不能与时钟上升沿过近,否则可能引起亚稳态。应使用同步复位或增加复位释放同步器。
UART接收端的FPGA实现并不复杂,但要达到工业级可靠性,需要从底层时序细节出发,充分考虑采样窗口、过采样率、状态机鲁棒性等多个维度。
建议开发者在实际项目中遵循以下原则:
最后,所有设计必须经过严格的仿真验证,包括正常传输、波特率偏移、毛刺注入、帧错误模拟等边界场景。只有经过充分验证的接收模块,才能在真实硬件环境中表现出稳定可靠的性能。