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。典型配置步骤为:
- 在中断控制器中为 IPI 分配一个未使用的中断通道,设置优先级。
- 将目标核字段指向接收核(例如 PE1)。
- 在接收核侧注册中断服务函数,函数内清除中断标志并处理事件。
- 发送核通过写
ICxxx的请求位触发中断。
IPI 的实时性取决于中断优先级和接收核当前的关中断状态。若接收核正在执行高优先级临界区,IPI 会被延迟到临界区结束。因此,在硬实时场景中,应把 IPI 优先级设置得足够高,同时尽量缩短接收核的关中断时间。
实时同步实践要点
把上述机制组合成一个可运行的多核同步方案,通常遵循以下模式:主核完成初始化后,通过消息单元向从核发送“初始化完成”事件;从核收到后开始执行自己的任务。任务运行期间,若从核需要向主核提交数据,先把数据写入共享内存的环形缓冲区,再通过 IPI 通知主核“数据就绪”。主核在 IPI 服务函数中读取缓冲区,读取完毕后更新读指针,并通过消息单元回送“已消费”确认。
这里的关键判断依据是:共享内存的读写指针必须用自旋锁保护,而 IPI 只负责“敲门”,不携带大数据。若把数据直接放在 IPI 的中断服务函数里搬运,会拉长中断响应时间,影响其他中断的实时性。另一个常见陷阱是 IPI 丢失:如果发送核在接收核尚未清除上一次中断标志时再次触发,部分硬件会合并请求。因此,接收核应在服务函数入口尽快清除标志,发送核则应在确认接收核已处理后再发送下一次。
最后需要强调,RH850 不同系列(U2A、F1K、E2x 等)的核启动寄存器名称、消息单元数量和 IPI 通道分配存在差异,但“主核初始化 → 写入口地址 → 放行从核 → 从核取指 → 共享内存/IPI 协同”这一基本骨架是一致的。实际编码时,应以对应系列的硬件手册为准,逐项核对寄存器偏移和位定义,避免因型号差异导致启动失败。