
目录一.概念二.处理三.深入分析1.保存现场2.如何实现四.案例分析1.未定义指令异常1.不用Usage Fault2.使用Usage Fault2.SVC异常3.Systick异常五.中断中断的使用1.确定引脚2.配置PB1引脚3.配置exti4.配置cpu5.完成EXTI1_IRQHandler函数记得清除中断标志位一.概念所有的中断源(按键、定时器等)它们发出的中断汇聚到中断控制器 再由中断控制器发信号给CPU告诉它发生了那些紧急情况。除了这些中断还有什么可以打断CPU的运行指令不对数据访问有问题reset信号等等这些都可以打断断CPU这些被称为异常中断属于一种异常ARM系统中如何处理异常与中断重点在于保存现场以及恢复现场 处理过程如下保存现场(各种寄存器)处理异常(中断属于一种异常)恢复现场不同的芯片不同的架构在这方面的处理稍有差别保存/恢复现场cortex M3/M4是硬件实现的cortex A7是软件实现的CPU中止当前执行跳转去执行处理异常的代码也有差异cortex M3/M4在向量表上放置的是函数地址cortex A7在向量表上放置的是跳转指令二.处理要想理解这个处理流程需要从向量表说起。在ARM架构里对于异常/中断它们的处理入口会整齐地排放在一起。 M3/M4的向量表中放置的是具体异常/中断的处理函数的地址。比如发生Reset异常时CPU就会从向量表里找到第1项得到Reset_Handler函数的地址跳转去执行。比如发生EXTI Line 0中断时CPU就会从向量表里找到第22项得到EXTI0_IRQHandler函数的地址跳转去执行。跳转之前硬件会保存现场函数执行完毕返回之后硬件会恢复现场; Vector Table Mapped to Address 0 at Reset AREA RESET, DATA, READONLY EXPORT __Vectors EXPORT __Vectors_End EXPORT __Vectors_Size __Vectors DCD __initial_sp ; Top of Stack DCD Reset_Handler ; Reset Handler DCD NMI_Handler ; NMI Handler DCD HardFault_Handler ; Hard Fault Handler DCD MemManage_Handler ; MPU Fault Handler DCD BusFault_Handler ; Bus Fault Handler DCD UsageFault_Handler ; Usage Fault Handler DCD 0 ; Reserved DCD 0 ; Reserved DCD 0 ; Reserved DCD 0 ; Reserved DCD SVC_Handler ; SVCall Handler DCD DebugMon_Handler ; Debug Monitor Handler DCD 0 ; Reserved DCD PendSV_Handler ; PendSV Handler DCD SysTick_Handler ; SysTick Handler ; External Interrupts DCD WWDG_IRQHandler ; Window Watchdog DCD PVD_IRQHandler ; PVD through EXTI Line detect DCD TAMPER_IRQHandler ; Tamper DCD RTC_IRQHandler ; RTC DCD FLASH_IRQHandler ; Flash DCD RCC_IRQHandler ; RCC DCD EXTI0_IRQHandler ; EXTI Line 0 DCD EXTI1_IRQHandler ; EXTI Line 1 DCD EXTI2_IRQHandler ; EXTI Line 2 DCD EXTI3_IRQHandler ; EXTI Line 3 DCD EXTI4_IRQHandler ; EXTI Line 4 DCD DMA1_Channel1_IRQHandler ; DMA1 Channel 1 DCD DMA1_Channel2_IRQHandler ; DMA1 Channel 2 DCD DMA1_Channel3_IRQHandler ; DMA1 Channel 3 DCD DMA1_Channel4_IRQHandler ; DMA1 Channel 4 DCD DMA1_Channel5_IRQHandler ; DMA1 Channel 5 DCD DMA1_Channel6_IRQHandler ; DMA1 Channel 6 DCD DMA1_Channel7_IRQHandler ; DMA1 Channel 7 DCD ADC1_2_IRQHandler ; ADC1 ADC2 DCD USB_HP_CAN1_TX_IRQHandler ; USB High Priority or CAN1 TX DCD USB_LP_CAN1_RX0_IRQHandler ; USB Low Priority or CAN1 RX0 DCD CAN1_RX1_IRQHandler ; CAN1 RX1 DCD CAN1_SCE_IRQHandler ; CAN1 SCE DCD EXTI9_5_IRQHandler ; EXTI Line 9..5 DCD TIM1_BRK_IRQHandler ; TIM1 Break DCD TIM1_UP_IRQHandler ; TIM1 Update DCD TIM1_TRG_COM_IRQHandler ; TIM1 Trigger and Commutation DCD TIM1_CC_IRQHandler ; TIM1 Capture Compare DCD TIM2_IRQHandler ; TIM2 DCD TIM3_IRQHandler ; TIM3 DCD TIM4_IRQHandler ; TIM4 DCD I2C1_EV_IRQHandler ; I2C1 Event DCD I2C1_ER_IRQHandler ; I2C1 Error DCD I2C2_EV_IRQHandler ; I2C2 Event DCD I2C2_ER_IRQHandler ; I2C2 Error DCD SPI1_IRQHandler ; SPI1 DCD SPI2_IRQHandler ; SPI2 DCD USART1_IRQHandler ; USART1 DCD USART2_IRQHandler ; USART2 DCD USART3_IRQHandler ; USART3 DCD EXTI15_10_IRQHandler ; EXTI Line 15..10 DCD RTCAlarm_IRQHandler ; RTC Alarm through EXTI Line DCD USBWakeUp_IRQHandler ; USB Wakeup from suspend DCD TIM8_BRK_IRQHandler ; TIM8 Break DCD TIM8_UP_IRQHandler ; TIM8 Update DCD TIM8_TRG_COM_IRQHandler ; TIM8 Trigger and Commutation DCD TIM8_CC_IRQHandler ; TIM8 Capture Compare DCD ADC3_IRQHandler ; ADC3 DCD FSMC_IRQHandler ; FSMC DCD SDIO_IRQHandler ; SDIO DCD TIM5_IRQHandler ; TIM5 DCD SPI3_IRQHandler ; SPI3 DCD UART4_IRQHandler ; UART4 DCD UART5_IRQHandler ; UART5 DCD TIM6_IRQHandler ; TIM6 DCD TIM7_IRQHandler ; TIM7 DCD DMA2_Channel1_IRQHandler ; DMA2 Channel1 DCD DMA2_Channel2_IRQHandler ; DMA2 Channel2 DCD DMA2_Channel3_IRQHandler ; DMA2 Channel3 DCD DMA2_Channel4_5_IRQHandler ; DMA2 Channel4 Channel5 __Vectors_End发生异常/中断时硬件上实现了这些事情保存现场把被中断瞬间的寄存器的值保存进栈里根据异常/中断号从向量表中得到函数地址跳转过去执行函数执行完后从栈中恢复现场保存现场、分辨异常/中断、跳转执行都是硬件实现的。 我们只需要在向量表中把处理函数的地址填进去就可以了。硬件承包了大部分的工作。M3/M4的向量表中存放的是函数地址。三.深入分析1.保存现场CPU每执行完一条指令都会检查有无中断/异常产生发现有中断/异常产生开始处理保存现场分辨异常/中断调用对应的异常/中断处理函数恢复现场不管是硬件还是软件实现第一步都是保存现场。在处理异常前把这些寄存器保存在栈中这称为保存现场在处理完异常后从栈中恢复这些寄存器这称为恢复现场还有一个程序状态寄存器对于M3/M4它被称为XPSR对于A7它被称为CPSR我们简称为PSR。 R0-R15、PSR就是所谓的现场。 发生异常/中断后在处理异常/中断前需要保存现场难道需要保存所有这些寄存器吗 不需要 在C函数中可以修改R0-R3、R12、R14(LR)以及PSR。如果C函数要用到这些寄存器就要把它们保存到栈里在函数结束前在从栈中恢复它们。 这些寄存器被拆分成2部分调用者保存的寄存器(R0-R3,R12,LR,PSR)、被调用者保存的寄存器(R4-R11)。 比如函数A调用函数B函数A应该知道R0-R3是用来传参数给函数B的函数B可以肆意修改R0-R3函数A不要指望函数B帮你保存R0-R3保存R0-R3是函数A的事情对于LR、PSR也是同样的道理保存它们是函数A的责任对于函数B我用到R4-R11中的某一个我都会在函数入口保存、在函数返回前恢复保证在B函数调用前后函数A看到的R4-R11保存不变假设函数B就是异常/中断处理函数函数B本身能保证R4-R11不变那么保存现场时只需要保存这些调用者保存的寄存器(R0-R3,R12,LR,PSR)PC2.如何实现硬件保存现场这里就存在一个问题C函数执行完后它返回LR所指示的位置。 难道把LR设置为被中断的程序的地址就行了吗 如果只是返回LR所指示的地方硬件帮我们保存在栈里的寄存器怎么恢复M3/M4在调用异常处理函数前把LR设置为一个特殊的值转给特殊的值被称为EXC_RETURN。 当PC寄存器的值等于EXC_RETURN时会触发异常返回机制简单地说会从栈里恢复R0-R3,R12,LR,PC,PSR等寄存器。四.案例分析1.未定义指令异常未定义指令即使还没有定义的指令也就是CPU不认识的指令。 很多时候我们故意在代码里插入一些伪造的指令故意让CPU执行到它时触发错误。 这在调试时很有用比如想打断点怎么实现呢 有很多种方法硬件监视点(watch point数量有限)、软件断点(数量无限)。 软件断点就是使用未定义指令来实现的比如想让程序执行到某个地址A时停下来可以这样做地址A上原来的指令是xxx我们故意把它改成yyy改成一条CPU无法识别的指令当CPU执行到地址A上的yyy指令时触发异常在异常处理函数里打印更多调试信息调试完毕后恢复地址A上的指令为xxx从地址A重新执行程序1.不用Usage Fault如果没使能使用错误异常的话那就是触发硬件错误异常发生了硬件错误异常直接调用了向量表里面的HardFault_Handler函数我们只需要完成这个函数即可。2.使用Usage Fault先得使能使用错误异常这得查看手册置位寄存器但是发现触发了UsageFault后会一直循环触发UsageFault这是不正确的还得去修改问题原因栈中保存的返回地址是执行这个未定义指令恢复栈后还会触发这个异常所以我们得改变这个栈中保存的返回地址PC指向下一条指令同时也得清除这个异常的寄存器。解决2.SVC异常在ARM指令中有一条指令 SVC #VAL它会触发一个异常。 在操作系统中比如各类RTOS或者Linux都会使用SVC指令故意触发异常从而导致内核的异常处理函数被调用进而去使用内核的服务。 比如Linux中各类文件操作的函数open、read、write它的实质都是SVC指令。 本节课程不讲解SVC在内核中的使用我们只是看看如何处理SVC触发的异常。SVC异常时栈里面保存的返回地址指向的SVC下一条指令所以我们并不需要修改。3.Systick异常Cortex-M处理器内部集成了一个小型的、名为SysTick的定时器。可以使用它来为操作系统提供系统时钟也可以把它当做一般的定时器。之所以在处理器内增加这样的定时器是为了提高软件的可以移植性。它是一个24位的定时器向下计数。在时钟源的驱动下计数值到达0时可以触发异常。它的框图如下主要涉及3个寄存器实现Systick异常点灯start.s: PRESERVE8 THUMB ; Vector Table Mapped to Address 0 at Reset AREA RESET, DATA, READONLY EXPORT __Vectors IMPORT HardFault_Handler IMPORT UsageFault_Handler IMPORT SVC_Handler IMPORT SysTick_Handler __Vectors DCD 0 DCD Reset_Handler ; Reset Handler DCD 0 ; NMI Handler DCD HardFault_Handler ; Hard Fault Handler DCD 0 ; MPU Fault Handler DCD 0 ; Bus Fault Handler DCD UsageFault_Handler_asm ; Usage Fault Handler DCD 0 ; Reserved DCD 0 ; Reserved DCD 0 ; Reserved DCD 0 ; Reserved DCD SVC_Handler ; SVCall Handler DCD 0 ; Debug Monitor Handler DCD 0 ; Reserved DCD 0 ; PendSV Handler DCD SysTick_Handler ; SysTick Handler AREA |.text|, CODE, READONLY ; Reset handler Reset_Handler PROC EXPORT Reset_Handler [WEAK] IMPORT main IMPORT SystemInit IMPORT uart_init IMPORT UsageFaultInit IMPORT SysTickInit IMPORT LedInit LDR SP, 0x20000000 0x20000 BL SystemInit BL uart_init BL UsageFaultInit LDR R0, 0 LDR R1, 0x11111111 LDR R2, 0x22222222 LDR R3, 0x33333333 LDR R12, 0x44444444 LDR LR, 0x55555555 DCD 0xffffffff SVC #1 BL SysTickInit BL LedInit ;BL main LDR R0, main BLX R0 ENDP UsageFault_Handler_asm PROC MOV R0, SP B UsageFault_Handler ENDP END systick.c: #include systick.h #include exception.h #include led.h void SysTickInit(void) { SysTick_Type *SysTick (SysTick_Type *)SysTick_BASE; /* 1. set period : 1S */ /* 2. set Load */ SysTick-VAL SYSTICK_FRE; SysTick-LOAD SYSTICK_FRE; /* 3. select clk source, enable systick, enable exception * BIT[2] : clk source, process clk * BIT[1] : enable exception * BIT[0] : enable systick */ SysTick-CTRL (12) | (11) | (10); } void SysTick_Handler(void) { SCB_Type * SCB (SCB_Type *)SCB_BASE_ADDR; static int led_on 0; /* led */ if(led_on) { /* let led off */ LedCtrl(0); } else { /* let led on */ LedCtrl(1); } led_on !led_on; /* clear exception status */ SCB-ICSR | SCB_ICSR_PENDSTCLR_Msk; } led.c: #include led.h void LedInit(void) { volatile unsigned int *pReg9; /* 使能GPIOF */ pReg9 (unsigned int *)(0x40023800 0x30); *pReg9 | (15); /* 设置GPIOF9为输出引脚 */ pReg9 (unsigned int *)(0x40021400 0x00); *pReg9 | (0x0118); /* 设置GPIOF9为推挽输出 */ pReg9 (unsigned int *)(0x40021400 0x04); *pReg9 ~(19); /* 设置GPIOF9为低速输出 */ pReg9 (unsigned int *)(0x40021400 0x08); *pReg9 ~(0x0318); } void LedCtrl(int on) { volatile unsigned int *pReg9; pReg9 (unsigned int *)(0x40021400 0x18); if(on) { /* 设置GPIOF9输出0 */ *pReg9 | (125); } else { /* 设置GPIOF9输出1 */ *pReg9 | (19); } }五.中断中断不可缺少的3部分中断源 中断源多种多样比如GPIO、定时器、UART、DMA等等。 它们都有自己的寄存器可以进行相关设置使能中断、中断状态、中断类型等等。中断控制器 各种中断源发出的中断信号汇聚到中断控制器。 可以在中断控制器中设置各个中断的优先级。 中断控制器会向CPU发出中断信号CPU可以读取中断控制器的寄存器判断当前处理的是哪个中断。 中断控制器有多种实现比如STM32F103中被称为NVICNested vectored interrupt controller(嵌套向量中断控制器)ARM9中一般是芯片厂家自己实现的没有统一标准Cortex A7中使用GIC(Generic Interrupt Controller)CPU CPU每执行完一条指令都会判断一下是否有中断发生了。 CPU也有自己的寄存器可以设置它来使能/禁止中断这是中断处理的总开关。中断的使用1.确定引脚1.使用的是stm32f407的gpio的irq原理图确认PB1引脚2.根据如下图来一一配置2.配置PB1引脚void key_init(void) { volatile unsigned int *pReg; /* 使能GPIOB */ pReg (unsigned int *)(0x40023800 0x30); *pReg | (11); /* 设置GPIOB1为输入引脚 */ pReg (unsigned int *)(0x40020400 0x00); *pReg ~(32); /* 设置GPIOB1为浮空输入外部接了上拉电阻 */ pReg (unsigned int *)(0x40020400 0x0C); *pReg ~(32); /* 配置中断线映射前要先使能syscfg */ pReg (unsigned int *)(0x40023800 0x44); *pReg | (114); /* 配置中断线映射 */ pReg (unsigned int *)(0x40013800 0x08); *pReg ~(0xF4); *pReg | (14); }3.配置extitypedef struct { volatile unsigned int IMR; volatile unsigned int EMR; volatile unsigned int RTSR; volatile unsigned int FTSR; volatile unsigned int SWIER; volatile unsigned int PR; }EXTI_TypeDef; void exti_init(void) { EXTI_TypeDef * exti (EXTI_TypeDef *)0x40013C00; /* 配置双边沿触发 */ exti-RTSR | (11); exti-FTSR | (11); /* 使能中断不屏蔽 */ exti-IMR | (11); } void exti_clear_int(int bit) { EXTI_TypeDef * exti (EXTI_TypeDef *)0x40013C00; /* 清除标志 */ exti-PR | (1bit); }4.配置nvic4typedef struct { volatile unsigned int ISER[8]; /*! Offset: 0x000 (R/W) Interrupt Set Enable Register */ volatile unsigned int RESERVED0[24]; volatile unsigned int ICER[8]; /*! Offset: 0x080 (R/W) Interrupt Clear Enable Register */ volatile unsigned int RSERVED1[24]; volatile unsigned int ISPR[8]; /*! Offset: 0x100 (R/W) Interrupt Set Pending Register */ volatile unsigned int RESERVED2[24]; volatile unsigned int ICPR[8]; /*! Offset: 0x180 (R/W) Interrupt Clear Pending Register */ volatile unsigned int RESERVED3[24]; volatile unsigned int IABR[8]; /*! Offset: 0x200 (R/W) Interrupt Active bit Register */ volatile unsigned int RESERVED4[56]; volatile unsigned char IP[240]; /*! Offset: 0x300 (R/W) Interrupt Priority Register (8Bit wide) */ volatile unsigned int RESERVED5[644]; volatile unsigned int STIR; /*! Offset: 0xE00 ( /W) Software Trigger Interrupt Register */ }NVIC_Type; void nvic_init(void) { NVIC_Type * nvic (NVIC_Type *)0xE000E100; nvic-ISER[0] | (17); } void nvic_clear_int(int bit) { NVIC_Type * nvic (NVIC_Type *)0xE000E100; if (bit 31) nvic-ICPR[0] | (1bit); }4.配置cpu; 使能CPU中断 CPSIE I ;清除PRIMASK使能中断5.完成EXTI1_IRQHandler函数记得清除中断标志位void EXTI1_IRQHandler(void) { /* GPIOB1 input data register */ unsigned int * pReg (unsigned int *)(0x40020400 0x10); if((*pReg (11)) 0) { LedCtrl_F10(1); puts(KEY1 pressed!\n\r); } else { LedCtrl_F10(0); puts(KEY1 released!\n\r); } /* 清除标志 */ exti_clear_int(1); nvic_clear_int(7); }若想要源码可以私信博主创作不易还望多多支持点赞转发。