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步,你记牢了:

  1. 保存现场:CPU自动把CPSR保存到SPSR_,把返回地址保存到LR_
  2. 切换模式:CPU切换到IRQ或FIQ模式,关掉对应中断(硬件自动置位I或F位)
  3. 查向量表:CPU从向量表取指令,跳转到中断服务程序
  4. 软件处理:ISR里保存剩余寄存器,识别中断源,执行处理逻辑
  5. 恢复现场:执行完ISR后,恢复寄存器,执行MOVS PC, LR返回

注意:很多新手在写ISR时忘了保存R0-R12。ARM硬件只自动保存了PC和CPSR,其他寄存器你得自己压栈。我曾经见过一个产品,ISR里用了R4没保存,结果主程序跑着跑着就崩了。

下面这张图展示了完整的中断处理流程:

ARM中断处理完整流程 外设触发中断信号 CPU检测到中断请求 硬件自动处理 保存CPSR → 切换模式 → 关中断 → 查向量表 软件ISR处理 保存寄存器 → 识别中断源 → 执行处理 → 清中断 恢复现场 → 返回主程序 关键寄存器变化 CPSR → SPSR_irq PC → LR_irq 模式位设为IRQ模式

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的选择是灵魂。地基不稳,上面盖什么都白搭。