1. 问题背景:Zynq 7010串口通信为何频繁丢包
在嵌入式系统开发中,串口通信是最基础也最容易出问题的环节之一。我在使用Xilinx Zynq 7010平台开发时,遇到了一个典型问题:当数据传输速率达到115200bps时,接收端会出现随机性丢包。通过逻辑分析仪抓取波形发现,问题并非出在物理层信号质量,而是由于处理器响应延迟导致的缓冲区溢出。
Zynq系列芯片的串口控制器(UART)采用双缓冲架构,但默认FIFO深度仅有16字节。当系统负载较高时,PS(处理系统)侧可能无法及时读取DR(数据寄存器),此时新到达的数据就会覆盖未读取的旧数据。特别是在Linux系统下,如果用户空间程序通过select/epoll机制轮询读取,上下文切换带来的延迟会使这个问题更加明显。
关键现象提示:当串口助手显示接收到的数据中出现不连续的跳变(如正常序列1,2,3,4突然变成1,4),且丢包程度与系统负载正相关时,大概率是FIFO溢出导致。
2. FIFO方案选型:软硬件实现对比
2.1 硬件FIFO方案(AXI Stream FIFO IP核)
在Vivado中调用AXI Stream FIFO IP核是最直接的解决方案。该方案的优势在于:
- 独立时钟域运行,支持异步读写
- 可配置深度(建议至少1024字节)
- 提供空/满状态标志位
- 吞吐量可达数百Mbps
具体参数配置示例:
tcl复制create_ip -name axis_data_fifo -vendor xilinx.com -library ip -version 2.0 \
-module_name uart_fifo
set_property -dict [list \
CONFIG.TDATA_NUM_BYTES {1} \
CONFIG.FIFO_DEPTH {1024} \
CONFIG.IS_ACLK_ASYNC {1} \
] [get_ips uart_fifo]
2.2 软件FIFO方案(环形缓冲区)
对于资源受限的场景,可以在PS侧实现环形缓冲区。以下是关键实现要点:
c复制#define FIFO_SIZE 1024
typedef struct {
uint8_t buffer[FIFO_SIZE];
volatile uint32_t head; // 必须加volatile
volatile uint32_t tail;
} uart_fifo_t;
// 中断服务例程中调用
void fifo_push(uart_fifo_t *fifo, uint8_t data) {
uint32_t next_head = (fifo->head + 1) % FIFO_SIZE;
if(next_head != fifo->tail) {
fifo->buffer[fifo->head] = data;
fifo->head = next_head;
} else {
// 触发溢出处理
}
}
两种方案对比如下:
| 特性 | 硬件FIFO | 软件FIFO |
|---|---|---|
| 最大吞吐量 | >100Mbps | <10Mbps |
| 延迟确定性 | 纳秒级 | 微秒级 |
| CPU占用 | 几乎为零 | 需要中断处理 |
| 资源消耗 | 消耗BRAM/FF | 仅消耗内存 |
| 跨时钟域支持 | 原生支持 | 需额外同步逻辑 |
3. 硬件FIFO的Vivado实现细节
3.1 IP核集成与连线
在Block Design中添加FIFO时需要注意:
- 连接UART的AXI Stream接口到FIFO的S_AXIS端口
- 将FIFO的M_AXIS连接到DMA或直接到GP端口
- 时钟域交叉处理:
- S_AXIS_ACLK连接UART时钟(通常为100MHz)
- M_AXIS_ACLK连接PS侧时钟(如FCLK_CLK0)
常见坑点:如果忘记勾选"Enable TLAST"选项,会导致DMA无法自动判断数据包边界。建议在AXI Stream接口上保留TLAST信号,即使UART协议本身不需要。
3.2 时序约束关键点
异步FIFO需要特别关注时钟域约束:
xdc复制set_clock_groups -asynchronous \
-group [get_clocks -include_generated_clocks sys_uart_clk] \
-group [get_clocks -include_generated_clocks fclk_clk0]
对于Zynq 7010器件,建议添加以下约束以避免亚稳态:
xdc复制set_property ASYNC_REG TRUE [get_cells -hier -filter {NAME =~ *sync_reg*}]
set_max_delay -from [get_clocks sys_uart_clk] \
-to [get_clocks fclk_clk0] 2.000
4. 软件侧驱动开发要点
4.1 Linux内核驱动适配
对于采用DMA的方案,需要修改设备树:
dts复制uart0: serial@e0000000 {
compatible = "xlnx,xuartps";
fifo-size = <1024>;
dmas = <&dmac_s 0>, <&dmac_s 1>;
dma-names = "rx", "tx";
};
驱动程序中关键的中断处理逻辑:
c复制static irqreturn_t uart_isr(int irq, void *dev_id)
{
struct uart_port *port = dev_id;
unsigned int isrstatus = xuartps_readreg(port, XUARTPS_ISR_OFFSET);
if(isrstatus & XUARTPS_IXR_RXOVR) {
// FIFO溢出处理
xuartps_writereg(port, XUARTPS_ISR_OFFSET, XUARTPS_IXR_RXOVR);
port->icount.overrun++;
}
// ...其他中断处理
return IRQ_HANDLED;
}
4.2 用户空间优化技巧
即使有了硬件FIFO,用户空间读取策略也影响性能:
c复制// 高效读取示例
struct epoll_event ev;
int epfd = epoll_create1(0);
ev.events = EPOLLIN | EPOLLET; // 边缘触发模式
epoll_ctl(epfd, EPOLL_CTL_ADD, uart_fd, &ev);
while(1) {
int n = epoll_wait(epfd, &ev, 1, -1);
if(n > 0) {
char buf[256];
int len;
while((len = read(uart_fd, buf, sizeof(buf))) > 0) {
// 处理数据
}
}
}
5. 实测数据与性能对比
在Zynq 7010开发板上进行压力测试(115200bps,8N1格式):
| 测试条件 | 无FIFO | 软件FIFO | 硬件FIFO |
|---|---|---|---|
| 持续传输1MB数据 | 丢包率12% | 丢包率0.3% | 零丢包 |
| CPU占用率 | 15% | 8% | <1% |
| 最大突发耐受量 | 16字节 | 512字节 | 1024字节 |
| 中断触发次数 | 每字节一次 | 每64字节一次 | 仅DMA完成中断 |
特别说明:当波特率提升到1Mbps时,纯软件方案开始出现约2%的丢包,而硬件方案仍保持稳定。这是因为硬件FIFO的吞吐量主要受限于AXI总线带宽(理论值可达数百Mbps)。
6. 进阶优化:动态深度调整策略
对于需要兼顾资源利用率和性能的场景,可以实现动态深度调整。在Vivado中创建带AXI-Lite控制接口的FIFO:
verilog复制module smart_fifo #(
parameter MAX_DEPTH = 2048
)(
input wire aclk,
input wire aresetn,
// AXI Stream接口
input wire [7:0] s_axis_tdata,
input wire s_axis_tvalid,
output wire s_axis_tready,
// ...其他标准AXI Stream信号
// AXI-Lite控制接口
input wire [31:0] s_axi_awaddr,
// ...完整AXI-Lite接口
);
reg [15:0] watermark_high;
reg [15:0] watermark_low;
always @(posedge aclk) begin
if(!aresetn) begin
watermark_high <= MAX_DEPTH - 16;
watermark_low <= 32;
end else if(s_axi_wvalid) begin
case(s_axi_awaddr[3:0])
4'h0: watermark_high <= s_axi_wdata[15:0];
4'h4: watermark_low <= s_axi_wdata[15:0];
endcase
end
end
assign s_axis_tready = (fifo_count < watermark_high);
assign m_axis_tvalid = (fifo_count > watermark_low);
endmodule
这种设计允许运行时通过PS调整:
- watermark_high:当FIFO存量超过此值时停止接收
- watermark_low:当FIFO存量低于此值时停止发送
实测表明,动态策略相比固定深度可以节省约30%的BRAM使用量,同时保持相同的抗丢包能力。
7. 常见问题排查指南
7.1 FIFO无法正常工作
检查清单:
- 确认AXI Stream连接的TREADY/TVALID握手信号(用ILA抓取)
- 验证时钟域配置是否正确(异步FIFO需要独立时钟)
- 检查复位信号是否满足时序要求(至少保持10个周期)
7.2 数据错位问题
典型表现:接收到的字节顺序错乱。解决方法:
- 在跨时钟域路径插入同步寄存器
- 检查AXI Stream的TKEEP/TLAST信号连接
- 确认字节序设置(Endianness)
7.3 性能不达预期
优化方向:
- 将FIFO存储在UltraRAM(如果器件支持)
- 启用FIFO的First-Word Fall-Through模式
- 调整DMA突发传输长度(建议设为64字节)
我在实际项目中遇到过一个隐蔽问题:当FIFO深度设置为非2的幂次方时,某些IP核版本会出现地址计算错误。解决方案是坚持使用512/1024/2048等标准深度值。
