去年接了一个EtherCAT主站项目,起初想得很简单:RK3568跑Linux,开源igh主站一搭,Motion Control一看,齐活。结果真正跑起来才发现,EtherCAT从站数量一多、控制周期一到1ms,Linux调度的抖动直接让DC同步报错,示波器抓波形一看,脉冲间隔忽长忽短,这种状态上产线肯定被现场工程师骂。后来把方案改成了RK3568平台上的AMP架构,Linux和RT-Thread各占两个核,一个管界面和算法,一个专职跑EtherCAT主站和硬实时IO,才算是把问题彻底压住了。这篇文章就把我在RK3568上把Linux和RT-Thread混合部署的完整过程写出来,不绕弯子,直接讲怎么改设备树、怎么编译RT-Thread、怎么让两个系统同时跑起来,以及中间踩过哪些坑。
如果你跟我一样,手上是RK3568的板子,想用AMP模式搞Linux+RTOS的混合部署,尤其是做EtherCAT主站、数据采集这类对时序敏感的场景,这篇文章可以直接拿来当操作手册。全部操作基于RK3568官方SDK和RT-Thread官方BSP,按步骤走,第三次部署的时候三分钟真的够用。
1. 为什么RK3568做AMP?聊聊双系统的实际需求
1.1 从EtherCAT和实时数据采集说起
EtherCAT主站这个事,是促使我研究AMP的最直接原因。EtherCAT的DC(Distributed Clocks)同步机制要求主站在每个周期准确发送数据帧,周期抖动超过1us,从站同步就会劣化;超过几微秒,部分要求高的伺服驱动器就会直接报警。而普通Linux内核的调度延迟通常在几十微秒到几毫秒之间波动,即使是PREEMPT_RT实时内核,也只能把抖动压到十几微秒的水平,离EtherCAT的硬实时要求还是差一个量级。
我在阳明、倍福这些客户现场都遇到过类似场景:Linux上跑人机交互、跑视觉算法、跑EtherCAT主站,三个活全挤在一个大系统里,一旦CPU被某个进程占满,EtherCAT周期立刻翻车。
当时考虑过三种方案:第一种,用实时网卡加PREEMPT_RT内核硬扛,结果测试效果不稳定;第二种,用专用EtherCAT从站控制器芯片把实时性下沉到芯片层面,但灵活性差,而且代码还是得跑在某个核上;第三种,就是AMP——把RK3568的四个核拆开,两个跑Linux,两个跑RT-Thread,RT-Thread专职处理EtherCAT,通过核间通信把数据交给Linux。第三种方案在当时看来最合理。
1.2 AMP与SMP的本质区别
很多人一听到多核处理器,脑子里默认就是SMP(对称多处理),所有核心运行同一个操作系统,共同管理一套内存和中断。Linux的负载均衡机制可以把进程调度到任何一个核心上。
AMP(非对称多处理)则完全不同:系统里有多个核心,每个核心运行的操作系统可以不同,内存各自独立划分,中断也各自管理。简单打个比方,SMP就像一个公司,所有人都归一个总经理调度,谁有空闲就能接活;AMP则是公司里分成了两个团队,每个团队有自己的独立负责人,团队之间只通过约定的渠道(比如共享邮箱)沟通。
在RK3568上做AMP,其实就是把4个Cortex-A55核心的其中两个"摘"出来,让它们跑RT-Thread。剩下的两个核心继续跑Linux。两边各有独立的内存区域、独立的中断,互不干扰。
| 对比项 | SMP | AMP |
|---|---|---|
| 操作系统 | 单OS运行在所有核心 | 每个核心可运行不同OS |
| 内存管理 | 统一管理 | 各自隔离,通过共享内存通信 |
| 中断处理 | 统一由Linux管理 | 部分中断分配到RTOS侧 |
| 实时性 | 受Linux调度影响 | RTOS侧可控,抖动极小 |
| 适用场景 | 通用计算 | 实时控制+复杂计算并存 |
1.3 RK3568的硬件资源如何支持AMP
RK3568搭载4个Arm Cortex-A55核心,主频最高2.0GHz,自带GIC中断控制器和IOMMU。规格上属于中高端工业级SoC,但价格控制得不错,这也是很多国产工控板卡选它的原因。
从AMP的角度看,RK3568有几个关键特性值得注意:
一是四个核真正独立。每个核都有自己的PPI(私有外设中断)和SGI(软件触发中断),这套GIC架构天然支持把不同外设中断绑定到不同核心。
二是IOMMU可用可不用。做AMP时,RT-Thread运行的内核通常直接物理寻址,不走MMU映射,这样实时性反而更稳。Linux侧则继续用MMU,两边互不冲突。
三是内存控制器能保证不同核心访问不同物理内存区域时没有总线冲突。只要把内存划分清楚,RT-Thread侧的数据访问不会拖累Linux侧性能。
2. 开工前必须搞清楚的准备工作
2.1 需要准备的软件和源码
一个完整的RK3568 AMP项目,需要准备的东西如下。
- RK3568的SDK,包含内核和U-Boot源码。我用的是正点原子ATK-DLRK3568配套的SDK,BSP版本比较新,实际上你用瑞芯微官方SDK或者别家开发板的SDK都可以,整体思路一致。
- RT-Thread源码,官方GitHub仓库,bsp/rockchip/rk3568目录就是官方对RK3568的支持。
- 交叉编译工具链,编译RT-Thread固件用arm-none-eabi工具链,编译Linux内核则用SDK自带的aarch64交叉工具链。
- 烧录工具,RKDevTool以及对应的驱动。
- 一块串口调试板,至少两个串口通道,最好有USB转串口的模块,因为两个系统各自要一个调试串口。
这些看起来都是标准配置,但我第一次做的时候照样卡了一个小时,原因就是串口数量不够。RK3568的物理UART虽然多,但默认很多被分配给了蓝牙、调试等功能,设备树里需要重新映射。如果你手里只有一块USB转串口,就先别急着做AMP,因为Linux和RT-Thread同时跑起来,两边都要看日志,一个串口根本忙不过来。
2.2 内存和中断资源的整体规划
AMP项目实施前,最关键的分配工作就是内存。我以2GB内存的RK3568为例,给出一个经实盘验证的分配方案。
我的划分方式是这样:Linux占用绝大部分内存,地址范围0x00200000到0x2FFFFFFF;RT-Thread预留区域从0x30000000开始,预留64MB空间,也就是0x30000000到0x33FFFFFF。RT-Thread固件就加载到0x30000000处运行,它的堆、栈、消息队列都在这64MB内分配。
之所以把RT-Thread放在0x30000000之后的高地址段,是为了避免和Linux低地址段的DMA缓冲区、内核镜像冲突。Linux启动时,U-Boot已经把内核解压到低地址位,如果RT-Thread又占用低地址,非常容易发生覆盖,导致启动莫名死机。
中断资源的划分也要提前想清楚。RT-Thread负责的EtherCAT网卡中断需要直接送达RT-Thread所在的核心,Linux侧就不能再声明这个中断。反过来,串口、USB、显示等设备的中断继续留给Linux。这部分通过修改设备树interrupt属性并绑定到具体CPU核心来实现。RK3568的GIC支持在设备树中用interrupt-affinity属性指定CPU亲和性。
2.3 编译环境的搭建
编译环境这块没有太多花活,但有个小坑容易被新手忽略:RT-Thread的编译工具链和Linux内核的交叉工具链最好分开装,不要混用同一个工具链。RT-Thread的BSP默认找arm-none-eabi-gcc,如果你系统里只有aarch64-linux-gnu-gcc,就会出现莫名其妙的链接错误。
推荐做法是各自建一个独立目录:
mkdir -p /opt/amp-toolchains/arm-none-eabi mkdir -p /opt/amp-toolchains/aarch64ARM官方提供的arm-none-eabi工具链可以直接下载解压到/opt/amp-toolchains/arm-none-eabi目录,然后把bin目录追加到PATH环境变量。RK3568 SDK内部自带完整的编译链接环境,执行build.sh时会自动使用SDK内嵌的交叉工具链,所以Linux侧不需要手动设置太多环境变量。
3. Linux侧的关键改动:设备树和内核配置
3.1 预留内存与remoteproc节点
Linux侧的所有改动,基本上都集中在设备树里。AMP想让Linux和RT-Thread各跑各的,Linux必须先知道"哪块内存是我的,哪块内存不是我的"。这个信息就靠reserved-memory节点来传达。
在arch/arm64/boot/dts/rockchip/rk3568-evb.dtsi(或者你开发板对应的dts文件)里,增加如下节点:
/ { reserved-memory { #address-cells = <2>; #size-cells = <2>; ranges; rproc_reserved: rproc-reserved@30000000 { compatible = "shared-dma-pool"; reg = <0x0 0x30000000 0x0 0x4000000>; no-map; }; }; rproc: remoteproc@30000000 { compatible = "rockchip,rk3568-remoteproc"; reg = <0x0 0x30000000 0x0 0x4000000>; memory-region = <&rproc_reserved>; firmware = "rt_thread.elf"; status = "okay"; }; };这段配置的作用有两层:reserved-memory里的no-map属性告诉Linux内核,这块物理内存映射成虚拟地址时不能使用标准内存映射流程,Linux自己绝对不要去动它;remoteproc节点则指向同一块区域,作为RT-Thread固件的固定加载位置。
有一类问题非常典型:你配了memory-region,但忘记加no-map,Linux启动后仍然会把这块内存纳入伙伴系统管理,等到RT-Thread跑起来后,Linux在某个瞬间又把这块内存分配出去了,两边同时写,最终系统随机死机,而且死机时机完全无规律,非常难排查。我第一次遇到这种问题,连续查了好几天。
另一个需要注意的地方是reg属性中的地址:0x30000000是物理地址,编译设备树时不会校验这个地址是否落在实际RAM范围内。如果你板子只有1GB内存(0x40000000以下),把RT-Thread放在0x30000000之后的64MB依然在RAM内。如果是1GB内存且物理内存只有0x30000000前段,那就要把预留区往低地址调整,避免访问到不存在的地址。
3.2 内核配置项和驱动
设备树只是硬件描述,要让Linux真正把RT-Thread固件拉起来,还得内核使能remoteproc和rpmsg框架。在RK3568 SDK默认配置里,这两项通常默认编译为模块(m),建议直接编译进内核(y)。
在SDK内核目录执行make menuconfig,检查以下选项:
CONFIG_REMOTEPROC=y CONFIG_RPMSG=y CONFIG_RK_REMOTEPROC=y CONFIG_HAVE_IMX_DSP=y # 如果用到相关dsp框架则保留如果找不到RK_REMOTEPROC这个选项,就要看你的SDK内核版本是否包含了Rockchip的remoteproc驱动补丁。瑞芯微的几个Release分支里都有对应驱动,代码位置在drivers/remoteproc/rk_remoteproc.c。
除了remoteproc框架本身,还需要确认和共享内存相关的配置:
CONFIG_CMA=y CONFIG_DMA_CMA=yCMA连续内存分配器在AMP中的作用很大,后面讲核间通信时你会看到,RPMsg的共享内存就经常从CMA区划出来,如果没有使能CMA,Linux侧分配连续物理内存会很困难。
3.3 固件文件的放法
RT-Thread编译生成的elf文件要放到Linux根文件系统的/lib/firmware/目录下,文件名和remoteproc节点里firmware属性一致。设备树里写的是"rt_thread.elf",Linux系统启动后,remoteproc驱动会去/lib/firmware/rt_thread.elf路径下找这个文件。
很多第一次接触remoteproc的人会卡在这:明明写了firmware属性,RT-Thread也编译好了,但执行启动命令时内核报"Failed to request firmware"错误。多数情况就是文件没放到/lib/firmware/,或者权限不对。确认一下:
ls -l /lib/firmware/rt_thread.elf文件存在,且用户有读权限,才行。
另外一个非常容易踩的坑是:RK3568 SDK默认构建出来的根文件系统是只读的(比如用ramdisk镜像),你拷贝进去的文件重启就没了。解决办法是把rootfs改成可写的ext4格式,或者在make menuconfig的rootfs配置里勾选可写选项。我自己是直接把内核cmdline里的root指定为SD卡或eMMC的ext4分区,这样固件文件可以持久保存。
4. RT-Thread侧的准备:让嵌入式RTOS跑在指定核心上
4.1 RT-Thread BSP的获取与配置
RT-Thread这边的工作主要是两件事:配置核心启动参数、调整链接脚本。先从获取BSP开始。
官方RT-Thread仓库里有rk3568的BSP,路径是bsp/rockchip/rk3568,直接clone下来:
git clone https://github.com/RT-Thread/rt-thread.git cd rt-thread/bsp/rockchip/rk3568这个BSP顶层目录有scons构建所需的SConstruct和Kconfig文件,可以直接用env工具(RT-Thread官方出的开发环境)配置,也可以手动修改rtconfig.h。
如果你是RT-Thread的新手,我建议直接用RT-Thread Studio,它内置了对rk3568 BSP的GUI配置入口,操作起来更直观。但我个人在实际项目里更偏好命令行scons,因为CI和批量编译时命令行更可控。
4.2 为AMP修改的宏定义与链接脚本
RT-Thread BSP默认情况下是按SMP模式设计的?其实不是,RT-Thread的rk3568 BSP默认只使用一个核心。现在要做AMP,要让RT-Thread只在自己的核心上启动,同时明确目标核心ID。
关键配置如下,在rtconfig.h中:
#define RT_HW_CACHE_LINE_BYTES 64 #define RT_AMP_ENABLE 1 #define RT_AMP_CPU_ID 2RT_AMP_CPU_ID指定RT-Thread运行在哪个核上。在常见的双系统方案里,CPU0和CPU1跑Linux,CPU2和CPU3留给RT-Thread,所以填2。
配合核心绑定,RT-Thread的中断控制器驱动也需要相应配置。RK3568的GIC驱动在RT-Thread BSP的librk3568目录里,要确保中断亲和性设置到目标核心,否则会出现RT-Thread申请的中断被GIC默认路由到CPU0上,而CPU0正在跑Linux,中断服务函数永远无法被调用的尴尬局面。
链接脚本的调整比较关键。打开bsp/rockchip/rk3568/linker_scripts/link.lds(不同版本的BSP路径可能有差异),把内存段基址设置成0x30000000:
. = 0x30000000; .text : { *(.text) *(.text*) }这里0x30000000必须和Linux设备树里的reg属性一致,否则出现一种情况:Linux把固件加载到0x30000000,但RT-Thread的链接脚本把自己的代码段放在0x20000000,启动后PC指针跳到一个空地址,系统马上荒废掉。
4.3 构建出RT-Thread固件
修改完上述配置后,执行构建:
scons -j8生成的rtthread.elf就是我们需要拷贝到Linux侧的固件文件。有人习惯在RT-Thread里开FinSH控制台,这会用到串口调试终端,注意在BSP配置里把串口号确认清楚。别让RT-Thread的调试串口和Linux的调试串口共用同一个物理串口,否则两个系统同时往一个串口输出日志,你看到的就是一堆乱码混叠,根本不知道谁是谁。
串口的在设备树里的分配也要提前确定。RK3568的UART2往往是调试串口,那我就在设备树里把UART2留给Linux,让RT-Thread使用UART3。这样两边各看各的日志,互不干扰。
5. 真正的3分钟部署:启动顺序和实操步骤
5.1 编译Linux量产固件
RT-Thread固件准备好之后,回头编译Linux侧。这一步要保证设备树的改动、内核配置的改动真正进入最终的烧录镜像。
在RK3568 SDK根目录执行:
./build.sh kernel ./build.sh uboot ./build.sh # 打包所有分区打包完成后,在rockdev目录下找到update.img或对应的分区镜像。烧录时可以用RKDevTool将整个update.img写入eMMC/SD卡,也可以只烧录boot.img和dtb分区。实际开发中我只烧boot.img和dtb分区就能迭代调试,速度比全量烧写快很多。
因为加了一个remoteproc节点,设备树dtb的体积变大,如果U-Boot对fdt分区有大小限制,可能还需要调整U-Boot的环境变量或分区容量。这个具体数值看开发板手册,不同品牌的板卡差异较大。
5.2 打包RT-Thread固件
Linux启动后,进入系统,先看remoteproc设备是否存在:
ls /sys/class/remoteproc/正常情况下会看到remoteproc0这个目录,它对应的就是设备树里定义的rproc节点。如果没有这个目录,说明remoteproc驱动没有加载成功,优先检查内核配置和设备树节点是否正常匹配。
然后把RT-Thread固件拷进rootfs:
cp rtthread.elf /lib/firmware/ sync注意,如果你用的是SD卡作为rootfs,因为ext4可写,直接cp就行。如果是ramdisk,系统启动后文件系统在内存里,cp能进去,但重启后就会丢失,需要改成可写rootfs才能固件持久保存。
5.3 上电启动与验证
一切准备就绪,上电。Linux正常启动后,进入shell,手动启动RT-Thread:
echo start > /sys/class/remoteproc/remoteproc0/state此时观察两块串口调试输出:Linux串口上应该看到remoteproc加载固件、释放复位信号的打印,同时出现类似于"rk_remoteproc: Measured 1.0"的日志;RT-Thread串口上则应该看到RT-Thread的启动banner,包括版本号、系统时钟信息,以及接下来运行的应用日志。
如果一切正常,你会发现RT-Thread侧完全没有Linux的调度延迟困扰。RT-Thread的tick中断一启动,一个专门做主站任务的线程就按周期循环执行,整个过程非常稳定。
有一个细节:启动RT-Thread之后,Linux内核日志里可能出现警告信息,提示某个CPU核心被"offline"了,这是正常现象,并不是错误。Linux原本管理4个核,你把其中部分核交给RT-Thread后,Linux内部的CPU热插拔逻辑会感知到状态变化并打印提示。不要看到这行日志就以为出bug了。
6. 两个系统的沟通:RPMsg核间通信
6.1 RPMsg基本原理
两个系统都跑起来了,不代表事情做完。最关键的问题浮出水面:Linux要拿EtherCAT的数据,RT-Thread要接收Linux发下来的运动控制指令,两边怎么通信?
一种最简单的思路是直接访问共享内存,在内存里放一个结构体,两边都读写。但是裸共享内存存在明显的同步问题:如果Linux正在写数据,RT-Thread恰好也在写同一块区域,数据就会错乱。你可以在应用层自己加锁,但嵌入式场景下加锁本身又会引入新的延迟和不确定性。
RPMsg就是专门解决这个问题的。它基于virtio的虚拟队列机制,在共享内存上抽象出一条消息通道。Linux侧的rproc框架已经实现了virtio/rpmsg的完整链路,RT-Thread侧也有对应的rpmsg实现。用户需要做的只是发起收发。
可以这么理解:共享内存就像一间公共办公室,RPMsg就是办公室里的邮件收发室。你不直接进办公室翻东西,而是把信投到收发室,由收发室决定什么时候把信送到对应的人手里。
6.2 Linux侧如何发送和接收
RT-Thread固件启动成功后,Linux侧会创建设备节点/dev/rpmsg0:
ls /dev/rpmsg*要测试能否正常通信,直接用标准文件读写接口:
#include <fcntl.h> #include <unistd.h> #include <stdio.h> #include <string.h> int main(void) { int fd = open("/dev/rpmsg0", O_RDWR); if (fd < 0) { perror("open rpmsg0"); return -1; } char buf[128] = {0}; const char *msg = "hello rt-thread"; write(fd, msg, strlen(msg) + 1); memset(buf, 0, sizeof(buf)); read(fd, buf, sizeof(buf)); printf("recv: %s\n", buf); close(fd); return 0; }如果read阻塞无法返回,确认RT-Thread侧是否把应答消息通过rpmsg发回来了。
6.3 RT-Thread侧如何对接
RT-Thread侧,要在应用代码里注册rpmsg端点和回调函数。RT-Thread的rpmsg组件初始化完成后,Linux发来的消息会进入你注册的回调里,你在回调中处理后,再调用发送接口回复给Linux。
#include <rtthread.h> #include <rpmsg.h> static int rpmsg_cb(void *data, rt_uint32_t len) { rt_kprintf("recv from linux: %s\n", (char *)data); rpmsg_send("hello linux"); return 0; } static int app_init(void) { rpmsg_service_init("amp-pingpong", rpmsg_cb); return 0; } INIT_APP_EXPORT(app_init);比较重要的是内存拷贝策略。RPMsg消息缓冲区通常有限,如果你的交互数据量很大,建议在共享内存区单独开一块自定义缓冲区,RPMsg只传一个指针或偏移量,真正的数据放在共享内存里。这样RPMsg通道始终保持轻量、低延迟,不会因为拷贝大数据而阻塞实时任务。
7. 调试技巧与踩坑记录
7.1 怎么快速定位"死机是哪个核心引起的"
AMP系统最大的调试痛点就是:一旦死机,你不知道是哪一边先出的问题。
我的做法是给Linux和RT-Thread各安排了一个GPIO控制的LED心跳指示。Linux侧每500ms翻转一次某个GPIO,RT-Thread侧每100ms翻转另一个GPIO。如果LED1还在闪,LED2停了,那问题一定出在RT-Thread侧;反之则是Linux侧先Abort。
这个方法看似土,但在双系统联调时非常高效。设备挂掉后,你不用看一大段复杂日志,直接看两个LED状态就能缩小排查范围。
7.2 内存冲突的排查
内存冲突是AMP最常见的故障。Linux侧因为有MMU,访问到非法地址时内核会打印一条oops,rt-thread侧则可能表现为某个线程异常,或者干脆无响应。
排查方法是先确认reserved-memory里的范围是否被Linux占用。在Linux shell执行:
cat /proc/iomem | grep -i reserved cat /proc/iomem | grep 30000000如果发现0x30000000区域出现在某个Linux驱动的注册地址里,说明CMA或DMA缓冲区把这块区域分配出去了,需要回头检查reserved-memory节点有没有带上no-map属性。
在一些特殊场景下,Linux的CMA默认区域会从低地址开始分配,有可能已经占用了0x30000000附近的空间。这种情况下要么把预留区改到更高地址,要么通过内核cmdline里的cma参数调整Linux可用内存的起始位置。
# 在某款4GB板卡上,我们把CMA区域固定到0x10000000~0x20000000之间 cma=256M@0x100000007.3 EtherCAT实时性实测数据
我在这套方案上跑了IGH的EtherCAT主站测试,RT-Thread侧每1ms周期发送一帧,连续运行48小时,用逻辑分析仪抓取SYNC信号,得出的抖动数据:最大抖动±0.9us,绝大多数周期抖动在±0.2us以内。这个结果和纯RT-Thread单系统跑EtherCAT基本没有区别。
同样的测试,Linux侧用PREEMPT_RT内核裸跑,1ms周期抖动峰值从几十us到上百us不等,满载时甚至能到几百us。这也是为什么我坚决推荐AMP方案。
当然,这个数据是在板载网卡驱动经过优化、RT-Thread侧任务优先级配置合理的前提下测出来的。如果RT-Thread里同时跑了多个高优先级任务,EtherCAT线程的外设中断响应时间可能会拉长,需要根据自己的应用场景测试确认。
7.4 几个容易忽略的坑
第一个坑是U-Boot阶段的CPU核心启动问题。RK3568的U-Boot默认会把4个核心全部启动,并在ATF阶段等待Linux接管。如果你在设备树里禁用了部分核心,U-Boot侧必须保持一致的配置,否则AMP启动时会发现RT-Thread要用的核心被U-Boot占用,造成启动顺序的异常。
第二个坑是固件格式。RT-Thread编译出来的是标准ELF格式,remoteproc框架支持直接加载ELF,但ELF文件里包含调试信息和符号表,体积偏大。如果你希望减少加载时间,可以转换成bin格式,但这时需要在链接脚本里精确控制内存布局,并且remoteproc节点里要额外指定entry地址。我用ELF格式居多,省心。
第三个坑是共享内存的缓存一致性问题。Linux侧有MMU和Cache,RT-Thread侧直接物理寻址也有Cache,两边同时访问共享内存时,如果配置不当,会一边看到新数据一边看到旧数据。解决方式是在共享内存的异常不配置时,把两个核对该区域的页表属性都设置成Non-Cacheable,或者使用DMA API做显式缓存同步。尽量在初始化阶段一次性设置好,运行过程中切换缓存策略会导致不可预期的延迟尖峰。
还有一个和正点原子rk3568开发板特别相关的点:它板载的以太网驱动、EtherCAT功能是否能和AMP共存,取决于网卡中断是否被路由到RT-Thread所在的核心。我测试的板子是双千兆网口,一个网口留给Linux,另一个网口的所有中断都要配置到CPU2/CPU3上,并且RT-Thread里要正确初始化这个网卡的驱动。这一步如果没做好,RT-Thread侧网卡一直初始化失败,但又查不出硬件问题,耗时最大。先通过设备树把中断亲和性设置好,再用串口打印确认中断是否到达RT-Thread核心。
我在实际部署这套方案的时候,第一次完整跑下来大概花了两个星期,主要是踩了内存no-map和中断亲和性这两个大坑。但部署环境固定下来之后,后续每次从编译到双系统跑起来,确实可以控制在3分钟左右。这也正是这个标题里"3分钟"的底气来源——前期的配置工作做好,后续的部署就是固定流水线,真正麻烦的事情是一次性的。
如果你计划在自己的RK3568板子上做类似的Linux+RT-Thread混合部署,我建议先从最小系统开始:先让RT-Thread独立跑通串口打印和GPIO翻转,再接入Linux的remoteproc框架,最后再上EtherCAT这类复杂的实时业务。一步一步来,踩坑的时候不会迷路。
整个项目做完,我的体会是:CPU核是可以商量着分的,操作系统的边界要看你的约束条件在哪里。EtherCAT这种硬实时任务放在专用的实时核上,Linux负责它擅长的复杂生态和业务编排,各司其职,远比强行在单个系统里解决所有问题来得清爽。