第5章 CHAPTER 5 外部中断的原理与应用 外部中断(EXTernal Interrupt,EXTI)通过GPIO引脚输入外部中断请求信号(上升沿或下降沿),引发中断——CPU在正常执行程序的过程中,由于某个事件的原因,暂停CPU正在执行的程序,转去执行处理该事件的中断服务程序,中断服务程序执行完后,再返回刚才暂停的位置,继续执行刚才被暂停的程序,这个过程叫作“中断”。 5.1中断的概念 5.1.1中断的作用 最初中断技术引入计算机系统,是为了消除快速CPU和慢速外设之间进行数据传输时的矛盾。因为慢速外设准备好数据需要很长时间,如果不使用中断技术,就会使快速CPU长时间等待,降低了CPU的效率。而使用中断技术后,在慢速外设准备数据期间,快速CPU可以处理其他事务,待慢速外设准备好数据后,向CPU发出中断请求。CPU收到慢速外设的中断请求后,暂停正在执行的程序,转去接收慢速外设的数据,接收完数据后,再返回刚才暂停处,继续执行刚才暂停的程序,这就提高了CPU的效率。除此之外,中断还具有以下几个功能。 (1) 可以实现多个外设同时工作,提高了效率。 (2) 可以实现实时处理,对采集的信息进行实时处理。 (3) 可以实现故障处理。由于故障是随机事件,事先无法预测,因而中断技术是故障处理的有效方法。 5.1.2中断的常见术语 (1) 中断源: 可以引起中断的事件或设备称为“中断源”。根据中断源不同,中断可分为3类: 由计算机本身的硬件异常引起的中断,称为内部异常中断; 由CPU执行中断指令引起的中断,称为软件中断或软中断; 由外部设备(输入设备/输出设备)请求引起的中断,称为硬件中断或外部中断。 (2) 中断请求、中断响应、中断服务、中断返回: 中断请求就是中断源对CPU发出处理中断的要求; 中断响应就是CPU转去执行中断服务程序; 中断服务就是CPU执行中断服务程序的过程; 中断返回就是CPU执行完中断服务程序后,返回响应中断时暂停的位置。一个完整的中断处理过程包含了中断请求、中断响应、中断服务和中断返回。 (3) 中断服务程序和中断向量: 处理中断的程序叫中断服务程序。中断服务程序的入口(起始)地址叫中断向量。 (4) 中断的优先级: 当多个中断源同时发出中断请求时,需要设置一个优先权等级以决定CPU响应中断请求(执行对应的中断服务程序)的先后顺序,这个优先权等级就是中断的优先级。 (5) 中断嵌套: 一个低中断优先级的中断在执行过程中,可以被高中断优先级的中断打断——CPU暂停正在执行的低优先级中断,转去执行高优先级的中断,高优先级中断执行完后,返回刚才暂停处继续执行低优先级中断,这个过程叫中断嵌套。 (6) 中断系统: 实现中断处理功能的软件、硬件系统。 5.2NVIC中断管理 CortexM3内核支持256个中断,其中包含了16个内核中断和240个外部中断,并且具有256级的可编程中断设置。STM32F103系列有60个可屏蔽中断(在STM32F107系列才有68个),这么多个中断是如何管理的呢? 5.2.1抢占优先级和响应优先级 STM32的中断优先级有两种: 一种是“抢占优先级”,另一种是“响应优先级”。优先级数值越小,优先级越高。抢占优先级具有“抢占”的属性,即“高抢占优先级”的中断可以打断“低抢占优先级”的中断。而响应优先级只有“响应”的属性,即这种优先级只影响哪个中断优先被响应。但要注意,由于抢占优先级可以打断其他中断,所以哪个中断先被响应是“抢占优先级”起决定作用,因为即使一个中断因为高的响应优先级而先被响应(CPU先去执行该中断的处理程序)了,也会因为低抢占优先级而被其他中断打断,实际还是先处理了高抢占优先级的中断。 例如,假定设置中断3(RTC中断)的抢占优先级为2,响应优先级为1。中断6(外部中断0)的抢占优先级为3,响应优先级为0。中断7(外部中断1)的抢占优先级为2,响应优先级为0。那么这3个中断的优先级顺序为: 中断7>中断3>中断6。 5.2.2中断优先级分组 中断分为5组,如表5.1所示。STM32使用中断优先级控制的寄存器组IP[240](全称是Interrupt Priority Register)设置中断的优先级,每个中断使用一个寄存器来确定优先级。STM32F103系列一共有60个可屏蔽中断,使用IP[59]~IP[0]。每个IP寄存器的高4位用来设置抢占和响应优先级的等级,低4位没有用到。那么在这高4位中,用多少位设置抢占优先级的等级,多少位用来设置响应优先级的等级?这是由“中断优先级分组”决定的。 表5.1中断优先级分组表 分组IP[7:4]分配情况 00位抢占优先级,4位响应优先级 11位抢占优先级,3位响应优先级 22位抢占优先级,2位响应优先级 33位抢占优先级,1位响应优先级 44位抢占优先级,0位响应优先级 当设置中断优先级分组为2组时,在IP寄存器中,抢占优先级和响应优先级都是2位来设置,因此,两个优先级等级范围都为0~3,那么编程设置时,两个优先级等级都不能超过3。 另外要注意,一旦中断优先级分组设置好了,也就意味着IP寄存器中设置抢占和响应优先级的位数分配好了,在程序中就不要再随意改动了。因为优先级分组一改,会造成程序中设置好的中断的优先级都跟着起变化,进而引起程序执行错误。 5.2.3NVIC中断管理相关函数 在misc.h中与NVIC相关的库函数声明的函数主要有两个: 优先级分组的函数NVIC_PriorityGroupConfig()和设置中断优先级的函数NVIC_Init()。示例代码如下: NVIC_InitTypeDefNVIC_InitStruct; NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); NVIC_InitStruct.NVIC_IRQChannel = TIM3_IRQn; NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE; NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 1; NVIC_InitStruct.NVIC_IRQChannelSubPriority = 2; NVIC_Init(&NVIC_InitStruct); 其中,中断优先级分组被设置为分组2,NVIC_InitStruct.NVIC_IRQChannel 的值是中断请求的名字,在stm32f10x.h文件中定义了所有中断请求的名字。  VIC_InitStruct.NVIC_IRQChannelPreemptionPriority指抢占优先级的等级。  VIC_InitStruct.NVIC_IRQChannelSubPriority指响应优先级的等级。 5.3EXTI外部中断 5.3.1中断请求信号的输入脚 STM32的每个GPIO引脚都可以复用为EXTI的外部中断请求信号输入脚。这里的复用是指STM32上电复位后引脚的功能不是EXTI输入,而是GPIO功能,但可以通过程序设置成EXTI输入功能。STM32的中断控制器支持19个外部中断/事件请求,其中0~15外部中断/事件请求对应外部I/O口的输入中断。16连接到PVD输出。17连接到RTC闹钟事件。18连接到USB唤醒事件。即STM32的外部中断线只有0~15共16根。那么I/O脚是如何和外部中断线对应起来的呢? 由图5.1可以看到,GPIO引脚和EXTI线的映射关系为: GPIOx.0映射到EXTI0,GPIOx.1映射到EXTI1,……,GPIOx.15映射到EXTI15。 图5.1GPIO脚和外部中断线的映射关系 5.3.2EXTI线对应的中断函数 I/O口外部中断在中断向量表中只分配了7个中断向量,也就是只能使用7个中断服务函数,如表5.2所示。 表5.2中断线与中断函数名对应表 EXTI线中断函数名 EXTI0EXTI0_IRQHandler EXTI1EXTI1_IRQHandler EXTI2EXTI2_IRQHandler EXTI3EXTI3_IRQHandler EXTI4EXTI4_IRQHandler EXTI9_5EXTI9_5_IRQHandler EXTI15_10EXTI15_10_IRQHandler 注意: 所有中断函数名都可以在startup_stm32f10x_md.s这样的启动文件中找到。 5.4EXTI的常用库函数 5.4.1函数EXTI_Init() 函数EXTI_Init()在stm32f10x_gpio.h文件中声明,具体描述如表5.3所示。 表5.3EXTI_Init()函数描述表 函数名EXTI_Init 函数原型void EXTI_ Init(EXTI_InitTypeDef*EXTI_InitStruct) 功能描述根据EXTI_InitStruct中指定的参数初始化外设EXTI寄存器 输入参数EXTI_InitStruct: 指向结构EXTI_InitTypeDef的指针,包含了外设EXTI的配置信息,参阅“Section: EXTI_InitTypeDef”,了解该参数允许的取值范围的更多内容 输出参数无 返回值无 先决条件无 被调用函数无 EXTI_InitTypeDef在文件stm32f10x_exti.h中定义: typedef struct { u32 EXTI_Line; EXTIMode_TypeDef EXTI_Mode; EXTIrigger_TypeDef EXTI_Trigger; FunctionalState EXTI_LineCmd; } EXTI_InitTypeDef; 参数EXTI_Mode设置了被使能线路的模式,参数值如表5.4所示。 表5.4EXTI_Mode取值 EXTI_Mode的值描述 EXTI_Mode_Event设置EXTI线路为事件请求 EXTI_Mode_Interrupt设置EXTI线路为中断请求 参数EXTI_Trigger设置了被使能线路的触发边沿,参数值如表5.5所示。 表5.5EXTI_Trigger取值 EXTI_Trigger的值描述 EXTI_Trigger_Falling设置输入线路下降沿为中断请求 EXTI_Trigger_Rising设置输入线路上升沿为中断请求 EXTI_Trigger_Rising_Falling设置输入线路上升沿和下降沿为中断请求 相关参数的定义实例代码如下: EXTI_InitStruct.EXTI_Line=EXTI_Line11|EXTI_Line13; EXTI_InitStruct.EXTI_LineCmd=ENABLE; EXTI_InitStruct.EXTI_Mode=EXTI_Mode_Interrupt; EXTI_InitStruct.EXTI_Trigger=EXTI_Trigger_Falling; EXTI_Init(&EXTI_InitStruct); 5.4.2函数GPIO_EXTILineConfig() 这个函数的声明在stm32f10x_gpio.h文件中,具体描述如表5.6所示。 表5.6GPIO_EXTILineConfig()函数描述表 函数名GPIO_EXTILineConfig 函数原型void GPIO_EXTILineConfig(u8 GPIO_PortSource,u8 GPIO_PinSource) 功能描述选择GPIO引脚用作外部中断线路 输入参数1GPIO_PortSource: 选择用作外部中断线源的GPIO端口 参阅“Section: GPIO_PortSource”,了解该参数允许的取值范围的更多内容 输入参数2EXTI_PinSource: 待设置的外部中断线路 该参数可以取GPIO_PinSourcex(x 可以是0~15) 输出参数无 返回值无 先决条件无 被调用函数无 函数调用实例代码如下: /* Selects PB.8 as EXTI Line 8 */ GPIO_EXTILineConfig(GPIO_PortSource_GPIOB, GPIO_PinSource8); 5.5EXTI的应用实例 5.5.1EXTI的初始化步骤 (1) 使能EXTI线所在的GPIO时钟和AFIO复用时钟。 (2) 初始化EXTI线所在的GPIO的输入输出模式。 (3) 将GPIO脚映射到对应的EXTI线上。 (4) 设置NVIC优先级分组,初始化NVIC。 (5) 初始化EXTI。 5.5.2EXTI应用实例 【例5.1】实现图5.2所示按键控制LED灯发光花样。 图5.2例5.1程序对应电路图 #include "stm32f10x.h" #include "exti.h" #include "led.h" #define bitband(A,n) *((volatile u32 *)((A&0xf0000000)+0x2000000+((((A&0xfffff)<<3)+n)<<2))) #define GPIOB_ODR_Addr (GPIOB_BASE+0x0C) #define PBout(n) bitband(GPIOB_ODR_Addr,n) void exti_init(void) { GPIO_InitTypeDef GPIO_InitStruct; EXTI_InitTypeDef EXTI_InitStruct; NVIC_InitTypeDef NVIC_InitStruct; RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO|RCC_APB2Periph_GPIOB |RCC_APB2Periph_GPIOC,ENABLE); GPIO_InitStruct.GPIO_Mode=GPIO_Mode_IPU; GPIO_InitStruct.GPIO_Pin=GPIO_Pin_0|GPIO_Pin_1; GPIO_Init(GPIOB,&GPIO_InitStruct); GPIO_InitStruct.GPIO_Mode=GPIO_Mode_IPU; GPIO_InitStruct.GPIO_Pin=GPIO_Pin_13; GPIO_Init(GPIOC,&GPIO_InitStruct); //GPIO_EXTILineConfig函数声明在stm32f10x_gpio.h中 GPIO_EXTILineConfig(GPIO_PortSourceGPIOB,GPIO_PinSource0); GPIO_EXTILineConfig(GPIO_PortSourceGPIOB,GPIO_PinSource1); GPIO_EXTILineConfig(GPIO_PortSourceGPIOC,GPIO_PinSource13); NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //NVIC相关函数声明在misc.h中 NVIC_InitStruct.NVIC_IRQChannel=EXTI0_IRQn; //中断请求名在stm32f10x.h中 NVIC_InitStruct.NVIC_IRQChannelCmd=ENABLE; NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority=0; NVIC_InitStruct.NVIC_IRQChannelSubPriority=2; NVIC_Init(&NVIC_InitStruct); NVIC_InitStruct.NVIC_IRQChannel=EXTI1_IRQn; NVIC_InitStruct.NVIC_IRQChannelCmd=ENABLE; NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority=1; //抢占优先级 NVIC_InitStruct.NVIC_IRQChannelSubPriority=2; //响应优先级 NVIC_Init(&NVIC_InitStruct); NVIC_InitStruct.NVIC_IRQChannel=EXTI15_10_IRQn; NVIC_InitStruct.NVIC_IRQChannelCmd=ENABLE; NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority=2; NVIC_InitStruct.NVIC_IRQChannelSubPriority=2; NVIC_Init(&NVIC_InitStruct); EXTI_InitStruct.EXTI_Line=EXTI_Line0|EXTI_Line1; EXTI_InitStruct.EXTI_LineCmd=ENABLE; EXTI_InitStruct.EXTI_Mode=EXTI_Mode_Interrupt; EXTI_InitStruct.EXTI_Trigger=EXTI_Trigger_Falling; EXTI_Init(&EXTI_InitStruct); EXTI_InitStruct.EXTI_Line=EXTI_Line13; EXTI_InitStruct.EXTI_LineCmd=ENABLE; EXTI_InitStruct.EXTI_Mode=EXTI_Mode_Interrupt; EXTI_InitStruct.EXTI_Trigger=EXTI_Trigger_Falling; EXTI_Init(&EXTI_InitStruct); } void EXTI0_IRQHandler(void) { if(EXTI_GetITStatus(EXTI_Line0)==SET) water_led_register(); EXTI_ClearITPendingBit(EXTI_Line0); } void EXTI1_IRQHandler(void) { if(EXTI_GetITStatus(EXTI_Line1)==SET) blink_register(); EXTI_ClearITPendingBit(EXTI_Line1); } void EXTI15_10_IRQHandler(void) { if(EXTI_GetITStatus(EXTI_Line13)==SET) centerflower_bitband(); EXTI_ClearITPendingBit(EXTI_Line13); } void Delay(u32 count) { u32 i = 0; for(; i < count; i++); } /********************************************************************* GPIO初始化函数步骤: 1.使能PB时钟、AFIO时钟 2.失能JTAG/SWD下载功能以恢复PB3,PB4的IO功能 3.初始化PB3,PB4,PB5为推挽输出 *********************************************************************/ void led_init_register(void) { RCC->APB2ENR |= (0x01|(0x01<<3)); AFIO->MAPR = (0x4<<24); GPIOB->CRL |= (0x01<<12)|(0x01<<16)|(0x01<<20); } void led_init_libfunc(void) { GPIO_InitTypeDef GPIO_InitStruct; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB|RCC_APB2Periph_AFIO,ENABLE); GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable,ENABLE); GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_InitStruct.GPIO_Pin = GPIO_Pin_3|GPIO_Pin_4|GPIO_Pin_5; GPIO_InitStruct.GPIO_Speed = GPIO_Speed_10MHz; GPIO_Init(GPIOB,&GPIO_InitStruct); } void water_led_register(void) { char i=4; //使用寄存器控制GPIOB输出 while(i!=0) { GPIOB->ODR = 0xffff; GPIOB->ODR &= ~(0x01<<3); Delay(3000000); GPIOB->ODR = 0xffff; GPIOB->ODR &= ~(0x01<<4); Delay(3000000); GPIOB->ODR = 0xffff; GPIOB->ODR &= ~(0x01<<5); Delay(3000000); i--; } } void blink_register(void) { char i=4; while(i!=0) { GPIOB->BRR = (0x01<<3)|(0x01<<4)|(0x01<<5); Delay(3000000); GPIOB->BSRR = (0x01<<3)|(0x01<<4)|(0x01<<5); Delay(3000000); GPIOB->BSRR = ((0x01<<3)|(0x01<<4)|(0x01<<5))<<16; Delay(3000000); GPIOB->BSRR = (0x01<<3)|(0x01<<4)|(0x01<<5); Delay(3000000); i--; } } void blink2water_libfunc(void) { char i=4; //使用库函数控制GPIOB输出 while(i!=0) { GPIO_SetBits(GPIOB,GPIO_Pin_3|GPIO_Pin_4|GPIO_Pin_5); Delay(3000000); GPIO_ResetBits(GPIOB,GPIO_Pin_3); Delay(3000000); GPIO_SetBits(GPIOB,GPIO_Pin_3|GPIO_Pin_4|GPIO_Pin_5); Delay(3000000); GPIO_ResetBits(GPIOB,GPIO_Pin_3); Delay(3000000); GPIO_WriteBit(GPIOB,GPIO_Pin_3|GPIO_Pin_4|GPIO_Pin_5,Bit_SET); Delay(3000000); GPIO_WriteBit(GPIOB,GPIO_Pin_4,Bit_RESET); Delay(3000000); GPIO_WriteBit(GPIOB,GPIO_Pin_3|GPIO_Pin_4|GPIO_Pin_5,Bit_SET); Delay(3000000); GPIO_WriteBit(GPIOB,GPIO_Pin_4,Bit_RESET); Delay(3000000); GPIO_Write(GPIOB,0xffff); Delay(3000000); GPIO_Write(GPIOB,~(0x01<<5)); Delay(3000000); GPIO_Write(GPIOB,0xffff); Delay(3000000); GPIO_Write(GPIOB,~(0x01<<5)); Delay(3000000); i--; } } void centerflower_bitband(void) { char i=4; //使用位带操作控制GPIOB输出 while(i!=0) { PBout(3)=1; PBout(4)=1; PBout(5)=1; Delay(3000000); PBout(3)=1; PBout(4)=0; PBout(5)=1; Delay(3000000); PBout(3)=0; PBout(4)=1; PBout(5)=0; Delay(3000000); i--; } } int main(void) { led_init_libfunc(); exti_init(); while(1) blink2water_libfunc(); }