一、中断的概念
在主程序运行过程中,出现了特定事件,使得CPU暂停当前正在运行的程序,转而去处理这个事件,等这个事件处理完之后,CPU再回到刚才被打断的位置继续处理,这就是中断。
那个打断CPU执行的特定事件,我们一般称之为中断源。被中断源打断的位置我们称为断点。处理特定事件的过程,我们称之为执行中断处理程序。

正在执行中断程序的时候,这个时候有可能被另外一个中断源给中断,CPU转而去执行另外一个中断源的中断处理程序,这叫中断嵌套。
中断B能否打断中断A,要看它们的优先级,优先级高的可以打断优先级低的,优先级低的无法打断优先级高的。

中断源可以是外部的,也可以是内部的。外部的叫外部中断源,内部的叫内部中断源(内部的中断有时候也叫异常)。
二、为什么需要中断
对于单片机来说,中断至关重要。因为在处理一些随机发生的事件时,如果没有中断机制,就只能采用轮询的方式一直检测,这种方式极其耗费系统资源,无法发挥单片机的性能。
比如我们要检测按键是否按下,如果没有中断,就需要循环的方式不断的去检测按键对应的IO口的电平,这是比较耗费CPU的时间的。如果要检测的更多按键的情况,CPU有可能会导致阻塞。
有了中断后事情就变的简单了,主程序不需要循环不断的去检测按键,当有按键按下的时候,CPU执行被打断,去执行按键处理程序。当没有按键按下的时候,CPU完全可以正常执行代码,丝毫不受任何的影响。
三、STM32的中断
Cortex-M3内核支持256个中断,包含16个内核中断和240个外部中断,并且具有256级的可编程中断设置。其内核设计的资源非常丰富,但在实际应用中,一个单片机几乎不可能全部用到如此多的中断,因此各个厂商通常都会对cortex-m3内核的中断进行裁剪。例如:STM32系列中最多支持84个中断,包括16个内核中断和68个可屏蔽中断,具有16级可编程的中断优先级。
在STM32F103系列中有70个中断,有10个内核中断和60个可编程的外部中断。
四、中断向量表
- 中断向量表需要在系统初始化的时候加载到内存中。
- 每一个中断的名称,对应着一个地址,这个地址是中断服务程序的入口地址。
- 中断向量表保存的就是一个中断名称和其对应的中断服务程序入口地址的对应关系。
- 下表中的灰色部分是内部中断,无背景部分是外部中断。
- 中断优先级值越小的,优先级越高。

五、STM32中的中断体系架构
中断体系架构中,最核心的部分就是内核,用于执行普通程序和响应中断后执行中断服务程序。
中断的信息发送到内核之前,需要先经过NVIC(中断向量控制器)进行处理,确定中断的响应顺序和抢占。
内核:Cortex-M3
NVIC:嵌套向量中断控制器
1.中断源
(1)内核中的其它控件:
-
- 复位
- 系统滴答定时器
- 等等内部中断
(2)片上外设
-
- 串口
- 定时器
- 2IC
- 等等片上外设
(3)片外外设
-
- GPIOA、GPIOB……接入的设备
- 由GPIOA、GPIOB……传入,先到AFIO模块,再传入EXTI模块。
为了简化芯片内部布局,片外外设由GPIOA、GPIOB……等管脚进入芯片后先接到AFIO引脚复用选择器模块。
- 每一组GPIO的0引脚传入的信号复用到一条线上。
- 每一组GPIO的1引脚传入的信号复用到一条线上。
- 每一组GPIO的2引脚传入的信号复用到一条线上。
……
GPIO模块一共有16条线传入到EXTI中,EXTI为了区别是具体的哪一组的哪一个接口传入的中断,需要对其进行提前配置。
另外还有PVD电源检测、RTC、USB检测、以太网唤醒(互联型产品)。因此EXTI一共有19(互联型20)条线接入。
经过EXTI控制后,再接入到NVIC嵌套向量中断控制器中处理

2.NVIC嵌套向量中断控制器
NVIC(nested vectored interrupt controller)嵌套向量中断控制器,和处理器内核的接口紧密相连,可以实现低延迟、高效率的中断处理。
嵌套向量中断控制器管理着包括:内核异常、外部中断等所有中断。决定那个中断的处理程序交给CPU来执行。
每一个外部中断都可以被使能或禁止,并且可以被设置为挂起状态或清除状态。
挂起:A中断产生,但是CPU正在处理B中断,且B中断的优先级较高,A中断将被挂起,待处理完B后再处理A。
处理器的中断可以是电平形式的也可以是脉冲形式(上下沿,常用)的。
3.中断优先级
NVIC为了方便管理中断,可以通过软件给每个中断设置优先级。NVIC用寄存器中的4个位来控制优先级,值小的优先级高。
优先级分为两种:抢占优先级和响应优先级
(1)规则
(2)在NVIC中,实现抢占优先级和响应优先级的底层原理
1)NVIC寄存器

实际使用时,每个中断寄存器中关于优先级的配置bit只有高4位可以操作,低四位无效。
在底层,实现抢占优先级的方法是分组。

在实际应用中,我们通常使用模式0b011,16个抢占优先级,不设置响应优先级。
4.EXTI 外部中断控制器
EXTI外部中断控制器主要处理芯片外部的中断

边沿检测电路:
通过上升沿、下降沿寄存器控制。
软件中断事件寄存器:
为了实现调试、模拟等功能,可以用软件模拟中断的触发
或门

请求挂起寄存器:
- 判断当前是否产生了中断,是一个状态寄存器,用于显示是否有中断产生。
- 当产生中断时,无论是否会被响应,先置位该寄存器的对应位。
- 中断置位,硬件自动完成。需要通过软件清零。
中断屏蔽寄存器:
配置是否屏蔽中断。
与门

事件屏蔽寄存器:
- 特定的事件,有特定的处理流程。不会通过CPU处理。硬件处理,更快、更高效。
- 但是,需要是特别特殊的事件,需要具备特殊的处理电路。
六、中断的应用案例
1.需求
使用中断实现如下功能:当按键KEY3按下翻转LED1的显示状态。
2.电路原理分析




根据电路原理图:
LED部分:(相关细节在之前博客中)
LED-1的阴极连接到了芯片的PA0引脚,阳极接到了3.3v电源。
当PA0引脚为高电平时,LED-1熄灭,PA0为低电平时,LED-1点亮。
独立按键KEY-3:
KEY3连接到了芯片的PF10引脚,另一端连接到了3.3v高电平。
可以将PF10引脚设置为下拉输入模式,并配置中断为下降沿触发。
当按键按下后引脚检测到下降沿触发中断。
在中断服务函数中改变LED-1的状态即可。
3.编程——寄存器实现方式
3.1.需要使用到的寄存器介绍
(1)RCC开启对应模块的时钟
RCC_APB2ENR

相关代码:
// 时钟配置,打开GPIOF的时钟,打开AFIOF的时钟
RCC->APB2ENR |= RCC_APB2ENR_IOPFEN;
RCC->APB2ENR |= RCC_APB2ENR_AFIOEN;
注意:开始写代码的时候,可能没有GPIOF这一组引脚,需要在"stm32f10x.h"头文件中将芯片对应的型号放开注释后再操作
#if !defined (STM32F10X_LD) && !defined (STM32F10X_LD_VL) && !defined (STM32F10X_MD) && !defined (STM32F10X_MD_VL) && !defined (STM32F10X_HD) && !defined (STM32F10X_HD_VL) && !defined (STM32F10X_XL) && !defined (STM32F10X_CL)
/* #define STM32F10X_LD */ /*!< STM32F10X_LD: STM32 Low density devices */
/* #define STM32F10X_LD_VL */ /*!< STM32F10X_LD_VL: STM32 Low density Value Line devices */
/* #define STM32F10X_MD */ /*!< STM32F10X_MD: STM32 Medium density devices */
/* #define STM32F10X_MD_VL */ /*!< STM32F10X_MD_VL: STM32 Medium density Value Line devices */
/* #define STM32F10X_HD */ /*!< STM32F10X_HD: STM32 High density devices */
/* #define STM32F10X_HD_VL */ /*!< STM32F10X_HD_VL: STM32 High density value line devices */
/* #define STM32F10X_XL */ /*!< STM32F10X_XL: STM32 XL-density devices */
/* #define STM32F10X_CL */ /*!< STM32F10X_CL: STM32 Connectivity line devices */
#endif

(2)GPIOA、GPIOF 控制管脚模式
配置GPIOA0管脚为推挽输出模式,CNF: 00; MODE:11;
GPIOx_CRL

配置GPIOF10管脚为下拉输入模式,CNF:10;MODE:00;
GPIOx_CRH


相关代码
// GPIOF10的管脚模式配置:下拉输入。CNF:10, MODE:00,
GPIOF->CRH |= GPIO_CRH_CNF10_1;
GPIOF->CRH &= ~GPIO_CRH_CNF10_0;
GPIOF->CRH &= ~GPIO_CRH_MODE10;
GPIOF->ODR &= ~GPIO_ODR_ODR10; //将引脚设为低电平,这时引脚为下拉模式
(3)AFIO 引脚复用选择器
寄存器:AFIO_EXTICRx x范围是0:4
AFIO寄存器地址映像


上面两张图的关系如下图:

AFIO_EXTICRx

在STM32F10X.H头文件中,对EXTICR的定义是一个具有四个元素的数组

typedef struct
{
__IO uint32_t EVCR;
__IO uint32_t MAPR;
__IO uint32_t EXTICR[4];
uint32_t RESERVED0;
__IO uint32_t MAPR2;
} AFIO_TypeDef;
相关代码
// AFIO配置引脚复用选择
AFIO->EXTICR[2] |= AFIO_EXTICR3_EXTI10_PF;//EXTICR[2];代表是数组的第三个元素
(4)EXTI 外部中断控制
EXTI_RTSR上升沿触发选择寄存器


EXTI_IMR中断屏蔽寄存器


相关代码
// 配置EXTI 上升沿触发有效 中断屏蔽寄存器不屏蔽
EXTI->RTSR |= EXTI_RTSR_TR10;
EXTI->IMR |= EXTI_IMR_MR10;
(5)NVIC中断控制
不使用寄存器配置,使用库函数配置即可
相关代码
// NVIC配置
NVIC_SetPriorityGrouping(0X03);//全部为抢占优先级
NVIC_SetPriority(EXTI15_10_IRQn, 3);
NVIC_EnableIRQ(EXTI15_10_IRQn);//打开中断请求
(6)清除中断标志位
EXTI_PR 对应位写1可以清除,注意,不是写0。


相关代码
// 1.先清除中断挂起标志位
EXTI->PR |= EXTI_PR_PR10;
3.2.整体配置思路
第一步:永远是先配置时钟
第二步:配置需要用到的GPIO引脚模式
第三步:配置AFIO引脚复用选择,设置好中断传入的路径
第四步:在EXTI寄存器中配置中断触发条件(上升沿还是下降沿,高电平还是低电平),关闭对应的外部中断屏蔽
第五步:配置NVIC的中断优先级模式(一般选择模式3,全部都是抢占优先级)、配置对应的中断优先级、打开NVIC的中断请求响应
第六步:写中断服务函数,函数中首先要清除中断挂起标志位
3.3.完整的中断配置代码
#include "key.h"
#include "delay.h"
#include "led.h"
volatile uint16_t key_press = 0;
// 按键初始化函数
void Key_Init(void)
{
// 1.时钟配置:打开GPIOF的时钟; 打开AFIO的时钟
RCC->APB2ENR |= RCC_APB2ENR_IOPFEN;
RCC->APB2ENR |= RCC_APB2ENR_AFIOEN;
// 2.配置GPIOF10端口的工作模式:下拉输入模式,CNF:10; MODE:00
GPIOF->CRH &= ~GPIO_CRH_CNF10_0;
GPIOF->CRH |= GPIO_CRH_CNF10_1;
GPIOF->CRH &= ~GPIO_CRH_MODE10;
GPIOF->ODR &=~ GPIO_ODR_ODR10;
// 3.配置AFIO引脚复用功能选择,选择对应的端口:AFIO F10,注意:EXTICR[]数组中,每一个数组元素由4个bit组成,代表了EXTI10映射到哪一组的GPIO中,这是一个独立的值,需要先清零再赋值
AFIO->EXTICR[2] &= ~0x0000f000;
AFIO->EXTICR[2] |= AFIO_EXTICR3_EXTI10_PF;
// 4.配置EXTI寄存器中断触发条件、关闭对应位的外部中断条件
EXTI->RTSR |= EXTI_RTSR_TR10;//从EXTI外部中断控制器到NVIC嵌套向量中断控制器一共有16根线是GPIO端口传入的中断,在这里选择第10根线,对应第10个引脚
EXTI->IMR |= EXTI_IMR_MR10;//关闭对应位置的中断屏蔽
// 5.配置NVIC嵌套中断向量控制器,配置响应模式(抢占、响应)、中断优先级、开启中断
NVIC_SetPriorityGrouping(3);//16个中断优先级全部为抢占优先级
NVIC_SetPriority(EXTI15_10_IRQn, 3);
NVIC_EnableIRQ(EXTI15_10_IRQn);
}
// 中断服务函数
void EXTI15_10_IRQHandler(void)
{
if(EXTI->PR & EXTI_PR_PR10)//判断中断来源,是否由EXTI10触发
{
// 先清掉中断挂起标志位,对应位置1,清掉
EXTI->PR |= EXTI_PR_PR10;
key_press = 1;
}
}
//按键功能
void Key_Toggle_LED(void)
{
if(key_press == 1)
{
key_press = 0;//先清零
Delay_nms(15);//延时防抖
if((GPIOF->IDR & GPIO_IDR_IDR10) != 0)
{
LED_Toggle(LED2);
while((GPIOF->IDR & GPIO_IDR_IDR10) != 0);//等待按键松开。
}
}
}



