2. 多核启动与核间同步机制

RH850 多核 MCU(如 RH850/U2A、RH850/F1K 系列)上电后,所有核并非同时开始执行用户代码。芯片内部存在一个由硬件决定的“引导核”(Boot Core,通常为 PE0),它负责完成最基本的时钟、RAM 与安全初始化,然后通过寄存器显式地“放行”其余从核。理解这一过程,是设计多核实时软件的第一步:如果从核在主核尚未准备好共享资源之前就跳入 C 代码,就会出现访问未初始化外设、共享内存竞争甚至总线错误。

主核引导与从核放行

RH850 的启动流程可以概括为“复位向量 → 主核初始化 → 从核启动地址设置 → 从核放行 → 各核进入各自入口”。复位释放后,只有主核从复位向量取指,执行启动代码(通常由 CS+ 或 GHS 提供的 startup 例程)。主核需要依次完成:设置 PLL 与时钟分频、初始化栈指针、清零 .bss、搬运 .data、配置 MPU、初始化共享内存区域,然后才允许从核启动。

从核的启动地址由专门的核启动地址寄存器(如 RH850/U2A 的 PE1START、PE2START 等,具体名称依系列而定)设置。主核在放行从核之前,必须先把从核的入口地址写入对应寄存器,再通过核启动控制寄存器(如 SBSTART 或 PEEN 类寄存器)置位使能。从核被放行后,会从该地址开始取指,因此该地址通常指向一段极短的汇编入口,负责设置从核自己的栈指针,再跳转到 C 入口函数。

/* 主核侧:设置从核入口并放行(示意,寄存器名依系列而定) */
#define PE1_ENTRY_ADDR   (0x00010000UL)   /* 从核1入口,位于共享代码区 */
#define PE1_START_REG    (*(volatile uint32_t*)0xFFD10010UL)
#define PE1_ENABLE_REG   (*(volatile uint32_t*)0xFFD10014UL)

void boot_core1(void)
{
    PE1_START_REG  = PE1_ENTRY_ADDR;  /* 先写入口地址 */
    __sync();                          /* 保证写入对其他核可见 */
    PE1_ENABLE_REG = 0x00000001UL;     /* 再置使能,从核开始取指 */
}

这里有一个容易出错的顺序问题:必须先写入口地址,再置使能位。若顺序颠倒,从核可能在地址尚未写入时就被放行,取到无效向量。写入后插入一次内存屏障(__sync() 或 SYNCI 指令),确保写操作真正落到总线,而不是停留在写缓冲中。

核间通信的硬件基础

RH850 提供多种核间通信通道,选择哪一种取决于数据量和实时性要求。常见的有三类:

  • 共享内存(Shared RAM):多核可访问的片上 RAM 区域,适合传递较大数据块或环形缓冲区。访问需要配合自旋锁或原子操作,否则会出现读写撕裂。
  • 消息单元(Message Unit / MSG):硬件消息寄存器组,通常提供多组邮箱,每组含数据寄存器和状态位,适合传递短消息、命令字或事件通知,硬件自动置位接收标志,无需软件轮询内存。
  • 数据闪存(Data Flash):用于掉电保持的配置参数,启动阶段主核可从数据闪存读取标定参数,再分发给从核,但它不是高频通信通道。

实际工程中常见的组合是:用消息单元传递“任务就绪”“数据已写入”这类短事件,用共享内存传递实际数据负载。这样既避免了共享内存轮询的开销,又保证了大数据传输的效率。

自旋锁与屏障指令

当多个核同时访问共享内存中的同一结构时,必须用自旋锁保护临界区。RH850 提供原子交换类指令(如 LDST 或基于 EX 的原子操作),可用来实现自旋锁。一个典型的自旋锁实现如下:

typedef volatile uint32_t spinlock_t;

static inline void spin_lock(spinlock_t *lock)
{
    uint32_t tmp;
    do {
        /* 原子交换:若返回0表示锁空闲,已成功获取 */
        __asm volatile ("ldst.w %0, %1" : "=r"(tmp) : "m"(*lock));
    } while (tmp != 0);
}

static inline void spin_unlock(spinlock_t *lock)
{
    *lock = 0;
    __sync();   /* 屏障:确保临界区内的写操作先于解锁可见 */
}

屏障指令在这里有两个作用:一是防止编译器把临界区内的访存指令重排到锁外;二是防止 CPU 的乱序执行或写缓冲导致其他核看到“锁已释放但数据尚未写入”的错误状态。RH850 提供 SYNCI(指令同步)和 SYNCE(数据同步)等屏障指令,在锁的获取与释放路径上必须成对使用。

核间中断(IPI)与实时同步

自旋锁解决的是“互斥”,而核间中断解决的是“通知”。RH850 的 INTC 支持将某个中断定向到指定核,通过设置 ICxxx 寄存器的目标核字段(如 TGT 位)即可实现 IPI。典型配置步骤为:

  1. 在中断控制器中为 IPI 分配一个未使用的中断通道,设置优先级。
  2. 将目标核字段指向接收核(例如 PE1)。
  3. 在接收核侧注册中断服务函数,函数内清除中断标志并处理事件。
  4. 发送核通过写 ICxxx 的请求位触发中断。

IPI 的实时性取决于中断优先级和接收核当前的关中断状态。若接收核正在执行高优先级临界区,IPI 会被延迟到临界区结束。因此,在硬实时场景中,应把 IPI 优先级设置得足够高,同时尽量缩短接收核的关中断时间。

RH850 多核启动与核间同步机制 主核 PE0(引导核) 复位向量取指 时钟/PLL/栈/MPU 初始化 共享内存区域初始化 写 PE1START 入口地址 置 PE1ENABLE 放行从核 进入主核应用任务 从核 PE1 等待放行(复位保持) 从 PE1START 取指 设置从核栈指针 跳转 C 入口函数 进入从核应用任务 等待 IPI / 轮询共享内存 共享内存 Shared RAM 环形缓冲区 / 数据块 需自旋锁保护 消息单元 MSG 邮箱寄存器 / 状态位 短消息 / 事件通知 核间中断 IPI ICxxx 目标核字段 实时事件通知 放行信号(写 PE1ENABLE) 主核先写入口地址再置使能;从核放行后从 PE1START 取指;共享资源需自旋锁 + 屏障保护

实时同步实践要点

把上述机制组合成一个可运行的多核同步方案,通常遵循以下模式:主核完成初始化后,通过消息单元向从核发送“初始化完成”事件;从核收到后开始执行自己的任务。任务运行期间,若从核需要向主核提交数据,先把数据写入共享内存的环形缓冲区,再通过 IPI 通知主核“数据就绪”。主核在 IPI 服务函数中读取缓冲区,读取完毕后更新读指针,并通过消息单元回送“已消费”确认。

这里的关键判断依据是:共享内存的读写指针必须用自旋锁保护,而 IPI 只负责“敲门”,不携带大数据。若把数据直接放在 IPI 的中断服务函数里搬运,会拉长中断响应时间,影响其他中断的实时性。另一个常见陷阱是 IPI 丢失:如果发送核在接收核尚未清除上一次中断标志时再次触发,部分硬件会合并请求。因此,接收核应在服务函数入口尽快清除标志,发送核则应在确认接收核已处理后再发送下一次。

在多核调试阶段,可以先用一个简单的“乒乓测试”验证同步链路:主核写一个递增计数到共享内存,发 IPI 给从核;从核读到后加一,再发 IPI 回主核。若计数能稳定递增且无跳变,说明自旋锁、屏障和 IPI 配置均正确。若出现计数回退或卡死,优先检查锁的原子性、屏障位置以及 IPI 目标核字段是否写对。

最后需要强调,RH850 不同系列(U2A、F1K、E2x 等)的核启动寄存器名称、消息单元数量和 IPI 通道分配存在差异,但“主核初始化 → 写入口地址 → 放行从核 → 从核取指 → 共享内存/IPI 协同”这一基本骨架是一致的。实际编码时,应以对应系列的硬件手册为准,逐项核对寄存器偏移和位定义,避免因型号差异导致启动失败。