4. 复位与启动:复位类型与启动流程

各位同学,今天我们来聊聊RH850的复位与启动。这个话题,说白了就是芯片怎么“醒过来”以及“怎么开始干活”。我做了这么多年汽车电子,发现很多问题都出在复位和启动阶段。你想想看,一个ECU上电瞬间,如果复位时序没处理好,后面跑得再稳也是白搭。

4.1 复位类型:芯片的三种“叫醒”方式

RH850的复位,我习惯把它分成三大类。每一类我都踩过坑,咱们一个一个说。

4.1.1 上电复位(POR)

这是最基础的复位。芯片一上电,内部电压检测电路就开始工作。当VCC电压上升到某个阈值(比如1.2V),POR就会释放,芯片开始初始化。

关键点:POR复位会清除所有寄存器到默认值。包括内核、外设、甚至调试接口的状态。

我在项目中遇到过一个问题:某次样机测试,上电后芯片死活不工作。查了半天,发现是电源纹波太大,导致POR反复触发。嗯,这里要注意——电源质量直接影响复位可靠性。

4.1.2 外部复位(RESET引脚)

外部复位是通过RESET引脚拉低实现的。这个引脚是异步的,也就是说,不管芯片在干啥,只要RESET拉低超过一定时间(通常几十微秒),芯片就强制复位。

我个人习惯在RESET引脚上加一个RC滤波电路。为什么?因为汽车环境电磁干扰严重,一个毛刺就可能误触发复位。我曾经在EMC测试时吃过这个亏,后来老老实实加了滤波。

复位类型 触发条件 复位范围 典型应用
上电复位 VCC上升到阈值 全芯片 首次上电
外部复位 RESET引脚拉低 全芯片 手动复位、看门狗输出
看门狗复位 WDT超时 内核+部分外设 程序跑飞恢复

4.1.3 看门狗复位(WDT)

看门狗复位,说白了就是“程序跑飞了,芯片自己救自己”。RH850的看门狗有独立时钟源,即使主时钟挂了,它照样能工作。

避坑指南:我曾经在调试时,把看门狗喂狗间隔设得太紧,结果正常流程下偶尔也会超时复位。后来我留了50%的余量,再也没出过问题。

看门狗复位有个特点:它不会复位所有寄存器。比如某些调试状态寄存器、复位原因寄存器,会保留下来。这样你可以在启动代码里读取复位原因,判断是上电复位还是看门狗复位。

4.2 启动模式:串行 vs 自举

RH850支持两种启动模式。我刚开始接触时也搞混过,其实区分起来很简单。

4.2.1 串行启动模式

串行启动,就是通过UART、CAN或LIN等接口,从外部主机下载程序。这个模式主要用于产线烧录或Bootloader更新。

我记得有一次,客户要求通过CAN总线远程升级固件。我用的就是串行启动模式。具体流程是这样的:

  1. 芯片上电后,先运行一小段固化在ROM里的Bootloader
  2. Bootloader检测启动引脚电平,判断是否进入串行模式
  3. 如果是,就通过CAN接收数据,写入Flash
  4. 写入完成后,跳转到用户程序

小技巧:串行启动时,建议在Bootloader里加一个超时机制。如果几秒内没收到有效数据,就自动跳转到用户程序。这样即使产线忘记烧录,设备也能正常启动。

4.2.2 自举启动模式

自举启动,就是从内部Flash直接启动。这是最常用的模式。芯片上电后,直接从Flash的0x00000000地址取指令执行。

这里有个细节:RH850的Flash在复位后默认是只读的。如果你想写Flash,必须先解锁。我见过有人直接在启动代码里写Flash,结果卡死了——因为没解锁。

4.3 启动代码流程:芯片的“起床三部曲”

启动代码,说白了就是芯片从复位到进入main函数之前干的事。我把它总结成三步。

4.3.1 第一步:硬件初始化

这一步是芯片自己完成的,不需要你写代码。包括:

  • 复位向量表加载
  • 堆栈指针初始化
  • 时钟源选择(默认用内部低速振荡器)
  • Flash控制器初始化

你想想看,如果连堆栈指针都没设好,你连函数调用都做不了。所以这一步是芯片的“本能反应”。

4.3.2 第二步:软件初始化

这一步需要你写代码。通常放在复位向量指向的入口函数里。我一般这么写:

void Reset_Handler(void)
{
    // 1. 读取复位原因
    uint32_t reset_cause = RST_GetResetCause();
    
    // 2. 初始化堆栈(如果硬件没做)
    __set_MSP(STACK_TOP);
    
    // 3. 初始化BSS段(清零)
    for(uint32_t *p = &__bss_start; p < &__bss_end; p++)
    {
        *p = 0;
    }
    
    // 4. 拷贝数据段(从Flash到RAM)
    for(uint32_t *p = &__data_load; p < &__data_end; p++)
    {
        *p = *(__data_load + (p - &__data_start));
    }
    
    // 5. 初始化时钟和PLL
    SystemClock_Init();
    
    // 6. 跳转到main
    main();
}

注意:BSS段清零和数据段拷贝,这两个步骤顺序不能乱。先清零,再拷贝。否则数据会被覆盖。

4.3.3 第三步:用户程序启动

进入main函数后,就是你的天下了。但这里有个坑:main函数里不要做太复杂的初始化。我建议把外设初始化放在main里,但把时钟、堆栈这些基础设置放在启动代码里。

我曾经在一个项目里,把CAN初始化放在了启动代码里。结果调试时发现,每次复位后CAN总线都会发一帧错误帧。后来移到main里,问题就解决了。为什么?因为启动代码执行时,其他外设还没准备好。

4.4 复位原因判断:一个实用的技巧

RH850有一个复位原因寄存器(RST_CAUSE)。你可以在启动代码里读取它,然后根据不同的复位原因做不同的处理。

void CheckResetCause(void)
{
    uint32_t cause = RST_GetResetCause();
    
    switch(cause)
    {
        case RST_CAUSE_POR:
            // 上电复位,做完整初始化
            FullInit();
            break;
            
        case RST_CAUSE_EXTERNAL:
            // 外部复位,可能是看门狗触发的
            // 保留部分数据
            PartialInit();
            break;
            
        case RST_CAUSE_WDT:
            // 看门狗复位,说明程序跑飞过
            // 记录错误日志
            LogError("WDT reset occurred");
            QuickInit();
            break;
            
        default:
            break;
    }
}

我的习惯:在产品中,我会把复位原因保存到备份RAM里。这样即使再次复位,也能追溯历史复位记录。对故障排查特别有用。

4.5 总结与避坑

好了,关于复位与启动,我最后再唠叨几句:

  • 电源质量:上电复位对电源纹波敏感,滤波要做好
  • 看门狗喂狗:留足余量,别卡着极限值喂
  • 启动代码顺序:BSS清零、数据拷贝、时钟初始化,顺序别搞反
  • 复位原因记录:这是排查问题的第一手线索

下一章,我们会讲时钟系统。到时候你会看到,时钟配置和复位是紧密相关的。嗯,今天就到这里,有问题随时问我。