18.1 中断基本概念
中断:中断系统是微控制器中用于实时响应和处理突发事件的重要机制。 中断是一个信号,表示一个事件需要立即被处理。这个信号可以由硬件(外部中断)或软件(内部中断)生成。在STM32微控制器中,中断可以有多种触发源,包括外部引脚(如用户按下按钮)、内部源(如定时器溢出或数据接收完成)等。
当中断发生时,如果它被使能(即允许中断),外部设备或内部模块(如计时器)会打断CPU的当前执行任务,然后强制CPU转而处理一个特定的任务(即中断服务程序,ISR)。中断服务完成后,CPU会从中断返回,恢复之前的状态,并继续执行之前被打断的任务。
中断优先级:当有多个中断源同时申请中断时,微控制器会根据中断源的优先级进行裁决,优先响应更加紧急的中断源 。在STM32中,中断优先级管理由NVIC负责。每个中断源都可以配置一个优先级,数字越低,优先级越高。
中断嵌套:当一个中断程序正在运行时,又有新的更高优先级的中断源申请中断,CPU再次暂停当前中断程序,转而去处理新的中断程序,处理完成后依次返回上一级低优先级的中断服务程序。
中断服务程序(Interrupt Service Routine,ISR):中断服务程序是响应中断信号而执行的函数。这些函数是预先定义的,目的是对特定的中断事件做出快速反应。
上述几个概念示意图,如图18.1.
图18.1-1 中断概念示意图
18.2 中断系统基本结构
图18.2-1 STM32中断系统结构
STM32中断系统主要包括中断向量表、NVIC(嵌套向量中断控制器)和EXTI(外部中断/事件控制器)等组件。
中断向量表:
中断向量表是一个存储在固定内存位置的表,其中包含了所有中断和异常处理程序的入口地址。
当特定的中断被触发时,处理器使用这个表来确定应该跳转到哪个地址来执行相应的中断服务程序(ISR)。
NVIC(Nested Vectored Interrupt Controller):
NVIC是ARM Cortex-M内核的一部分,负责管理所有中断。
它允许每个中断源都有自己的优先级设置,以及嵌套中断,即一个高优先级的中断可以打断正在运行的低优先级中断服务程序。
NVIC支持每个中断源的优先级分为16个或更多的优先级,这些优先级中数值较小的优先级更高。
NVIC还包含中断使能寄存器、中断清除寄存器、状态和控制寄存器等,用于控制中断的使能、禁用和状态监测。
EXTI(External Interrupt/Event Controller):
EXTI是用于处理从GPIO引脚或其他一些内置外设(如PVD、RTC和USB唤醒事件)传入的外部信号的中断系统。
它可以被配置为响应特定的信号边沿(上升沿、下降沿或者两者)来触发中断。
EXTI包含多个中断线路,每个线路对应于一个或多个GPIO引脚,这些线路可以单独配置来响应特定的事件。
18.3 NVIC 简介
18.3.1 功能介绍
NVIC(Nested Vectored Interrupt Controller,嵌套向量中断控制器)是STM32微控制器中负责中断和异常管理的重要组件。它实现了对中断请求的接收、排序、处理和响应,是STM32实现多任务处理和实时响应能力的关键。以下是NVIC的基本功能介绍:
中断管理:NVIC能够接收来自外设、软件或异常产生的中断请求,并根据配置的优先级对这些请求进行排序和处理。
嵌套中断:支持中断嵌套,即高优先级的中断可以打断正在执行的低优先级中断,实现中断的抢占式处理。
向量中断:中断服务程序的入口地址存储在中断向量表中,NVIC通过中断向量表快速定位并跳转到相应的中断服务程序。
优先级配置:允许为每个中断源配置不同的优先级,包括抢占优先级和子优先级(也称为响应优先级),以决定中断的响应顺序和嵌套关系。
NVIC是内核的器件,内核有关的资料《Cortex-M3权威指南》中有更为详细的描述。Cortex-M3内核都支持256个中断,其中包含了16个系统中断和240个外部中断,并且具有256级的可编程中断设置。芯片厂商一般不会把内核的这些资源全部用完,如STM32F103C8T6的系统中断有10个,外部中断有60个。STM32F103C8T6的中断向量表如下图所示,其实灰色部分为系统中断,其余为外部中断。
图18.3-1 STM32F10xxx产品(小容量、中容量和大容量)的向量表
在标准库文件stm32f10x.h 这个头文件中,IRQn_Type 这个结构体包含了F103 系列全部的中断声明。 在这个结构体中,根据不同的产品类型,有不同的声明,我们以我们使用的F103C8T6系列(MD)为例,摘取如下:
NonMaskableInt_IRQn = -14, MemoryManagement_IRQn = -12, USB_HP_CAN1_TX_IRQn = 19, USB_LP_CAN1_RX0_IRQn = 20,
|
18.3.2 NVIC 寄存器
NVIC相关的寄存器定义了可以在core_cm3.h 文件中找到。代码如下:
上序代码可以看出,NVIC 的结构体定义给每个寄存器都预留了很多位,也许是为了日后扩展功能。具体芯片用了多少,还是要参考对应参考《Cortex-M3 内核编程手册》。STM32F103的中断在这些寄存器的控制下有序的执行。下面介绍这几个寄存器:
ISER[8]:全称Interrupt Set-Enable Registers,中断使能寄存器,用于使能中断。向该寄存器的某位写“1”可以使能对应的中断。CM3内核支持256个中断,这里用8个32位寄存器来控制,每个位控制一个中断。但是STM32F103的可屏蔽中断最多只有60个,所以对我们来说,有用的就是两个(ISER[0]和ISER[1]),总共可以表示64个中断。而STM32F103只用了其中的60个。ISER[0]的bit0~31分别对应中断0~31;ISER[1]的bit0~27对应中断32~59,这样总共60个中断就可以分别对应上了。要使能某个中断,必须设置相应的ISER位为1,使该中断被使能(这里仅仅是使能,还要配合中断分组、屏蔽、IO口映射等设置才算是一个完整的中断设置)。
ICER[8]:全称Interrupt Clear Enable Registers,是一个中断除能寄存器组。中断失能寄存器,用于禁止中断。向该寄存器的某位写“1”可以禁止对应的中断。该寄存器组与ISER的作用恰好相反,是用来清除某个中断的使能的。其对应位的功能,也和ICER一样。这里要专门设置一个ICER来清除中断位,而不是向ISER写0来清除,是因为NVIC的这些寄存器都是写1有效的,写0是无效的。
ISPR[8]:全称Interrupt Set Pending Registers,是一个中断使能挂起控制寄存器组。每个位对应的中断和ISER是一样的。通过置1,可以将正在进行的中断挂起,而执行同级或更高级别的中断。写0无效。
ICPR[8]:全称Interrupt Clear Pending Registers,是一个中断解挂控制寄存器组。中断解挂寄存器,用于解除中断的挂起状态。向该寄存器的某位写“1”可以解除对应中断的挂起状态。其作用与ISPR相反,对应位也和ISER是一样的。通过设置1,可以将挂起的中断解挂。写0无效。
IABR[8]:全称Interrupt Active Bit Registers,是一个中断激活标志位寄存器组。中断激活状态位寄存器,只读寄存器,用于指示中断是否正在被处理。对应位所代表的中断和ISER一样,如果为1,则表示该位所对应的中断正在被执行。这是一个只读寄存器,通过它可以知道当前在执行的中断是哪一个。在中断执行完了由硬件自动清零。
IP[240]:全称Interrupt Priority Registers,是一个中断优先级控制的寄存器组。中断优先级寄存器,用于设置中断的优先级。每个中断都有一个对应的优先级寄存器。STM32F103的中断分组与这个寄存器组密切相关。IP寄存器组由240个8bit寄存器组成,每个可屏蔽中断占用8bit,这样总共可以表示240个可屏蔽中断。而STM32F103只用到了60个。IP[59]~IP[0]分别对应中断59~0。而且每个可屏蔽中断占用的8bit也并没有全部使用,而是只用了高4位。这4位,又分为抢占优先级和子优先级。抢占优先级在前,子优先级在后。而这两个优先级各占几个位又要根据SCB->AIRCR中的中断分组设置来决定。关于中断优先级控制的寄存器组我们下面再讲。
在配置中断的时候我们一般只用ISER、ICER 和IP 这三个寄存器。
18.3.3 中断优先级
STM32中的中断优先级可以分为:抢占式优先级和响应优先级,响应优先级也称子优先级,每个中断源都需要被指定这两种优先级。如果有多个中断同时响应,抢占优先级高的就会抢占抢占优先级低的优先得到执行,如果抢占优先级相同,就比较子优先级。如果两个或者多个中断的抢占式优先级和响应优先级都相同时,那么就遵循自然优先级,看中断向量表的中断排序,数值越小,优先级越高。
如前面所述,在NVIC有专门的寄存器“中断优先级寄存器NVIC_IPRx”(数组为240个,实际只用了60个,即NVIC_IPR0-NVIC_IPR59共60个寄存器),用来配置外部中断的优先级,IPR宽度为8bit,原则上每个外部中断可配置的优先级为0~255,数值越小,优先级越高。但是绝大多数CM3 芯片都会精简设计,只使用了高四位[7:4],低四位取零,这样最多只有16级中断嵌套,即2^4=16。
NVIC的中断优先级由优先级寄存器的高4位决定,并对这4位可以进行了分组,分为高n位的抢占优先级和低4-n位的响应优先级。优先级的分组由内核外设SCB 的应用程序中断及复位控制寄存器AIRCR 的PRIGROUP[10:8] 位决定,F103 分为了5 组。如下图所示。通过该图我们可以清楚的看到组0~4对应的配置关系。例如优先级分组设置为3,那么STM32所有的60个中断,每个中断的中断优先寄存器的高四位中的最高3位是抢占优先级,低1位是响应优先级。每个中断,你可以设置抢占优先级为0~7,响应优先级为1或0。抢占优先级的级别高于响应优先级,数值越小所代表的优先级就越高。
图18.3-2 AIRCR 中断分组设置
18.3.4 NVIC编程步骤
NVIC中断编程的思路步骤大致如下:
Step1:选择优先级分组
设置优先级分组可调用库函数NVIC_PriorityGroupConfig (uint32_t NVIC_PriorityGroup)实现,有关NVIC中断相关的库函数都在库文件misc.c和misc.h中。 参数NVIC_PriorityGroup可以选择NVIC_PriorityGroup_0~NVIC_PriorityGroup_4,分别对应图18.3-2中的5个分组。
需要注意NVIC_PriorityGroupConfig是整个程序中只需要设置一次。当设置好了中断优先级分组后,其他各种外设对应的中断向量的中断优先级即基于目前设置分组进行设置。假设配置为NVIC_PriorityGroup_0或者NVIC_PriorityGroup_4,那么给多个外设分别设置NVIC_InitStructure的响应优先级或抢占优先级是无效的。所以如果工程里面有用到许多的外设中断,那么在确定了优先级分组后再给每个外设对应的中断向量配置优先级。如果例程中多个外设配置函数下重复使用NVIC_PriorityGroupConfig()进行配置,那么真正起作用的是最后一次赋值。所以建议可以对优先级分组函数NVIC_PriorityGroupConfig进行统一管理,比如可以放在主函数main中。
Step2:初始化NVIC_InitTypeDef结构体
这一步要实现3个动作,分别是选择中断源,配置抢占优先级和响应优先级,并使能中断。NVIC_InitTypeDef 结构体在固件库头文件misc.h中定义。如下代码所示:
uint8_t NVIC_IRQChannelPreemptionPriority; uint8_t NVIC_IRQChannelSubPriority; FunctionalState NVIC_IRQChannelCmd;
|
结构体成员解释如下:
NVIC_IROChannel:用来设置中断源,不同的中断中断源不一样。注意这里不要写错,写错了程序也不会报错,只会导致不响应中断。具体的成员配置可参考IRQn_Type结构体定义,即前面《18.3.1 功能介绍》中的IRQn_Type代码。
NVIC_IRQChannelPreemptionPriority:抢占优先级,具体的值要根据优先级分组来确定,具体参考前面图18.3-2中提供的取值范围。
NVIC_IRQChannelSubPriority:响应(子)优先级,具体的值要根据优先级分组来确定,具体参考前面图18.3-2中提供的取值范围。
对抢占优先级和响应优先级的取值,库代码里也有明确的提示,和我们前面描述是一样,截图如下,供参考。
图18.3-3 优先级设置库文件提示
NVIC_IRQChannelCmd:中断使能(ENABLE)或者失能(DISABLE)。操作的是NVIC_ISER和NVIC_ICER 这两个寄存器。
Step3: 编写中断服务函数
在启动文件startup_stm32f10x_hd.s中已预先为每个中断都写了一个中断服务函数,只是这些中断函数都为空,目的只是为初始化中断向量表。实际的中断服务函数都要我们重新编写,为了方便管理我们一般把中断服务函数统一写在stm32f10x_it.c这个库文件中。且中断服务函数的函数名必须跟启动文件里面预先设置的一样,如果写错,系统就在中断向量表中找不到中断服务函数的入口,会直接跳转到启动文件里面预先执行写好的空函数,并且在里面无限循环,实现不了中断。
在讲完下节EXTI后,我们会编写一个简单的中断程序,结合这个程序,我们理解起来将会更加容易。
18.4 EXTI简介
EXTI(英文External interrupt/Event controller)—外部中断/事件控制器,管理了控制器的20个中断/事件线。每个中断/事件线都对应一个边沿检测器,可以实现输入信号的上升沿或下降沿的检测。检测到中断或事件之后,EXTI可以实现对每个中断/事件线的单独配置,可以单独配置为中断或者事件。
18.4.1 EXTI逻辑框图
图18.4-1 EXTI逻辑框图
如上图为EXTI的逻辑功能框图。在图中可以看到很多信号线上打一个斜杠并标注“20”字样,这表示在控制器内部类似的信号线路有20个,我们只要明白其中一个的原理即可,所有的线路原理都是一样的。
从EXTI功能框图可以看到有两条主线,一条是由输入线到NVIC中断控制器,一条是由输入线到脉冲发生器。这正好就是EXTI的两大功能,即产生中断与产生事件。下面根据标号顺序进行讲解,图示中也已经根据颜色进行了区分。
中断线路:我们首先看一下EXTI功能框图产生中断的线路,最终信号是流入NVIC控制器中。即上图中红色加蓝色组成的线路(①-②-③-④-⑤)。
编号①是输入线,是线路的信息输入端,它可以通过配置寄存器设置为任何一个GPIO口,或者是一些外设的事件。输入线一般都是存在电平变化的信号。
编号②是一个边沿检测电路,它会根据上升沿触发选择寄存器(EXTI_RTSR)和下降沿触发选择寄存器(EXTI_FTSR)对应位的设置来控制信号触发。边沿检测电路以输入线作为信号输入端,如果检测到有边沿跳变就输出有效信号1给编号③电路,否则输出无效信号0。而EXTI_RTSR和EXTI_FTSR两个寄存器可以控制需要检测哪些类型的电平跳变过程,可以是只有上升沿触发、只有下降沿触发或者上升沿和下降沿都触发。
编号③是一个或门电路,它的一个输入来自编号②电路,另一个输入来自软件中断事件寄存器(EXTI_SWIER),我们可以通过对EXTI_SWIER的读写操作就可以启动中断/事件线。或门是有1就输出为1,这两个输入随便一个是有效信号1就可以输出1给编号④和编号⑥电路。
编号④电路是一个与门电路,它的一个输入是编号③电路,另外一个输入来自中断屏蔽寄存器(EXTI_IMR)。与门电路要求输入都为1才输出1,所以如果EXTI_IMR设置为0,那么不管编号③电路的输出信号是1还是0,最终编号④电路输出的信号都为0;如果EXTI_IMR设置为1时,最终编号④电路输出的信号才则由编号③电路的输出信号决定,这样我们就可以通过控制EXTI_IMR来实现是否产生中断的目的。编号④电路输出的信号会被保存到挂起寄存器(EXTI_PR)内,如果编号④电路输出为1就会把EXTI_PR对应位置1。
编号⑤是将请求挂起寄存器(EXTI_PR)的内容输出到NVIC内,从而实现系统中断事件控制。
事件线路:下面我们看一下EXTI功能框图产生事件的线路最终输出一个脉冲信号。 即上图中红色加绿色组成的线路(①-②-③-⑥-⑦-⑧)。
编号⑥电路是一个与门电路,它的一个输入来自编号③电路,另外一个输入来自事件屏蔽寄存器(EXTI_EMR)。如果EXTI_EMR设置为0,那么不管编号③电路的输出信号是1还是0,最终编号⑥电路输出的信号都为0;如果EXTI_EMR设置为1,最终编号⑥电路输出的信号则由编号③电路的输出信号决定,这样我们可以通过控制EXTI_EMR来实现是否产生事件的目的。
编号⑦是一个脉冲发生器电路,当它的输入端(编号⑥电路的输出端)输入一个有效信号1时就会产生一个脉冲,如果输入端是无效信号就不产生输出脉冲。
编号⑧是一个脉冲信号,即产生事件的线路的最终产物,这个脉冲信号可以输出给其他外设电路使用,比如定时器TIM、模拟数字转换器ADC等等,这样的脉冲信号一般用来触发TIM或者ADC开始转换。
中断线路和事件线路的区别: 产生中断线路的目的是把输入信号输入到NVIC,然后运行中断服务函数,实现一个功能,属于软件级的。而产生事件线路的目的是传输一个脉冲信号给其他外设使用,是电路级别的信号传输,属于硬件级的功能。
18.4.2 中断/事件线
EXTI可配置20个中断/事件线,每个GPIO都可以被设置为输入线,占用EXTI0至EXTI15,还有4根用于特定的外设事件,详见下图。
图18.4-2 EXTI 中断/事件线
从上图可见,STM32F1供给IO口使用的中断线只有16个,但是STM32F1的IO口却远远不止16个,所以STM32把GPIO管脚GPIOx.0~GPIOx.15(x=A,B,C,D,E,F,G,H,I不同型号管脚数不一样,原理是一样的)分别对应中断线0~15。这样子每个中断线对应了多个IO口,以线0为例:它对应了GPIOA.0、GPIOB.0、GPIOC.0、GPIOD.0、GPIOE.0、GPIOF.0、GPIOG.0等。而中断线每次只能连接到1个IO口上,这样就需要通过配置决定对应的中断线配置到哪个GPIO上。
GPIO 和中断线映射关系是在寄存器 AFIO_EXTICR1 ~ AFIO_EXTICR4 中配置的。 如下图示意:
图18.4-3 外部中断通用I/O映像
图18.4-4 AFIO_EXTICR1 寄存器说明
AFIO_EXTICR1寄存器配置EXTI0到EXTI3线,包含的外部中断的引脚包括PAx到PGx,x=0到3。AFIO_EXTICR2寄存器配置EXTI4到EXTI7线,包含的外部中断的引脚包括PAx到PGx,x=4到7,AFIO_EXTICR2寄存器请打开参考手册查看,原理和AFIO_EXTICR1一样。AFIO_EX-TICR3和AFIO_EXTICR4以此类推。
同时需要注意,通过AFIO_EXTICRx配置GPIO线上的外部中断/事件,必须先使能AFIO时钟。
按教程中断/事件线与输入源的图18.4-2看,可以使用EXTI0至15线路对应到使用16个GPIO做外部中断,需要注意的是这16个线路并非都有单独的中断源,从stm32f10x.h查看对应芯片型号有EXTI0_IRQn、EXTI1_IRQn、EXTI2_IRQn、EXTI3_IRQn、EXTI4_IRQn、EXTI9_5_IRQn、EXTI15_10_IRQn,系统的中断函数同样也是EXTI0_IRQHandler、EXTI1_IRQHandler、EXTI2_IRQHandler、EXTI3_IRQHandler、EXTI4_IRQHandler、EXTI9_5_IRQHandler、EXTI15_10_IRQHandler。除了前面4个线路有单独的中断函数,后面5至9和10至15线路是复用的。那么如何区分到底是哪个线路的中断呢?可以结合后面的EXTI结构体内容来理解,EXTI_Lines在寄存器中都是一一对应状态标位,中断函数复用,因此在EXTI9_5_IRQHandler和EXTI15_10_IRQHandler的中断函数里面使用多次EXTI_GetITStatus函数可以判断出具体线路。
18.4.3 EXTI编程要点
18.4-5 EXTI配置示意图
我们进行EXTI编程的时候,可参考上图的示意图,编程流程也基本如此。即:
开启时钟:配置RCC(复位和时钟控制)以开启所涉及的外设时钟。
配置GPIO:选择GPIO端口为输入模式,并设置相应的引脚。
配置AFIO:选择中断引脚或进行引脚重映射。
配置EXTI:选择中断/事件线、触发方式等,并使能EXTI。
配置NVIC:设置中断优先级分组,并为EXTI中断分配优先级和使能中断。
编写中断服务函数:
通过以上配置,EXTI系统就可以根据外部信号的变化产生中断或事件,从而实现对外部事件的快速响应和处理。
18.4.4 EXTI 初始化结构体详解
标准库函数对每个外设都建立了一个初始化结构体,结构体成员用于设置外设工作参数,并由外设初始化配置函数调用,这些设定参数将会设置外设相应的寄存器,达到配置外设工作环境的目的。EXTI对应的初始化结构体是EXTI_InitTypeDef,对应的初始化函数为EXTI_Init()。初始化结构体和初始化库函数配合使用是标准库精髓所在,理解了初始化结构体每个成员的意义,可以帮助我们加深对该外设运用。初始化结构体定义在stm32f10x_exti.h文件中,初始化库函数定义在stm32f10x_exti.c文件中,编程时我们可以参考这两个文件内的注释使用。 初始化结构体的标准库函数代码如下:
EXTIMode_TypeDef EXTI_Mode; EXTITrigger_TypeDef EXTI_Trigger; FunctionalState EXTI_LineCmd;
|
①EXTI_Line:EXTI中断/事件线选择,可选EXTI0至EXTI19,可参考表EXTI中断_事件线选择。
②EXTI_Mode:EXTI模式选择,可选产生中断(EXTI_Mode_Interrupt)或产生事件(EXTI_Mode_Event)。
③EXTI_Trigger:EXTI边沿触发事件,可选上升沿触发(EXTI_Trigger_Rising)、下降沿触发(EXTI_Trigger_Falling)或者上升沿和下降沿都触发(EXTI_Trigger_Rising_Falling)。
④EXTI_LineCmd:控制是否使能EXTI线,可选使能EXTI线(ENABLE)或禁用(DISABLE)。
18.5 外部中断实验
现在我们综合前面所学,做一个综合实验。也没必要设计太复杂的实验,因为原理都是一样的。我们可直接使用前面《第15章.GPIO输入-按键检测》按键检测的那个例程即可,即按键作为外部输入,检测到按键后翻转LED灯。简单的连接示意图如下:
图18.5-1 EXTI按键中断接线图
18.5.1 LED相关程序设计
LED灯部分这里就不讲了,直接全部copy 《第15章.GPIO输入-按键检测》中的LED.c和LED.h.
18.5.2 按键相关程序设计
按键相关我们分别生成EXTI.h和EXTI.c两个文件,EXTI.h主要进行按键相关的硬件配置,EXTI.c进行按键相关的NVIC和EXTI配置,分别介绍如下:
18.5.2.1 按键硬件配置程序
#define KEY_INT_GPIO_PORT GPIOA #define KEY_INT_GPIO_CLK (RCC_APB2Periph_GPIOA|RCC_APB2Periph_AFIO) #define KEY_INT_GPIO_PIN GPIO_Pin_0 #define KEY_INT_EXTI_PORTSOURCE GPIO_PortSourceGPIOA #define KEY_INT_EXTI_PINSOURCE GPIO_PinSource0 #define KEY_INT_EXTI_LINE EXTI_Line0 #define KEY_INT_EXTI_IRQ EXTI0_IRQn #define KEY_IRQHandler EXTI0_IRQHandler void EXTI_Key_Config(void);
|
前面已经多次提过,使用宏定义指定与硬件电路设计相关配置,对于程序移植或升级非常方便。在上面的宏定义中,我们指定了按键对应的硬件资源,我们除了开启GPIO的端口时钟外,还开启了AFIO的时钟,这是因为后面我们配置EXTI信号源的时候需要用到AFIO的外部中断控制寄存器AFIO_EXTICRx。
18.5.2.2 NVIC 配置
static void NVIC_Configuration(void) NVIC_InitTypeDef NVIC_InitStructure; NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1); NVIC_InitStructure.NVIC_IRQChannel = KEY_INT_EXTI_IRQ; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure);
|
18.5.2.3 EXTI配置
void EXTI_Key_Config(void) GPIO_InitTypeDef GPIO_InitStructure; EXTI_InitTypeDef EXTI_InitStructure; RCC_APB2PeriphClockCmd(KEY_INT_GPIO_CLK,ENABLE); GPIO_InitStructure.GPIO_Pin = KEY_INT_GPIO_PIN; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; GPIO_Init(KEY_INT_GPIO_PORT, &GPIO_InitStructure); GPIO_EXTILineConfig(KEY_INT_EXTI_PORTSOURCE, KEY_INT_EXTI_PINSOURCE); EXTI_InitStructure.EXTI_Line = KEY_INT_EXTI_LINE; EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt; EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising; EXTI_InitStructure.EXTI_LineCmd = ENABLE; EXTI_Init(&EXTI_InitStructure);
|
首先,GPIO_InitTypeDef和EXTI_InitTypeDef结构体定义两个用于GPIO和EXTI初始化配置的变量,关于这两个结构体前面都已经讲解过,不再赘述。使用GPIO之前必须开启GPIO端口的时钟,由于用到了EXTI还要开启AFIO时钟。调用NVIC_Configuration函数完成对按键优先级配置并使能中断通道。
作为中断/事件输入线时需把GPIO配置为输入模式,我们这里方便起见就没有设计外部电路,直接设置为上拉输入。
GPIO_EXTILineConfig函数用来指定中断/事件线的输入源,实际上就是设定外部中断配置寄存器的AFIO_EXTICRx值,该函数有两个参数,第一个参数指定GPIO端口源(A,B,C,D,E….),第二个参数为选择对应GPIO引脚编号(0~15)。我们实验是为产生中断并执行中断服务函数,因此EXTI选择中断模式,按键选择上升沿触发,并使能EXTI线。
18.5.3 中断服务函数
void KEY_IRQHandler(void) if(EXTI_GetITStatus(KEY_INT_EXTI_LINE) != RESET) EXTI_ClearITPendingBit(KEY_INT_EXTI_LINE);
|
图18.5-2 中断服务函数
如上图18.5-2所示,我们中断服务函数写在了系统中断文件stm32f10x_it.c中。为确保中断确实发生,我们一般会在中断服务函数中调用中断标志位状态读取函数读取外设中断标志位来判断标志位状态。EXTI_GetITStatus函数用来获取EXTI的中断标志位状态,如果EXTI线有中断发生函数返回“SET”否则返回“RESET”。EXTI_GetITStatus函数是通过读取EXTI_PR寄存器值来判断EXTI线中断状态的。按键的中断服务函数就是让LED翻转状态。执行任务后需要调用EXTI_ClearITPendingBit函数清除EXTI的中断标志位。
18.5.4 主函数
主函数很简单,LED_Init()函数定义在led.c文件内,完成LED对应GPIO口的初始化配置。EXTI_Key_Config函数完成按键的GPIO和EXTI配置。
例程文件已上传
|