ZYNQ---PL端串口IP核中断驱动实战
2026/7/15 3:38:23 网站建设 项目流程

1. 为什么需要PL端串口扩展

很多工程师第一次接触ZYNQ时都会遇到一个尴尬的问题:PS端自带的两个UART接口根本不够用。在实际项目中,我们经常需要连接多个传感器、显示屏或调试终端,这时候就需要在PL端扩展串口。

我去年做过一个工业控制项目,需要同时连接4个Modbus RTU设备,PS端的两个串口完全不够分配。这时候在PL端用AXI UART16550 IP核扩展串口就成了最佳选择。这个IP核不仅兼容标准16550串口控制器,还支持中断驱动,实测波特率可以达到3Mbps,完全满足工业场景需求。

2. Vivado硬件工程搭建

2.1 创建基础工程

首先新建一个ZYNQ7020工程(其他ZYNQ型号也适用),在Block Design中添加ZYNQ7 Processing System核。这里有个小技巧:建议先双击ZYNQ核进入配置界面,在PS-PL Configuration中勾选"Fabric Resets"和"Interrupts"下的PL-PS中断选项,这样后续添加中断时会更方便。

接着添加AXI UART16550 IP核,这个核在Xilinx官方IP库里就能找到。我在多个项目中发现,这个IP核的FIFO深度设置很有讲究:

  • 对于低速串口(115200bps以下),16字节FIFO足够
  • 对于高速通信(1Mbps以上),建议设置为64或128字节

2.2 关键连接配置

完成IP核添加后,需要特别注意几个关键连接:

  1. 将UART核的AXI接口连接到ZYNQ的GP接口(通常是M_AXI_GP0)
  2. 将UART核的中断输出连接到ZYNQ的IRQ_F2P[0:0]
  3. 将UART核的串口信号引出到顶层(注意TX/RX交叉连接)

这里有个我踩过的坑:如果发现中断无法触发,请检查Vivado是否自动生成了concat IP。有时候需要手动添加一个Concat核,将多个中断信号合并后再接入ZYNQ。

2.3 引脚约束与生成

完成连接后,建议先运行Validate Design检查连接关系。确认无误后,在约束文件中添加如下典型配置:

set_property PACKAGE_PIN Y18 [get_ports uart_tx] set_property IOSTANDARD LVCMOS33 [get_ports uart_tx] set_property PACKAGE_PIN AA18 [get_ports uart_rx] set_property IOSTANDARD LVCMOS33 [get_ports uart_rx]

最后生成Bitstream时,建议勾选"-bin_file"选项,这样会同时生成.bin文件,方便后续通过SD卡更新固件。

3. SDK软件工程开发

3.1 创建基础工程

导出硬件到SDK后,新建一个空白应用工程。这里推荐使用BSP模板,它会自动包含UART驱动库。我习惯先测试基础收发功能,再添加中断处理,这样排查问题更有条理。

在BSP设置中,需要确认:

  • 已启用xuartns550驱动
  • 中断控制器设置为ScuGic(ZYNQ的标准配置)
  • 编译器优化等级建议先设为-O0方便调试

3.2 中断服务程序实现

Xilinx提供了标准的中断示例,但实际使用时需要做以下关键修改:

  1. 在xparameters.h中确认中断ID定义正确:
#define UART_IRPT_INTR XPAR_FABRIC_AXI_UART16550_0_IP2INTC_IRPT_INTR
  1. 中断初始化代码需要包含以下关键步骤:
// 初始化中断控制器 XScuGic_Config *IntcConfig = XScuGic_LookupConfig(INTC_DEVICE_ID); XScuGic_CfgInitialize(&IntcInstance, IntcConfig, IntcConfig->CpuBaseAddress); // 设置中断优先级和触发类型 XScuGic_SetPriorityTriggerType(&IntcInstance, UART_IRPT_INTR, 0xA0, 0x3); // 连接中断处理函数 XScuGic_Connect(&IntcInstance, UART_IRPT_INTR, (Xil_ExceptionHandler)XUartNs550_InterruptHandler, &UartInstance); // 启用中断 XScuGic_Enable(&IntcInstance, UART_IRPT_INTR);
  1. 中断处理函数建议按这个模板编写:
void UartIntrHandler(void *CallBackRef, u32 Event, unsigned int EventData) { static u8 buffer[64]; if (Event == XUN_EVENT_RECV_DATA) { int count = XUartNs550_Recv(&UartInstance, buffer, sizeof(buffer)); // 处理接收到的数据 for(int i=0; i<count; i++){ xil_printf("Recv: 0x%02X\r\n", buffer[i]); } } }

3.3 调试技巧

调试中断驱动时,我总结了几条实用经验:

  1. 先用示波器确认硬件线路正常
  2. 在中断入口处添加调试打印,确认中断是否触发
  3. 检查中断屏蔽寄存器(IER)设置是否正确
  4. 使用XUartNs550_GetInterruptMask()读取当前中断状态

如果遇到数据丢失问题,可以尝试:

  • 增大FIFO深度
  • 提高中断优先级
  • 在中断服务程序中尽快读取数据

4. 性能优化实战

4.1 中断响应时间测试

通过GPIO翻转+逻辑分析仪测量,在150MHz CPU频率下,从中断触发到进入ISR的平均延迟约为1.2us。这个时间主要消耗在:

  • 中断控制器响应时间:约0.4us
  • 上下文保存:约0.3us
  • 跳转到ISR:约0.5us

对于波特率115200的串口(每个字节间隔86us),这个延迟完全可接受。但在3Mbps时(每个字节间隔3.3us),就需要考虑DMA方案了。

4.2 缓冲区设计

为了避免数据丢失,我设计了一个双缓冲机制:

#define BUF_SIZE 256 typedef struct { u8 buffer1[BUF_SIZE]; u8 buffer2[BUF_SIZE]; u8 *active_buf; volatile int write_idx; } DoubleBuffer; void IntrHandler(void *CallBackRef, u32 Event, unsigned int EventData) { DoubleBuffer *dbuf = (DoubleBuffer *)CallBackRef; if(Event == XUN_EVENT_RECV_DATA) { int avail = XUartNs550_Recv(&UartInstance, dbuf->active_buf + dbuf->write_idx, BUF_SIZE - dbuf->write_idx); dbuf->write_idx += avail; if(dbuf->write_idx >= BUF_SIZE/2) { // 切换缓冲区 ProcessBuffer(dbuf->active_buf, dbuf->write_idx); dbuf->active_buf = (dbuf->active_buf == dbuf->buffer1) ? dbuf->buffer2 : dbuf->buffer1; dbuf->write_idx = 0; } } }

4.3 实际项目经验

在一个智能电表项目中,我们需要同时处理4个串口数据。通过以下优化实现了稳定运行:

  1. 为每个串口分配独立的中断优先级
  2. 使用RTOS的任务通知机制唤醒处理线程
  3. 在Vivado中为每个UART核分配独立的中断线(避免共享中断线)

关键配置参数如下表:

参数推荐值说明
中断优先级0xA0-0xC0高于系统任务
FIFO深度64字节平衡延迟和资源
接收超时4字符时间避免数据滞留
波特率容差<3%保证稳定性

5. 常见问题解决

5.1 中断不触发

这是新手最常见的问题,可按以下步骤排查:

  1. 确认Vivado中中断线已正确连接
  2. 检查SDK中的中断ID是否与硬件设计一致
  3. 验证中断控制器是否已正确初始化
  4. 用XUartNs550_GetInterruptStatus()读取中断状态寄存器

5.2 数据丢失

遇到数据丢失时,建议:

  1. 降低波特率测试
  2. 增大FIFO深度
  3. 检查中断服务程序是否耗时过长
  4. 使用逻辑分析仪捕获实际波形

5.3 性能瓶颈

当需要更高性能时,可以考虑:

  1. 使用AXI DMA+串口方案
  2. 在PL端实现数据预处理
  3. 改用更高端的ZYNQ UltraScale+系列

我在一个高速数据采集项目中,通过将UART IP核时钟提升到100MHz,配合DMA传输,实现了5Mbps的稳定传输。关键是在Vivado中正确设置时钟域交叉(Clock Domain Crossing)参数。

6. 进阶应用

6.1 与Linux驱动集成

如果需要移植到Linux系统,需要注意:

  1. 在设备树中添加UART节点
  2. 配置正确的时钟频率
  3. 实现中断资源映射

典型设备树配置示例:

axi_uart16550_0: serial@43c00000 { compatible = "xlnx,xps-uart16550-2.00.a"; reg = <0x43c00000 0x10000>; interrupts = <0 29 4>; clock-frequency = <100000000>; current-speed = <115200>; };

6.2 多串口管理

管理多个PL串口时,我推荐采用面向对象的设计模式:

typedef struct { XUartNs550 instance; DoubleBuffer buffer; u32 baudrate; } UartDevice; void Uart_Init(UartDevice *dev, u16 dev_id, u16 intr_id) { XUartNs550_Initialize(&dev->instance, dev_id); XUartNs550_SetHandler(&dev->instance, IntrHandler, dev); // ...其他初始化 } void Uart_Send(UartDevice *dev, const u8 *data, size_t len) { XUartNs550_Send(&dev->instance, data, len); }

这种设计在需要管理4个以上串口时特别有用,代码可维护性大大提高。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询