ARM Cortex-M中的中断与异常

ARM Cortex-M系列处理器与以往的ARM7TDMI、ARM920T相差很多,以往中断控制器都由IP授权的各家芯片厂商自行定义,而ARM Cortex-M则把中断控制器统一起来,命名为NVIC(嵌套向量中断控制)。正如其名,ARM Cortex-M NVIC支持中断嵌套功能:当一个中断触发并且系统进行响应时,处理器硬件会将当前运行的部分上下文寄存器自动压入中断栈中,这部分的寄存器包括PSR,R0,R1,R2,R3以及R12寄存器。当系统正在服务一个中断时,如果有一个更高优先级的中断触发,那么处理器同样的会打断当前运行的中断服务例程,然后把老的中断服务例程上下文的PSR,R0,R1,R2,R3和R12寄存器自动保存到中断栈中。这些部分上下文寄存器保存到中断栈的行为完全是硬件行为,这一点是与其他ARM处理器最大的区别(以往都需要依赖于软件保存上下文)。

另外,在ARM Cortex-M系列处理器上,所有中断都采用中断向量表的方式进行处理,即当一个中断触发时,处理器将直接判定是哪个中断源,然后直接跳转到相应的固定位置进行处理。而在ARM7、ARM9中,一般是先跳转进入IRQ入口,然后再由软件进行判断是哪个中断源触发,获得了相对应的中断服务例程入口地址后,再进行后续的中断处理。ARM7、ARM9的好处在于,所有中断它们都有统一的入口地址,便于OS的统一管理。而ARM Cortex-M系列处理器则恰恰相反,每个中断服务例程必须排列在一起放在统一的地址上(这个地址必须要设置到NVIC的中断向量偏移寄存器中)。

中断向量表一般由一个数组定义(或在起始代码中给出)。在STM32上,默认采用起始代码给出:

代码清单:初始化代码中的中断向量表

  1. __Vectors DCD __initial_sp ; Top of Stack
  2. DCD Reset_Handler ; Reset Handler
  3. DCD NMI_Handler ; NMI Handler
  4. DCD HardFault_Handler ; Hard Fault Handler
  5. DCD MemManage_Handler ; MPU Fault Handler
  6. DCD BusFault_Handler ; Bus Fault Handler
  7. DCD UsageFault_Handler ; Usage Fault Handler
  8. DCD 0 ; Reserved
  9. DCD 0 ; Reserved
  10. DCD 0 ; Reserved
  11. DCD 0 ; Reserved
  12. DCD SVC_Handler ; SVCall Handler
  13. DCD DebugMon_Handler ; Debug Monitor Handler
  14. DCD 0 ; Reserved
  15. DCD PendSV_Handler ; PendSV Handler
  16. DCD SysTick_Handler ; SysTick Handler
  17.  
  18.  
  19. NMI_Handler PROC
  20. EXPORT NMI_Handler [WEAK]
  21. B .
  22. ENDP
  23. HardFault_Handler PROC
  24. EXPORT HardFault_Handler [WEAK]
  25. B .
  26. ENDP
  27.  

请注意代码后面的[WEAK]标识,它是符号弱化标识,在[WEAK]前面的符号如NMI_Handler、HardFault_Handler)将被执行弱化处理,如果整个代码在链接时遇到了名称相同的符号(例如与NMI_Handler相同名称的函数),那么代码将使用未被弱化定义的符号(与NMI_Handler相同名称的函数),而与弱化符号相关的代码将被自动丢弃。

RT-Thread在Cortex-M系列上也遵循这样的方法,当用户需要使用自定义的中断服务例程时,只需要定义相同名称的函数覆盖弱化符号即可。例如用户需要自定义自己的串口2中断处理函数,那么可以在代码中自己实现USART2_IRQHandler函数,在系统编译链接时,中断向量表中将只保留这份USART2_IRQHandler函数,而不是经过WAEK修饰的USART2_IRQHandler函数。