三个核、三份固件、一个调试口。稍不注意就是随机死机加下载互相覆盖。
这篇文章把我做多核 RISC-V 工程最终跑通的方案整理出来:一个内核一套独立工程、链接脚本做硬地址隔离、脚本一键编三核、单 OpenOCD 实例自适应下载。全文讲的不是"照着抄",而是每一步为什么这么做、不做会怎样,最后有 17 条实战踩过的坑。
这篇会讲到:
- 多核工程最容易撞的三道坎,以及为什么必须从编译阶段就解决、不能拖到运行时
- 一个内核一套 EIDE 工程的完整配置思路,三个核到底差在哪几处
- 链接脚本怎么实现硬地址隔离,以及那个必须知道的 gp 寻址陷阱
- 怎么用一条命令编三个核,怎么用一条命令烧三个核
work-area这个"单独烧都正常、批量烧就炸"的元凶
0 写在前面:多核 MCU 开发到底有多折磨?
单核芯片做久了,刚换到多核 RISC-V 上,第一反应往往是"不就是多编译几份固件吗"。
真上手就知道不是这么回事。三个内核就是三份独立的固件镜像,各有各的链接地址,各有各的运行时数据。你拿单核那套思路直接套,一定会撞上下面三道坎。
第一道坎:工程怎么组织。
三个核塞进一个工程,编译产物互相打架;拆成三个工程,又不知道怎么统一编译、统一管理。
第二道坎:内存怎么分。
代码、RAM、栈不隔离,一个核跑起来就把另一个核的变量覆盖了。症状是随机死机、复现概率飘忽——这是最难查的一类问题,没有之一。
第三道坎:固件怎么烧。
芯片没有 Flash 控制器,标准下载命令直接报错;多核下载时,后烧的那份还会把前面内核的内存缓冲区盖掉。
这篇文章把这三道坎一次性填平。
最终你会得到这样的开发体验:改完代码,一条命令编译三个核(也可以只在 EIDE 界面里编某一个);一条命令烧三个核(也能单独烧某一个)。三个核各自独立,编译和调试互不干扰。
整套方案不依赖任何商业 IDE——不用 Keil,不用 IAR,全靠开源工具链加 VS Code 插件。
1 方案总览
1.1 整体架构
整体结构看这张图就够了。左边是工程组织:VS Code 用多根工作区把三个内核的独立工程并排管起来。右边是输出:编译产出六个文件(三个 elf 加三个 bin),下载统一交给一个 OpenOCD 实例。
┌───────────────────── VS Code 多根工作区 ─────────────────────┐
│ Core0 (Master主核) Core1 (Slave从核) Core2 (Slave从核) │
│ ├ .eide/eide.yml ├ .eide/eide.yml ├ .eide/eide.yml │
│ ├ core0.ld ├ core1.ld ├ core2.ld │
│ └ build/Debug/ └ build/Debug/ └ build/Debug/ │
│ core0.elf core1.elf core2.elf │
│ core0.bin core1.bin core2.bin │
└──────────────────────────────────────────────────────────────┘
│ │
│ ①编译:EIDE界面 / unify_builder脚本 │ ②下载:OpenOCD
▼ ▼
三份独立链接脚本 单实例 OpenOCD
内存地址完全互不重叠 按内核基址自动切换work-area
1.2 三大核心设计思路
思路一:一个内核,一套独立 EIDE 工程。
三个核的链接脚本、内存基址、输出产物完全不同。有人会想省事合成一个工程——千万别。三份配置的链接脚本和产物名必然不一样,混在一起改配置,迟早互相污染。
正确做法是用 VS Code 的多根工作区(multi-root workspace),把三个工程文件夹并排管起来。
思路二:靠链接脚本做硬地址隔离。
给每颗内核分配专属的代码段、RAM 数据段和栈空间,各内核的内存区间严格不重叠。
关键在于:要从编译阶段就杜绝内存踩踏,而不是等到运行时再调试。地址一旦重叠,你面对的就是概率性随机崩溃,排查成本高到无法接受。
思路三:不需要为每个内核建独立的调试目标。
芯片的调试模块可以直接访问完整的总线地址空间,所以一个调试目标就足以操作全部内核。
下载时根据镜像的加载地址,自动切换到该内核专属的 work-area 临时缓冲区即可——既避免了下载时缓冲区互相覆盖,又省掉了维护三套调试目标的复杂度。
1.3 示例内存布局
地址规划是整套方案的地基。下面是我用的布局,你替换成自己芯片的实际参数就行。
| 内存区域 | 起始地址 | 大小 | 说明 |
|---|---|---|---|
| Core0 代码 | CORE0_BASE | 64 KB | 主核,负责唤醒启动从核 |
| Core0 数据与栈 | CORE0_BASE + 0x10000 | 64 KB | 栈顶在+0x1FF00,栈向下生长 |
| Core1 代码 | CORE1_BASE | 64 KB | 从核 |
| Core1 数据与栈 | CORE1_BASE + 0x10000 | 64 KB | 栈顶在+0x1FF00 |
| Core2 代码 | CORE2_BASE | 64 KB | 从核 |
| Core2 数据与栈 | CORE2_BASE + 0x10000 | 64 KB | 栈顶在+0x1FF00 |
⚠️ 重中之重:内存区间绝对不能重叠。
栈或 RAM 一旦重叠,现象就是:单核跑一切正常,多核一起跑就随机崩溃,而且很难复现。所以三个基址必须错开,每个核占用的完整区间(代码区加数据区)不能侵占到下一个核的起始地址。
还有个容易漏掉的细节:我这个布局里数据区结束在
+0x20000,栈顶在+0x1FF00——说明栈是向下生长的。指针初始化到+0x1FF00后往低地址压栈,可用空间大约 8 KB,往下最多碰到数据区边界。栈和数据区之间必须留够余量,这是多核工程里最容易被低估的一点。
2 环境与工具版本
| 组件 | 版本 | 备注 |
|---|---|---|
| 目标芯片 | RISC-V 三核 MCU(1 主 2 从) | 示例适配 rv32imafdc 架构 |
| IDE | VS Code | 依赖多根工作区特性 |
| 构建插件 | EIDE(cl.eide)3.27.2 | 负责工程管理、编译、上传 |
| 工具链 | xPack RISC-V GCC 15.2.0 | 命令为riscv-none-elf-gcc |
| 下载工具 | OpenOCD 0.11.0 | 单实例完成三核固件烧写 |
| 调试探针 | J-Link USB-JTAG | CMSIS-DAP 也可兼容 |
| 宿主系统 | Windows | 脚本使用 cmd 与 PowerShell |
3 第一步:用多根工作区统一管理三个内核工程
这一步的目标很简单:让三个内核出现在同一个 VS Code 窗口里,但各自保持独立的构建上下文。
做法是新建一个工作区配置文件,声明三个文件夹分别指向三个内核的工程目录,并起个能看懂的名字(主核、从核一、从核二)。这样在资源管理器里它们并排显示,切换、搜索、构建互不干扰。
配置文件里有几项设置值得单独说,都是实际用下来觉得不能少的。
第一项,让 IntelliSense 复用 EIDE 的编译参数。
把 C/C++ 插件的配置提供者指向 EIDE 之后,头文件路径和宏定义会自动同步,你就不用再手写那份繁琐的 C 语言工程配置文件了。省掉手工维护的价值很高——一旦路径和实际编译不一致,代码提示就是错的,比没有提示更坑。
第二项,指定 RISC-V 工具链的安装目录和命令前缀。
这里用到多根工作区的变量语法,可以跨文件夹精确定位工具链路径:以某个具名文件夹为基准向上跳,再进入工具链目录。
第三项,文件排除规则。
EIDE 会自动把工具链和 OpenOCD 下载到工程目录下,这两样加起来有几万个文件。不把它们从资源管理器里隐藏掉,全局搜索会被这些无关文件彻底淹没,日常开发体验直接毁掉。
第四项,推荐扩展列表。
除了 EIDE 本身,建议装上链接脚本的语法高亮插件(改 .ld 时舒服很多)、YAML 支持插件(改 .eide/eide.yml 需要)、以及串口监视器插件(看目标板输出)。
💡 小技巧:把上面这些配置存成一个以
.code-workspace结尾的文件。之后双击它就能直接打开整套多核工程,不用再一个个开文件夹。
4 第二步:为每个内核配置独立的 EIDE 工程
每个内核目录下都有自己的工程配置文件。三份配置绝大部分内容一模一样,只有三处不同:工程名、链接脚本路径、下载基地址。
记住这一点,维护就简单了——复制一份,改这三个地方。
4.1 三处差异
| 内核 | 工程名 | 链接脚本 | 下载基地址 |
|---|---|---|---|
| Core0 | core0 | core0.ld | CORE0_BASE |
| Core1 | core1 | core1.ld | CORE1_BASE |
| Core2 | core2 | core2.ld | CORE2_BASE |
4.2 关键编译选项解读
除了这三处差异,其余配置三个核共用。其中有几个编译选项值得说明,因为它们的取舍直接影响固件体积和可调试性。
函数与数据分节(-ffunction-sections 加 -fdata-sections,配合链接时移除未使用段)
让每个函数、每个变量都独立成段,链接阶段就能精确裁掉没被引用到的部分。多核工程的收益特别明显,因为三个固件体积能同时缩小。
寄存器快速保存恢复(-msave-restore)
让中断上下文的寄存器保存与恢复走库函数而不是内联展开,能明显缩减中断处理代码的体积。
关闭链接器松弛优化(--no-relax)
这个选项我在调试阶段强烈建议常开。松弛优化会插入压缩指令、改变指令长度,导致反汇编出来的地址和源码行号对不上。一旦对不上,你后面所有基于地址的排查都会事倍功半。
不使用工具链自带启动文件(-nostartfiles)
多核芯片的复位向量布局和多核唤醒逻辑是我们自己写的,工具链默认的启动流程在这里不适用。
符号与行号信息格式选 DWARF 2
兼容性最好,能避免工具链版本差异带来的解析问题。
架构与 ABI,三个核必须完全一致
这条单独强调。混用不同 ABI(比如一个核用单精度浮点、另一个用双精度)会导致链接期 ABI 不兼容,或者更隐蔽的浮点寄存器使用不一致——症状是"某个核的浮点计算结果莫名其妙不对",非常难查。
5 第三步:用链接脚本实现三核内存硬隔离
三个内核各有一份链接脚本,代码结构一模一样,只有最顶部那几个内存常量不同。
这是全套方案最核心的一步,我按结构拆开讲。
5.1 链接脚本的组织结构
以主核为例,脚本骨架按顺序可以分成六块。
第一块,顶部常量区。
只在这里定义三个值:本内核代码区起始地址、RAM 数据区起始地址、RAM 结束地址。最后一个专门留给末尾的越界断言用。改地址只需要改这三个常量,其余部分三份脚本完全通用。
第二块,代码段。
把代码区定位到基址,依次收纳启动段、复位向量段、正文段、只读数据段、小只读段和小 BSS 段。
这里有个提醒:复位向量段和它的排序子段一定要用 KEEP 保护起来。否则链接器裁剪未使用段时可能把它丢掉——那样烧进去的固件第一件事就是跑飞。
第三块,数据段——这里藏着整个方案最关键的设计:加载地址与运行地址分离。
为什么要分离?因为代码存在只读介质里,但可写数据在运行时必须待在 RAM。所以数据段的**加载地址(LMA)**跟在代码后面(随镜像一起烧写),**运行地址(VMA)**落在 RAM 区。启动代码负责把数据从加载地址搬到运行地址。
脚本里用两个符号标记这个搬移过程:一个是数据段的加载地址,一个是它的运行地址。这两个值必须不同——如果相等,说明数据段其实没有真正分离到 RAM,要么搬移代码白跑,要么根本不需要搬移。
这里还有一个必须记住的 ABI 约束:小只读数据段(.srodata)也要归进小数据段。
原因是编译器对小常量会生成 gp 寄存器相对寻址,这种寻址范围只有正负 2 KB,而 gp 指向小数据段。如果把 .srodata 留在只读区(ROM),偏移量会直接越出范围。
这是很多人第一次写 RISC-V 链接脚本必踩的坑,我自己也在这上面栽过。
第四块,零初始化段。
BSS 类段用 NOLOAD 属性标记,表示不占加载空间,启动时清零即可。这里同时给出栈顶符号,指向基址加固定偏移的位置。
第五块,链接期断言——投入产出比最高的一段。
ASSERT(_end <= CORE0_RAM_END, "Core0 data/bss overflow!")
ASSERT(__global_pointer$ >= CORE0_RAM_BASE, "Core0 gp不在RAM区间!")
第一句检查数据段和 BSS 段的结束地址没超出本核 RAM 区间,也就是"数据段涨大压到下一个核"这个事故从此会在链接阶段直接报错,而不是等到运行时随机崩。第二句检查 gp 指针确实落在 RAM 区间内。
多核工程里最恶心的故障就是内存越界:症状随机死机、崩的位置每次不同、几乎没法用打印定位。而这两行断言只增加几毫秒链接时间,就能把它变成一条明确的编译错误。
第六块,符号信息段。
注意这里要用通配写法,因为 .debug 只是名字,真正要保留的是它下面一大票子段(.debug_info、.debug_line 等等)。
不要用丢弃指令删这些段——那样既删不干净子段,还会连符号表一起丢掉,后续就没法把地址对回源码了。真要减体积,用 objcopy 在链接之后剥离。
5.2 一个必须自查的点:孤儿段
链接脚本是你写的,但编译器生成的段名你不一定都知道。凡是脚本里没收纳的段,链接器会自己找地方安放,位置往往出人意料。
常见的容易漏掉的段:异常帧信息段、全局构造函数段(数组形式的初始化和析构列表)、线程局部存储段。它们分别对应"开了栈回溯信息"、“有全局对象构造或构造属性函数”、"用了线程局部变量"这几种情况。
自查方法很简单:编译一次,用 objdump 列出全部段,看看有没有你脚本里没写过的段名。发现陌生的段就补进脚本,或者明确决定丢弃它。
5.3 编译完必做:链接布局校验
链接脚本写完别急着下载,先用两条命令把地址布局核一遍。检查编译产物,比"烧进去跑起来看看"便宜太多。
第一条命令用 nm 列出关键符号地址,重点看四个:代码区起止符号、数据段结束符号、栈顶符号、gp 指针符号。
第二条命令用 objdump 列出所有段的运行地址和加载地址。
判读标准六条:
- 代码段的运行地址等于本核基址,且运行地址与加载地址相同(代码不需要搬移)。
- 数据段的运行地址落在本核 RAM 区间内,加载地址在只读区——两者必须不同,这才说明分离生效。
- gp 指针符号落在小数据段内(数据段结束符号附近),绝对不能指向只读区。
- 栈顶符号等于脚本里设定的值。
- 三个内核的所有内存区间两两不重叠。
- 没有出现没被脚本收纳的孤儿段。
🎯 最佳实践:把断言写进链接脚本,内存溢出直接编译失败。多核随机死机里大约八成根源来自内存越界,这个投入太值了。
6 第四步:编译(单核构建与脚本一键编译三核)
6.1 界面单核编译
每个工程都自带一组标准任务:构建、完整重建、清理、下载。在对应内核文件夹下按 Ctrl+Shift+B,选择构建或重建,编的就是那个内核。
这里有个容易混淆的点值得澄清:"完整重建"和"增量编译"的差别,在改配置的场景下是致命的。
凡是修改过工程配置文件或链接脚本,必须走完整重建。否则用的是上一次生成的构建参数,你的改动根本不生效,然后你还会以为是改错了地方——白白浪费半天。
6.2 脚本一键编译全部三核
要在持续集成里跑,或者只是想脱离界面一次性编完三个核,可以直接调用 EIDE 背后的构建后端程序。
脚本干这几件事:先检查构建程序是否存在(不存在就提示确认 EIDE 插件版本);然后依次进入三个内核目录,逐个执行完整重建;每次构建后检查返回码,失败就置失败标记,但继续编剩下的核(这样一次运行能看到所有核的问题,而不是修一个再跑一次);成功的话再用体积统计工具打印这个内核各段的占用。
最后汇总:只要任何一个核失败,脚本就返回非零退出码,方便上层流水线判断。
为什么要在编译脚本里打印体积?
因为多核工程最容易出的事故,就是某个核的数据段悄悄涨大了,越过自己那段 RAM,压到下一个核的代码上。体积统计会把代码段、数据段、BSS 段分别列出来,配合链接脚本里的地址规划,一眼就能看出有没有超界的趋势。
这比等到运行崩溃再回头查,主动得多。
7 第五步:OpenOCD 下载(单目标自适应多核烧录)
7.1 先理解 work-area 这个坑
work-area 是 OpenOCD 在目标 RAM 里划出的一块临时缓冲区,用来搬运数据、以及在目标上执行下载算法。
如果三个核共用同一块 work-area,后下载的固件就会覆盖前面内核的缓冲区,固件运行直接异常。
这就是"单独烧某一个核一切正常、批量下载就炸"的元凶。
所以下载配置的核心逻辑就一句话:根据镜像的加载地址,自动切换到该内核专属的 work-area。
一个容易被误解的细节:单纯往内存里加载数据时,OpenOCD 通常能通过调试模块直接写总线,不一定真会用到 work-area。但只要涉及需要目标执行代码的操作(Flash 烧写算法、大块数据搬运、以及某些目标的内存访问兜底路径),work-area 就会被真正使用。
所以"按核隔离 work-area"不是多余的保险,而是一旦触发就必须正确的前提。
7.2 OpenOCD 配置说明
这份配置文件按顺序理解,逐段说明每一项的作用。
第一段,内存参数区。 定义三个内核基址和 work-area 大小。
这里必须强调:三个基址必须是真实的、互不相同的值。如果留成相同的占位值,后面的 work-area 选择逻辑会永远走最后一个分支,静默选中同一块区域,而且不报任何错误——整套"按核隔离"的设计就悄悄失效了。
第二段,探针与传输配置。 包括探针驱动类型、JTAG 时钟频率、传输协议,以及复位策略。下载场景下复位配置越简单越好,用"不参与复位"最稳,不要从别处照抄一堆复位时序进来。
第三段,TAP 与目标创建。 这里有个必须替换的参数:JTAG 器件的 ID 校验值。留成占位值的话,OpenOCD 在初始化阶段就会因为 ID 校验失败直接退出。同时为调试目标配置初始 work-area,指向主核区域。
第四段,时钟使能——这是最容易漏掉的一步。
写内存虽然通过调试模块直接访问总线,但很多芯片的 SRAM 控制器和总线矩阵本身需要先使能时钟才能被访问。
漏了这一步的症状非常有迷惑性:OpenOCD 报告写入成功,但回读全是零或全 F,直到校验阶段才报错。 正确做法是在复位初始化事件里写入时钟使能寄存器。
第五段,init 命令,触发上面的初始化流程。
第六段,也是最有技术含量的一段:重定义下载命令。
为什么要重定义?因为多核芯片常常没有 Flash 控制器(或者工程阶段先跑 RAM 版本)。此时内置的下载命令会去调 Flash 写操作,但没有 Flash 存储区,直接报错说找不到对应存储区。所以要改成直接往内存里加载。
重定义后的命令按这个顺序工作:
- 参数解析。第一个参数是文件名;后续参数里,十六进制或十进制数字当加载地址,等于
verify就打开校验开关,等于bin就标记这是裸二进制格式。 - 选择 work-area。用加载地址和三个内核基址做比较,判断这是哪个核,选中对应的 work-area 区域。拿到十六进制地址时注意要用数值比较,不是字符串比较,否则地址比大小会得出荒谬结论。
- 复位、停机、切换 work-area。先触发复位初始化,再停机,然后把工作区切到刚选中的区域。
- 写入并可选校验。裸二进制走"指定地址加格式"的加载路径;ELF 文件直接按它自己的段表布局——因为 ELF 自带段地址,不需要也不应该再指定加载地址。
⚠️ 这里必须澄清一个常见误解:自定义命令里的那个地址参数,只用于选择 work-area,不参与 ELF 的实际写入地址计算。ELF 写到哪儿由它自己的段表决定。
但如果你改用系统内置的下载命令,或者直接调用底层加载命令给 ELF 传了地址,就可能走成"把整个文件当成一整块搬到该地址"的路径——结果是把 ELF 文件头当成代码写进内存,程序一跑就飞。
7.3 三核一键下载脚本
下载脚本干这些事:先声明三个内核各自的镜像路径和下载基址(这三个基址必须与各自链接脚本里的基址完全一致);打印一份对照表供人工确认;然后循环调用子过程逐个下载。
子过程里做三件事:检查该内核镜像文件是否存在(不存在就报错,而不是把空文件写进去);调用 OpenOCD 执行下载;检查返回码。
校验开关强烈建议常开。
理由是:多核场景下地址写错通常不会报错,只会静默写飞——OpenOCD 说下载成功,但程序行为完全不对。回读比对是最便宜的保险。校验通过时 OpenOCD 会打印确认信息;不匹配则直接报错,脚本里的返回码判断因此是有效的。
7.4 ELF 与裸二进制的区别
| 输入类型 | 命令形式 | 说明 |
|---|---|---|
| ELF 文件 | 只给文件名,可加校验标志 | 文件自带段表,按段自己的地址布局写入,最省心 |
| 裸二进制 | 文件名 + 加载基址 + 格式标志 | 二进制没有地址信息,必须显式指定地址和格式 |
另外两点补充。
EIDE 插件界面本身也支持对单个内核点上传,用来单独烧录。
固件侧建议每个内核启动后打印自己的 hart id 和一个固定的魔术字,串口输出三个核各一行。这样能确认三颗内核都真正跑起来了,而不是只看下载工具打印了一句"完成"。
7.5 还有个隐蔽问题:停机范围
自定义命令里的停机操作,只停了当前调试目标对应的那个 hart。
如果芯片其他从核在复位后是自由运行的,而它们又会执行你正要写入的那段 RAM,就会出现"边写内存边执行"的竞态。
稳妥做法:写入之前把要写的内核都置于停机状态(多建几个调试目标分别停机,或者先写时钟与复位寄存器把从核按住)。
8 踩坑总结
编译与链接相关
- 千万不要把多个内核塞进同一个 EIDE 工程,内存规划和编译选项一定会互相污染。多根工作区、一个内核一个工程才是正道。
- 三核的架构与 ABI 配置必须完全一致。混用单精度与双精度 ABI 会出现隐蔽的浮点运行故障。
- 工程调试阶段建议开启
--no-relax,关闭链接器松弛优化,保证反汇编和源码行号对齐。 - 改过工程配置文件或链接脚本,必须执行完整重建。增量编译会沿用旧的构建参数,改动不生效。
- 构建参数文件是 EIDE 自动生成的中间产物,禁止手动修改。要改就改工程配置文件,让插件重新生成。
- 链接脚本一定要写断言,把 RAM 和栈越界从随机运行崩溃,提前变成编译期报错。
- 工作区配置里一定要排除 EIDE 自动下载的工具链目录,否则全局搜索会被几万个无关文件淹没。
- 小只读常量段务必放进小数据段。gp 寄存器寻址只有正负 2 KB 范围,放到只读区会产生寻址越界。
- 不要用丢弃指令删调试符号段,那种写法不会处理子段;要剥离符号请用 objcopy。
- 编译结束用 objdump 检查有没有孤儿段(异常帧信息段、构造函数段等)。没被脚本收纳的段会被链接器随机安放地址。
OpenOCD 下载相关
- 无 Flash 的芯片必须重定义下载命令,否则原生命令调用 Flash 写操作会报"找不到存储区"。
- work-area 缓冲区必须跟随内核基址切换。共用缓冲区会导致后烧录的固件覆盖前面内核的临时内存,出现"单烧正常、批量下载异常"。
- ELF 镜像不要额外传入加载地址。传入的地址仅用于切换 work-area;给 ELF 再加二进制格式标志会把文件头当成代码写入内存,直接跑飞。
- 下载务必带上回读校验。多核地址配置错误经常不会抛错,只会让固件静默异常。
- 停机只会停止当前调试目标对应的 hart。其他从核上电自动跑的场景,下载前需要手动把它们按住,防止边写内存边执行。
- Windows 下 OpenOCD 提示缺少 DLL,需要先切换到它所在目录再运行。
固件运行相关
- 部分芯片上电瞬间调试接口未就绪,直接下载会报"无法停机"。解决方案:上电后短暂延时,或者下载前先执行一次复位。
9 写在最后
做多核 RISC-V 的难点,从来不在写业务驱动,而在工程组织、内存规划、工具链适配这三件事上。
| 层次 | 解决什么问题 | 核心手段 |
|---|---|---|
| 工程组织 | 三份固件互不干扰 | VS Code 多根工作区,一个内核一个 EIDE 工程 |
| 内存规划 | 杜绝内核互相踩踏 | 三份链接脚本做代码、RAM、栈的完整地址隔离,加链接期断言 |
| 编译构建 | 一键批量编译,提前发现内存溢出 | 调用构建后端脚本化,加体积统计 |
| 固件下载 | 无 Flash 芯片的多核烧录 | 单 OpenOCD 目标,重定义下载命令,自动切换 work-area |
| 结果校验 | 确认固件真正烧录并运行 | OpenOCD 回读校验,加各内核打印 hart id 与魔术字 |
💡 几个忠告
- 多核大约八成的诡异 bug,根源来自内存地址重叠。优先把内存隔离做好,这一步做对了,后面基本就顺了。
- 能在编译链接阶段捕获的问题,不要留到运行时调试。断言几行代码就能换来的确定性,太值。
work-area是多核下载最隐蔽的坑,也是"单烧一切正常、批量下载就炸"的元凶。- 不要迷信下载工具打印的"编程完成",回读校验必不可少。
这套方案可以轻松扩展更多核:新增一个内核,只需要新增一个工程文件夹、新增一份链接脚本、在编译和下载脚本里各加一项。
CSDN 标签:#RISC-V #多核开发 #VSCode #EIDE #OpenOCD #嵌入式 #单片机 #编译工具链
124

被折叠的 条评论
为什么被折叠?



