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总线远程升级固件。我用的就是串行启动模式。具体流程是这样的:
- 芯片上电后,先运行一小段固化在ROM里的Bootloader
- Bootloader检测启动引脚电平,判断是否进入串行模式
- 如果是,就通过CAN接收数据,写入Flash
- 写入完成后,跳转到用户程序
小技巧:串行启动时,建议在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清零、数据拷贝、时钟初始化,顺序别搞反
- 复位原因记录:这是排查问题的第一手线索
下一章,我们会讲时钟系统。到时候你会看到,时钟配置和复位是紧密相关的。嗯,今天就到这里,有问题随时问我。