ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

Zynq-7020 AMP双核通信实战:Vitis 2023/2024平台搭建与共享内存中断实现

Zynq-7020 AMP双核通信实战:Vitis 2023/2024平台搭建与共享内存中断实现 从一块Zynq-7020开发板到双核通信跑通我前后折腾了快一个月。Vitis 2023.1、2023.2再到2024.1每个版本都有各自的脾气。这篇文章就把我在这期间踩过的坑、验证过的方案、还有最终稳定运行的AMP双核通信实现原原本本整理出来。这篇文章要解决的核心问题很简单在Zynq-7010/7020上怎么让两个Cortex-A9核各跑各的程序AMP模式并且通过共享内存加核间中断实现双向通信。整个过程基于Vitis 2023/2024工具链包括平台搭建、FSBL配置、双核应用工程创建、BOOT.BIN生成以及SD卡启动制作。适合正在做Zynq异构多核开发、或者想把Linux和裸机程序放在同一颗芯片上跑的工程师参考。1. 为什么Zynq双核要搞AMP1.1 双核的两种活法SMP与AMPZynq-7000系列的PSProcessing System里集成了两颗ARM Cortex-A9处理器主频最高可到1GHz7010/7020通常跑666MHz或800MHz。两颗核摆在那里怎么用就有讲究了。SMP对称多处理是我们最常见的用法两颗核跑同一个操作系统由操作系统统一调度任务分发到哪个核上执行用户基本无感。日常做Linux开发时用的就是SMP模式内核线程和用户进程会负载均衡地分配到CPU0和CPU1上。AMP非对称多处理则完全不一样。两颗核各跑各的互相独立。最常见的设计是CPU0跑Linux负责网络、文件系统、人机交互这些复杂任务CPU1跑裸机程序或RTOS负责实时控制、数据采集这类对时序要求高的任务。两个核之间通过约定的机制通信各司其职。从软件角度看AMP比SMP麻烦得多因为你得自己管好两个执行环境的内存布局、启动流程和数据交互。但Zynq这类SoC之所以要推AMP是因为很多工业场景确实需要“一套硬件里同时塞一个通用OS和一个实时环境”——外接两个芯片既增加成本和面积又引入额外的通信延迟。1.2 三种主流的AMP组合方案Zynq-7010/7020上做AMP常见的有三条技术路线各有利弊组合方案CPU0CPU1适用场景难度双裸机裸机程序裸机程序两个简单裸机任务、入门学习较低Linux 裸机Linux裸机程序主控处理复杂业务协核做实时控制中等Linux RTOSLinuxFreeRTOS等实时性要求更高、多任务协作较高我自己最终落地的是Linux加裸机AMP这套组合CPU0跑PetaLinux负责网络、Qt上位机交互和日志存储CPU1跑一个裸机程序专门处理AD采样和电机控制保证每个控制周期是硬实时的。这样的好处是CPU1的裸机程序没有Linux调度延迟干扰中断响应稳定而且调试起来也直观。双裸机AMP则适合先把通信机制跑通理解整个启动流程和共享内存原理。如果你第一次接触Zynq AMP我建议先按双裸机走一遍再上Linux组合否则调试时变量太多出了问题不好定位。1.3 核间通信的本质共享内存加中断AMP双核通信绕不开两个基本问题数据怎么传信号怎么通知。数据传递最直接的方式就是共享内存。两个核都能访问同一片物理内存CPU0把数据写入约定地址CPU1从同一地址读出来。不用走任何总线协议也没有OS参与实时性极高。信号通知则需要用到核间中断。Zynq的GIC通用中断控制器支持软件触发的中断SGISoftware Generated InterruptCPU0可以向CPU1发送一个指定ID的SGICPU1收到后会在中断处理函数里响应比如去共享内存读新数据。反过来也一样。共享内存加SGI这套组合就是AMP通信的基石。但实现的时候有几个关键点容易翻车内存地址分配、Cache一致性、中断配置、启动顺序。下面几章我把每个点都展开讲包括实际操作中会踩到的具体问题。2. 通信机制的硬件基础2.1 Zynq双核架构的关键细节做AMP开发前得先把Zynq的处理器系统结构搞清楚这直接决定了后面启动配置和内存分配怎么写。Zynq-7000的两颗Cortex-A9核每颗核都有自己的L1 Cache32KB指令加32KB数据两核共享一个512KB的L2 Cache。这个L2 Cache由PL310控制器管理它既能保证一致性也能在某些场景下引发数据不同步的坑。双核同时访问同一块DDR区域时如果数据被各自缓存在本核L1里另一个核是看不到的。再说内存映射。Zynq的内存布局有几个重要区域0x00000000到0x0003FFFF256KB的OCM片上内存访问速度固定不受DDR控制器影响0x00100000开始DDR的映射起始地址也就是我们跑程序和放数据的大内存池0xFFFF0000到0xFFFFFFFFOCM的高地址别名区域AMP设计里OCM高地址区经常被用作共享内存和核间通信缓冲区。原因很简单它不走DDR控制器的复杂仲裁两个核访问延迟都低且稳定不会出现DDR带宽争抢导致的不确定性。而且它的地址固定不需要给DDR预留空间省去不少地址映射上的麻烦。处理器内部的私有外设也需要区分清楚。比如私有定时器Private Timer、看门狗以及GIC中的PPIPrivate Peripheral Interrupt都是每个核私有的另一个核碰不到。所以给CPU1做定时中断时要走它自己的私有定时器不能用CPU0的。2.2 共享内存选型与Cache一致性处理共享内存选OCM还是DDR取决于数据量和实时性要求。OCM只有256KB刨去高地址区域做共享通信实际能用的更有限。适合存放状态标志、控制命令和少量采样数据。比如我在实际项目里用OCM高地址的8KB做环形缓冲区用来在CPU0和CPU1之间传递电机控制指令和状态反馈完全够用。如果两核之间要传大数据块比如图像帧、大批量采集数据那就必须用DDR了。DDR空间大但随之而来的是Cache一致性问题——这是AMP通信最容易踩坑的地方而且出错时现象极其诡异。打个比方CPU0往内存地址0x20000000写了一段数据但写完后数据还躺在线程自己的L1 Cache里没有真正落到DDR物理内存上。CPU1从这个地址去读它读的也是自己L1 Cache里的旧缓存结果读出来的是残留的垃圾数据。两边看着都是在操作同一个内存地址实际上各看各的缓存互相看不见对方的更新。处理Cache一致性的思路有两种第一种把共享内存区域配置成Non-Cacheable不可缓存。在Vitis的BSP里可以通过Xil_SetTlbAttributes()接口把指定地址段的MMU属性改掉。这种方式最省心不用每次读写都手动刷新缓存但代价是该区域的访问性能会打折扣。对于数据量不大的控制命令传递影响完全可忽略。第二种保留Cacheable属性但每次写完后调用Xil_DCacheFlush()把数据刷到内存读之前调用Xil_DCacheInvalidate()把本地缓存作废。这个方式通信性能更好但代码里很容易漏掉一次刷新或作废操作一旦漏了排查起来相当痛苦。初学者我强烈建议直接用第一种Non-Cacheable方案等跑通了再优化。我在实际项目中也是这么干的OCM共享区设置为Non-Cacheable后通信的正确性一下子稳定了之前时好时坏的数据错乱问题彻底消失。2.3 SGI核间中断的配置与触发流程核间中断在Zynq上走GIC。Zynq的GIC支持16个SGI中断编号从0到15。每个SGI都可以定向发给指定的CPU通过GICD_SGIR寄存器来触发。从软件角度完整流程分配置和触发两步。配置阶段每个核都要在自己那一侧把要用到的SGI中断注册好、使能起来。以CPU1为例它得在GIC里给某个SGI号注册中断处理函数设置触发方式为边沿触发然后在GIC使能该中断。这样当SGI发过来时CPU1就能在中断上下文里进入处理函数。触发阶段CPU0通过写GICD_SGIR寄存器向CPU1发送指定SGI。GICD_SGIR寄存器的结构里有个Target List字段用来指定发给哪个核。给CPU1发就在对应位写1。写完之后CPU1的GIC就会把它转成一个标准中断事件CPU1进入中断流程。有个细节值得注意SGI的Target List既可以指定单个核也可以广播到两个核。在某些双核同步场景下广播SGI非常有用比如让两核同时开始一次采样。我在做多核同步启动时就用过CPU0向两个核广播SGI的方式效果很好。2.4 启动流程FSBL怎么把两个核都跑起来在Zynq上程序的启动链路是BootROM加载FSBLFirst Stage Boot LoaderFSBL初始化DDR、加载bitstream如果有PL逻辑、把应用程序从启动介质拷贝到内存然后跳转执行。但对于AMP这里有个关键问题FSBL默认只启动CPU0。CPU1的启动有两条路第一条路让FSBL直接启动CPU1。修改FSBL源码中的XFSBL_AMP_SUPPORT宏为1重新编译FSBL。这样FSBL在启动完CPU0后会自动检查BOOT.BIN里是否有标记给CPU1的分区有的话就加载到指定地址并释放CPU1的复位。这种方式需要在生成BOOT.BIN时明确指定哪个分区归属CPU1。第二条路CPU0的应用程序里手动启动CPU1。FSBL只管启动CPU0CPU0的代码里通过操作SLCR寄存器把CPU1的入口地址写进去然后解除CPU1的复位让CPU1从指定地址开始运行。这种方式的优点是启动时机完全由CPU0的业务逻辑决定比较灵活。我在实际项目里用的是第二条路因为CPU0跑Linux后希望在Linux系统起来、网络就绪之后再启动CPU1而不是一上电就让CPU1跑——这样如果远程升级CPU1固件可以先停掉CPU1再更新安全性更高。3. Vitis 2023/2024双核工程搭建实操3.1 Vivado里搭一个最小的硬件工程用Vitis做嵌入式开发第一步永远是先在Vivado里把硬件工程建好导出XSA文件。在Vivado中创建框图Block Design添加ZYNQ7 Processing System重点配置这几个部分DDR配置选择开发板对应的DDR颗粒型号和总线位宽我这块板子是DDR3L位宽32bit容量1GBUART一定要使能一个UART用于调试输出不然程序跑没跑起来你不知道时钟PS输入时钟频率根据开发板实际晶振频率来我这里是33.333MHz如果你不需要PL逻辑可以不在框图里添加任何IP只用PS配置完成后综合、实现、生成比特流这一步可以跳过纯PS工程不需要bit文件。但导XSA之前有一个关键操作在File菜单里选择Export Hardware勾选Include bitstream如果没生成bitstream就不勾选然后导出XSA文件。这里有个Vitis 2023/2024的细节坑如果XSA是在Vivado 2023.1导出而你用Vitis 2024.1打开某些情况会提示平台版本过低或直接打不开。尽量保证Vivado和Vitis的年份版本一致或者用官方发布时配套的版本组合比如Vivado 2023.2配Vitis 2023.2。3.2 创建Platform工程并生成FSBL打开Vitis选择工作空间目录。第一次进入后在File菜单选择New然后选Platform Project。在弹出的对话框里Platform Project Name填一个名字比如zynq_amp_platform然后选择之前导出的XSA文件。创建完Platform工程后左侧的Flow Navigator里会看到平台工程的结构里面包含了zynq_amp_platform这个平台目录下面还有psu_cortexa9_0对应CPU0和psu_cortexa9_1对应CPU1两个处理器的BSP条目。Vitis会自动根据XSA里处理器配置生成对应CPU域的独立BSP。如果你用的是较早的Vitis版本可能只看到CPU0的BSPCPU1的BSP需要手动添加。在platform.spr文件上右键选择Board Support Package Settings在弹出的界面里找到相关选项确认CPU1的BSP已创建。双核工程的关键一步是FSBL。在Platform工程中会看到一个fsbl相关的条目它也是独立编译的。为了让FSBL支持AMP模式需要修改源码中的配置宏。在Vitis的源码编辑器中找到fsbl.h里面有一个XFSBL_AMP_SUPPORT的宏默认值是0改成1保存后重新编译FSBL。这里有几点要特别注意修改FSBL宏之后需要重新生成镜像时一定要用新编译的FSBL很多人改了宏忘了重新Build Platform工程导致最终BOOT.BIN里还是老FSBL。另外修改完fsbl.h后建议在Platform工程的build选项里执行Clean后再重新Build防止增量编译把新改动漏掉。3.3 创建CPU0和CPU1应用工程Platform工程搞定后开始创建两个应用工程。先建CPU0的应用工程。File菜单选New再选Application Project弹出的向导里选择刚刚建好的平台工程然后在Processor选项里选择psu_cortexa9_0CPU0。模板选择Hello World这步的目的是先把这个应用工程和BSP框架搭起来后续代码我们替换成自己的通信逻辑。再建CPU1的应用工程。同样操作但在Processor选项里选psu_cortexa9_1CPU1。模板同样选Hello World。工程建好之后在Project Explorer中可以看到两个独立的Application工程分别挂载在平台的不同CPU域下。编译的时候需要分别编译Vitis会把编译结果放在各工程自己的Debug或Release目录下。有一点容易搞混Vitis里的平台工程和应用工程是分开的应用工程依赖平台工程生成的头文件和库。有时候改了平台工程的配置比如修改了共享内存地址相关设置一定要重新Build平台工程然后再Rebuild应用工程否则应用工程引用的还是旧的库文件报一些莫名其妙的链接错误。3.4 修改链接脚本规划内存地址这是AMP能否跑通最关键的一环。两个核的程序都放在DDR里但地址必须错开不能互相覆盖。打开CPU0应用工程找到src目录下的lscript.ld链接脚本。Vitis创建的裸机工程默认把所有段都链接到DDR起始地址通常是0x00100000。CPU0的程序保留在这个默认位置把_stack和_heap的大小按需配置其余不动。打开CPU1应用工程的lscript.ld。这里要把加载地址改到一个不冲突的高位地址。比如DDR总容量是1GB从0x00100000开始那么CPU1的程序可以放到0x30000000附近完全避开CPU0的内核镜像、设备树、文件系统这些占用的区域。修改方式是在链接脚本里找到所有包含0x00100000的位置统一改成0x30000000。除了程序区域还得在链接脚本里预留共享内存区。共享内存我放在OCM高地址0xFFFF0000这块区域。这个不需要在链接脚本里专门留空间因为它是CPU0和CPU1约定好的一块固定物理地址只要两边的代码都用同一个宏定义这个地址就行。在工程代码里定义一个头文件统一声明共享内存基地址和大小// amp_common.h #define SHARED_MEM_BASE 0xFFFF0000 #define SHARED_MEM_SIZE 0x2000 // 8KB #define SHARED_MSG_OFFSET 0x0000 // 消息数据区 #define SHARED_FLAG_OFFSET 0x1FF8 // 状态标志区 #define SGI_TO_CPU1_ID 15 // CPU0 - CPU1 的中断号 #define SGI_TO_CPU0_ID 14 // CPU1 - CPU0 的中断号这两个头文件要让CPU0和CPU1的应用工程都能看到。最省事的办法是放到两个工程都能引用的共同目录或者直接在两个工程里各复制一份。3.5 生成BOOT.BIN并制作SD卡所有工程编译通过后就要生成最终的启动镜像BOOT.BIN。这个文件里包含了启动链路需要的所有内容SD卡启动时BootROM会加载它。在Vitis里对Platform工程或任意应用工程右键选择Create Boot Image。弹出的界面里按照顺序添加以下分区第一个分区必须是FSBL的ELF文件选择刚编译好的FSBL路径在platform/fsbl/Debug/fsbl.elf如果有PL bitstream接下来添加bit文件纯PS工程可以跳过添加CPU0应用工程的ELF文件partition type选bootloader有时是data添加CPU1应用工程的ELF文件重点来了必须在这个分区的属性里找到Processor或CPU相关选项明确指定为CPU1分区配置好后点击Create Image生成BOOT.BINVitis会把它放在默认输出目录。制作SD启动卡看起来是体力活但其实也有讲究。先把SD卡格式化为FAT32格式。然后需要拷贝的文件取决于你要跑什么如果双裸机AMP只需要把BOOT.BIN拷到SD卡根目录如果CPU0要跑Linux那么除了BOOT.BIN还需要拷入U-Boot的boot.scr或uEnv.txt、设备树*.dtb、Linux内核映像image.ub。这些文件通常在PetaLinux工程的images/linux目录下使用PetaLinux工程时boot.bin、boot.scr、image.ub这几个文件都是构建产物或者需要自己生成的具体生成方式要看PetaLinux的配置。很多基础镜像里已经带了boot.scr直接用就行。SD卡做成后开发板拨到SD卡启动模式插上卡上电按复位键串口终端上应该能看到FSBL的启动打印接着看到应用工程里的打印信息。如果两个核的打印都出来了说明AMP启动基本成功。4. 双核通信代码实现与实测4.1 共享内存协议设计共享内存有了中断有了但两核传输什么数据、格式怎么定需要设计一套简单可靠的通信协议。我用的协议很朴素共享内存区里放一个环形缓冲区外加一组状态标志。结构体大致如下#define RING_BUF_SIZE 2048 typedef struct { volatile unsigned int head; volatile unsigned int tail; volatile unsigned char data[RING_BUF_SIZE]; } ring_buffer_t; typedef struct { volatile unsigned int flag_start; // CPU1 已启动标志 volatile unsigned int flag_ready; // CPU1 已就绪标志 volatile unsigned int msg_count; // 传输计数 volatile ring_buffer_t ring_cmd; // CPU0 - CPU1 命令环 volatile ring_buffer_t ring_status; // CPU1 - CPU0 状态环 } amp_shared_mem_t;所有字段都用volatile修饰提示编译器不要优化掉这些内存访问。尤其是在Non-Cacheable模式下CPU1读共享标志时拿到的必须是内存里的实时值。环形缓冲区的读写逻辑是经典的head和tail指针比较。CPU0作为生产者往环形队列里写数据时先修改headCPU1作为消费者只读head、修改自己的tail。反过来也一样CPU1往里写状态时动tailCPU0读的时候只动head。这种单生产单消费模式下两个核之间不需要加锁也不会出现读写竞争。使用流程是这样CPU0先检查共享区的flag_start是否为1如果为1说明CPU1已经起来并且完成了GIC初始化可以接收中断了。然后CPU0往环形缓冲区写入命令数据更新head指针再触发SGI通知CPU1。CPU1的中断处理函数里检查环形缓冲区的head和tail如果不相等就取出数据并更新tail。4.2 CPU1端轮询加中断接收CPU1的裸机程序结构包含三块初始化、注册中断、主循环。初始化阶段CPU1要做几件事。配置UART用于打印调试信息初始化GIC把共享内存区域的MMU属性改成Non-Cacheable然后在GIC里注册SGI中断处理函数并使其生效。初始化完成后CPU1在共享内存里把flag_start置1告诉CPU0自己准备好了。然后进入主循环等待SGI中断的到来。中断处理函数是通信逻辑的核心void SGI_Handler(void *CallbackRef) { amp_shared_mem_t *mem (amp_shared_mem_t *)SHARED_MEM_BASE; // 从环形缓冲区读取CPU0发来的命令 while (mem-ring_cmd.tail ! mem-ring_cmd.head) { unsigned char cmd mem-ring_cmd.data[mem-ring_cmd.tail]; mem-ring_cmd.tail (mem-ring_cmd.tail 1) % RING_BUF_SIZE; process_cmd(cmd); } mem-msg_count; }这里必须注意中断处理函数里不要做耗时操作比如串口打印。实测下来如果打印数据量大中断处理时间过长下一次SGI到来时会丢中断。我的做法是在中断里只做数据搬运和置标志具体的协议解析放到主循环里处理。还有一个细节中断里更新tail指针后最好加一条内存屏障指令避免编译器或处理器乱序执行导致CPU0看到的tail比实际小。在ARM裸机环境下可以用dmb指令或者GCC的__sync_synchronize()。4.3 CPU0端通过SGI通知CPU1CPU0端如果是裸机程序逻辑和CPU1几乎对等只是角色反过来。但如果是跑Linux情况就不同了。我在项目中CPU0跑的是PetaLinuxCPU1是裸机程序。在Linux侧我写了一个简单的内核驱动里面实现了SGI的发送和共享内存的映射。用户态程序通过IOCTL接口往驱动里写命令驱动负责把命令写入共享内存并触发SGI。SGI的触发在Linux内核里不像裸机那么直接。裸机环境下可以直接操作GICD_SGIR寄存器而Linux下要操作硬件寄存器得用iowrite32()去写物理地址。如果CPU0应用里不方便在内核态操作还有一个简单办法在设备树里预留共享内存然后在用户态通过/dev/mem或者UIO框架去访问SGI的触发则通过一个小的内核模块实现。如果CPU0跑裸机触发SGI的代码很简洁void send_sgi_to_cpu1(void) { // GICD_SGIR 寄存器地址Target List 指向 CPU1 #define GICD_SGIR 0xF8F00F00 #define TARGET_CPU1 0x02 unsigned int sgi_val (15 24) | (TARGET_CPU1 16); Xil_Out32(GICD_SGIR, sgi_val); }这段代码的核心是把SGI编号15放到高字节目标CPUCPU1对应bit1放到Target List字段。写完后硬件自动把中断发给CPU1。在AMP调试时有个小技巧可以先不用中断让CPU1轮询共享内存里的标志位。等到通信逻辑验证正确了再切换成中断方式。这样能快速区分问题是出在通信协议还是中断路由上。4.4 实测结果与性能观察我用的开发板是Zynq-7020CPU时钟666MHzDDR3 1GB。CPU0跑PetaLinuxCPU1跑裸机程序。共享内存用OCM高地址区域Non-Cacheable。简单压力测试CPU0每毫秒往环形缓冲区写一条16字节命令触发一次SGICPU1收到中断后从缓冲区读取并回写一条8字节状态。跑了72小时msg_count计数超过2.5亿次没有出现数据错乱和丢包。SGI中断的响应延迟我用示波器测过从CPU0写SGIR寄存器到CPU1中断处理函数第一条指令执行大约1.2微秒左右。这个延迟对于电机控制、实时采样这类应用完全够用。如果是双裸机模式响应延迟会更低一些因为少了Linux侧调度和驱动的额外开销。实测双裸机下SGI延迟可以压到0.6到0.8微秒。如果共享内存放到DDR而不是OCM访问延迟会略高但在高负载下DDR带宽被大量占用时通信延迟抖动会明显变大。这也是我坚持用OCM做控制命令通道的原因。不过OCM容量有限大批量数据传输我另外开辟了一个DDR缓冲区通过OCM里的控制通道来管理。5. 常见问题与排查技巧实录5.1 CPU1程序不启动这是AMP开发里最常见的现象CPU0跑得欢CPU1一点反应没有。排查链路基本是固定的先确认共享内存里的flag_start是不是一直为0。如果CPU0程序在等待这个标志但CPU1没置位说明CPU1可能压根没跑起来。这时候先看串口——如果CPU1的程序里有串口初始化正常应该打印启动信息。没有打印就检查以下几点第一BOOT.BIN里正确指定CPU1分区了吗。很多人生成BOOT.BIN时把CPU1的ELF也当普通分区加进去了没有标记目标CPU。FSBL在AMP模式下就不知道要去启动它。回到Create Boot Image界面确认CPU1分区的CPU属性被正确设置为CPU1。第二CPU1的链接地址是否正确。CPU1的程序如果链接在DDR地址0x00100000和CPU0的镜像重叠了运行时程序代码可能已经被CPU0覆盖自然跑不起来。改链接脚本让CPU1程序落在高位地址。第三当你选择让CPU0在应用里手动启动CPU1而不是依赖FSBL要确认启动代码写对了地址。CPU1的入口地址必须和链接脚本里的_vector_table地址一致。链接脚本改成了0x30000000那启动代码里写入的地址也必须对应不然CPU1从错误地址取指要么跑飞要么直接异常。5.2 通信数据错乱或时好时坏数据错乱十有八九是Cache一致性问题。我刚开始用DDR做共享内存时CPU1收到的数据每隔一段时间就蹦出几个乱码一开始以为是中断丢数据后来才发现是Cache没刷新。如果共享内存区域是Cacheable的必须保证CPU0写完共享数据后显式Xil_DCacheFlush()CPU1读之前做Xil_DCacheInvalidate()。每次写、每次读都别漏。漏一次可能几十万次传输里就错一两回非常难查。如果共享内存在OCM上这个问题理论上不存在——OCM的访问不走Cacheable路径。但我在Vitis的默认BSP里遇到过一个情况BSP把OCM高地址也默认映射成了Cacheable导致OCM共享区域数据时好时坏。解决办法是在初始化时显式调用Xil_SetTlbAttributes(SHARED_MEM_BASE, NORM_NON_CACHE)把共享内存所在的页改成Non-Cacheable一劳永逸。还有一个容易忽略的点环形缓冲区的head和tail都加了volatile但如果在中断和主循环之间共享这些变量要确保编译优化没有把所有访问都优化掉。用volatile是第一步必要时用内存屏障指令。5.3 Vitis 2023/2024的版本坑Vitis 2023和2024之间的差异不小我在切换版本时遇到过几次比较耽误时间的坑。如果工程是在Vitis 2023.1创建的用Vitis 2023.2打开有时会提示平台版本不兼容需要重新Import平台或者重新生成XSA。2024版本切换到2024 IDE界面布局变化很大老用户很容易找不到入口。我的建议是确定一个版本后一条路走到黑不要中途切换除非你能接受重新走一遍平台导入和FSBL生成的流程。另外一个具体问题Vitis 2024.1在部分版本升级后FSBL的源码存放路径有变化直接在旧路径下搜索fsbl.h可能找不到。在Vitis里找到Platform工程下的fsbl源码目录右键属性看完整路径再进去改宏。还有个和烧写相关的经典报错提示“a valid FSBL file is required for flash operation”。这个发生在用Vitis的Program Flash烧写QSPI Flash时烧写参数里没指定FSBL文件或者指定的路径不对。在Burn Flash或Program Flash界面里一定要先选择FSBL的ELF文件它不只是加载镜像还要负责Flash初始化。如果你用的是非官方支持的NAND Flash型号FSBL里可能没有对应的初始化序列烧写前需要确认FSBL是否支持该型号否则擦除和编程都会失败。5.4 SD卡启动的常见问题整理制作SD启动卡时踩过的问题顺便整理一下用到时可以当速查表用。现象可能原因解决办法上电后串口无任何输出启动模式拨码错误检查开发板启动模式跳线确认QSPI/SD拨到SD位置有BootROM提示但FSBL没启动BOOT.BIN不在SD根目录或文件名不对确认文件在FAT32分区根目录文件名保持BOOT.BIN全大写FSBL启动后卡住XSA里的DDR配置与实际内存颗粒不匹配回Vivado核对DDR型号重新生成XSA和FSBLLinux内核起不来image.ub和设备树版本不匹配使用PetaLinux images/linux目录下配套的dtb和image.ub双核启动后CPU0正常CPU1不跑BOOT.BIN分区未指定CPU1或链接地址冲突重新生成BOOT.BIN检查CPU1分区属性和链接地址PetaLinux 2025.1生成boot.scr和image.ub后boot.scr是U-Boot的脚本文件控制着Linux内核和设备树的加载方式。如果你的SD卡里手动修改过分区结构或文件名记得同步更新boot.scr里的环境变量否则U-Boot可能按脚本里的旧路径找文件找不到就启动失败。最后再分享一个关于FSBL的体会。很多从单片机转来做Zynq的开发者会困惑为什么BOOT.BIN烧写进Flash时非要指定FSBL不能直接烧应用程序。原因是Zynq的BootROM本身不认识和初始化DDR必须有FSBL这个中介先把DDR、时钟、MIO这些外设初始化好才能把应用程序从Flash搬到DDR里执行。没有FSBLBootROM加载完自己那一段就不知道该干嘛了。理解了这一层后面做任何启动定制都不会被类似的问题卡住。我在这个项目里最大的收获是把AMP的启动链路、内存属性和中断路由这三件事彻底打通了。很多教程只讲了概念但实际动手时卡住你的往往就是FSBL的一个宏没改、缓存没刷新、SGI的Target List写错了一个bit。希望这篇文章能帮你少走这些弯路让你把精力放在业务逻辑上而不是环境配置上。
返回列表