中断通常把CPU内部产生的紧急事件叫做异常,比如非法指令(除零)、地址访问越界等;把来自CPU外部的片上外设产生的紧急事件叫做中断,比如GPIO引脚电平变化、定时器溢出等。
异常和中断的效果基本一致,都是暂停当前任务,优先执行紧急事件。因此一般将中断和异常统称为中断。
在学习8086微处理器的中断系统时,就有专门的中断控制器芯片8259A,用于管理中断。
同样的,STM32的中断如此之多,配置起来并不容易,因此我们需要一个强大而方便的中断控制器NVIC (Nested Vectored Interrupt Controller)。NVIC是属于Cortex内核的器件,不可屏蔽中断(NMI)和外部中断都由它来处理,而SYSTICK不是由NVIC来控制的。
NVIC is a dedicated hardware unit inside the Cortex-M based microcontrollers that is responsible of the exceptions handling.
NVIC 是基于 Cortex-M 的微控制器内部的专用硬件单元,负责异常处理。
Figure 1 shows the relation between the NVIC unit, the Processor Core and peripherals. Here we have to distinguish two types of peripherals: those external to the Cortex-M core, but internal to the STM32 MCU (e.g. timers, UARTS, and so on), and those peripherals external to the MCU at all.
图 1 显示了 NVIC 单元、处理器内核和外围设备之间的关系。 在这里,我们必须区分两种类型的外设:那些在 Cortex-M 内核外部但在 STM32 MCU 内部(例如定时器、UARTS 等),以及那些完全在 MCU 外部的外设。
The source of the interrupts coming from the last kind of peripherals are the MCU I/O, which can be both configured as general purpose I/O (e.g. a tactile switch connected to a pin configured as input) or to drive an external advanced peripheral (e.g. I/Os configured to exchange data with an ethernet phyther through the RMII interface). A dedicated programmable controller, named External Interrupt/Event Controller (EXTI), is responsible of the interconnection between the external I/O signals and the NVIC controller, as we will see next.
来自最后一种外设的中断源是 MCU I/O,它既可以配置为通用 I/O(例如,连接到配置为输入的引脚的触觉开关),也可以驱动外部高级外设 (例如,配置为通过 RMII 接口与以太网 phyther 交换数据的 I/O)。 一个名为外部中断/事件控制器 (EXTI) 的专用可编程控制器负责外部 I/O 信号和 NVIC 控制器之间的互连,我们将在下面看到。
STM32Fl03微控制器基千ARM CortexM3内核设计。 它的NVIC具有以下特性:
(1)支待 84 个异常,包括 16 个内部异常和 68 个非内核异常中断。
(2) 使用 4 位优先级设置,具有 16 级可编程异常优先级。
(3)中断响应时处理器状态的自动保存无须额外的指令。
(4)中断返回时处理楛状态的自动恢复无须额外的指令。
(5)支持嵌套和向量中断。
(6)支持中断尾链技术。
中断优先级决定了一个中断是否能被屏蔽以及在未屏蔽的情况下何时可以响应。
下面从分组、实现等方面具体讲述STM32Fl03中断优先级管理。
STM32Fl03中断优先级分为抢占优先级和子优先级。
(1) 抢占优先级 (preempting priority)
又称组优先级或者占先优先级,标识了一个中断的抢占式优先响应能力的高低。 抢占优先级决定了是否会有中断嵌套发生。 例如,一个具有高抢占先优先级的中断会打断当前正在执行的中断服务程序,转而执行它对应的中断服务程序。
(2)子优先级(sub-priority)
又称从优先级,仅在抢占优先级相同时才有影响,它标识了一个中断非抢占式优先响应能力的高低。 即在抢占优先级相同的情况下,如果有中断正被处理,那么高子优先级的中断只好等待正被响应的低子优先级中断处理结束后才能得到响应。 在抢占优先级相同的情况下,如果没有中断正被处理,那么高子优先级的中断将优先被响应。
在固件库中,NVIC 的结构体定义可谓是颇有远虑,给每个寄存器都预留了很多位,恐怕为的是日后扩展功能。不过 STM32F103 可用不了这么多,只是用了部分而已。
NVIC 结构体的定义,来自固件库头文件:core_cm3.h
其中,__IO
在此段程序的前面有了宏定义,看下面:
volatile 关键字是一种类型修饰符,用它声明的类型变量表示可以被某些编译器未知的因素更改。 volatile 提醒编译器它后面所定义的变量随时都有可能改变,因此编译后的程序每次需要存储或读取这个变量的时候,都会直接从变量地址中读取数据。
uint32_t
代表是无符号整型变量。
在配置中断的时候我们一般只用 ISER、ICER 和 IP 这三个寄存器,作用分别如下:
(1)ISER 用来使能中断;
(2)ICER 用来失能中断;
(3)IP 用来设置中断优先级
详解static inline关键字
针对蜂拥而至的春运购票人群,火车站作了以下规定:第一,军人优先购票(军人比非军人具有更高的抢占优先级);第二,军衔高的优先购票(高军衔军人比低军衔军人具有更高的子优先级);第三,年龄大的优先购票(军衔相同的军人年纪大的具有更高的优先级,会得到更快的响应)。
STM32Fl03各个中断对应的中断服务程序的入口地址统一存放在STM32Fl03的中断向量表中。STM32Fl03的中断向量表一般位于其存储器的 0 地址处。
中断服务程序,在结构上与函数非常相似。 但不同的是,函数一般有参数有返回值,并在应用程序中被人为显式地调用执行,而中断服务程序一般没有参数也没有返回值,并只有中断发生时才会被自动隐式地调用执行。 每个中断都有自己的中断服务程序,用来记录中断发生后要执行的真正意义上的处理操作。
STM32Fl03 所有的中断服务函数在该微控制器所属产品系列的启动代码文件中都有预先定义。 用户 开发自己的STM32Fl03应用时可在文件stm32fl0x_it. c中使用C语言编写函数重新定义之。
STM32Fl03的 中断服务函数 通常以 PPP _IRQHandler 命名(其中,PPP 是中断对应的外设名),并具有以下特点:
(1) 预置弱定义属性。
除了复位程序以 外,STM32Fl03其他所有中断服务程序都在启动代码中预设了弱定义(WEAK)属性。 用户可以在其他文件中编写同名的中断服务函数替代在启动代码中默认的中断服务程序。
(2)全C实现
STM32Fl03中断服务程序,可以全部使用C语言编程实现,无须像以前ARM7或ARM9处理器那样要在中断服务程序的首尾加上汇编语言“封皮 “用来保护和恢复现场(寄存器)。 STM32Fl03的中断处理过程中,保护和恢复现场的工作由硬件自动完成 ,无须用户操心。 用户只需集中精力编写中断服务程序即可。
用户在开发STM32Fl03应用时,可以根据实际需求在中断服务程序文件stm32flOx_it. c中使用C语言添加或修改相关中断的中断服务程序,在最后链接 生成可执行程序阶段,会使用用户自定义的同名中断服务程序替代 启动代码中原来默认的中断服务程序。
例如,要 更新定时器2的中断服务程序(其他的中断服务程序可由此类推而得),可直接在STM32Fl03中断服务程序文件stm32fl0x_it. c中新增或修改定时器2的中断服务程序, 如下所示:
void TIM2_IRQHandler(void)
{
... //
}
尤其需要注意的是,在更新STM32Fl03中断服务程序时,必须确保STM32Fl03 中断服务程序文件(stm32fl0x_it. c)中的中断服务程序名(如TIM2_IRQHandler)和启动代码 (startup_stm32fl0x_xx.s) 中的中断服务程序名(如TIM2_IRQHandler) 相同,否则在链接生成可执行文件时无法使用用户 自定义的中断服务程序替换原来默认的中断服务程序。
当中断发生时,STM32Fl03将通过查找中断向量表来找到对应的中断服务程序的入口地址。
因此,中断向量表的建立必须在用户应用程序执行前完成,通常在启动过程中完成。用户可以根据应用需求选择在Flash或在RAM中建立中断向量表。
(1)在Flash中建立中断向量表
如果把中断向量表放在Flash中,则无须重新定位中断向量表。嵌入式应用程序运行过程中,每个中断对应固定的中断服务程序不能更改。这也是默认设置。
(2)在RAM中建立中断向蜇表
如果把中断向量表放在RAM中,则需要重定位中断向最表。嵌入式应用程序运行过程中,可根据需要动态地改变中断服务程序。
当执行中断服务程序时,STM32F103进入Handler模式,并使用主堆栈的栈项指针MSP。
因此,与上一步建立中断向量表一样,栈空间的分配和初始化也必须在用户应用程序执行前完成,通常也是在启动过程中完成的。本步又可分为分配栈空间和初始化栈两部分:
(1)分配栈空间
栈空间的分配通常位于STM32F103启动代码的起始位置。为了保证在中断响应和返回时有足够的空间来保护和恢复现场(xPSR、PC、LR、R12、R3–R0共8个寄存器),应在RAM中为栈分配足够大的空间,避免中断发生(尤其是嵌套中断)时主堆栈溢出。
在预算栈空间大小时,除了要计入最深函数调用时对栈空间的需求,还需要判定最多可能有多少级中断嵌套。
一个麻烦但很保险的方法是假设每个中断都可以嵌套。对于每一级嵌套的中断,都要保存和恢复ARM Cortex-M3内核中的8个寄存器,至少需要8个字(32B)的空间。并且如果中断服务程序过于复杂,还可能有更多的栈空间需求。
(2)初始化栈
栈的初始化工作通常在STM32F103微控制器上电复位后执行复位服务程序时完成。
不同于在启动代码中系统自动完成的前两步,建立 STM32Fl03中断的第三步:设置中断优先级是用户在应用程序中编写代码配置NVIC实现的。
中断优先级的设置又可依次分为以下两步完成:
(1)设置中断优先级分组的位数
STM32F103中断优先级用4个二进制位表示,可以分成两组:抢占优先级和子优先级。
设置中断优先级分组的位数,即确定在表示中断优先级的4位中抢占优先级和子优先级各占几位。
根据实际开发中用到的中断总数以及是否存在中断嵌套,中断优先级位的分组可以有5种方式:
● NVIC_ Priority Group_ 0: 抢占优先级0位,子优先级4位,此时不会发生中断嵌套。
● NVIC_ Priority Group_1: 抢占优先级1位,子优先级3位。
● NVIC_ Priority Group_2: 抢占优先级2位.子优先级2位。
● NVIC_ Priority Group_3: 抢占优先级3位,子优先级1位。
● NVIC_ Priority Group_4: 抢占优先级4位,子优先级0位。
(2)设置中断的抢占优先级和子优先级
根据中断优先级分组情况,分别设置中断的抢占优先级和子优先级。
例如,如果使用 NVIC_PriorityGroup_1对4位中断优先级进行分组(即抢占优先级1位, 子优先级3位),那么某中断(通道)的抢占优先级应在0和1中取值设置,子优先级在 0~7 中取值设置。
在设置完中断的 优先级 后,通过失效中断总屏蔽位 和 分屏蔽位,可以使能对应的中断。
中断设置 的最后一步 是编写中断服务程序代码。
STM32Fl03中断服务程序名已在启动代码中指定, 一般PPP_IRQHandler,其中PPP为对应外设名,如TIM1、USARTl等。
STM32Fl03中断服务程序 (如TIMI_IRQHandler )的具体内容由用户使用C语言编写,实现对中断的具体处理。 尤其需要注意的 是,通常在中断服务程序最后 、退出中断服务程序前清除对应中断的标志位,表示该中断巳处理完毕, 否则,该中断请求始终存在,该中断服务程序将被反复执行
STM32F103的通用I/O
引脚可以被直接映射为外部中断通道或事件输出, 用于产生中断/事件请求。
那么,如何在通用 I/O
引脚 上产生中断/事件请求的呢?
答案就是STM32Fl03微控制器上的另一个片上外设————外部中断/事件控制器EXTI。
在信号线上打一个斜杠并标注“20”字样,这个表示在控制器内部类似的信号线路有 20 个,这与 EXTI 总共有 20 个中断/事件线是吻合的。所以我们只要明白其中一个的原理,那其他 19 个线路原理也就知道了。
EXTI 可分为两大部分功能:
① 产生中断
② 产生事件
编号 1 是输入线,EXTI 控制器有 19 个中断/事件输入线,这些输入线可以通过寄存器设置为任意一个 GPIO,也可以是一些外设的事件。输入线一般是存在电平变化的信号。
编号 2 是一个边沿检测电路,它会根据 上升沿触发选择寄存器(EXTI_RTSR) 和 下降沿触发选择寄存器(EXTI_FTSR) 对应位的设置来控制信号触发。
边沿检测电路以 输入线 作为信号输入端,如果检测到有边沿跳变就输出有效信号 1 给编号 3 电路,否则输出无效信号0。
而 EXTI_RTSR 和 EXTI_FTSR 两个寄存器可以控制需要检测哪些类型的电平跳变过程,可以是只有上升沿触发、只有下降沿触发或者上升沿和下降沿都触发。
编号 3 电路实际就是一个或门电路,它一个输入来自编号 2 电路,另外一个输入来自软件中断事件寄存器(EXTI_SWIER)。EXTI_SWIER 允许我们通过程序控制就可以启动中断/事件线,这在某些地方非常有用。我们知道或门的作用就是有 1 就为 1,所以这两个输入随便一个有有效信号 1 就可以 输出 1 给 编号 4 和 编号 6 电路。
编号 4 电路是一个与门电路,它一个输入是编号 3 电路,另外一个输入来自中断屏蔽寄存器(EXTI_IMR)。与门电路要求输入都为 1 才输出 1,导致的结果是如果 EXTI_IMR 设置为 0 时,那不管编号 3 电路的输出信号是 1 还是 0,最终编号 4 电路输出的信号都为 0;
如果 EXTI_IMR 设置为 1 时,最终编号 4 电路输出的信号才由编号 3 电路的输出信号决定,这样我们可以简单的控制 EXTI_IMR 来实现是否产生中断的目的。
编号 4 电路输出的信号会被保存到挂起寄存器(EXTI_PR)内,如果确定编号 4 电路输出为 1 就会把EXTI_PR 对应位置 1。
编号 5 是将 EXTI_PR 寄存器内容输出到 NVIC 内,从而实现系统中断事件控制。
接下来我们来看看绿色虚线指示的电路流程。它是一个产生事件的线路,最终输出一个脉冲信号。
产生事件线路是在编号 3 电路之后与中断线路有所不同,之前电路都是共用的。
编号6 电路是一个与门,它一个输入来自编号 3 电路,另外一个输入来自事件屏蔽寄存器(EXTI_EMR)。如果 EXTI_EMR 设置为 0 时,那不管编号 3 电路的输出信号是 1 还是 0,最终编号 6 电路输出的信号都为 0;如果 EXTI_EMR 设置为 1 时,最终编号 6 电路输出的信号才由编号 3 电路的输出信号决定,这样我们可以简单的控制 EXTI_EMR 来实现是否产生事件的目的。
编号 7 是一个脉冲发生器电路,当它的输入端,即编号 6 电路的输出端,是一个有效信号 1 时就会产生一个脉冲;如果输入端是无效信号就不会输出脉冲。
编号 8 是一个脉冲信号,就是产生事件的线路最终的产物,这个脉冲信号可以给其他外设电路使用,比如定时器 TIM、模拟数字转换器 ADC 等等,这样的脉冲信号一般用来触发 TIM 或者 ADC 开始转换。
如何配置中断呢?
从GPIO到NVIC这一路出现的所有的外设都配置好,就可以了。
第一步:
配置RCC,将涉及到的外设时钟都打开
第二步:
配置GPIO,选择端口为输入模式。
第三步:
配置AFIO,选择用到的GPIO,连接到后面的EXTI
第四步:
配置EXTI,选择边沿触发方式,比如上升沿,下降沿,或者双边沿。可以选择响应方式,中断响应,或者事件响应。
第五步:
配置NVIC,给中断选择一个合适的优先级。
最后,中断信号进行进入CPU了!!!
现在到keil里写程序吧
go
配置RCC,将涉及到的外设时钟都打开
需要用到的外设是:**GPIO、AFIO、EXTI、NVIC,**通过下面这个存储器映射表,可见,**GPIO、AFIO ** 均挂在 APB2 上,因此写下面两个命令:
EXTI 也是挂在 APB2上,但是RCC_APB2PeriphClockCmd( )
无法配置EXTI,
为什么? EXTI的时钟一直开着,处于使能状态!
同时,NVIC 的时钟也是一直处于使能状态,与EXTI不同的是,NVIC 位于内核里面,
配置GPIO,选择端口为输入模式。
输入模型如何选择?
去参考手册里,看一下,
我们这里GPIOB作为外部中断输入引脚,要接到 EXTI, 参考手册里建议,设置成:浮空输入、带上拉输入、或者带下拉输入。
这里,我们将其设置为带上拉输入,这种模式下,平时是低电平,当变成高电平时,代表有中断出现。
之所以选取这种模式,是因为:
我们的外部中断源,对射式红外传感器,没有阻挡时,引脚输出是低电平,有阻挡时,引脚产生高电平。
对于AFIO,ST公司没有为其分配专门的库函数文件,它的设置函数,和GPIO放在了一起。
下面这个函数是用来复位AFIO外设的。调用这个函数之后,AFIO的所有设置都被清除了。
下面这个函数,就可以AFIO的数据选择器,来选择我们想要的中断引脚。
下面是函数的定义。可以看到,这个函数虽然是以GPIO开头命名的,但操作的是AFIO的寄存器。
根据上面函数的说明,写出如下的命令。
NVIC是内核外设,它的库函数放在 misc.h
和 misc.c
两个文件中。
写到这里,中断的配置,也就是铺垫工作就已经做好了。
下面的工作,是,写中断函数,也就是说,当中断发生时,CPU需要执行的指令。
中断函数的命名,要遵循ST库函数的命令规则,大家打开启动文件,
在启动文件中,已经定义好了中断函数的名字,每个中断通道都定义了一个中断函数。
我们的外部中断源是从 pin14 这个引脚进来的,所以函数名选择为:EXTI15_10_IRQHandler
,pin10
到 pin15
,六个通道的中断发生时,都会执行这个函数。
所以,当执行这个函数时,应该先判断是不是pin14
发生的中断,如果是,则执行相应的命令。
注意:“全局变量的默认初始值是 0,局部变量的默认初始值是随机的没有规律。”