3. 嵌入式开发环境搭建
异常处理,说白了就是CPU在遇到突发状况时的应急机制。我刚开始接触ARM时,总觉得这东西离我挺远——不就是个中断嘛,配好就能用。直到有一次产品在现场频繁死机,查了三天才发现是异常向量表放错了位置。嗯,从那以后我再也不敢小看这章内容了。
3.1 异常向量表——CPU的紧急电话本
ARM的异常向量表,你可以把它想象成一个紧急电话本。CPU遇到异常时,会直接翻到这个电话本,找到对应的处理函数入口。这个表固定在内存的特定位置。
核心要点:ARM有两种向量表模式——低端向量(0x00000000)和高端向量(0xFFFF0000)。Cortex-A系列默认用高端向量,但很多Bootloader会把它重映射到SRAM里。
我见过不少工程师在移植Linux时,忘了配置VBAR(Vector Base Address Register),结果中断一进来就跑飞了。你想想看,CPU拿着错误的电话本,怎么可能找到正确的人?
异常向量表的结构如下(以ARMv7-A为例):
; 异常向量表(每个向量4字节)
0x00000000: Reset_Handler ; 复位
0x00000004: Undefined_Handler ; 未定义指令
0x00000008: SVC_Handler ; 软中断(SWI)
0x0000000C: Prefetch_Handler ; 预取指中止
0x00000010: Data_Handler ; 数据中止
0x00000014: NOP ; 保留(早期用于IRQ)
0x00000018: IRQ_Handler ; 普通中断
0x0000001C: FIQ_Handler ; 快速中断
实战技巧:我个人习惯在向量表里放跳转指令(LDR PC, [PC, #offset]),而不是直接放函数地址。这样重定位时只需要改一个表,不用改每个向量。
3.2 异常优先级——谁先谁后?
ARM的异常有固定的优先级。高优先级的异常可以打断低优先级的处理。这个优先级是硬件写死的,你改不了。
| 优先级 | 异常类型 | 说明 |
|---|---|---|
| 1(最高) | 复位 | 优先级最高,不可屏蔽 |
| 2 | 数据中止 | 内存访问异常 |
| 3 | FIQ | 快速中断 |
| 4 | IRQ | 普通中断 |
| 5 | 预取指中止 | 指令预取异常 |
| 6 | 未定义指令 | 非法指令 |
| 7(最低) | SVC(软中断) | 系统调用 |
这里有个坑:FIQ的优先级高于IRQ。这意味着如果FIQ和IRQ同时触发,CPU会先处理FIQ。我在做高速数据采集时就用过这个特性——把时间敏感的数据通道挂在FIQ上,普通控制逻辑走IRQ。
3.3 中断处理流程——CPU到底干了啥?
当一个中断发生时,ARM内部会走一套固定的流程。我把它拆成5步,你记牢了:
- 保存现场:CPU自动把CPSR保存到SPSR_
,把返回地址保存到LR_ - 切换模式:CPU切换到IRQ或FIQ模式,关掉对应中断(硬件自动置位I或F位)
- 查向量表:CPU从向量表取指令,跳转到中断服务程序
- 软件处理:ISR里保存剩余寄存器,识别中断源,执行处理逻辑
- 恢复现场:执行完ISR后,恢复寄存器,执行MOVS PC, LR返回
注意:很多新手在写ISR时忘了保存R0-R12。ARM硬件只自动保存了PC和CPSR,其他寄存器你得自己压栈。我曾经见过一个产品,ISR里用了R4没保存,结果主程序跑着跑着就崩了。
下面这张图展示了完整的中断处理流程:
3.4 FIQ与IRQ的区别——快与稳的博弈
FIQ和IRQ都是中断,但设计思路完全不同。我当年在做一个工业控制器时,就因为这个选择吃过亏——选了IRQ处理高速脉冲,结果丢了不少数据。
它们的核心区别如下:
| 对比项 | FIQ | IRQ |
|---|---|---|
| 优先级 | 高(可打断IRQ) | 低(被FIQ打断) |
| 向量位置 | 0x1C(最后一个) | 0x18 |
| 私有寄存器 | R8-R14(5个) | R13-R14(2个) |
| 响应速度 | 快(无需保存R8-R12) | 慢(需保存R0-R12) |
| 典型应用 | 高速数据流、实时控制 | 普通外设、按键、通信 |
为什么FIQ更快?说白了就是ARM给FIQ开了后门——它多了5个私有寄存器(R8-R12)。ISR里可以直接用这些寄存器,不用压栈出栈。你想想看,省了5次内存访问,速度能不快吗?
实战建议:我个人习惯把时间敏感的中断源挂在FIQ上。比如高速ADC采样、PWM周期中断。普通的中断,比如UART接收、按键扫描,走IRQ就够了。别什么中断都往FIQ上挂,FIQ的ISR里不能嵌套,处理时间长了会影响系统响应。
3.5 中断嵌套与优先级管理
ARM硬件只支持两级中断优先级(FIQ和IRQ)。但实际项目中,我们需要更多优先级。怎么办?
常用的做法是:
- 硬件方案:使用GIC(Generic Interrupt Controller),支持最多256个优先级
- 软件方案:在ISR里开中断,允许更高优先级的中断打断当前处理
避坑指南:我曾经在Cortex-A9上做过一个项目,GIC配置了16级优先级。结果有个中断处理时间太长,把低优先级的中断饿死了。后来我加了个看门狗定时器,在ISR里检测执行时间,超时就报错。嗯,这招挺管用。
中断嵌套的代码示例(简化版):
IRQ_Handler:
; 保存现场
SUB LR, LR, #4
STMFD SP!, {R0-R12, LR}
; 读取GIC中断号
LDR R0, =GICC_BASE
LDR R1, [R0, #GICC_IAR]
; 开中断(允许嵌套)
MRS R2, CPSR
BIC R2, R2, #0x80 ; 清除I位
MSR CPSR_c, R2
; 调用具体ISR
BL Handle_Interrupt
; 关中断
ORR R2, R2, #0x80
MSR CPSR_c, R2
; 写EOI寄存器
STR R1, [R0, #GICC_EOIR]
; 恢复现场
LDMFD SP!, {R0-R12, PC}^
经验之谈:中断嵌套是把双刃剑。开嵌套能提高实时性,但也会增加栈深度和调试难度。我建议你在项目初期先关嵌套跑通,确认没问题再开。别一上来就搞复杂了,出了问题你都不知道是中断嵌套导致的还是代码本身有bug。
好了,异常处理这块内容就讲到这里。记住一句话:异常向量表是地基,中断流程是骨架,FIQ/IRQ的选择是灵魂。地基不稳,上面盖什么都白搭。