国产ZYNQ双核通信实战从零搭建AMP环境与SGI中断对话系统在嵌入式开发领域多核处理器的协同工作一直是提升系统性能的关键。国产ZYNQ系列芯片凭借其灵活的可编程逻辑和强大的ARM多核架构为开发者提供了丰富的设计可能性。本文将带您一步步实现ZYNQ芯片上两个ARM核心之间的对话——通过AMP非对称多处理架构和SGI软件生成中断机制建立可靠的核间通信。1. 环境准备与工程创建开始之前确保您已准备好以下工具和环境国产ZYNQ开发板如紫光同创、复旦微等Vivado设计套件版本2018.3或更高SDK开发环境USB转串口调试工具创建独立的SDK工程是AMP开发的第一步。不同于对称多处理SMPAMP要求为每个核心创建单独的可执行文件。具体操作如下打开Vivado并完成基本的硬件设计包括ZYNQ处理器的配置导出硬件平台到SDK在SDK中创建两个独立的Application Project第一个工程命名为CPU0_APP处理器选择ps7_cortexa9_0第二个工程命名为CPU1_APP处理器选择ps7_cortexa9_1两个工程都选择Hello World模板作为起点注意务必确认每个工程关联到正确的CPU核心这是后续通信能正常工作的基础。2. 内存配置与链接脚本修改默认的链接脚本可能不适合AMP场景我们需要手动调整以确保两个核心的程序能正确加载到DDR内存中。以下是关键修改步骤2.1 修改icf链接文件对于CPU0_APP工程找到lscript.ld文件或对应的icf文件修改内存区域定义确保代码段(.text)和数据段(.data)位于DDR中为堆栈分配足够空间建议至少16KB/* CPU0内存布局示例 */ MEMORY { ps7_ddr_0_S_AXI_BASEADDR : ORIGIN 0x00100000, LENGTH 0x1FF00000 ps7_ram_0_S_AXI_BASEADDR : ORIGIN 0x00000000, LENGTH 0x00030000 ps7_ram_1_S_AXI_BASEADDR : ORIGIN 0xFFFF0000, LENGTH 0x0000FE00 }对于CPU1_APP工程需要确保内存区域不与CPU0冲突/* CPU1内存布局示例 */ MEMORY { ps7_ddr_0_S_AXI_BASEADDR : ORIGIN 0x10100000, LENGTH 0x1FF00000 ps7_ram_0_S_AXI_BASEADDR : ORIGIN 0x00000000, LENGTH 0x00030000 ps7_ram_1_S_AXI_BASEADDR : ORIGIN 0xFFFF0000, LENGTH 0x0000FE00 }2.2 中断栈大小调整在AMP架构中每个核心需要独立的中断处理能力。默认的栈大小可能不足容易导致各种异常。建议在启动代码中增加栈大小#define IRQ_STACK_SIZE 0x2000 /* 8KB中断栈 */ #define FIQ_STACK_SIZE 0x1000 /* 4KB快速中断栈 */ #define SVC_STACK_SIZE 0x2000 /* 8KB管理模式栈 */3. SGI中断配置与实现SGISoftware Generated Interrupt是ARM架构中用于核间通信的重要机制。在ZYNQ芯片上SGI中断号为0-15。下面我们实现CPU0和CPU1之间的双向中断通信。3.1 中断号定义与初始化首先在两个工程中定义统一的中断号/* 公共头文件或各自工程中的定义 */ #define SGI_CPU0_TO_CPU1 14 /* CPU0发给CPU1的中断号 */ #define SGI_CPU1_TO_CPU0 15 /* CPU1发给CPU0的中断号 */然后初始化GIC通用中断控制器/* GIC初始化代码以CPU0为例 */ Status FGicPs_SetupInterruptSystem(IntcInstance); if(Status ! GIC_SUCCESS) { fmsh_print(GIC初始化失败\n\r); return GIC_FAILURE; }3.2 中断处理函数注册为每个核心注册对应的中断处理函数/* CPU0的中断处理函数 */ void SGI_CPU1_Handler(void *InstancePtr) { fmsh_print(CPU0收到来自CPU1的中断!\n\r); /* 可以在这里设置标志位或进行其他处理 */ } /* CPU1的中断处理函数 */ void SGI_CPU0_Handler(void *InstancePtr) { fmsh_print(CPU1收到来自CPU0的中断!\n\r); /* 可以在这里设置标志位或进行其他处理 */ }注册中断到GIC/* CPU0注册中断 */ Status FGicPs_Connect(IntcInstance, SGI_CPU1_TO_CPU0, (FMSH_InterruptHandler)SGI_CPU1_Handler, IntcInstance); /* CPU1注册中断 */ Status FGicPs_Connect(IntcInstance, SGI_CPU0_TO_CPU1, (FMSH_InterruptHandler)SGI_CPU0_Handler, IntcInstance);3.3 中断触发与测试在两个核心的主循环中我们可以定期触发中断来测试通信/* CPU0触发中断给CPU1 */ FGicPs_SoftwareIntr(IntcInstance, SGI_CPU0_TO_CPU1, (11)); /* CPU1触发中断给CPU0 */ FGicPs_SoftwareIntr(IntcInstance, SGI_CPU1_TO_CPU0, (10));4. 系统固化与调试技巧完成代码开发后需要将程序固化到开发板中进行测试。4.1 生成启动文件为每个核心生成独立的ELF可执行文件创建包含两个程序的启动镜像BOOT.BIN在镜像中指定每个程序运行的CPU核心4.2 常见问题排查在实际开发中可能会遇到以下问题及解决方案问题现象可能原因解决方案系统启动后无输出程序未正确加载到指定核心检查启动镜像配置确认每个程序关联到正确的CPU中断不触发GIC配置错误或中断号冲突确认两个核心使用不同的SGI中断号检查GIC初始化代码系统随机崩溃栈大小不足或内存冲突增加栈大小检查链接脚本确保内存区域不重叠通信不稳定中断处理耗时过长优化中断处理函数避免复杂操作4.3 高级应用共享内存通信除了中断通知外双核之间通常还需要共享数据。可以使用片上内存OCM作为共享区域在Vivado中预留OCM空间通常为256KB在链接脚本中定义共享区域通过volatile指针访问共享内存配合SGI中断实现高效的数据交换/* 共享内存定义示例 */ #define SHARED_MEM_BASE 0xFFFF0000 volatile uint32_t *shared_data (uint32_t *)SHARED_MEM_BASE;在实际项目中我曾遇到一个有趣的调试案例双核通信时偶尔会丢失中断。经过排查发现是因为中断处理函数中进行了耗时操作导致后续中断被淹没。解决方案是简化中断处理仅设置标志位主循环中处理实际任务。这种中断轮询的混合模式在嵌入式开发中非常实用。