4. RH850启动流程与初始化代码

RH850上电之后,CPU并不会直接跳到main()。从复位释放到应用逻辑开始运行,中间要经过一条严格确定的启动链:复位向量取址、启动代码搬运、时钟树建立、看门狗处理、内存与栈初始化,最后才进入C运行环境。动力系统控制器对这段过程尤其敏感——时钟没稳定就访问Flash、看门狗没配置就长时间初始化、RAM没清零就使用变量,都会导致偶发死机或标定数据异常。本节按真实执行顺序拆解这条链,并给出可参考的初始化代码结构。

复位向量与启动入口

RH850的复位向量位于固定地址,复位释放后CPU从该地址取出第一条指令地址,跳转到启动代码。以RH850/P1x系列为例,复位向量通常位于代码Flash起始区域,向量表中保存的是启动入口地址而非指令本身。启动代码一般用汇编编写,第一件事是关闭全局中断、设置栈指针,避免在C环境未建立前发生异常。

; 启动入口示意(RH850汇编风格)
_start:
    di                      ; 关闭全局中断
    mov     #__stack_top, sp ; 设置栈顶
    mov     #0, r0
    ; 后续跳转到C初始化
    jarl    _sys_init, lp

这里的关键是栈指针必须在任何函数调用之前建立。栈顶地址由链接脚本定义,通常放在RAM高端,并预留中断栈空间。动力系统软件中,主栈与中断栈建议分离,避免中断嵌套时冲垮任务栈。

启动代码:从复位到C环境

启动代码承担三类工作:段初始化、时钟与看门狗前置处理、跳转C入口。段初始化包括把.data段从Flash拷贝到RAM、把.bss段清零。这两步不做,全局变量和静态变量初值就是错的。RH850工具链通常在链接脚本中给出各段起止符号,启动代码按符号搬运。

; .data搬运与.bss清零示意
    mov     #__data_rom_start, r6
    mov     #__data_ram_start, r7
    mov     #__data_ram_end, r8
copy_data:
    cmp     r7, r8
    bge     copy_done
    ld.w    0[r6], r9
    st.w    r9, 0[r7]
    add     4, r6
    add     4, r7
    br      copy_data
copy_done:
    mov     #__bss_start, r7
    mov     #__bss_end, r8
    mov     #0, r9
clear_bss:
    cmp     r7, r8
    bge     clear_done
    st.w    r9, 0[r7]
    add     4, r7
    br      clear_bss
clear_done:

搬运和清零完成后,才允许调用C函数。此时若时钟仍为复位默认的低速时钟,可以先用内部高速时钟完成基本初始化,再切换到PLL,避免在未稳定时钟下访问外设。

时钟初始化

RH850的时钟树一般由主振荡器、PLL、分频器和各外设时钟域组成。动力系统控制器常用外部晶振作为参考,经PLL倍频到CPU主频,再分频给CAN、ADC、定时器等外设。初始化顺序必须是:使能主振荡器→等待稳定→配置PLL→等待锁定→切换系统时钟源→配置各分频器。任何一步跳过等待,都可能让后续外设工作在错误频率下。

/* 时钟初始化示意,参数为教学示例 */
#define MOSC_STABLE_WAIT   (10000u)
#define PLL_LOCK_WAIT      (20000u)

void Clock_Init(void)
{
    /* 1. 使能主振荡器并等待稳定 */
    MOSC_CTRL = MOSC_ENABLE;
    while ((MOSC_STATUS & MOSC_STABLE) == 0u) { /* 超时保护 */ }

    /* 2. 配置PLL:示例目标CPU 160MHz */
    PLL_CTRL = PLL_ENABLE | PLL_MULT_20;
    while ((PLL_STATUS & PLL_LOCKED) == 0u) { /* 超时保护 */ }

    /* 3. 切换系统时钟到PLL */
    SYS_CLK_SEL = SYS_CLK_PLL;
    while (SYS_CLK_SEL != SYS_CLK_PLL) { }

    /* 4. 外设分频:CAN 40MHz,ADC 80MHz */
    CAN_CLK_DIV = DIV_4;
    ADC_CLK_DIV = DIV_2;
}

示例中PLL倍频20倍、目标160MHz属于教学假设,实际值必须按芯片数据手册和板级晶振频率计算。切换系统时钟后,建议读取时钟监视寄存器确认当前时钟源,而不是只依赖写入值。

看门狗配置

看门狗是启动阶段最容易出问题的地方。RH850通常有独立看门狗和窗口看门狗,复位后可能处于默认使能状态。如果启动代码执行时间超过看门狗溢出周期,芯片会在进入main()前反复复位。正确做法是在启动早期就明确看门狗状态:要么先喂狗并延长溢出时间,要么在初始化阶段暂时关闭,进入主循环前再按安全策略重新使能。

/* 看门狗初始化示意 */
void WDT_Init(void)
{
    WDT_UNLOCK = UNLOCK_KEY;      /* 解除写保护 */
    WDT_CTRL   = WDT_DISABLE;     /* 初始化阶段先关闭 */
    WDT_UNLOCK = LOCK_KEY;

    /* 主循环前重新使能,示例溢出约100ms */
    WDT_UNLOCK = UNLOCK_KEY;
    WDT_CTRL   = WDT_ENABLE | WDT_OVERFLOW_100MS;
    WDT_UNLOCK = LOCK_KEY;
}

动力系统软件中,看门狗喂狗点应放在任务调度正常完成的路径上,而不是放在定时器中断里无条件喂狗,否则任务卡死时看门狗也失去保护意义。

内存初始化与安全启动机制

内存初始化除了.data搬运和.bss清零,还包括栈检查、ECC初始化(若RAM带ECC)和MPU配置。RH850部分型号的RAM带ECC,上电后需先写入再读取以初始化ECC校验位,否则首次读取可能报ECC错误。安全启动机制则通过校验启动代码和关键标定区的CRC或签名,确认Flash内容未被篡改后再跳转应用。典型流程是:Bootloader校验应用区CRC→校验通过则跳转,失败则进入安全状态并记录故障码。

RH850 复位到应用运行的启动链 复位向量 固定地址取入口 启动代码 关中断 / 设栈 / 段搬运 时钟初始化 MOSC→PLL→分频 看门狗配置 先关闭 / 后使能 内存初始化 .data搬运 / .bss清零 / ECC 安全启动校验 CRC / 签名校验 校验通过? CRC / 签名 跳转应用 进入 main() 是 安全状态 记录故障码 / 限制输出 否 关键约束:栈指针先于函数调用建立;时钟稳定后再访问外设;看门狗在初始化阶段不得溢出;安全校验失败必须进入可控状态

把这条链落到工程上,建议在启动代码中保留明确的阶段标记,例如在进入时钟初始化前翻转一个GPIO,在进入main()前再翻转一次。用示波器或逻辑分析仪测量这两个标记之间的时间,就能判断启动耗时是否超出看门狗窗口。若发现复位循环,优先检查看门狗溢出周期与启动代码执行时间的关系,再检查时钟是否真正锁定。安全启动校验失败时,不应直接跳转到应用,而应停留在安全状态并置位故障码,便于售后读取。

启动阶段的调试建议:先用调试器在复位向量处设置断点,单步确认栈指针和段搬运符号地址正确;再在时钟切换后读取时钟状态寄存器,确认实际时钟源与目标一致;最后测量启动标记翻转时间,与看门狗溢出周期对比,留出至少30%余量。