☰
ZYNQ双核AMP:Linux与FreeRTOS的OpenAMP异构通信实践
2026/10/5 6:00:48 网站建设 项目流程

第一次在ZYNQ上把Linux和FreeRTOS同时跑起来的时候,我心里其实是有点忐忑的。折腾ZYNQ的人大多都听说过“双核A9”,但真要用起来,很多人第一反应就是SMP跑一个Linux拉到——确实,Linux对SMP支持得天衣无缝,根本不用你操心核间调度。可一旦你开始做真实产品就会发现,很多时候你希望一个核跑Linux处理网络协议栈、文件系统、UI交互,另一个核跑FreeRTOS专注电机控制、采集时序、实时响应。这种“一个芯片里住着两个操作系统”的玩法,就是典型的AMP(非对称多处理)。而OpenAMP,就是Xilinx官方推荐、也是目前最成熟的那座桥。

这篇文章写的是2018.3版本的工具链:Vivado 2018.3、PetaLinux 2018.3、Xilinx SDK 2018.3,加上Xilinx维护的OpenAMP开源仓库。版本虽老,但这一版OpenAMP已经进入Linux内核主线(4.14),设备树写法、remoteproc框架、rpmsg通信模型都相对稳定,非常适合拿来做入门AMP的跳板。整篇文章适合有两三个月ZYNQ开发经验、想从单核裸机或单Linux往上再走一步的工程师,我会尽量把从硬件工程、PetaLinux配置、FreeRTOS程序编写到实际加载通信的整个链路摊开讲。

1. 先把OpenAMP这套东西搞明白再动手

1.1 为什么非要用Linux+FreeRTOS这种“一核一系统”的组合

很多人问我:两个A9核跑同一个Linux不就行了?内核自带SMP,两个核自动负载均衡,省事多了。但做产品的人会告诉你,SMP在大部分场景确实省事,可就怕你碰上“既要又要”的需求——既要Linux的生态,又要硬实时的响应。

比如一个稍微复杂点的运动控制系统:上位机通过网口给指令,Linux那边跑Modbus/TCP、Web配置页面、日志存储,这些都是再舒服不过的活儿。但底层的编码器采集、PID计算、PWM输出,要求的是微妙级抖动,Linux那边一个中断延迟就把你波形搞花了。你要是把实时任务放到独立核上跑FreeRTOS,把Linux单纯当成一个“超级外设”来用,两边互不干扰,开发难度和稳定性都会好很多。这就是AMP的核心思路:按实时性需求把任务拆开,每个核分配最合适的操作系统。

另一个很实际的理由是生态隔离。很多工业现场要求控制部分通过安全认证、代码可审计,如果控制逻辑和UI逻辑混在同一个Linux内核里,评审会很麻烦。AMP架构下,实时控制代码就是一个独立小固件,逻辑清晰,测试方便,认证路径也短。

1.2 OpenAMP到底拆成了几块,各管什么活

OpenAMP是开源异步多处理器框架,2018年之后Xilinx把它和Linux内核原生的remoteproc/rpmsg机制做了深度整合。它主要由三部分组成:libmetal、open-amp、以及Linux内核里的remoteproc/rpmsg驱动框架。

libmetal是硬件抽象层。它向上提供统一的设备访问接口、内存映射、DMA、原子操作、睡眠唤醒这类基础能力,让open-amp可以跨平台跑在Linux用户态、Linux内核态、裸机、RTOS上。open-amp则是核心协议栈,封装了remoteproc远程固件加载、virtio虚拟设备、rpmsg消息传递三套机制。Linux侧通过remoteproc框架把FreeRTOS的elf固件加载进DDR,通过rpmsg建立共享内存上的消息通道;FreeRTOS侧则跑一个轻量的open-amp库,作为remote端响应Linux发来的IPI中断和virtio请求。

拿生活里的场景类比:Linux是“包工头”,FreeRTOS是“老师傅”。包工头负责把老师傅的图纸(elf固件)放进工位(DDR指定地址),然后敲敲门(IPI中断)说开工;两个师傅之间信息沟通靠一条共享走廊(共享内存),走廊两头各挂一个信箱(vring),一个投递一个收取,这就叫virtio。

2018.3这个版本,Xilinx把OpenAMP demo代码、devicetree片段、meta-openamp层都维护在GitHub上,分支就是对应当年的发布版本。你直接从2018.3分支拉代码,比自己从零写设备树和裸机初始化省下至少一周的调试时间。

2. 整体设计思路与硬件工程准备

2.1 开发板和工具链,你真不用非得买ZedBoard

我手上用的是Zynq-7020,1GB DDR3,板子型号是ZedBoard。其实你只要有Zynq-7000系列开发板、SD卡、USB-UART线就行,Zybo、MicroZed、黑金、米联客的都大同小异。7020的双核A9跑Linux+FreeRTOS绰绰有余,没必要上Zynq UltraScale+那套MPSoC,架构和操作会复杂不少(那边多出R5核和A53核,虽然原理相通,但翻车概率高)。

工具链要严格对版本号,不然OpenAMP仓库里2018.3分支的代码和PetaLinux不匹配,编译链会出莫名其妙的问题。我用的就是:

  • Vivado 2018.3 + Xilinx SDK 2018.3
  • PetaLinux 2018.3(对应内核xlnx_rebase_v4.14)
  • Xilinx/open-amp、libmetal、meta-openamp的2018.3分支

有一点要说在前面:2018.3版本的硬件描述文件还是.hdf后缀,不是新版的.xsa,在SDK里通过File -> Export Hardware导出。习惯新版的人别搞混。

2.2 Vivado工程里的关键配置:地址空间要提前“分家”

硬件工程本身不复杂:PS端使能UART1、SD0、ENET0、DDR,只要跑过Hello World的人都会配。真正需要提前规划的是DDR地址空间划分。既然要让Linux和FreeRTOS各占一块互不干扰的内存,你就要像分房产一样,在硬件设计之初就把边界划好。

以7020的1GB DDR为例,我的划分习惯是:Linux占0x00000000~0x3EBFFFFF,大约1007MB;FreeRTOS从0x3EC00000开始,往上留约20MB空间给固件、共享内存和资源表。划分逻辑其实就一句话:Linux越稳越好,FreeRTOS需要多少给多少。Linux那边跑内核、跑文件系统、跑网络,对内存的需求远大于一个精简RTOS,所以大头给它。FreeRTOS这边一个简单控制固件编译出来可能也就几百KB,预留20MB已经是富余到奢侈了,将来加协议栈、加缓冲队列都有空间。

这里有必要提醒一下:共享内存的地址最好靠近DDR高地址端,且不要和FreeRTOS固件镜像重叠。具体三个段分别是:FreeRTOS固件elf加载地址、vring共享缓冲区地址、资源表(resource table)地址。这三个地址后面要写进设备树、写进FreeRTOS的链接脚本,任何一处没对齐,通信就会失败,而且失败的方式通常是“系统一卡,日志空白”。

2.3 FSBL要选支持双核启动的版本,别拿默认编译随便用

OpenAMP跑通的另一个关键前置条件是CPU1的启动。Zynq系统上电后,FSBL会启动CPU0,但默认不启动CPU1。要让CPU1运行FreeRTOS,必须在FSBL里显式地把CPU1的入口地址写进0xFFFFFFF0寄存器,然后发送SEV事件唤醒它。

Xilinx官方OpenAMP demo的BSP里其实已经带了修改好的FSBL,细节是在fsbl_main.c里增加了一段逻辑:检测到某个magic值(demo里常用0x13579BDF之类的标记)就去启动CPU1。如果是自己从零新建FSBL,你需要在main函数末尾加上类似这样的代码:

#define CPU1_START_ADDR 0xFFFFFFF0 #define SEV() __asm__("sev") void StartCpu1(void) { volatile uint32_t *cpu1_start = (volatile uint32_t *)CPU1_START_ADDR; *cpu1_start = 0x3EC00000; /* CPU1入口地址,与FreeRTOS链接脚本保持一致 */ dmb(); SEV(); }

不要小看这段代码,很多人Linux起来了、FreeRTOS也编译出来了,但CPU1就是不动,最后查来查去发现FSBL压根没释放CPU1。FSBL是整个启动链的第一个环节,它没干的事,后面U-Boot和Linux是不会替你干的。很多教程说“用SDK里现成FSBL就行”,但那个“现成”是指跑Linux的CPU0单核场景,做AMP必须换成支持CPU1启动的版本。最简单的做法是直接拉OpenAMP example工程里的fsbl源码编译,或者用meta-openamp里的补丁对官方FSBL打补丁再编译。我踩过这个坑,所以在这里啰嗦三遍也不嫌多。

3. PetaLinux端环境搭建与配置

3.1 创建PetaLinux工程的正确姿势

PetaLinux 2018.3安装好之后,先创建一个空工程,再把Vivado导出的HDF导进来。命令不复杂:

petalinux-create -t project --template zynq --name amp_linux cd amp_linux petalinux-config --get-hw-description=/path/to/hdf_dir --silentconfig

这里有个小体感:2018.3的petalinux-config相对慢,特别是第一次解析HDF的时候,屏幕上会刷大量硬件信息,别以为卡死了。等它落到命令行提示符,再用petalinux-config -c kernel打开内核配置菜单。

内核配置里要确保这几项打开,缺一个remoteproc的设备节点都起不来:

CONFIG_REMOTEPROC=y CONFIG_ZYNCQ_REMOTEPROC=y CONFIG_RPMSG=y CONFIG_RPMSG_VIRTIO=y CONFIG_RPMSG_USER_DEV_DRIVER=y

其中ZYNCQ_REMOTEPROC是Zynq平台remoteproc驱动,RPMSG_USER_DEV_DRIVER会在/dev下生成rpmsg0字符设备,方便你用echo/cat直接测试通信。强烈建议把这个驱动编成模块而不是编进内核,因为调试时你会频繁地rmmod/modprobe。

3.2 设备树里必须写清楚的三个节点

PetaLinux的设备树主要改project-spec/meta-user/recipes-bsp/device-tree/files/system-user.dtsi。OpenAMP能不能跑通,设备树是重中之重,要写的节点主要就三类:保留内存(reserved-memory)、remoteproc设备节点、以及rpmsg虚拟设备节点。可以直接参考meta-openamp仓库2018.3分支里的zynq-openamp.dtsi,我摘一段关键结构说明:

reserved-memory { #address-cells = <1>; #size-cells = <1>; ranges; rproc_0_reserved: rproc@3ec00000 { compatible = "shared-dma-pool"; reg = <0x3ec00000 0x1400000>; no-map; }; }; remoteproc0: remoteproc@0 { compatible = "xlnx,zynq-remoteproc"; firmware = "freertos_demo.elf"; reg = <0x3ec00000 0x10000000>; vring0 = <0x3ef10000>; vring1 = <0x3ef14000>; ... };

重点解释几个字段:

  • no-map;表示这段内存从Linux的页表映射中拿掉。Linux看到这块地址时不会去cache、不会去分配,彻底“让路”给FreeRTOS。如果不写no-map,后果就是Linux的cache和FreeRTOS的写操作互相污染,调试时你会看到rpmsg消息读出来是乱码、固件偶尔加载失败,非常折磨人。
  • firmware属性指定了要加载的FreeRTOS固件文件名,对应/lib/firmware/freertos_demo.elf。改设备树的时候记得文件要放对位置。
  • vring0/vring1是共享内存里两个环形缓冲区的地址。Linux通过virtio向这两个vring读写数据,FreeRTOS端也必须在相同地址建vring。这是两边唯一的信息通道,地址必须一字不差。
  • reg里填的0x3ec00000和长度0x10000000只是个示意,实际值要覆盖FreeRTOS固件、vring、资源表所在的整个区域,让驱动知道它管理的remote内存范围。

这里我建议你直接把meta-openamp里的dtsi拿过来改成自己的地址,别自己从空文件开始写。设备树少了某个属性,问题表现往往是“模块加载成功但start报错”,日志还含含糊糊,查起来会让你怀疑人生。

3.3 PetaLinux构建与BOOT.BIN打包

设备树改完之后,执行构建:

petalinux-build

构建完成之后,把FreeRTOS的elf固件拷贝到镜像根文件系统的/lib/firmware目录下。常规做法是:

mkdir -p project-spec/meta-user/recipes-app/freertos-firmware/files cp freertos_demo.elf project-spec/meta-user/recipes-app/freertos-firmware/files/

再写一个简单的bbappend把它安装进rootfs。如果嫌麻烦,也可以等系统启动后通过scp拷贝到/lib/firmware,但要是开机自动加载的需求,还是放rootfs里干净。

最后打包BOOT.BIN:

petalinux-package --boot --fsbl zynq_fsbl.elf --fpga system.bit --u-boot --kernel --force

这个命令会把FSBL、bitstream、U-Boot、Linux内核打包成一个BOOT.BIN,同时生成boot.scr。注意FSBL一定要用支持AMP启动的那个版本,别用默认替代。打包完成后把BOOT.BIN、image.ub、boot.scr三个文件拷到SD卡第一分区(FAT32格式)即可。

4. FreeRTOS端程序编写与编译

4.1 SDK工程里怎么把OpenAMP库“塞”进去

FreeRTOS端的代码我选择在Xilinx SDK 2018.3里编译,因为BSP、编译器、调试器一条龙,省去折腾arm-none-eabi工具链的时间。新建一个Application Project,OS Platform选择freertos,然后需要把open-amp和libmetal的源码同时加进工程里。

拉代码的姿势:

git clone -b 2018.3 https://github.com/Xilinx/open-amp.git git clone -b 2018.3 https://github.com/Xilinx/libmetal.git

然后在SDK里分别把open-amp/lib、libmetal/lib下面的源码添加进工程目录,并把头文件路径配好:open-amp/lib/include、libmetal/lib/include、以及裸机BSP生成的include目录。

还有一个不起眼但很容易漏的配置:编译宏METAL_INTERNAL和一些平台相关的宏,比如METAL_MAX_DEVICE_REGIONS、VIRTIO_MAX_QUEUES。如果你编译时报这些宏未定义,去libmetal仓库的README里找它要求的配置项,逐个加到SDK工程的编译选项里即可。

4.2 写一个能跑通的FreeRTOS通信程序

FreeRTOS端的核心流程其实很固定:初始化libmetal、定义资源表、注册vdev、启动rpmsg端点、然后轮询收消息。以官方echo_test为例,缩略后的主流程是这样:

#include "openamp/open_amp.h" #include "metal/device.h" #include "metal/sys.h" #define SHM_BASE_ADDR 0x3EF00000 #define SHM_SIZE 0x100000 static struct metal_device *shm_device; static struct virtio_device *vdev; static struct rpmsg_device *rpdev; static void rpmsg_read_cb(struct rpmsg_device *rpdev, void *data, int len, uint32_t src, void *priv) { rpmsg_send(rpdev, src, data, len); /* 原样回显 */ } int main(void) { metal_init(); metal_device_open("generic", "shm", &shm_device); /* 注册共享内存区域 */ metal_io_init(&shm_device->io, SHM_BASE_ADDR, SHM_SIZE, -1); /* 通过资源表初始化virtio */ vdev = rproc_virtio_create_vdev(...); rpdev = rpmsg_virtio_create_rpmsg_device(vdev, ...); rpmsg_register_callback(rpdev, RPMSG_READ_CB, rpmsg_read_cb); /* 告诉Linux,remote已经就绪 */ rproc_virtio_wait_remote_ready(vdev); while (1) { /* 处理收到的rpmsg消息 */ rpmsg_virtio_rx_callback(vdev); vTaskDelay(1); } }

这段代码只是一个骨架。实际还要处理resource table的初始化、virtio队列参数的填充、IPI中断回调注册等等。这些内容在Xilinx/open-amp仓库的examples/echo_test目录里都有现成的,直接基于它改比从零写靠谱得多。我个人的建议是第一次跑通之前不要改任何逻辑,就用官方echo_test原封不动编译,等printf能在共享内存上回显了,再把自己的业务逻辑往里面加。

4.3 资源表和链接脚本:决定了FreeRTOS是否“藏得住”

资源表(resource table)是整个OpenAMP握手的基础,里面记录了这个remote固件如何使用内存、占用哪些中断、分配了哪些vring。它的结构通常长这样:

#define NUM_VRINGS 2 #define VRING0_ADDR 0x3EF10000 #define VRING1_ADDR 0x3EF14000 #define VRING_ALIGN 64 static struct fw_rsc_vdev rproc_vdev = { .id = VIRTIO_ID_RPMSG, .num_of_vrings = NUM_VRINGS, .vring = { { VRING0_ADDR, VRING_ALIGN, 256, 0, 0 }, { VRING1_ADDR, VRING_ALIGN, 256, 0, 0 } }, };

注意vring数量、地址、对齐、buffer size,这些必须和Linux侧设备树里的vring0/vring1完全一致。不对齐,virtio找翻队列;地址不一致,Linux往你预期的地方写数据,FreeRTOS却从另一个地址读,消息全部丢失。2018.3版的open-amp还把资源表符号resource_table硬编码导出,Linux端remoteproc驱动会解析这个符号来找到资源表,这个符号不能删、不能改小写,否则加载时直接报no resource table found。

链接脚本方面,最简单是在SDK默认lscript.ld基础上,把FreeRTOS的堆栈段整个放到0x3EC00000起始的区域,保证和Linux设备树以及FSBL里的CPU1入口地址一致。同时确认__heap_start、__heap_end之间的区域,不要和vring地址重叠。我见过有人在1GB DDR的板子把堆栈地址配到0x20000000,结果Linux一启动就直接把FreeRTOS代码覆盖了,整个系统像得了癫痫一样反复重启。

5. 启动、加载与双核通信验证

5.1 从SD卡启动到加载FreeRTOS固件的完整链路

整个启动链路是这样的:Zynq上电 -> BootROM读SD卡 -> FSBL执行 -> FSBL启动CPU0跑U-Boot -> U-Boot加载Linux内核和设备树 -> Linux启动 -> 用户在内核态触发remoteproc加载FreeRTOS固件 -> 固件在CPU1上跑起来。

Linux起来之后,进入终端确认设备树节点是否生效:

ls /sys/class/remoteproc/remoteproc0/

正常情况下你能看到firmware、state、name等文件。然后开始加载固件:

echo freertos_demo.elf > /sys/class/remoteproc/remoteproc0/firmware echo start > /sys/class/remoteproc/remoteproc0/state

执行echo start后,Linux端remoteproc驱动解析elf、把代码段数据段拷贝到0x3EC00000开始的地址、写CPU1启动入口、发SEV唤醒CPU1。你会在串口上看到FreeRTOS的启动日志,比如官方demo里会打印一行hello。那一刻的心情感慨,估计只有从零把AMP跑通过的人才能体会。

如果一切顺利,cat /sys/class/remoteproc/remoteproc0/state应该显示running。

5.2 用rpmsg跑通一次echo,眼见为实

加载固件之后,再加载rpmsg用户态驱动模块:

modprobe rpmsg_user_dev_driver

这时查看/dev下应该有rpmsg0设备节点。然后开启两个终端,一个负责读,一个负责写。先开读:

cat /dev/rpmsg0

再开写:

echo "hello from linux" > /dev/rpmsg0

如果整个链路没问题,FreeRTOS会把这条消息原样回显,在cat终端里你会看到一行hello from linux。这行回显意味着Linux把消息写进vring,通过IPI通知CPU1,FreeRTOS从中读取并回写,又一次IPI通知Linux,Linux从vring读回来——整个共享内存+中断的闭环彻底打通了。

我也试过用dmesg看内核日志,能看到类似virtio_rpmsg_bus: virtio0: creating channel 30 addr 0x0的信息,代表rpmsg通道建立成功。这些日志对后续排查非常有价值。

5.3 能不能开机自动加载FreeRTOS固件

很多产品场景下,希望Linux一启动CPU1就自动跑起来,不需要手动echo。有几条路可以走:

第一条是让U-Boot在启动Linux之前就把FreeRTOS固件加载好,然后Linux的remoteproc驱动检测到remote端已经就绪,直接进入running状态。需要把FreeRTOS固件打包成U-Boot可识别的镜像(.bin格式),在U-Boot环境变量里加一条fatload mmc 0 0x3EC00000 freertos.bin之类,再跳转CPU1。

第二条是Linux启动后通过systemd服务自动执行echo start > /sys/class/remoteproc/remoteproc0/state。好处是逻辑直观,出问题随时手动停掉,适合调试阶段。

第三条是在内核rootfs的init脚本里加上加载步骤。我实际项目中用的是第二条,稳定、低耦合,而且哪天不想让RTOS跑了,删个服务就行。

6. 常见问题排查与避坑记录

6.1 那些让我通宵过的问题,逐条拆给你看

问题一:echo start之后卡死,系统无反应。

这是最典型的共享内存未正确保留。检查设备树reserved-memory节点是否加了no-map;,没有的话Linux会把这块区域映射成cacheable,FreeRTOS写入的数据被CPU0的cache视线挡住,virtio状态永远不一致。其次检查vring地址和FreeRTOS端资源表是否完全一致,地址差一个字节都可能死锁。

问题二:固件可以start,但rpmsg收不到任何回显。

这种情况先dmesg | tail看virtio通道有没有建立。如果日志里没有channel信息,大概率是资源表里virtio设备ID和Linux端预期不匹配,或者vring大小或对齐值不一致。2018.3的默认vring对齐值是64,缓冲256个描述符,如果你改过,两边必须同步改。

问题三:FreeRTOS固件加载失败,提示找不到resource table。

确认elf里有没有导出resource_table符号。有时候编译器优化会把符号给优化掉,或者你改了链接脚本导致符号所在段被丢弃。用nm freertos_demo.elf | grep resource_table看一下,没有的话在链接脚本里强制保留该符号。

问题四:固件能加载,但CPU1跑飞,串口无输出。

大概率是FSBL没正确设置CPU1入口地址,或者入口地址和FreeRTOS链接脚本不一致。请回查FSBL里0xFFFFFFF0寄存器写入的地址值,和lscript.ld里向量表地址。如果FSBL是默认版本,请更换为OpenAMP demo配套的FSBL。

问题五:Linux起来后/sys/class/remoteproc下没有设备节点。

这是设备树没配对。用ls /proc/device-tree/remoteproc@0/检查节点是否存在,存在的话检查compatible是否为xlnx,zynq-remoteproc,驱动有没有编进内核。驱动没编进去就去看内核配置里的CONFIG_ZYNCQ_REMOTEPROC。

问题六:第一次start成功,stop之后再start却失败。

remoteproc框架对remote固件的行为有状态约束。FreeRTOS端收到stop后如果没有做完整的资源释放,第二次start时两边状态机对不上。调试阶段最省事的办法是直接重启Linux再加载一遍,产品化再做优雅的reset流程。

6.2 常规文档里不会写的几条经验

经验一:地址规划表一定要做成文档贴在工位上。

我把0x3EC00000到0x3FFFFFFF这段内存的每一块用途都写在了一个表格里,包括固件镜像、vring0、vring1、资源表、共享数据缓冲区,甚至标注了每个地址在设备树、FreeRTOS链接脚本、FSBL三处各自出现的位置。后面改一次方案,先改表,再对着表改三处代码。别问我为什么吃这么多亏才养成这习惯。

经验二:调试时先用官方demo验证整个链路,再动自己的业务代码。

很多人喜欢一上来就写自己复杂的协议,结果通信没通,根本分不清是OpenAMP的问题还是业务逻辑的问题。我现在的套路是:第一天只求把echo_test跑通;第二天把FreeRTOS端改成LED翻转,让Linux定时发指令;第三天再上真正的业务数据。分步走,每走一步都有明确的可观测信号。

经验三:两个系统的打印信息别混在同一个串口。

调AMP时,Linux日志和FreeRTOS日志如果打到同一个串口,两边打印一混,你根本分不清谁是谁。我建议Linux用UART1,FreeRTOS用UART0,或者FreeRTOS的日志通过rpmsg回传Linux再打印。隔离好日志通道,调试效率直接翻倍。

经验四:学会从Linux侧看remote端的状态。

cat /sys/class/remoteproc/remoteproc0/state只能看到running/offline,想深入定位问题就用echo 4 > /proc/sys/kernel/printk打开内核debug日志,remoteproc的加载细节会全部打出来。再看dmesg里virtio、rpmsg通道的建立过程,基本能定位八成的握手问题。

这个内容后续还可以往几个方向扩展。比如可以把rpmsg消息改造成自定义协议,在Linux侧写一个守护进程,FreeRTOS侧定义好命令字,两个系统之间形成一套稳定可靠的远程调用通道;再比如把共享内存区域单独划一块出来做高速数据交换,绕开rpmsg小消息的限制,适合图像传输、波形数据回传这类大流量场景;甚至可以在FreeRTOS侧把网络协议栈也跑起来,让CPU1直接收网口数据,CPU0只做管理面。每次扩展,我自己的体会都是:只要地址规划好,OpenAMP这套框架的想象力确实比想象中更大。先别急着追求花哨功能,把2018.3这个版本的echo_test跑通,你就已经掌握了ZYNQ双核世界里最关键的那把钥匙。

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

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

立即咨询